lifebeyondfife / Decider

An Open Source .Net Constraint Programming Solver
MIT License
150 stars 21 forks source link

F# interop example #54

Closed toburger closed 3 years ago

toburger commented 3 years ago

Hi, I'm very interested in logic programming and found some interesting implementations in different programming languages. I wanted to use a CSP solver on dotnet and as my primary language is F# I wanted to try it out in a F# script and evolve it then into a DSL that fits my needs.

This is the (1:1) translated script from the readme written in F#:

#r "nuget: Decider, 0.5.1"

open System
open Decider.Csp.Integer
open Decider.Csp.Global
open Decider.Csp.BaseTypes

let s = VariableInteger("s", 0, 9)
let e = VariableInteger("e", 0, 9)
let n = VariableInteger("n", 0, 9)
let d = VariableInteger("d", 0, 9)
let m = VariableInteger("m", 1, 9)
let o = VariableInteger("o", 0, 9)
let r = VariableInteger("r", 0, 9)
let y = VariableInteger("y", 0, 9)
let c0 = VariableInteger("c0", 0, 1)
let c1 = VariableInteger("c1", 0, 1)
let c2 = VariableInteger("c2", 0, 1)
let c3 = VariableInteger("c3", 0, 1)

let (==) left right = ExpressionInteger.(=)(left, right)
let expr i = ExpressionInteger(i)

let constraints: IConstraint list = [
    AllDifferentInteger([ s; e; n; d; m; o; r; y ])
    ConstraintInteger(d + e == (expr 10 * c0) + y)
    ConstraintInteger(n + r + c0 == (expr 10 * c1) + e)
    ConstraintInteger(e + o + c1 == (expr 10 * c2) + n)
    ConstraintInteger(s + m + c2 == (expr 10 * c3) + o)
    ConstraintInteger(c3 == m)
]

let variables: IVariable<int> list = [ c0; c1; c2; c3; s; e; n; d; m; o; r; y ]
let state = StateInteger(variables, constraints)
let searchResult = state.Search()

Console.WriteLine($"    {s} {e} {n} {d} ")
Console.WriteLine($"  + {m} {o} {r} {e} ")
Console.WriteLine($"  ---------")
Console.WriteLine($"  {m} {o} {n} {e} {y} ")

Console.WriteLine("Runtime:\t{0}\nBacktracks:\t{1}\n", state.Runtime, state.Backtracks)

I had to introduce two helper functions:

The explicit type hints (IConstraint list, IVariable<int> list) are not something a F# developer is used to deal with often but necessary, as the list cannot infer the common base class. But this inconvenience can be easily solved by either introduce some helper functions or introduce computation expressions that help declare the constraints and the variables.

Note that this is a very naive translation from the C# code into F#. But I can imagine that out of this plumbing code could emerge a much friendlier DSL. :relaxed:

This is NOT a complaint about anything! It is merely intended to help the interested F# developer to getting started with this library. Thanks for this library!

lifebeyondfife commented 3 years ago

Thanks for the write-up, @toburger. This is fantastic.

I made a decision when starting the library to consider the constraint, variable, and domain types generic despite only implementing them for integers to begin with. This may have made more sense if I ever came back to implement more types, but I've lived with the base classes since then.

I like the proof of concept you have developed. If you'd allow, I could include this as an example solution, and reference in the README.md that it's possible to use Decider for F# as well as C#? (with credit to yourself, of course)

toburger commented 3 years ago

Of course, feel free to use my code as an example! :relaxed:


I have a very simple example which seems a bit childish but I think it explains very well a sample domain space where such CPS are very useful:

True story:

This is the solution of a homework the son of a friend had to solve during homeschooling and he (the father) asked a couple of his (not necessary developer) friends on how to solve the following problem as a formula:

Given a kickboard has three rolls and a city roller has two rolls we want to know how many kickboards and how many city rollers are parked.

We know two things:

Now being a developer I wanted to solve it programmatically, so here we go! 😄

open Decider.Csp.Integer
open Decider.Csp.BaseTypes

let solve rolls rollers =
    let (==) left right = ExpressionInteger.(=)(left, right)
    let expr i = ExpressionInteger(i)

    let kickboards = VariableInteger("kickboards", 0, rollers)
    let cityrollers = VariableInteger("cityrollers", 0, rollers)

    let constraints: IConstraint list = [
        ConstraintInteger(kickboards + cityrollers == expr rollers)
        ConstraintInteger(kickboards * expr 3 + cityrollers * expr 2 == expr rolls)
    ]

    let variables: IVariable<int> list = [
        kickboards
        cityrollers
    ]

    let state = StateInteger(variables, constraints)
    match state.Search() with
    | StateOperationResult.Solved ->
        Some {| Kickboards = kickboards.Value
                Cityrollers = cityrollers.Value |}
    | _ ->
        None

solve 37 15
// val it : {| Cityrollers: int; Kickboards: int |} option =
// Some { Cityrollers = 8
//               Kickboards = 7 }
lifebeyondfife commented 3 years ago

Thanks for this, Tobias. I'm appreciative for sharing and for your time. You can see the above example in the following commit https://github.com/lifebeyondfife/Decider/commit/88622380a77f80f375b0456773b7cbe5e4476516

I've added a copyright notice in the file for your work, and an acknowledgement to @toburger in the README.md. Happy to accept a PR if you want to make my tweaking less messy / more authentically F#.