Current section
Files
Jump to
Current section
Files
src/backtracking_engine.erl
-module(backtracking_engine).
-behaviour(gen_statem).
-export([start_link/0, start_link/1]).
-export([solve/2, add_constraint/2, remove_constraint/2, reset/1, get_solution/1]).
-export([init/1, callback_mode/0, terminate/3, code_change/4]).
-export([idle/3, problem_analysis/3, variable_selection/3, value_assignment/3,
constraint_checking/3, backtracking/3, solution_found/3,
optimization/3, learning/3, failed/3]).
-record(backtrack_data, {
problem_id :: term(),
variables = [] :: [term()],
domains = #{} :: #{term() => [term()]},
constraints = [] :: [term()],
assignments = #{} :: #{term() => term()},
search_stack = [] :: [#{term() => term()}],
solution_count = 0 :: non_neg_integer(),
best_solution = undefined :: undefined | #{term() => term()},
optimization_function = undefined :: undefined | fun(),
learning_data = #{} :: #{term() => term()},
heuristics = #{} :: #{term() => term()},
statistics = #{} :: #{atom() => term()},
max_solutions = 1 :: pos_integer(),
timeout = infinity :: timeout(),
start_time :: erlang:timestamp()
}).
start_link() ->
gen_statem:start_link(?MODULE, [], []).
start_link(Options) ->
gen_statem:start_link(?MODULE, Options, []).
solve(Pid, Problem) ->
gen_statem:call(Pid, {solve, Problem}).
add_constraint(Pid, Constraint) ->
gen_statem:call(Pid, {add_constraint, Constraint}).
remove_constraint(Pid, Constraint) ->
gen_statem:call(Pid, {remove_constraint, Constraint}).
reset(Pid) ->
gen_statem:call(Pid, reset).
get_solution(Pid) ->
gen_statem:call(Pid, get_solution).
init(Options) ->
Data = #backtrack_data{
start_time = erlang:timestamp(),
max_solutions = proplists:get_value(max_solutions, Options, 1),
timeout = proplists:get_value(timeout, Options, infinity),
optimization_function = proplists:get_value(optimization_function, Options, undefined),
heuristics = #{
variable_selection => most_constrained_variable,
value_selection => least_constraining_value,
constraint_propagation => arc_consistency,
learning => conflict_directed_backjumping
},
statistics = #{
nodes_visited => 0,
backtracks => 0,
constraint_checks => 0,
learning_events => 0
}
},
{ok, idle, Data}.
callback_mode() -> [state_functions, state_enter].
idle(enter, _OldState, Data) ->
{keep_state, Data};
idle({call, From}, {solve, Problem}, Data) ->
NewData = initialize_problem(Problem, Data),
{next_state, problem_analysis, NewData, [{reply, From, ok}]};
idle({call, From}, reset, Data) ->
ResetData = Data#backtrack_data{
variables = [],
domains = #{},
constraints = [],
assignments = #{},
search_stack = [],
solution_count = 0,
best_solution = undefined,
learning_data = #{},
start_time = erlang:timestamp(),
statistics = #{
nodes_visited => 0,
backtracks => 0,
constraint_checks => 0,
learning_events => 0
}
},
{keep_state, ResetData, [{reply, From, ok}]};
idle({call, From}, get_solution, Data) ->
{keep_state, Data, [{reply, From, {ok, Data#backtrack_data.best_solution}}]};
idle(EventType, Event, Data) ->
handle_common_events(EventType, Event, Data).
problem_analysis(enter, _OldState, Data) ->
AnalysisData = analyze_problem_structure(Data),
case validate_problem(AnalysisData) of
valid ->
{next_state, variable_selection, AnalysisData};
{invalid, Reason} ->
{next_state, failed, AnalysisData#backtrack_data{statistics =
maps:put(failure_reason, Reason, AnalysisData#backtrack_data.statistics)}}
end;
problem_analysis(EventType, Event, Data) ->
handle_common_events(EventType, Event, Data).
variable_selection(enter, _OldState, Data) ->
case select_next_variable(Data) of
{ok, Variable} ->
UpdatedData = Data#backtrack_data{
statistics = maps:update_with(nodes_visited, fun(X) -> X + 1 end, 1,
Data#backtrack_data.statistics)
},
{next_state, value_assignment, UpdatedData, [{state_timeout, 0, {variable_selected, Variable}}]};
no_variables ->
{next_state, solution_found, Data}
end;
variable_selection(state_timeout, {variable_selected, Variable}, Data) ->
{next_state, value_assignment, Data, [{state_timeout, 0, {assign_variable, Variable}}]};
variable_selection(EventType, Event, Data) ->
handle_common_events(EventType, Event, Data).
value_assignment(enter, _OldState, Data) ->
{keep_state, Data};
value_assignment(state_timeout, {assign_variable, Variable}, Data) ->
case select_value_for_variable(Variable, Data) of
{ok, Value} ->
NewAssignments = maps:put(Variable, Value, Data#backtrack_data.assignments),
NewData = Data#backtrack_data{assignments = NewAssignments},
{next_state, constraint_checking, NewData,
[{state_timeout, 0, {check_constraints, Variable, Value}}]};
no_values ->
{next_state, backtracking, Data}
end;
value_assignment(EventType, Event, Data) ->
handle_common_events(EventType, Event, Data).
constraint_checking(enter, _OldState, Data) ->
{keep_state, Data};
constraint_checking(state_timeout, {check_constraints, Variable, Value}, Data) ->
UpdatedStats = maps:update_with(constraint_checks, fun(X) -> X + 1 end, 1,
Data#backtrack_data.statistics),
UpdatedData = Data#backtrack_data{statistics = UpdatedStats},
case check_all_constraints(Variable, Value, UpdatedData) of
consistent ->
propagate_constraints(Variable, Value, UpdatedData),
{next_state, variable_selection, UpdatedData};
inconsistent ->
learn_from_conflict(Variable, Value, UpdatedData),
{next_state, backtracking, UpdatedData};
{inconsistent, ConflictInfo} ->
LearnedData = advanced_conflict_learning(ConflictInfo, UpdatedData),
{next_state, backtracking, LearnedData}
end;
constraint_checking(EventType, Event, Data) ->
handle_common_events(EventType, Event, Data).
backtracking(enter, _OldState, Data) ->
UpdatedStats = maps:update_with(backtracks, fun(X) -> X + 1 end, 1,
Data#backtrack_data.statistics),
BacktrackData = Data#backtrack_data{statistics = UpdatedStats},
case perform_intelligent_backtrack(BacktrackData) of
{ok, RestoredData} ->
{next_state, variable_selection, RestoredData};
{backjump, JumpData} ->
{next_state, variable_selection, JumpData};
no_more_choices ->
{next_state, failed, BacktrackData}
end;
backtracking(EventType, Event, Data) ->
handle_common_events(EventType, Event, Data).
solution_found(enter, _OldState, Data) ->
SolutionData = record_solution(Data),
case SolutionData#backtrack_data.solution_count >= SolutionData#backtrack_data.max_solutions of
true ->
case SolutionData#backtrack_data.optimization_function of
undefined ->
{next_state, learning, SolutionData};
_OptFunc ->
{next_state, optimization, SolutionData}
end;
false ->
{next_state, backtracking, SolutionData}
end;
solution_found(EventType, Event, Data) ->
handle_common_events(EventType, Event, Data).
optimization(enter, _OldState, Data) ->
OptimizedData = optimize_solution(Data),
{next_state, learning, OptimizedData};
optimization(EventType, Event, Data) ->
handle_common_events(EventType, Event, Data).
learning(enter, _OldState, Data) ->
LearnedData = perform_learning_phase(Data),
{next_state, idle, LearnedData};
learning({call, From}, get_solution, Data) ->
{keep_state, Data, [{reply, From, {ok, Data#backtrack_data.best_solution}}]};
learning(EventType, Event, Data) ->
handle_common_events(EventType, Event, Data).
failed(enter, _OldState, Data) ->
{keep_state, Data};
failed({call, From}, get_solution, Data) ->
{keep_state, Data, [{reply, From, {error, no_solution}}]};
failed({call, From}, reset, Data) ->
{next_state, idle, reset_data(Data), [{reply, From, ok}]};
failed(EventType, Event, Data) ->
handle_common_events(EventType, Event, Data).
terminate(_Reason, _State, _Data) ->
ok.
code_change(_Vsn, State, Data, _Extra) ->
{ok, State, Data}.
initialize_problem({Variables, Domains, Constraints}, Data) ->
Data#backtrack_data{
variables = Variables,
domains = Domains,
constraints = Constraints,
assignments = #{},
search_stack = [#{}],
solution_count = 0,
best_solution = undefined,
start_time = erlang:timestamp()
}.
analyze_problem_structure(Data) ->
Variables = Data#backtrack_data.variables,
Constraints = Data#backtrack_data.constraints,
ConstraintGraph = build_constraint_graph(Variables, Constraints),
VariableOrdering = compute_variable_ordering(Variables, ConstraintGraph),
DomainSizes = compute_domain_statistics(Data#backtrack_data.domains),
UpdatedHeuristics = maps:merge(Data#backtrack_data.heuristics, #{
constraint_graph => ConstraintGraph,
variable_ordering => VariableOrdering,
domain_statistics => DomainSizes
}),
Data#backtrack_data{heuristics = UpdatedHeuristics}.
validate_problem(Data) ->
case {Data#backtrack_data.variables, Data#backtrack_data.domains} of
{[], _} -> {invalid, no_variables};
{_, Domains} when map_size(Domains) =:= 0 -> {invalid, no_domains};
_ ->
case check_domain_consistency(Data) of
true -> valid;
false -> {invalid, inconsistent_domains}
end
end.
select_next_variable(Data) ->
UnassignedVars = [V || V <- Data#backtrack_data.variables,
not maps:is_key(V, Data#backtrack_data.assignments)],
case UnassignedVars of
[] -> no_variables;
Vars ->
Strategy = maps:get(variable_selection, Data#backtrack_data.heuristics,
most_constrained_variable),
{ok, apply_variable_selection_heuristic(Strategy, Vars, Data)}
end.
select_value_for_variable(Variable, Data) ->
Domain = maps:get(Variable, Data#backtrack_data.domains, []),
AvailableValues = filter_available_values(Variable, Domain, Data),
case AvailableValues of
[] -> no_values;
Values ->
Strategy = maps:get(value_selection, Data#backtrack_data.heuristics,
least_constraining_value),
{ok, apply_value_selection_heuristic(Strategy, Variable, Values, Data)}
end.
check_all_constraints(Variable, Value, Data) ->
Constraints = Data#backtrack_data.constraints,
Assignments = Data#backtrack_data.assignments,
check_constraints_recursive(Constraints, Variable, Value, Assignments).
perform_intelligent_backtrack(Data) ->
case Data#backtrack_data.search_stack of
[_Current] -> no_more_choices;
[_Current | Rest] ->
case maps:get(learning, Data#backtrack_data.heuristics, basic_backtrack) of
conflict_directed_backjumping ->
perform_backjump(Data);
basic_backtrack ->
perform_basic_backtrack(Rest, Data)
end
end.
record_solution(Data) ->
CurrentSolution = Data#backtrack_data.assignments,
UpdatedCount = Data#backtrack_data.solution_count + 1,
BestSolution = case Data#backtrack_data.optimization_function of
undefined -> CurrentSolution;
_OptFunc when Data#backtrack_data.best_solution =:= undefined -> CurrentSolution;
OptFunc ->
case OptFunc(CurrentSolution, Data#backtrack_data.best_solution) of
better -> CurrentSolution;
_ -> Data#backtrack_data.best_solution
end
end,
Data#backtrack_data{
solution_count = UpdatedCount,
best_solution = BestSolution
}.
optimize_solution(Data) ->
case Data#backtrack_data.optimization_function of
undefined -> Data;
OptFunc ->
OptimizedSolution = apply_optimization_techniques(Data#backtrack_data.best_solution, OptFunc),
Data#backtrack_data{best_solution = OptimizedSolution}
end.
perform_learning_phase(Data) ->
LearningStats = maps:update_with(learning_events, fun(X) -> X + 1 end, 1,
Data#backtrack_data.statistics),
UpdatedHeuristics = learn_from_search_experience(Data),
Data#backtrack_data{
statistics = LearningStats,
heuristics = maps:merge(Data#backtrack_data.heuristics, UpdatedHeuristics)
}.
handle_common_events({call, From}, {add_constraint, Constraint}, Data) ->
NewConstraints = [Constraint | Data#backtrack_data.constraints],
{keep_state, Data#backtrack_data{constraints = NewConstraints}, [{reply, From, ok}]};
handle_common_events({call, From}, {remove_constraint, Constraint}, Data) ->
NewConstraints = lists:delete(Constraint, Data#backtrack_data.constraints),
{keep_state, Data#backtrack_data{constraints = NewConstraints}, [{reply, From, ok}]};
handle_common_events(_EventType, _Event, _Data) ->
{keep_state_and_data, [postpone]}.
build_constraint_graph(_Variables, _Constraints) ->
#{}.
compute_variable_ordering(Variables, _Graph) ->
Variables.
compute_domain_statistics(Domains) ->
maps:map(fun(_Var, Domain) -> length(Domain) end, Domains).
check_domain_consistency(_Data) ->
true.
apply_variable_selection_heuristic(_Strategy, [Var|_], _Data) ->
Var.
filter_available_values(_Variable, Domain, _Data) ->
Domain.
apply_value_selection_heuristic(_Strategy, _Variable, [Value|_], _Data) ->
Value.
check_constraints_recursive([], _Variable, _Value, _Assignments) ->
consistent;
check_constraints_recursive([_Constraint|Rest], Variable, Value, Assignments) ->
check_constraints_recursive(Rest, Variable, Value, Assignments).
propagate_constraints(_Variable, _Value, _Data) ->
ok.
learn_from_conflict(_Variable, _Value, _Data) ->
ok.
advanced_conflict_learning(_ConflictInfo, Data) ->
Data.
perform_backjump(Data) ->
{ok, Data}.
perform_basic_backtrack([PrevState|_], Data) ->
{ok, Data#backtrack_data{assignments = PrevState}}.
apply_optimization_techniques(Solution, _OptFunc) ->
Solution.
learn_from_search_experience(_Data) ->
#{}.
reset_data(Data) ->
Data#backtrack_data{
variables = [],
domains = #{},
constraints = [],
assignments = #{},
search_stack = [],
solution_count = 0,
best_solution = undefined
}.