We want to redefine Coef to be Rational32. However, we want to be
able to build linear expressions as easily as before. We will do this
by retaining the overloads with i32, and introduce new overloads for
operations with Rational32s.
This constraint unifies two variables. This is done by substituting
one variable for another in all of the current constraints, resulting
a new set of constraints.
We would like to make the equality constraint perform the more general
substitution: "coef1 var1 -> coef2 var2 + constant". While this
constraint is less general, it can be used when the variables are not
integer quantities.
Separate the list of puzzle constraints from the puzzle structure.
This allows different stages of the puzzle solution search to have
different constraints.
Functions to manipulate candidates during the solution search now
always return a Result instead of sometimes returning a bool, and
other times returning an Option.
We opted to use Result over Option mainly to (ab)use the try! macro.
In addition, it also makes it emit a warning when we forget to handle
the result. (We tend to assume people don't just ignore the result
and continue as normal, instead of putting the puzzle in a special
contradiction detected state.)
Constraints must now implement the substitution of "from" with "to".
The implementations can be sanity checked when we come to build the
list of constraints that each variable wakes up.
In the future, we would like to make the more general substitution:
from -> scale * to + constant.
We will implement unification by substituting one variable for
another, creating a new set of constraints. Only the affected
constraints need to perform the substitution, the others can simply be
cloned. As such, we will store constraints using reference counting.
This function takes a LHS and RHS only to give the illusion of an
equality. It simply subtracts one from the other because our
equations are all in the form: coef_i var_i + constant == 0.
This is a very simple equality constraint. It only assigns a variable
when all other variables in the equation have been assigned. Possible
improvements:
- We can find the min and max range by considering the coefficients
and the max and min values of the other variables in the equation.
- When there are only 2 variables remaining, we can eliminate the
incompatible candidates and then substitute one variable for the
other in other constraints.
While these functions can be found in rational number libraries,
support for rational numbers is an extra complexity that we do not
wish to tackle at this stage. We'll use these to keep things simple.
This allows one to easily build linear constraint equations by writing
equations with the tokens.
Note that variables will always take integer values, but coefficients
may be generalised to support rationals.
The constraint creation functions now take an IntoIterator allowing
you to use generic collections, and even perform maps and filters.
This is useful, for example, when the variables are in a 2d array.
This is a very simple all different constraint, e.g.
- It does not know that if there are more variables than candidates,
it has reached a contradiction.
- It does not know that, if there are as many variables as candidates,
if only one variable can take on a candidate value, then all other
candidates for that variable can be eliminated.
Each constraint is a little sub-program that attempts to reduce the
search space. They should only be run as required, i.e. when the
candidates of the variables they use were updated, though this has not
yet been implemented.
Constraints are traits so that puzzles may implement their own
specialised contraints as required. We have split the trait into an
"on_assigned" call and an "on_updated" call for clarity. The provided
methods simply return true, indicating no contradiction.
The puzzle search type is a puzzle with some variables assigned. It
will be passed (mutably) to constraints for them to do their thing.
All changes to a variables' candidates must go through this object so
that we can wake up affected constraints.
When a valid solution has been found, we will create a Solution
structure. The values assigned to the puzzle variables can be
accessed using the indexing notation: solution[var].
Candidates are always an i32 (rather than a generic integer type) to
more easily perform arithmetic. The puzzles we will deal with should
always be small enough such that we do not exceed the limits of i32.
Candidate groups are separated into an enum. This is to help us keep
it general, as we would like to include a range option in the future.
We have chosen to return a variable token for every variable created,
instead of referencing puzzle variables by string or something else.
Puzzle variables should only be used in the puzzle that created it,
though this is neither checked nor enforced.
Eventually, we would also like to overload the standard arithmetic
symbols to ergonomically build constraint expressions.