Skip to content

Commit

Permalink
Merge pull request #31 from ajfAfg/memoize-in-transform_
Browse files Browse the repository at this point in the history
memoize in transform
  • Loading branch information
ajfAfg authored Jan 8, 2024
2 parents 94acbfd + eca850d commit b965e93
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 60 deletions.
24 changes: 24 additions & 0 deletions src/core/mutable_map.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
% NOTE: API resembles the module `maps`.
-module(mutable_map).

-export([new/0, put/3, find/2]).

-export_type([t/0, key/0, value/0]).

-opaque t() :: ets:table().

-type key() :: term().
-type value() :: term().

-spec new() -> t().
new() -> ets:new(?MODULE, [set, private]).

-spec put(key(), value(), t()) -> true.
put(Key, Value, MutableMap) -> ets:insert(MutableMap, {Key, Value}).

-spec find(key(), t()) -> {ok, value()} | error.
find(Key, MutableMap) ->
case ets:lookup(MutableMap, Key) of
[{Key, Value}] -> {ok, Value};
[] -> error
end.
114 changes: 71 additions & 43 deletions src/core/optimum_supervision_tree_solver.erl
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@
-type dag_vertex() :: [dependency_digraph:vertex()].
-type connected_dag() :: digraph:graph().
-type connected_dag_vertex() :: [dependency_digraph:vertex()].
% NOTE:
% - mutable_map:key() = sets:set(connected_dag_vertex())
% - mutable_map:value() = supervision_tree:t()
-type memo() :: mutable_map:t().

-spec solve(dependency_graph:t(),
all_local_minimum_vertex_splitters_solver:take_all_local_minimum_vertex_splitters()) ->
supervision_tree:t().
solve(DependencyGraph, TakeAllLocalMinimumVertexSplitters) ->
Graph = dependency_digraph:from_dependency_graph(DependencyGraph),
transform(digraph_utils:condensation(Graph), TakeAllLocalMinimumVertexSplitters).
Digraph =
digraph_utils:condensation(
dependency_digraph:from_dependency_graph(remove_loop(DependencyGraph))),
transform(Digraph, TakeAllLocalMinimumVertexSplitters).

% NOTE: The argument graph is assumed to be a DAG.
-spec transform(dag(),
Expand All @@ -21,56 +27,78 @@ solve(DependencyGraph, TakeAllLocalMinimumVertexSplitters) ->
transform(DAG, TakeAllLocalMinimumVertexSplitters) ->
case digraph_utils:components(DAG) of
[] -> throw(impossible);
[_] -> transform_(DAG, TakeAllLocalMinimumVertexSplitters);
[_] -> transform_(DAG, TakeAllLocalMinimumVertexSplitters, mutable_map:new());
Components ->
% NOTE: Remove an unneeded supervisor
Fun = fun ({rest_for_one, [V]}) -> V;
(Other) -> Other
end,
{one_for_one,
lists:map(fun(Component) ->
Fun(transform_(digraph_utils:subgraph(DAG, Component),
TakeAllLocalMinimumVertexSplitters))
remove_unneeded_supervisor(transform_(digraph_utils:subgraph(DAG,
Component),
TakeAllLocalMinimumVertexSplitters,
mutable_map:new()))
end,
Components)}
end.

-spec transform_(connected_dag(),
all_local_minimum_vertex_splitters_solver:take_all_local_minimum_vertex_splitters()) ->
all_local_minimum_vertex_splitters_solver:take_all_local_minimum_vertex_splitters(),
memo()) ->
supervision_tree:t().
transform_(ConnectedDAG, TakeAllLocalMinimumVertexSplitters) ->
Candidates =
[begin
% NOTE: `VertexSplitter` =/= []
SubAcacia =
option:get(transform_into_acacia(lists:reverse(sort_by_topological_ordering(VertexSplitter,
ConnectedDAG)))),
SubTrees =
begin
SubGraph =
digraph_utils:subgraph(ConnectedDAG,
digraph:vertices(ConnectedDAG) -- VertexSplitter),
SubConnectedDAGs =
lists:map(fun(Component) -> digraph_utils:subgraph(SubGraph, Component)
end,
digraph_utils:components(SubGraph)),
% NOTE: Remove an unneeded supervisor
Fun = fun ({rest_for_one, [V]}) -> V;
(Other) -> Other
end,
[Fun(transform_(SubConnectedDAG, TakeAllLocalMinimumVertexSplitters))
|| SubConnectedDAG <- SubConnectedDAGs]
end,
case SubTrees of
[] -> SubAcacia;
_ -> merge(SubAcacia, {one_for_one, SubTrees})
end
end
|| VertexSplitter <- TakeAllLocalMinimumVertexSplitters(ConnectedDAG)],
hd(lists:sort(fun(Tree1, Tree2) ->
supervision_tree:calc_cost(Tree1) =< supervision_tree:calc_cost(Tree2)
end,
Candidates)).
transform_(ConnectedDAG, TakeAllLocalMinimumVertexSplitters, Memo) ->
case mutable_map:find(
sets:from_list(
digraph:vertices(ConnectedDAG)),
Memo)
of
{ok, Tree} -> Tree;
error ->
Candidates =
[begin
% NOTE: `VertexSplitter` =/= [] is satisfied, so no exception is thrown here.
SubAcacia =
option:get(transform_into_acacia(lists:reverse(sort_by_topological_ordering(VertexSplitter,
ConnectedDAG)))),
SubTrees =
begin
SubGraph =
digraph_utils:subgraph(ConnectedDAG,
digraph:vertices(ConnectedDAG)
-- VertexSplitter),
SubConnectedDAGs =
lists:map(fun(Component) ->
digraph_utils:subgraph(SubGraph, Component)
end,
digraph_utils:components(SubGraph)),
[remove_unneeded_supervisor(transform_(SubConnectedDAG,
TakeAllLocalMinimumVertexSplitters,
Memo))
|| SubConnectedDAG <- SubConnectedDAGs]
end,
case SubTrees of
[] -> SubAcacia;
_ -> merge(SubAcacia, {one_for_one, SubTrees})
end
end
|| VertexSplitter <- TakeAllLocalMinimumVertexSplitters(ConnectedDAG)],
Tree =
hd(lists:sort(fun(Tree1, Tree2) ->
supervision_tree:calc_cost(Tree1)
=< supervision_tree:calc_cost(Tree2)
end,
Candidates)),
mutable_map:put(
sets:from_list(
digraph:vertices(ConnectedDAG)),
Tree,
Memo),
Tree
end.

