|
|
"""Tests for the ``sympy.simplify._cse_diff.py`` module.""" |
|
|
|
|
|
import pytest |
|
|
|
|
|
from sympy.core.symbol import (Symbol, symbols) |
|
|
from sympy.core.numbers import Integer |
|
|
from sympy.core.function import Function |
|
|
from sympy.core import Derivative |
|
|
from sympy.functions.elementary.exponential import exp |
|
|
from sympy.matrices.immutable import ImmutableDenseMatrix |
|
|
from sympy.physics.mechanics import dynamicsymbols |
|
|
from sympy.simplify._cse_diff import (_forward_jacobian, |
|
|
_remove_cse_from_derivative, |
|
|
_forward_jacobian_cse, |
|
|
_forward_jacobian_norm_in_cse_out) |
|
|
from sympy.simplify.simplify import simplify |
|
|
from sympy.matrices import Matrix, eye |
|
|
|
|
|
from sympy.testing.pytest import raises |
|
|
from sympy.functions.elementary.trigonometric import (cos, sin, tan) |
|
|
from sympy.simplify.trigsimp import trigsimp |
|
|
|
|
|
from sympy import cse |
|
|
|
|
|
|
|
|
w = Symbol('w') |
|
|
x = Symbol('x') |
|
|
y = Symbol('y') |
|
|
z = Symbol('z') |
|
|
|
|
|
q1, q2, q3 = dynamicsymbols('q1 q2 q3') |
|
|
|
|
|
|
|
|
k = Function('k')(x, y) |
|
|
f = Function('f')(k, z) |
|
|
|
|
|
zero = Integer(0) |
|
|
one = Integer(1) |
|
|
two = Integer(2) |
|
|
neg_one = Integer(-1) |
|
|
|
|
|
|
|
|
@pytest.mark.parametrize( |
|
|
'expr, wrt', |
|
|
[ |
|
|
([zero], [x]), |
|
|
([one], [x]), |
|
|
([two], [x]), |
|
|
([neg_one], [x]), |
|
|
([x], [x]), |
|
|
([y], [x]), |
|
|
([x + y], [x]), |
|
|
([x*y], [x]), |
|
|
([x**2], [x]), |
|
|
([x**y], [x]), |
|
|
([exp(x)], [x]), |
|
|
([sin(x)], [x]), |
|
|
([tan(x)], [x]), |
|
|
([zero, one, x, y, x*y, x + y], [x, y]), |
|
|
([((x/y) + sin(x/y) - exp(y))*((x/y) - exp(y))], [x, y]), |
|
|
([w*tan(y*z)/(x - tan(y*z)), w*x*tan(y*z)/(x - tan(y*z))], [w, x, y, z]), |
|
|
([q1**2 + q2, q2**2 + q3, q3**2 + q1], [q1, q2, q3]), |
|
|
([f + Derivative(f, x) + k + 2*x], [x]) |
|
|
] |
|
|
) |
|
|
|
|
|
|
|
|
def test_forward_jacobian(expr, wrt): |
|
|
expr = ImmutableDenseMatrix([expr]).T |
|
|
wrt = ImmutableDenseMatrix([wrt]).T |
|
|
jacobian = _forward_jacobian(expr, wrt) |
|
|
zeros = ImmutableDenseMatrix.zeros(*jacobian.shape) |
|
|
assert simplify(jacobian - expr.jacobian(wrt)) == zeros |
|
|
|
|
|
|
|
|
def test_process_cse(): |
|
|
x, y, z = symbols('x y z') |
|
|
f = Function('f') |
|
|
k = Function('k') |
|
|
expr = Matrix([f(k(x,y), z) + Derivative(f(k(x,y), z), x) + k(x,y) + 2*x]) |
|
|
repl, reduced = cse(expr) |
|
|
p_repl, p_reduced = _remove_cse_from_derivative(repl, reduced) |
|
|
|
|
|
x0 = symbols('x0') |
|
|
x1 = symbols('x1') |
|
|
|
|
|
expected_output = ( |
|
|
[(x0, k(x, y)), (x1, f(x0, z))], |
|
|
[Matrix([2 * x + x0 + x1 + Derivative(f(k(x, y), z), x)])] |
|
|
) |
|
|
|
|
|
assert p_repl == expected_output[0], f"Expected {expected_output[0]}, but got {p_repl}" |
|
|
assert p_reduced == expected_output[1], f"Expected {expected_output[1]}, but got {p_reduced}" |
|
|
|
|
|
|
|
|
def test_io_matrix_type(): |
|
|
x, y, z = symbols('x y z') |
|
|
expr = ImmutableDenseMatrix([ |
|
|
x * y + y * z + x * y * z, |
|
|
x ** 2 + y ** 2 + z ** 2, |
|
|
x * y + x * z + y * z |
|
|
]) |
|
|
wrt = ImmutableDenseMatrix([x, y, z]) |
|
|
|
|
|
replacements, reduced_expr = cse(expr) |
|
|
|
|
|
|
|
|
replacements_core, jacobian_core, precomputed_fs_core = _forward_jacobian_cse(replacements, reduced_expr, wrt) |
|
|
assert isinstance(jacobian_core[0], type(reduced_expr[0])), "Jacobian should be a Matrix of the same type as the input" |
|
|
|
|
|
|
|
|
replacements_norm, jacobian_norm, precomputed_fs_norm = _forward_jacobian_norm_in_cse_out( |
|
|
expr, wrt) |
|
|
assert isinstance(jacobian_norm[0], type(reduced_expr[0])), "Jacobian should be a Matrix of the same type as the input" |
|
|
|
|
|
|
|
|
jacobian = _forward_jacobian(expr, wrt) |
|
|
assert isinstance(jacobian, type(expr)), "Jacobian should be a Matrix of the same type as the input" |
|
|
|
|
|
|
|
|
def test_forward_jacobian_input_output(): |
|
|
x, y, z = symbols('x y z') |
|
|
expr = Matrix([ |
|
|
x * y + y * z + x * y * z, |
|
|
x ** 2 + y ** 2 + z ** 2, |
|
|
x * y + x * z + y * z |
|
|
]) |
|
|
wrt = Matrix([x, y, z]) |
|
|
|
|
|
replacements, reduced_expr = cse(expr) |
|
|
|
|
|
|
|
|
replacements_core, jacobian_core, precomputed_fs_core = _forward_jacobian_cse(replacements, reduced_expr, wrt) |
|
|
assert isinstance(replacements_core, type(replacements)), "Replacements should be a list" |
|
|
assert isinstance(jacobian_core, type(reduced_expr)), "Jacobian should be a list" |
|
|
assert isinstance(precomputed_fs_core, list), "Precomputed free symbols should be a list" |
|
|
assert len(replacements_core) == len(replacements), "Length of replacements does not match" |
|
|
assert len(jacobian_core) == 1, "Jacobian should have one element" |
|
|
assert len(precomputed_fs_core) == len(replacements), "Length of precomputed free symbols does not match" |
|
|
|
|
|
|
|
|
replacements_norm, jacobian_norm, precomputed_fs_norm = _forward_jacobian_norm_in_cse_out(expr, wrt) |
|
|
assert isinstance(replacements_norm, type(replacements)), "Replacements should be a list" |
|
|
assert isinstance(jacobian_norm, type(reduced_expr)), "Jacobian should be a list" |
|
|
assert isinstance(precomputed_fs_norm, list), "Precomputed free symbols should be a list" |
|
|
assert len(replacements_norm) == len(replacements), "Length of replacements does not match" |
|
|
assert len(jacobian_norm) == 1, "Jacobian should have one element" |
|
|
assert len(precomputed_fs_norm) == len(replacements), "Length of precomputed free symbols does not match" |
|
|
|
|
|
|
|
|
def test_jacobian_hessian(): |
|
|
L = Matrix(1, 2, [x**2*y, 2*y**2 + x*y]) |
|
|
syms = [x, y] |
|
|
assert _forward_jacobian(L, syms) == Matrix([[2*x*y, x**2], [y, 4*y + x]]) |
|
|
|
|
|
L = Matrix(1, 2, [x, x**2*y**3]) |
|
|
assert _forward_jacobian(L, syms) == Matrix([[1, 0], [2*x*y**3, x**2*3*y**2]]) |
|
|
|
|
|
|
|
|
def test_jacobian_metrics(): |
|
|
rho, phi = symbols("rho,phi") |
|
|
X = Matrix([rho * cos(phi), rho * sin(phi)]) |
|
|
Y = Matrix([rho, phi]) |
|
|
J = _forward_jacobian(X, Y) |
|
|
assert J == X.jacobian(Y.T) |
|
|
assert J == (X.T).jacobian(Y) |
|
|
assert J == (X.T).jacobian(Y.T) |
|
|
g = J.T * eye(J.shape[0]) * J |
|
|
g = g.applyfunc(trigsimp) |
|
|
assert g == Matrix([[1, 0], [0, rho ** 2]]) |
|
|
|
|
|
|
|
|
def test_jacobian2(): |
|
|
rho, phi = symbols("rho,phi") |
|
|
X = Matrix([rho * cos(phi), rho * sin(phi), rho ** 2]) |
|
|
Y = Matrix([rho, phi]) |
|
|
J = Matrix([ |
|
|
[cos(phi), -rho * sin(phi)], |
|
|
[sin(phi), rho * cos(phi)], |
|
|
[2 * rho, 0], |
|
|
]) |
|
|
assert _forward_jacobian(X, Y) == J |
|
|
|
|
|
|
|
|
def test_issue_4564(): |
|
|
X = Matrix([exp(x + y + z), exp(x + y + z), exp(x + y + z)]) |
|
|
Y = Matrix([x, y, z]) |
|
|
for i in range(1, 3): |
|
|
for j in range(1, 3): |
|
|
X_slice = X[:i, :] |
|
|
Y_slice = Y[:j, :] |
|
|
J = _forward_jacobian(X_slice, Y_slice) |
|
|
assert J.rows == i |
|
|
assert J.cols == j |
|
|
for k in range(j): |
|
|
assert J[:, k] == X_slice |
|
|
|
|
|
|
|
|
def test_nonvectorJacobian(): |
|
|
X = Matrix([[exp(x + y + z), exp(x + y + z)], |
|
|
[exp(x + y + z), exp(x + y + z)]]) |
|
|
raises(TypeError, lambda: _forward_jacobian(X, Matrix([x, y, z]))) |
|
|
X = X[0, :] |
|
|
Y = Matrix([[x, y], [x, z]]) |
|
|
raises(TypeError, lambda: _forward_jacobian(X, Y)) |
|
|
raises(TypeError, lambda: _forward_jacobian(X, Matrix([[x, y], [x, z]]))) |
|
|
|