0 = y + 2a second step should suffice to finde the solution. Here is a test case that wants to prove this:
0 = x + y
[Test]
public void Test2SimpleConstraintsAndTwoSteps() {
// Try to solve:
// 0 = y + 2
// 0 = x + y
...set as in previous test...
// First step
IEnumerable<SolverNode> expanded1 =
SolverNode.SolverStep(new[] { current },
new Dictionary<Variable, VariableRangeRestriction>(),
out solutionOrNull);
// Second step: Find that x = 2, substitute 2 for x everywhere,
// and declare the equations to be solved.
IEnumerable<SolverNode> expanded2 =
SolverNode.SolverStep(expanded1,
new Dictionary<Variable, VariableRangeRestriction>(),
out solutionOrNull);
Assert.AreEqual(1, expanded2.Count());
Assert.IsNotNull(solutionOrNull);
}
public void Test2SimpleConstraintsAndTwoSteps() {
// Try to solve:
// 0 = y + 2
// 0 = x + y
...set as in previous test...
// First step
IEnumerable<SolverNode> expanded1 =
SolverNode.SolverStep(new[] { current },
new Dictionary<Variable, VariableRangeRestriction>(),
out solutionOrNull);
// Second step: Find that x = 2, substitute 2 for x everywhere,
// and declare the equations to be solved.
IEnumerable<SolverNode> expanded2 =
SolverNode.SolverStep(expanded1,
new Dictionary<Variable, VariableRangeRestriction>(),
out solutionOrNull);
Assert.AreEqual(1, expanded2.Count());
Assert.IsNotNull(solutionOrNull);
}
However, this test is red: solutionOrNull remains null. If we look into our solver code, it is clear why: Towards the end of our first attempt of a solver algorithm, I stated that we have to
- record that –C is a solution for V;
- create the rewriting V → –C.
VariableValueRestrictions
First, we need a tiny class that remembers that a variable has a certain value. As we started with a general class "VariableRangeRestriction" in the posting mentioned, we create an abstract class with the former name, and a derived class that can only hold a single value:
public abstract class VariableRangeRestriction {
private readonly Variable _variable;
protected VariableRangeRestriction(Variable variable) {
_variable = variable;
}
public Variable Variable { get { return _variable; } }
}
public class VariableValueRestriction : VariableRangeRestriction {
private readonly double _value;
public VariableValueRestriction(Variable variable, double value)
: base(variable) {
_value = value;
}
public double Value { get { return _value; } }
}
private readonly Variable _variable;
protected VariableRangeRestriction(Variable variable) {
_variable = variable;
}
public Variable Variable { get { return _variable; } }
}
public class VariableValueRestriction : VariableRangeRestriction {
private readonly double _value;
public VariableValueRestriction(Variable variable, double value)
: base(variable) {
_value = value;
}
public double Value { get { return _value; } }
}
With this, we can now improve the code of FindBestRewrite—we add knowledge about the variable and its definitive value:
private VariableToConstantRewrite FindBestRewrite(...) {
...
foreach (var c in Constraints.OfType<ScalarConstraint>()) {
...
if (matcher.TryMatch(c)) {
Variable variable = matcher.Match(v);
double value = -matcher.Match(e).Value;
_variableInRangeKnowledges.Add(variable,
new VariableValueRestriction(variable, value));
return new VariableToConstantRewrite(variable, value);
}
}
throw new NotSupportedException("I found no 0=V+E-Constraint");
}
...
foreach (var c in Constraints.OfType<ScalarConstraint>()) {
...
if (matcher.TryMatch(c)) {
Variable variable = matcher.Match(v);
double value = -matcher.Match(e).Value;
_variableInRangeKnowledges.Add(variable,
new VariableValueRestriction(variable, value));
return new VariableToConstantRewrite(variable, value);
}
}
throw new NotSupportedException("I found no 0=V+E-Constraint");
}
Removing 0 = C
One test run later, we know that this was not enough. A bit of debugging shows why: The second node created actually does have VariableValueRestrictions for both variables, so it did find the solution. But our method to find a solution node was to check for one without constraints—however, our rewritings left us with two constraints of the form 0 = 0 that were never removed. Therefore, we never arrived at a node without constraints.
Obviously, there are two ways to solve the problem:
- Throw away constraints of the form 0 = 0.
- Replace the solution check with another one.
- If we find such a constraint with constant zero, then we need a "rewrite" that just removes this constraint.
- If we find one with a constant other than zero, our constraints want to state (probably after some rewriting) that "zero is not zero." In this case, the SolverNode's expansion must be an empty set!
private VariableToConstantRewrite FindBestRewrite(...) {
var z = new TypeMatchTemplate<Constant>();
var zeroEqualsConstant = new EqualsZeroConstraintTemplate(z);
...as before...
foreach (var c in Constraints.OfType<ScalarConstraint>()) {
var matcher2 = new ScalarConstraintMatcher(zeroEqualsConstant);
if (matcher2.TryMatch(c)) {
if (matcher2.Match(z).Value == 0) {
return new RemoveThisConstraint(c);
} else {
return new MarkAsDeadEnd(this);
}
}
...as before...
}
throw new NotSupportedException("I found no 0=V+E-Constraint");
}
var z = new TypeMatchTemplate<Constant>();
var zeroEqualsConstant = new EqualsZeroConstraintTemplate(z);
...as before...
foreach (var c in Constraints.OfType<ScalarConstraint>()) {
var matcher2 = new ScalarConstraintMatcher(zeroEqualsConstant);
if (matcher2.TryMatch(c)) {
if (matcher2.Match(z).Value == 0) {
return new RemoveThisConstraint(c);
} else {
return new MarkAsDeadEnd(this);
}
}
...as before...
}
throw new NotSupportedException("I found no 0=V+E-Constraint");
}
We now need two more rewriting classes (the ones that ReSharper shows red), and the return type of FindBestRewrite is now wrong ... Obviously, we need to redesign the code around FindBestRewrite and ExecuteRewrites somewhat. Here is a suggestion which might hold out for some time: Instead of writing more and more rewrite classes, we let FindBestRewrite return a Func that creates new SolverNodes; and instead of the separate method ExecuteRewrites, we just execute the returned Func. Expand then looks like this:
public IEnumerable<SolverNode> Expand(...) {
Func<IEnumerable<SolverNode>> rewrite =
FindBestRewrite(previousValues);
return rewrite();
}
Func<IEnumerable<SolverNode>> rewrite =
FindBestRewrite(previousValues);
return rewrite();
}
Here, then is the new FindBestRewrite method that checks each constraint against two patterns—first, against 0 = C, then against 0 = V + C:
private Func<IEnumerable<SolverNode>> FindBestRewrite(...) {
var z = new TypeMatchTemplate<Constant>();
var zeroEqualsConstant = new EqualsZeroConstraintTemplate(z);
var v = new TypeMatchTemplate<Variable>();
var e = new TypeMatchTemplate<Constant>();
var zeroEqualsVPlusE = new EqualsZeroConstraintTemplate(v + e);
foreach (var c in Constraints.OfType<ScalarConstraint>()) {
{
var matcher2 = new ScalarConstraintMatcher(zeroEqualsConstant);
if (matcher2.TryMatch(c)) {
if (matcher2.Match(z).Value == 0) {
// Just drop 0 = 0
return () => new[] {
new SolverNode(Constraints.Except(new[] {c}), this)
};
} else {
// Unsolvable!
return () => new SolverNode[0];
}
}
}
{
var matcher = new ScalarConstraintMatcher(zeroEqualsVPlusE);
if (matcher.TryMatch(c)) {
Variable variable = matcher.Match(v);
double value = -matcher.Match(e).Value;
_variableInRangeKnowledges.Add(variable,
new VariableValueRestriction(variable, value));
return () => {
...code from former ExecuteRewrites method...
};
}
}
}
throw new NotSupportedException("I found no 0=V+E-Constraint");
}
var z = new TypeMatchTemplate<Constant>();
var zeroEqualsConstant = new EqualsZeroConstraintTemplate(z);
var v = new TypeMatchTemplate<Variable>();
var e = new TypeMatchTemplate<Constant>();
var zeroEqualsVPlusE = new EqualsZeroConstraintTemplate(v + e);
foreach (var c in Constraints.OfType<ScalarConstraint>()) {
{
var matcher2 = new ScalarConstraintMatcher(zeroEqualsConstant);
if (matcher2.TryMatch(c)) {
if (matcher2.Match(z).Value == 0) {
// Just drop 0 = 0
return () => new[] {
new SolverNode(Constraints.Except(new[] {c}), this)
};
} else {
// Unsolvable!
return () => new SolverNode[0];
}
}
}
{
var matcher = new ScalarConstraintMatcher(zeroEqualsVPlusE);
if (matcher.TryMatch(c)) {
Variable variable = matcher.Match(v);
double value = -matcher.Match(e).Value;
_variableInRangeKnowledges.Add(variable,
new VariableValueRestriction(variable, value));
return () => {
...code from former ExecuteRewrites method...
};
}
}
}
throw new NotSupportedException("I found no 0=V+E-Constraint");
}
This is not yet a satisfying design—we expect that we will write many more such "if match then do" rules. That would create a FindBestRewrite method that gets longer and longer. However, for the moment, we keep this simple design, and run our test again. And it is still red.
A correct and complete test case
One second of thinking reveals why: We need four steps to solve our equations:
- Substitute –2 for y everywhere.
- Remove the resulting 0 = 0 constraint.
- Substitute 2 for x everywhere.
- Remove the second resulting 0 = 0 constraint.
[Test]
public void Test2SimpleConstraintsAndTwoSteps() {
// Try to solve:
// 0 = y + 2
// 0 = x + y
EqualsZeroConstraint e1 =
new EqualsZeroConstraint(
new NamedVariable("y") + new Constant(2));
EqualsZeroConstraint e2 =
new EqualsZeroConstraint(
new NamedVariable("x") + new NamedVariable("y"));
var current = new SolverNode(new[] { e1, e2 }, null);
SolverNode solutionOrNull;
var NoRestrictions =
new Dictionary<Variable, VariableRangeRestriction>();
// First step
IEnumerable<SolverNode> expanded1 =
SolverNode.SolverStep(new[] { current },
NoRestrictions,
out solutionOrNull);
Assert.IsNull(solutionOrNull);
// Second step: Remove 0 = 0
IEnumerable<SolverNode> expanded2 =
SolverNode.SolverStep(expanded1,
NoRestrictions,
out solutionOrNull);
Assert.IsNull(solutionOrNull);
// Third step: Find that x = 2, substitute 2 for x everywhere.
IEnumerable<SolverNode> expanded3 =
SolverNode.SolverStep(expanded2,
NoRestrictions,
out solutionOrNull);
Assert.IsNull(solutionOrNull);
// Fourth step: Remove 0 = 0.
IEnumerable<SolverNode> expanded4 =
SolverNode.SolverStep(expanded3,
NoRestrictions,
out solutionOrNull);
Assert.AreEqual(1, expanded4.Count());
// Check that we found the right solution.
Assert.IsNotNull(solutionOrNull);
AssertVariable(solutionOrNull.VariableKnowledge, -2, "y");
AssertVariable(solutionOrNull.VariableKnowledge, 2, "x");
}
private static void AssertVariable(
IDictionary<Variable, VariableRangeRestriction> solution,
double expected, string variablename) {
VariableValueRestriction varKnowledge =
solution[new NamedVariable(variablename)]
as VariableValueRestriction;
Assert.IsNotNull(varKnowledge);
Assert.AreEqual(expected, varKnowledge.Value, 1e-10);
}
public void Test2SimpleConstraintsAndTwoSteps() {
// Try to solve:
// 0 = y + 2
// 0 = x + y
EqualsZeroConstraint e1 =
new EqualsZeroConstraint(
new NamedVariable("y") + new Constant(2));
EqualsZeroConstraint e2 =
new EqualsZeroConstraint(
new NamedVariable("x") + new NamedVariable("y"));
var current = new SolverNode(new[] { e1, e2 }, null);
SolverNode solutionOrNull;
var NoRestrictions =
new Dictionary<Variable, VariableRangeRestriction>();
// First step
IEnumerable<SolverNode> expanded1 =
SolverNode.SolverStep(new[] { current },
NoRestrictions,
out solutionOrNull);
Assert.IsNull(solutionOrNull);
// Second step: Remove 0 = 0
IEnumerable<SolverNode> expanded2 =
SolverNode.SolverStep(expanded1,
NoRestrictions,
out solutionOrNull);
Assert.IsNull(solutionOrNull);
// Third step: Find that x = 2, substitute 2 for x everywhere.
IEnumerable<SolverNode> expanded3 =
SolverNode.SolverStep(expanded2,
NoRestrictions,
out solutionOrNull);
Assert.IsNull(solutionOrNull);
// Fourth step: Remove 0 = 0.
IEnumerable<SolverNode> expanded4 =
SolverNode.SolverStep(expanded3,
NoRestrictions,
out solutionOrNull);
Assert.AreEqual(1, expanded4.Count());
// Check that we found the right solution.
Assert.IsNotNull(solutionOrNull);
AssertVariable(solutionOrNull.VariableKnowledge, -2, "y");
AssertVariable(solutionOrNull.VariableKnowledge, 2, "x");
}
private static void AssertVariable(
IDictionary<Variable, VariableRangeRestriction> solution,
double expected, string variablename) {
VariableValueRestriction varKnowledge =
solution[new NamedVariable(variablename)]
as VariableValueRestriction;
Assert.IsNotNull(varKnowledge);
Assert.AreEqual(expected, varKnowledge.Value, 1e-10);
}
And finally, we have a green test → blog about it!
No comments:
Post a Comment