{"id":16701863,"url":"https://github.com/bokner/fixpoint","last_synced_at":"2025-04-05T22:08:57.187Z","repository":{"id":193603256,"uuid":"651728799","full_name":"bokner/fixpoint","owner":"bokner","description":"Constraint programming solver","archived":false,"fork":false,"pushed_at":"2025-03-20T21:03:39.000Z","size":4342,"stargazers_count":58,"open_issues_count":6,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-29T21:08:09.446Z","etag":null,"topics":["constraint-programming","discrete-optimization","elixir-lang","operations-research"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bokner.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-09T23:37:55.000Z","updated_at":"2025-03-20T21:03:42.000Z","dependencies_parsed_at":"2024-04-16T22:01:22.327Z","dependency_job_id":"ece315c4-232e-4623-97d4-520635cfe1ba","html_url":"https://github.com/bokner/fixpoint","commit_stats":null,"previous_names":["bokner/cpsolver"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bokner%2Ffixpoint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bokner%2Ffixpoint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bokner%2Ffixpoint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bokner%2Ffixpoint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bokner","download_url":"https://codeload.github.com/bokner/fixpoint/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247406091,"owners_count":20933803,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["constraint-programming","discrete-optimization","elixir-lang","operations-research"],"created_at":"2024-10-12T18:45:57.942Z","updated_at":"2025-04-05T22:08:57.160Z","avatar_url":"https://github.com/bokner.png","language":"Elixir","readme":"# Constraint Programming Solver\n\n## The approach \nThe implementation follows the ideas described in Chapter 12, \"Concepts, Techniques, and Models\n  of Computer Programming\" by Peter Van Roy and Seif Haridi.\n\n[An overview of CP implementation in Mozart/Oz.](http://mozart2.org/mozart-v1/doc-1.4.0/fdt/index.html)\n## Status\n\nProof of concept. Not suitable for use in production. Significant API changes and core implementation rewrites are expected.\n\n## Intro\n\n[![Run in Livebook](https://livebook.dev/badge/v1/black.svg)](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Fbokner%2Ffixpoint%2Fblob%2Fmain%2Flivebooks%2Ffixpoint.livemd)\n\n\n## Implemented constraints\n\n- `equal`, `not_equal`, `less_or_equal`\n- `absolute`\n- `all_different`\n- `inverse`\n- `sum`, `modulo`\n- `element`, `element2d`\n- `circuit`\n- `OR`\n\n## Features\n- views (linear combinations of variables in constraints)\n- partial support for reified constraints  \n- solving constraint satisfaction (CSP) and constrained optimization (COP) problems\n- parallel search\n- pluggable search strategies\n- distributed solving  \n\n## Installation\nThe package can be installed by adding `fixpoint` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:fixpoint, \"~\u003e 0.8.28\"}\n  ]\nend\n```\n\n## Usage\n  \n### Getting started  \n\nLet's solve the following *constraint satisfaction problem*:\n\n***Given two sets of values***\n\n x = {1,2}, y = {0, 1}\n\n***, find all solutions such that*** $x$ $\\neq$ $y$\n\nFirst step is to create a model that describes the problem we want to solve.\nThe model consists of *variables* and *constraints* over the variables.\nIn this example, we have 2 variables $x$ and $y$ and a single constraint $x$ $\\neq$ $y$\n\n```elixir\nalias CPSolver.IntVariable\nalias CPSolver.Constraint.NotEqual\nalias CPSolver.Model\n## Variable constructor takes a domain (i.e., set of values), and optional parameters, such as `name`\nx = IntVariable.new([1, 2], name: \"x\")\ny = IntVariable.new([0, 1], name: \"y\")\n## Create NotEqual constraint\nneq_constraint =  NotEqual.new(x, y)\n```\nNow create an instance of `CPSolver.Model`:\n```elixir\nmodel = Model.new([x, y], [neq_constraint])\n```\nOnce we have a model, we pass it to `CPSolver.solve/1,2`.\n\nWe can either solve asynchronously:\n```elixir\n## Asynchronous solving doesn't block \n{:ok, solver} = CPSolver.solve_async(model)\nProcess.sleep(10)\n## We can check for solutions and solver state and/or stats,\n## for instance:\n## There are 3 solutions: {x = 1, y = 0}, {x = 2,  y = 0}, {x = 2, y = 1} \niex(46)\u003e CPSolver.solutions(solver)\n[[1, 0], [2, 0], [2, 1]]\n\n## Solver reports it has found all solutions    \niex(47)\u003e CPSolver.status(solver)\n:all_solutions \n\n## Some stats\niex(48)\u003e CPSolver.statistics(solver)\n%{\n  elapsed_time: 2472,\n  solution_count: 3,\n  active_node_count: 0,\n  failure_count: 0,\n  node_count: 5\n}\n\n```\n, or use a blocking call:\n```elixir\niex(49)\u003e {:ok, results} = CPSolver.solve(model)\n{:ok,\n %{\n   status: :all_solutions,\n   statistics: %{\n     elapsed_time: 3910,\n     solution_count: 3,\n     active_node_count: 0,\n     failure_count: 0,\n     node_count: 5\n   },\n   variables: [\"x\", \"y\"],\n   objective: nil,\n   solutions: [[2, 1], [1, 0], [2, 0]]\n }}\n```\n\n\n\n\n### API\n```elixir\n#################\n# Solving       \n#################\n# \n# Asynchronous solving.\n# Takes CPSolver.Model instance and solver options as a Keyword. \n# Creates a solver process that runs asynchronously\n# and could be controlled and queried for produced solutions and/or status as it runs.\n# The solver process is alive even after the solving is completed.\n# It's the responsibility of a caller to dispose of it when no longer needed.\n# (by calling CPSolver.dispose/1)\n  \n{:ok, solver} = CPSolver.solve_async(model, solver_opts)\n\n# Synchronous solving.\n# Takes CPSolver.Model instance and solver options as a Keyword. \n# Starts the solver and gets the results (solutions and/or solver stats) once the solver finishes.\n{:ok, solver_results} = CPSolver.solve(model, solver_opts)\n```\n\n, where \n- ```model``` - [specification of the model](#model-specification);\n- ```solver_opts (optional)``` - [solver options](#configuring-solver).\n\n### Model specification\n\n#### For CSP (constraint satisfaction problem):\n\n```elixir\nmodel = CPSolver.Model.new(variables, constraints)\n```\n, where\n-  `variables` is a list of [variables](lib/solver/variables/variable.ex) up to a concrete implementation.\n  \n  Currently, the only implementation supported is for [variables over integer finite domain](lib/solver/variables/int_variable.ex).\n- `constraints` is a list of [constraints](lib/solver/core/constraint.ex). \n  \n  [Available constraints](lib/solver/constraints/)\n\n#### For COP (constraint optimization problem):\n  \n```elixir\nmodel = CPSolver.Model.new(variables, constraints, objective: objective)\n```\nThe same as for CSP, but with additional `:objective` option. The objective is constructed by using\n`CPSolver.Objective.minimize/1` and `CPSolver.Objective.maximize/1`. \n\n[Example of COP model](lib/examples/knapsack.ex) \n\n### Configuring solver\n\nAvailable options:\n\n- solution_handler: function()\n\n  A callback that gets called performed every time the solver finds a new solution. The single argument is a list of tuples\n\n  `{variable_name, variable_value}`\n\n- timeout: integer()\n  \n  Time to wait (in milliseconds) for terminating `CPSolver.solve_sync/2` call. Defaults to 30_000.\n- stop_on: term() | condition_fun()\n  \n  Condition for stopping the solving. Currently, only `{:max_solutions, max_solutions}` condition is available.\n  Defaults to `nil`.\n\n- search: {variable_choice(), value_choice()}\n  \n  [Search strategy](#search). \n\n- space_threads: integer()\n\n  Defines the number of processes for parallel search. Defaults to 8.\n\n- distributed: boolean() | [Node.t()]\n  \n  If `true`, all connected nodes will participate in distributed solving.\n  Alternatively, one can specify the sublist of connected nodes.\n  Defaults to `false`.\n    \n\n### Distributed solving\n\n*Fixpoint* allows to solve an instance of CSP/COP problem using multiple cluster nodes.\n\nNote: *Fixpoint* **will not configure the cluster nodes!** \nIt's assumed that each node has the cluster membership and the `fixpoint` dependency is installed on it.\nThe solving starts on a 'leader' node, and then the work is distributed across participating nodes.\nThe 'leader' node coordinates the process of solving through shared solver state.\n\nLet's collect all solutions for 8-Queens problem using distributed solving.\n\nFor demonstration purposes, we will spawn peer nodes like so:\n\n```zsh\niex --name leader --cookie solver -S mix\n```\n\n```elixir\n### Let's spawn 2 worker nodes...\nworker_nodes = Enum.map([\"node1\", \"node2\"], fn node -\u003e \n  {:ok, _pid, node_name} = :peer.start(%{name: node, longnames: true, args: ['-setcookie', 'solver']})\n  :erpc.call(node_name, :code, :add_paths, [:code.get_path()])\n  node_name\nend)\n```\n\nThen we'll pass spawned worker nodes to the solver: \n\n```elixir\n## To convince ourselves that the solving runs on worker nodes, we'll use a solution handler:\nsolution_handler = fn solution -\u003e IO.puts(\"#{inspect Enum.map(solution, fn {_name, solution} -\u003e solution end)} \u003c- #{inspect Node.self()}\") end \n{:ok, _solver} = CPSolver.solve_async(CPSolver.Examples.Queens.model(8), \n  distributed: worker_nodes, \n  solution_handler: solution_handler)\n``` \n\n\n### Search\n\n*Fixpoint* allows to specify strategies for searching for feasible and/or optimal solutions.\n\nThis is controlled by `:search` option, which is a tuple {`variable_choice`, `value_choice`}.\n\nGenerally, `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. \n\nLikewise, `value_choice` is either an implementation of [value partition](lib/solver/search/value_partition.ex), or an identificator of out-of-box implementation.\n\nAvailable standard search strategies:\n\n- For `variable_choice`: \n  - `:first_fail` : choose the unfixed variable with smallest domain size\n  - `:input_order` : choose the first unfixed variable in the order defined by the model \n\n- For `value_choice`\n  - `:indomain_min, :indomain_max, :indomain_random` : choose minimal, maximal and random value from the variable domain, respectively\n\nDefault search strategy is `{:first_fail, :indomain_min}`\n\n\nThe choice of search strategy may significantly affect the performance of solving,\n\nas the following example shows: \n\n#### Let's use some out-of-box strategies for solving an instance of Knapsack problem,\n\n\n```elixir\nalias CPSolver.Examples.Knapsack\n## First, use the default strategy\n{:ok, results} = CPSolver.solve(Knapsack.tourist_knapsack_model())\nresults.statistics\n\n%{\n  elapsed_time: 689543,\n  solution_count: 114,\n  active_node_count: 0,\n  failure_count: 1614,\n  node_count: 3455\n}\n\n## Now, use the :indomain_max for the value choice. \n## Decision variables for items have {0,1} domain, where 1 means that the item will be packed.\n## Hence, :indomain_max tells the solver to try to include the items first (i.e. choose 1 over 0).\n## \n##\n{:ok, results} = CPSolver.solve(Knapsack.tourist_knapsack_model(), search: {:first_fail, :indomain_max})\n\niex(main@zephyr.local)21\u003e results.statistics\n%{\n  elapsed_time: 301501,\n  solution_count: 14,\n  active_node_count: 0,\n  failure_count: 693,\n  node_count: 1413\n}\n```\nThe solution time for :indomain_max is more than twice less compared to the default value choice strategy\n\n## [Examples](lib/examples)\n\n#### [Reindeer Ordering](lib/examples/reindeers.ex)\n\nShows how to put together a model that solves a simple riddle.\n\n#### [N-Queens](lib/examples/queens.ex)\n\nClassical N-Queens problem\n\n#### [Sudoku](lib/examples/sudoku.ex)\n\nNo explanation needed :-)\n\n#### [SEND+MORE=MONEY](lib/examples/send_more_money.ex)\n\nCryptoarithmetics problem - a riddle that involves arithmetics.\n\n#### [Knapsack](lib/examples/knapsack.ex)\n\nConstraint 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 :-)\n\n#### [Quadratic Assignment](lib/examples/qap.ex)\n\nConstraint Optimization Problem - assign facilities to locations so the cost of moving goods between facilities is minimized.\n\n#### [Travelling Salesman problem](lib/examples/tsp.ex)\n\nhttps://en.wikipedia.org/wiki/Travelling_salesman_problem\n\n#### [SAT Solver](lib/examples/sat_solver.ex)\n\n#### [Stable Marriage problem](lib/examples/stable_marriage)\n\nhttps://en.wikipedia.org/wiki/Stable_marriage_problem\n \n#### [`xkcd` comic](livebooks/xkcd_np.livemd)\n\nTwo combinatorial problems from https://xkcd.com/287/\n\n#### [Fixpoint models created by Håkan Kjellerstrand](http://hakank.org/elixir/)\n\n#### [Zebra puzzle](https://en.wikipedia.org/wiki/Zebra_Puzzle)\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbokner%2Ffixpoint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbokner%2Ffixpoint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbokner%2Ffixpoint/lists"}