-spec remove_loop(dependency_graph:t()) -> dependency_graph:t().
remove_loop(Graph) -> maps:map(fun(From, Tos) -> lists:delete(From, Tos) end, Graph).

-spec remove_unneeded_supervisor(supervision_tree:t()) -> supervision_tree:t().
remove_unneeded_supervisor({_, [Child]}) -> Child;
remove_unneeded_supervisor(Tree) -> Tree.

% NOTE: The argument graph is assumed to be a DAG.
-spec sort_by_topological_ordering([dag_vertex()], dag()) -> [dag_vertex()].
Expand Down
21 changes: 13 additions & 8 deletions test/dependency_graph_generator.erl
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
-module(dependency_graph_generator).

-export([dependency_graph/0]).
-export([dependency_graph/1]).

-import(proper_helper, [limited_atom/0]).

-include_lib("proper/include/proper.hrl").

dependency_graph() ->
dependency_graph(MaxVertexNum) ->
?LET(List,
non_empty(list(limited_atom())),
?SUCHTHAT(L, non_empty(list(limited_atom())), length(L) =< MaxVertexNum),
begin
Vertices = lists:uniq(List),
lists:foldl(fun(From, Acc) ->
Tos = my_lists:sublist_randomly(Vertices),
maps:put(From, Tos, Acc)
MaximumValidEdges = [{V1, V2} || V1 <- Vertices, V2 <- Vertices],
% NOTE: When the number of vertices is the same as the number of edges,
% it often generates a graph that are complex to compute vertex splitters,
% in my experience.
Edges =
lists:nthtail(length(MaximumValidEdges) - length(Vertices),
my_lists:shuffle(MaximumValidEdges)),
lists:foldl(fun({V1, V2}, Acc) -> maps:update_with(V1, fun(Vs) -> [V2 | Vs] end, Acc)
end,
#{},
Vertices)
maps:from_keys(Vertices, []),
Edges)
end).
14 changes: 7 additions & 7 deletions test/prop_all_local_minimum_vertex_splitters_solver.erl
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ dependency_graph_to_connected_dag(G) ->
dependency_digraph:from_dependency_graph(G)),
digraph_utils:subgraph(Digraph, hd(digraph_utils:components(Digraph))).

