f8c17c513b654036430696c31f3694db28c34f4e
1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # This file is free software, which comes along with NIT. This software is
4 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
5 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
6 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
7 # is kept unaltered, and a notification of the changes is added.
8 # You are allowed to redistribute it and sell it, alone or is a part of
11 # Basic framework for active backtrack solver
13 # This module provides a simple abstract class `BacktrackProblem[S,A]` to be specialized for a specific problem.
15 # The concrete class `BacktrackSolver` is used to configure, query, and run a solver for a given problem.
18 # Abstract backtrack problem of states (`S`) and actions (`A`).
20 # This class serves to model search problems using a backtracking approach.
21 # A state, `S`, is a point in the search problem and fully model a given state of the world.
22 # An action, `A`, is an available mean of transition between two states.
23 # While there is a potential large number of distinct states and actions, there should be only
24 # a small number of possible actions from a specific state (thus, a small, or at least finite, branching factor).
26 # The point this class is that the state is a mutable object, the roles of the actions is to modify
29 # This abstract class is generic and made to work with any kind of states and actions.
30 # Therefore, specific subclasses must be developed to implements the required services:
40 # The method `solve` returns a new solver for a backtrack search.
41 class BacktrackProblem[S
: Object,A
]
42 # The starting state of the problem.
43 # It is this object that will be modified by `apply_action` and `backtrack`.
44 fun initial_state
: S
is abstract
46 # The available and applicable actions for a given state
47 # Because of `backtracking`, actions must also be reversible (see `backtrack`).
49 # If there is no available actions, null (or an empty collection) must be returned.
51 # In order to optimise the search time, it is sensible to return `null`
52 # (or an empty collection) as early as possible.
54 # Node: to help some specific implementations, the current node is also available.
55 # See `BacktrackNode` for details.
56 fun actions
(state
: S
, node
: BacktrackNode[A
]): nullable Collection[A
] is abstract
58 # Modify `state` by applying `action`
59 # The `action` comes from an earlier invocation of `actions`.
60 fun apply_action
(state
: S
, action
: A
) is abstract
62 # Modify `state` by undoing `action`
63 # Because of this method, it is important that any action can be undone
64 # knowing only the post-state and the action.
65 fun backtrack
(state
: S
, action
: A
) is abstract
67 # Is the state a goal state?
68 # Once a goal state is found, the solver is automatically stopped.
69 # See `BacktrackSolver.run`.
70 fun is_goal
(state
: S
): Bool is abstract
73 fun solve
: BacktrackSolver[S
,A
] do
74 return new BacktrackSolver[S
,A
](self, initial_state
)
78 # A running solver for a given problem, that can be configured and controlled.
81 # # Basic run and results.
83 # 1. Instantiate it with the method `solve` from `BacktrackProblem`.
84 # 2. Apply the method `run`, that will search and return a solution.
85 # 3. Retrieve information from the solution.
88 # var p: BacktrackProblem = new MyProblem
89 # var solver = p.solve
90 # var res = solver.run
92 # print "Found solution in {res.depth} actions: {res.plan.join(", ")}"
93 # print "The state of the solution is: {solver.state}"
98 # # Step-by-step runs and multiple runs
100 # The `run_steps` method (see also `steps`, and `steps_limit`) can be used to run only a maximum number of steps.
101 # Thus, this method can be used as a *co-routine* and be run periodically for a small amount of time.
103 # `run` and `run_steps` return the next solution.
104 # A subsequent call to `run` returns the following solution and so on.
106 # When there is no more solutions available, `null` is returned and `is_running` become false.
108 # Between run, the state of the current search can be read.
113 # Internally, solvers use a zipper on the virtual search-tree where nodes are elements in the apply/backtrack graph.
114 # See the class `BacktrackNode` for details
116 # The `run` and `node` methods return a `BacktrackNode` that can be used to retrieve a lot of useful information,
117 # like the full `path` or the `plan`.
118 # If only the solved state is required, the `state` method from the solver gives it.
119 class BacktrackSolver[S
: Object, A
]
120 # The problem currently solved
121 var problem
: BacktrackProblem[S
,A
]
124 # Do not modify it directly: the solver will do that by its own use of
125 # `problem.apply_action` and `problem.backtrack`.
128 # The current `node` in the backtrack-zipper.
129 var node
: nullable BacktrackNode[A
] = null
131 # Is the solver still running?
132 # A running solver has not yet exhausted all the possible solutions.
133 var is_running
= true
136 private fun start
: BacktrackNode[A
]
139 var node
= new BacktrackNode[A
](null, null, 0, 0)
145 # The total steps executed since the beginning.
148 # Limit in the number of steps for a `run`.
150 # One can modify this value then `run` or just call `run_steps`.
152 # Use 0 for no limit.
154 var steps_limit
= 0 is writable
156 # Update `steps_limit` then just run some additional steps.
157 # Return the `node` corresponding to the found solution, or `null` if no solution is found.
158 fun run_steps
(steps
: Int): nullable BacktrackNode[A
]
160 steps_limit
= self.steps
+ steps
164 # Run the solver and return the next solution found (if any).
165 # Return null is one of these is true:
166 # * `steps_limit` is reached
167 # * no more reachable solution, in this case `is_running` become false.
168 fun run
: nullable BacktrackNode[A
]
171 # Not yet started, of finished?
173 if steps
> 0 then return null
175 var res
= problem
.is_goal
(state
)
176 if res
then return node
180 if steps_limit
> 0 and steps
> steps_limit
then break
183 var totry
= node
.totry
185 # It is the first visit in this state?
186 if totry
== null then
187 var actions
= problem
.actions
(state
, node
)
188 if actions
!= null and not actions
.is_empty
then
197 # No remaining actions?
198 if totry
== null or totry
.is_empty
then
202 #print "no more action"
208 problem
.backtrack
(state
, a
)
214 problem
.apply_action
(state
, a
)
215 #print "Play {a or else ""}"
216 node
= new BacktrackNode[A
](node
, a
, node
.depth
+1, steps
)
218 var res
= problem
.is_goal
(state
)
228 redef fun to_s
do return "{node or else "#0"}"
231 # A node in the backtrack-zipper visited by a `BacktrackSolver`.
233 # The solver visits the virtual search tree with a zipper.
235 # A node is the zipper (this class) is associated to:
236 # * a state of the problem (indirectly),
237 # * the actions not yet explored from the state (see `totry`)
238 # * the action that yields to the state (see `action`), used to backtrack.
239 # * and the parent node in the zipper (see `parent`).
241 # There is no direct link between a node and a state; it is unneeded
242 # since the same state is used, and mutated, during the whole execution of the solver.
244 # This class is exposed to allow queries on the solution provided by the solver.
245 class BacktrackNode[A
]
246 # The previous node in the backtrack-zipper
247 var parent
: nullable BacktrackNode[A
]
249 # The last action executed
250 var action
: nullable A
252 # The remaining actions to try from this node
253 var totry
: nullable Array[A
] = null
255 # The depth of `self` in the search-tree.
258 # The number of steps needed by the solver to process `self`.
259 # This is just a useless generation number, but could be used to evaluate
260 # the behavior of search algorithms.
263 # Build a sequence of nodes from the initial node to `self`
264 # ensure `result.first.parent == null and result.last == self`
265 fun path
: Sequence[BacktrackNode[A
]]
267 var res
= new List[BacktrackNode[A
]]
270 while node
!= null do
277 # Build a sequence of actions from the initial state to `self`
278 # See `path` for a more detailed plan.
279 fun plan
: Sequence[A
]
281 var res
= new List[A
]
282 var node
: nullable BacktrackNode[A
] = self
283 while node
!= null do
285 if a
!= null then res
.unshift
(a
)
292 var res
= "#{steps} d={depth}"
294 if tt
!= null then res
+= " tt={tt.join(" ")}"