Packages
fixpoint
0.14.8
0.22.1
0.21.5
0.21.4
0.21.3
0.21.2
0.21.1
0.21.0
0.20.6
0.20.5
0.20.4
0.20.3
0.20.2
0.20.1
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.18.2
0.18.1
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.9
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.13.5
0.13.4
0.13.2
0.13.1
0.12.9
0.12.8
0.12.7
0.12.6
0.12.5
0.12.4
0.12.2
0.12.1
0.11.8
0.11.7
0.11.6
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.46
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.28
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.1.3
0.1.2
0.1.1
0.1.0
Constraint Programming Solver
Current section
Files
Jump to
Current section
Files
README.md
# Constraint Programming Solver
## The approach
The implementation follows the ideas described in Chapter 12, "Concepts, Techniques, and Models
of Computer Programming" by Peter Van Roy and Seif Haridi.
[An overview of CP implementation in Mozart/Oz.](http://mozart2.org/mozart-v1/doc-1.4.0/fdt/index.html)
## Status
Proof of concept. Not suitable for use in production. Significant API changes and core implementation rewrites are expected.
## Intro
[](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Fbokner%2Ffixpoint%2Fblob%2Fmain%2Flivebooks%2Ffixpoint.livemd)
## Implemented constraints
- `equal`, `not_equal`, `less_or_equal`
- `absolute`
- `all_different`
- `inverse`
- `sum`, `modulo`
- `element`, `element2d`
- `circuit`
- `OR`
## Features
- views (linear combinations of variables in constraints)
- partial support for reified constraints
- solving constraint satisfaction (CSP) and constrained optimization (COP) problems
- parallel search
- pluggable search strategies
- distributed solving
## Installation
The package can be installed by adding `fixpoint` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:fixpoint, "~> 0.8.28"}
]
end
```
## Usage
### Getting started
Let's solve the following *constraint satisfaction problem*:
***Given two sets of values***
x = {1,2}, y = {0, 1}
***, find all solutions such that*** $x$ $\neq$ $y$
First step is to create a model that describes the problem we want to solve.
The model consists of *variables* and *constraints* over the variables.
In this example, we have 2 variables $x$ and $y$ and a single constraint $x$ $\neq$ $y$
```elixir
alias CPSolver.IntVariable
alias CPSolver.Constraint.NotEqual
alias CPSolver.Model
## Variable constructor takes a domain (i.e., set of values), and optional parameters, such as `name`
x = IntVariable.new([1, 2], name: "x")
y = IntVariable.new([0, 1], name: "y")
## Create NotEqual constraint
neq_constraint = NotEqual.new(x, y)
```
Now create an instance of `CPSolver.Model`:
```elixir
model = Model.new([x, y], [neq_constraint])
```
Once we have a model, we pass it to `CPSolver.solve/1,2`.
We can either solve asynchronously:
```elixir
## Asynchronous solving doesn't block
{:ok, solver} = CPSolver.solve_async(model)
Process.sleep(10)
## We can check for solutions and solver state and/or stats,
## for instance:
## There are 3 solutions: {x = 1, y = 0}, {x = 2, y = 0}, {x = 2, y = 1}
iex(46)> CPSolver.solutions(solver)
[[1, 0], [2, 0], [2, 1]]
## Solver reports it has found all solutions
iex(47)> CPSolver.status(solver)
:all_solutions
## Some stats
iex(48)> CPSolver.statistics(solver)
%{
elapsed_time: 2472,
solution_count: 3,
active_node_count: 0,
failure_count: 0,
node_count: 5
}
```
, or use a blocking call:
```elixir
iex(49)> {:ok, results} = CPSolver.solve(model)
{:ok,
%{
status: :all_solutions,
statistics: %{
elapsed_time: 3910,
solution_count: 3,
active_node_count: 0,
failure_count: 0,
node_count: 5
},
variables: ["x", "y"],
objective: nil,
solutions: [[2, 1], [1, 0], [2, 0]]
}}
```
### API
```elixir
#################
# Solving
#################
#
# Asynchronous solving.
# Takes CPSolver.Model instance and solver options as a Keyword.
# Creates a solver process that runs asynchronously
# and could be controlled and queried for produced solutions and/or status as it runs.
# The solver process is alive even after the solving is completed.
# It's the responsibility of a caller to dispose of it when no longer needed.
# (by calling CPSolver.dispose/1)
{:ok, solver} = CPSolver.solve_async(model, solver_opts)
# Synchronous solving.
# Takes CPSolver.Model instance and solver options as a Keyword.
# Starts the solver and gets the results (solutions and/or solver stats) once the solver finishes.
{:ok, solver_results} = CPSolver.solve(model, solver_opts)
```
, where
- ```model``` - [specification of the model](#model-specification);
- ```solver_opts (optional)``` - [solver options](#configuring-solver).
### Model specification
#### For CSP (constraint satisfaction problem):
```elixir
model = CPSolver.Model.new(variables, constraints)
```
, where
- `variables` is a list of [variables](lib/solver/variables/variable.ex) up to a concrete implementation.
Currently, the only implementation supported is for [variables over integer finite domain](lib/solver/variables/int_variable.ex).
- `constraints` is a list of [constraints](lib/solver/core/constraint.ex).
[Available constraints](lib/solver/constraints/)
#### For COP (constraint optimization problem):
```elixir
model = CPSolver.Model.new(variables, constraints, objective: objective)
```
The same as for CSP, but with additional `:objective` option. The objective is constructed by using
`CPSolver.Objective.minimize/1` and `CPSolver.Objective.maximize/1`.
[Example of COP model](lib/examples/knapsack.ex)
### Configuring solver
Available options:
- solution_handler: function()
A callback that gets called performed every time the solver finds a new solution. The single argument is a list of tuples
`{variable_name, variable_value}`
- timeout: integer()
Time to wait (in milliseconds) for terminating `CPSolver.solve_sync/2` call. Defaults to 30_000.
- stop_on: term() | condition_fun()
Condition for stopping the solving. Currently, only `{:max_solutions, max_solutions}` condition is available.
Defaults to `nil`.
- search: {variable_choice(), value_choice()}
[Search strategy](#search).
- space_threads: integer()
Defines the number of processes for parallel search. Defaults to 8.
- distributed: boolean() | [Node.t()]
If `true`, all connected nodes will participate in distributed solving.
Alternatively, one can specify the sublist of connected nodes.
Defaults to `false`.
### Distributed solving
*Fixpoint* allows to solve an instance of CSP/COP problem using multiple cluster nodes.
Note: *Fixpoint* **will not configure the cluster nodes!**
It's assumed that each node has the cluster membership and the `fixpoint` dependency is installed on it.
The solving starts on a 'leader' node, and then the work is distributed across participating nodes.
The 'leader' node coordinates the process of solving through shared solver state.
Let's collect all solutions for 8-Queens problem using distributed solving.
For demonstration purposes, we will spawn peer nodes like so:
```zsh
iex --name leader --cookie solver -S mix
```
```elixir
### Let's spawn 2 worker nodes...
worker_nodes = Enum.map(["node1", "node2"], fn node ->
{:ok, _pid, node_name} = :peer.start(%{name: node, longnames: true, args: ['-setcookie', 'solver']})
:erpc.call(node_name, :code, :add_paths, [:code.get_path()])
node_name
end)
```
Then we'll pass spawned worker nodes to the solver:
```elixir
## To convince ourselves that the solving runs on worker nodes, we'll use a solution handler:
solution_handler = fn solution -> IO.puts("#{inspect Enum.map(solution, fn {_name, solution} -> solution end)} <- #{inspect Node.self()}") end
{:ok, _solver} = CPSolver.solve_async(CPSolver.Examples.Queens.model(8),
distributed: worker_nodes,
solution_handler: solution_handler)
```
### Search
*Fixpoint* allows to specify strategies for searching for feasible and/or optimal solutions.
This is controlled by `:search` option, which is a tuple {`variable_choice`, `value_choice`}.
Generally, `variable_choice` is either an implementation of [variable selector](lib/solver/search/variable_selector.ex), or an identificator of out-of-box implementation that fronts such an implementation.
Likewise, `value_choice` is either an implementation of [value partition](lib/solver/search/value_partition.ex), or an identificator of out-of-box implementation.
Available standard search strategies:
- For `variable_choice`:
- `:first_fail` : choose the unfixed variable with smallest domain size
- `:input_order` : choose the first unfixed variable in the order defined by the model
- For `value_choice`
- `:indomain_min, :indomain_max, :indomain_random` : choose minimal, maximal and random value from the variable domain, respectively
Default search strategy is `{:first_fail, :indomain_min}`
The choice of search strategy may significantly affect the performance of solving,
as the following example shows:
#### Let's use some out-of-box strategies for solving an instance of Knapsack problem,
```elixir
alias CPSolver.Examples.Knapsack
## First, use the default strategy
{:ok, results} = CPSolver.solve(Knapsack.tourist_knapsack_model())
results.statistics
%{
elapsed_time: 689543,
solution_count: 114,
active_node_count: 0,
failure_count: 1614,
node_count: 3455
}
## Now, use the :indomain_max for the value choice.
## Decision variables for items have {0,1} domain, where 1 means that the item will be packed.
## Hence, :indomain_max tells the solver to try to include the items first (i.e. choose 1 over 0).
##
##
{:ok, results} = CPSolver.solve(Knapsack.tourist_knapsack_model(), search: {:first_fail, :indomain_max})
iex(main@zephyr.local)21> results.statistics
%{
elapsed_time: 301501,
solution_count: 14,
active_node_count: 0,
failure_count: 693,
node_count: 1413
}
```
The solution time for :indomain_max is more than twice less compared to the default value choice strategy
## [Examples](lib/examples)
#### [Reindeer Ordering](lib/examples/reindeers.ex)
Shows how to put together a model that solves a simple riddle.
#### [N-Queens](lib/examples/queens.ex)
Classical N-Queens problem
#### [Sudoku](lib/examples/sudoku.ex)
No explanation needed :-)
#### [SEND+MORE=MONEY](lib/examples/send_more_money.ex)
Cryptoarithmetics problem - a riddle that involves arithmetics.
#### [Knapsack](lib/examples/knapsack.ex)
Constraint Optimization Problem - packing items so they fit the knapsack ***and*** maximize the total value. Think Indiana Jones trying to fill his backpack with treasures in the best way possible :-)
#### [Quadratic Assignment](lib/examples/qap.ex)
Constraint Optimization Problem - assign facilities to locations so the cost of moving goods between facilities is minimized.
#### [Travelling Salesman problem](lib/examples/tsp.ex)
https://en.wikipedia.org/wiki/Travelling_salesman_problem
#### [SAT Solver](lib/examples/sat_solver.ex)
#### [Stable Marriage problem](lib/examples/stable_marriage)
https://en.wikipedia.org/wiki/Stable_marriage_problem
#### [`xkcd` comic](livebooks/xkcd_np.livemd)
Two combinatorial problems from https://xkcd.com/287/
#### [Fixpoint models created by HÃ¥kan Kjellerstrand](http://hakank.org/elixir/)
#### [Zebra puzzle](https://en.wikipedia.org/wiki/Zebra_Puzzle)