dependency_connected_dag() ->
dependency_connected_dag(MaxVertexNum) ->
{'$call',
?MODULE,
dependency_graph_to_connected_dag,
[dependency_graph_generator:dependency_graph()]}.
[dependency_graph_generator:dependency_graph(MaxVertexNum)]}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% all_local_minimum_vertex_splitters_solver:solve_in_polynomial_time_without_correctness/1 %%%
Expand All @@ -24,7 +24,7 @@ prop_solve_in_polynomial_time_without_correctness1(doc) ->

prop_solve_in_polynomial_time_without_correctness1() ->
?FORALL(ConnectedDAG,
dependency_connected_dag(),
dependency_connected_dag(1000),
begin
lists:all(fun(X) -> X end,
[dependency_digraph:satisfy_vertex_splitter_constraint1(ConnectedDAG,
Expand All @@ -45,7 +45,7 @@ prop_solve_in_exp_time_with_correctness1(doc) ->

prop_solve_in_exp_time_with_correctness1() ->
?FORALL(ConnectedDAG,
dependency_connected_dag(),
dependency_connected_dag(20),
begin
lists:all(fun(X) -> X end,
[dependency_digraph:satisfy_vertex_splitter_constraint1(ConnectedDAG,
Expand All @@ -59,7 +59,7 @@ prop_solve_in_exp_time_with_correctness2(doc) ->

prop_solve_in_exp_time_with_correctness2() ->
?FORALL(ConnectedDAG,
dependency_connected_dag(),
dependency_connected_dag(20),
begin
lists:all(fun(X) -> X end,
[dependency_digraph:satisfy_vertex_splitter_constraint2(ConnectedDAG,
Expand All @@ -73,7 +73,7 @@ prop_solve_in_exp_time_with_correctness3(doc) ->

prop_solve_in_exp_time_with_correctness3() ->
?FORALL(ConnectedDAG,
dependency_connected_dag(),
dependency_connected_dag(20),
begin
lists:all(fun(VertexSplitter) ->
lists:all(fun(Vertex) ->
Expand All @@ -92,7 +92,7 @@ prop_solve_in_exp_time_with_correctness4(doc) ->

prop_solve_in_exp_time_with_correctness4() ->
?FORALL(ConnectedDAG,
dependency_connected_dag(),
dependency_connected_dag(20),
begin
?IMPLIES(length([V
|| V <- digraph:vertices(ConnectedDAG),
Expand Down
32 changes: 32 additions & 0 deletions test/prop_mutable_map.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-module(prop_mutable_map).

-compile(export_all).

-import(proper_helper, [random_type/0]).

-include_lib("proper/include/proper.hrl").

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% mutable_map:put/3 and mutable_map:find/2 %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
prop_put_and_find1(doc) -> "The last put term can be found".

prop_put_and_find1() ->
?FORALL({Key, Value1, Value2},
tuple([random_type(), random_type(), random_type()]),
begin
MutableMap = mutable_map:new(),
mutable_map:put(Key, Value1, MutableMap),
mutable_map:put(Key, Value2, MutableMap),
{ok, Value2} =:= mutable_map:find(Key, MutableMap)
end).

prop_put_and_find2(doc) -> "A term that is not put cannot be found".

prop_put_and_find2() ->
?FORALL(Key,
random_type(),
begin
MutableMap = mutable_map:new(),
error =:= mutable_map:find(Key, MutableMap)
end).
4 changes: 2 additions & 2 deletions test/prop_optimum_supervision_tree_solver.erl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ prop_solve1(doc) ->

prop_solve1() ->
?FORALL(Graph,
dependency_graph_generator:dependency_graph(),
dependency_graph_generator:dependency_graph(25),
begin
Digraph = dependency_digraph:from_dependency_graph(Graph),
lists:all(fun(V) ->
Expand Down Expand Up @@ -53,7 +53,7 @@ prop_solve2(doc) -> "About cost, exp time & correct ≤ polynomial time & incorr

prop_solve2() ->
?FORALL(Graph,
dependency_graph_generator:dependency_graph(),
dependency_graph_generator:dependency_graph(18),
supervision_tree:calc_cost(
optimum_supervision_tree_solver:solve(Graph,
fun all_local_minimum_vertex_splitters_solver:solve_in_exp_time_with_correctness/1))
Expand Down

0 comments on commit b965e93

Please sign in to comment.