Files
solver/tests/test_expr.py
forbes-0023 98051ba0c9 feat: add Phase 1 constraint solver addon, move prior content to GNN/
- Move existing OndselSolver, GNN ML layer, and tooling into GNN/
  directory for integration in later phases
- Add Create addon scaffold: package.xml, Init.py
- Add expression DAG with eval, symbolic diff, simplification
- Add parameter table with fixed/free variable tracking
- Add quaternion rotation as polynomial Expr trees
- Add RigidBody entity (7 DOF: position + unit quaternion)
- Add constraint classes: Coincident, DistancePointPoint, Fixed
- Add Newton-Raphson solver with symbolic Jacobian + numpy lstsq
- Add pre-solve passes: substitution + single-equation
- Add DOF counting via Jacobian SVD rank
- Add KindredSolver IKCSolver bridge for kcsolve integration
- Add 82 unit tests covering all modules

Registers as 'kindred' solver via kcsolve.register_solver() when
loaded by Create's addon_loader.
2026-02-20 20:35:47 -06:00

196 lines
4.5 KiB
Python

"""Tests for the expression DAG."""
import math
import pytest
from kindred_solver.expr import (
ONE,
ZERO,
Add,
Const,
Cos,
Div,
Mul,
Neg,
Pow,
Sin,
Sqrt,
Sub,
Var,
)
class TestConst:
def test_eval(self):
assert Const(3.0).eval({}) == 3.0
def test_diff(self):
assert Const(5.0).diff("x") == ZERO
def test_eq(self):
assert Const(1.0) == Const(1.0)
assert Const(1.0) != Const(2.0)
def test_vars(self):
assert Const(1.0).vars() == set()
class TestVar:
def test_eval(self):
assert Var("x").eval({"x": 7.0}) == 7.0
def test_diff_self(self):
assert Var("x").diff("x") == ONE
def test_diff_other(self):
assert Var("x").diff("y") == ZERO
def test_vars(self):
assert Var("x").vars() == {"x"}
class TestOperators:
def test_add(self):
x = Var("x")
e = x + 3.0
assert isinstance(e, Add)
assert e.eval({"x": 2.0}) == 5.0
def test_radd(self):
x = Var("x")
e = 3.0 + x
assert e.eval({"x": 2.0}) == 5.0
def test_sub(self):
x = Var("x")
e = x - 1.0
assert e.eval({"x": 5.0}) == 4.0
def test_mul(self):
x = Var("x")
e = x * 2.0
assert e.eval({"x": 3.0}) == 6.0
def test_div(self):
x = Var("x")
e = x / 2.0
assert e.eval({"x": 6.0}) == 3.0
def test_neg(self):
x = Var("x")
e = -x
assert e.eval({"x": 3.0}) == -3.0
def test_pow(self):
x = Var("x")
e = x**2
assert e.eval({"x": 3.0}) == 9.0
class TestDiff:
def test_add(self):
x = Var("x")
e = x + Const(5.0)
d = e.diff("x").simplify()
assert d.eval({}) == 1.0
def test_mul_product_rule(self):
x = Var("x")
e = x * x # x^2
d = e.diff("x").simplify()
# d/dx(x*x) = x + x = 2x
assert d.eval({"x": 3.0}) == 6.0
def test_pow_const_exp(self):
x = Var("x")
e = Pow(x, Const(3.0)) # x^3
d = e.diff("x").simplify()
# d/dx(x^3) = 3x^2
assert abs(d.eval({"x": 2.0}) - 12.0) < 1e-10
def test_sin(self):
x = Var("x")
e = Sin(x)
d = e.diff("x").simplify()
# d/dx sin(x) = cos(x)
assert abs(d.eval({"x": 0.0}) - 1.0) < 1e-10
def test_cos(self):
x = Var("x")
e = Cos(x)
d = e.diff("x").simplify()
# d/dx cos(x) = -sin(x)
assert abs(d.eval({"x": 0.0}) - 0.0) < 1e-10
def test_div_quotient_rule(self):
x = Var("x")
e = Div(x, x + Const(1.0)) # x / (x+1)
d = e.diff("x")
# d/dx x/(x+1) = 1/(x+1)^2
val = d.eval({"x": 1.0})
assert abs(val - 0.25) < 1e-10
def test_sqrt(self):
x = Var("x")
e = Sqrt(x)
d = e.diff("x")
# d/dx sqrt(x) = 1/(2*sqrt(x))
val = d.eval({"x": 4.0})
assert abs(val - 0.25) < 1e-10
class TestSimplify:
def test_add_zero(self):
x = Var("x")
e = Add(ZERO, x).simplify()
assert isinstance(e, Var) and e.name == "x"
def test_mul_one(self):
x = Var("x")
e = Mul(ONE, x).simplify()
assert isinstance(e, Var) and e.name == "x"
def test_mul_zero(self):
x = Var("x")
e = Mul(ZERO, x).simplify()
assert e == ZERO
def test_const_fold(self):
e = Add(Const(2.0), Const(3.0)).simplify()
assert isinstance(e, Const) and e.value == 5.0
def test_neg_neg(self):
x = Var("x")
e = Neg(Neg(x)).simplify()
assert isinstance(e, Var) and e.name == "x"
def test_pow_zero(self):
x = Var("x")
e = Pow(x, ZERO).simplify()
assert e == ONE
def test_pow_one(self):
x = Var("x")
e = Pow(x, ONE).simplify()
assert isinstance(e, Var) and e.name == "x"
def test_sub_zero(self):
x = Var("x")
e = Sub(x, ZERO).simplify()
assert isinstance(e, Var) and e.name == "x"
class TestComplex:
def test_polynomial(self):
"""Test a quadratic: 3x^2 + 2x + 1, derivative = 6x + 2."""
x = Var("x")
e = Const(3.0) * x * x + Const(2.0) * x + Const(1.0)
assert abs(e.eval({"x": 2.0}) - 17.0) < 1e-10
d = e.diff("x").simplify()
assert abs(d.eval({"x": 2.0}) - 14.0) < 1e-10
def test_vars_set(self):
x, y = Var("x"), Var("y")
e = x * y + Sin(x)
assert e.vars() == {"x", "y"}