diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/abstract_nodes.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/abstract_nodes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..03c537c1851df881df3a5c6234fd70892b1ec6b0 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/abstract_nodes.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/algorithms.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/algorithms.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc38771edf88e6c821d2b099ee431955150087fc Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/algorithms.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/approximations.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/approximations.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..912885621926072702a70f5a608ca479dc650bd1 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/approximations.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/ast.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/ast.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6d708f6b10d0af55895af8cba90e2724b79e4377 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/ast.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/cfunctions.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/cfunctions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c632af76a53192f934d5af42c344274d1775da52 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/cfunctions.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/cutils.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/cutils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3de5f39c2d5402001d313c3ae00fbc83f63ea955 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/cutils.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/cxxnodes.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/cxxnodes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d59caf976cf548beace9949e23587bccf87831e Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/cxxnodes.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/fnodes.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/fnodes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19b367f4961882a38d646bf3e9d8ceef40c3b49c Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/fnodes.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/futils.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/futils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb9951d51f46c0422e2b200acb29294af9b274a0 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/futils.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/matrix_nodes.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/matrix_nodes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e959356ff0358fd85fccb98c3d8443e958aad4c8 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/matrix_nodes.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/pynodes.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/pynodes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c76ba9ef7951b3c446b1af8574d1be0dc13c5b3d Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/pynodes.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/pyutils.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/pyutils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5003227e1cf824e4bf465cb4180fdb3ce711b28 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/pyutils.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/codegen/__pycache__/rewriting.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/__pycache__/rewriting.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4495d8554be71a5525a0d85b64f46d4006b6d786 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/__pycache__/rewriting.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/codegen/tests/__pycache__/test_algorithms.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/tests/__pycache__/test_algorithms.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7acbb7e082d278c56f661dc3aaa6e7b03cc75f0a Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/tests/__pycache__/test_algorithms.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/codegen/tests/__pycache__/test_rewriting.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/tests/__pycache__/test_rewriting.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a077def4688473656cc4538f4ce5b30f5bc9d13 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/tests/__pycache__/test_rewriting.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/codegen/tests/__pycache__/test_scipy_nodes.cpython-312.pyc b/lib/python3.12/site-packages/sympy/codegen/tests/__pycache__/test_scipy_nodes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c709b923b3bb661d49ed19956464b754e3327fff Binary files /dev/null and b/lib/python3.12/site-packages/sympy/codegen/tests/__pycache__/test_scipy_nodes.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/core/tests/__pycache__/test_evalf.cpython-312.pyc b/lib/python3.12/site-packages/sympy/core/tests/__pycache__/test_evalf.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..14c4b3e6fec84bfe342d4266b72c05829079ab78 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/core/tests/__pycache__/test_evalf.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/core/tests/__pycache__/test_noncommutative.cpython-312.pyc b/lib/python3.12/site-packages/sympy/core/tests/__pycache__/test_noncommutative.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..820ee357dc3a5e7870df092394bce7c6b752bd0f Binary files /dev/null and b/lib/python3.12/site-packages/sympy/core/tests/__pycache__/test_noncommutative.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_assumptions.py b/lib/python3.12/site-packages/sympy/core/tests/test_assumptions.py new file mode 100644 index 0000000000000000000000000000000000000000..574e90178fb489fe99c99ea0c72df57ceec4b249 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_assumptions.py @@ -0,0 +1,1335 @@ +from sympy.core.mod import Mod +from sympy.core.numbers import (I, oo, pi) +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (asin, sin) +from sympy.simplify.simplify import simplify +from sympy.core import Symbol, S, Rational, Integer, Dummy, Wild, Pow +from sympy.core.assumptions import (assumptions, check_assumptions, + failing_assumptions, common_assumptions, _generate_assumption_rules, + _load_pre_generated_assumption_rules) +from sympy.core.facts import InconsistentAssumptions +from sympy.core.random import seed +from sympy.combinatorics import Permutation +from sympy.combinatorics.perm_groups import PermutationGroup + +from sympy.testing.pytest import raises, XFAIL + + +def test_symbol_unset(): + x = Symbol('x', real=True, integer=True) + assert x.is_real is True + assert x.is_integer is True + assert x.is_imaginary is False + assert x.is_noninteger is False + assert x.is_number is False + + +def test_zero(): + z = Integer(0) + assert z.is_commutative is True + assert z.is_integer is True + assert z.is_rational is True + assert z.is_algebraic is True + assert z.is_transcendental is False + assert z.is_real is True + assert z.is_complex is True + assert z.is_noninteger is False + assert z.is_irrational is False + assert z.is_imaginary is False + assert z.is_positive is False + assert z.is_negative is False + assert z.is_nonpositive is True + assert z.is_nonnegative is True + assert z.is_even is True + assert z.is_odd is False + assert z.is_finite is True + assert z.is_infinite is False + assert z.is_comparable is True + assert z.is_prime is False + assert z.is_composite is False + assert z.is_number is True + + +def test_one(): + z = Integer(1) + assert z.is_commutative is True + assert z.is_integer is True + assert z.is_rational is True + assert z.is_algebraic is True + assert z.is_transcendental is False + assert z.is_real is True + assert z.is_complex is True + assert z.is_noninteger is False + assert z.is_irrational is False + assert z.is_imaginary is False + assert z.is_positive is True + assert z.is_negative is False + assert z.is_nonpositive is False + assert z.is_nonnegative is True + assert z.is_even is False + assert z.is_odd is True + assert z.is_finite is True + assert z.is_infinite is False + assert z.is_comparable is True + assert z.is_prime is False + assert z.is_number is True + assert z.is_composite is False # issue 8807 + + +def test_negativeone(): + z = Integer(-1) + assert z.is_commutative is True + assert z.is_integer is True + assert z.is_rational is True + assert z.is_algebraic is True + assert z.is_transcendental is False + assert z.is_real is True + assert z.is_complex is True + assert z.is_noninteger is False + assert z.is_irrational is False + assert z.is_imaginary is False + assert z.is_positive is False + assert z.is_negative is True + assert z.is_nonpositive is True + assert z.is_nonnegative is False + assert z.is_even is False + assert z.is_odd is True + assert z.is_finite is True + assert z.is_infinite is False + assert z.is_comparable is True + assert z.is_prime is False + assert z.is_composite is False + assert z.is_number is True + + +def test_infinity(): + oo = S.Infinity + + assert oo.is_commutative is True + assert oo.is_integer is False + assert oo.is_rational is False + assert oo.is_algebraic is False + assert oo.is_transcendental is False + assert oo.is_extended_real is True + assert oo.is_real is False + assert oo.is_complex is False + assert oo.is_noninteger is True + assert oo.is_irrational is False + assert oo.is_imaginary is False + assert oo.is_nonzero is False + assert oo.is_positive is False + assert oo.is_negative is False + assert oo.is_nonpositive is False + assert oo.is_nonnegative is False + assert oo.is_extended_nonzero is True + assert oo.is_extended_positive is True + assert oo.is_extended_negative is False + assert oo.is_extended_nonpositive is False + assert oo.is_extended_nonnegative is True + assert oo.is_even is False + assert oo.is_odd is False + assert oo.is_finite is False + assert oo.is_infinite is True + assert oo.is_comparable is True + assert oo.is_prime is False + assert oo.is_composite is False + assert oo.is_number is True + + +def test_neg_infinity(): + mm = S.NegativeInfinity + + assert mm.is_commutative is True + assert mm.is_integer is False + assert mm.is_rational is False + assert mm.is_algebraic is False + assert mm.is_transcendental is False + assert mm.is_extended_real is True + assert mm.is_real is False + assert mm.is_complex is False + assert mm.is_noninteger is True + assert mm.is_irrational is False + assert mm.is_imaginary is False + assert mm.is_nonzero is False + assert mm.is_positive is False + assert mm.is_negative is False + assert mm.is_nonpositive is False + assert mm.is_nonnegative is False + assert mm.is_extended_nonzero is True + assert mm.is_extended_positive is False + assert mm.is_extended_negative is True + assert mm.is_extended_nonpositive is True + assert mm.is_extended_nonnegative is False + assert mm.is_even is False + assert mm.is_odd is False + assert mm.is_finite is False + assert mm.is_infinite is True + assert mm.is_comparable is True + assert mm.is_prime is False + assert mm.is_composite is False + assert mm.is_number is True + + +def test_zoo(): + zoo = S.ComplexInfinity + assert zoo.is_complex is False + assert zoo.is_real is False + assert zoo.is_prime is False + + +def test_nan(): + nan = S.NaN + + assert nan.is_commutative is True + assert nan.is_integer is None + assert nan.is_rational is None + assert nan.is_algebraic is None + assert nan.is_transcendental is None + assert nan.is_real is None + assert nan.is_complex is None + assert nan.is_noninteger is None + assert nan.is_irrational is None + assert nan.is_imaginary is None + assert nan.is_positive is None + assert nan.is_negative is None + assert nan.is_nonpositive is None + assert nan.is_nonnegative is None + assert nan.is_even is None + assert nan.is_odd is None + assert nan.is_finite is None + assert nan.is_infinite is None + assert nan.is_comparable is False + assert nan.is_prime is None + assert nan.is_composite is None + assert nan.is_number is True + + +def test_pos_rational(): + r = Rational(3, 4) + assert r.is_commutative is True + assert r.is_integer is False + assert r.is_rational is True + assert r.is_algebraic is True + assert r.is_transcendental is False + assert r.is_real is True + assert r.is_complex is True + assert r.is_noninteger is True + assert r.is_irrational is False + assert r.is_imaginary is False + assert r.is_positive is True + assert r.is_negative is False + assert r.is_nonpositive is False + assert r.is_nonnegative is True + assert r.is_even is False + assert r.is_odd is False + assert r.is_finite is True + assert r.is_infinite is False + assert r.is_comparable is True + assert r.is_prime is False + assert r.is_composite is False + + r = Rational(1, 4) + assert r.is_nonpositive is False + assert r.is_positive is True + assert r.is_negative is False + assert r.is_nonnegative is True + r = Rational(5, 4) + assert r.is_negative is False + assert r.is_positive is True + assert r.is_nonpositive is False + assert r.is_nonnegative is True + r = Rational(5, 3) + assert r.is_nonnegative is True + assert r.is_positive is True + assert r.is_negative is False + assert r.is_nonpositive is False + + +def test_neg_rational(): + r = Rational(-3, 4) + assert r.is_positive is False + assert r.is_nonpositive is True + assert r.is_negative is True + assert r.is_nonnegative is False + r = Rational(-1, 4) + assert r.is_nonpositive is True + assert r.is_positive is False + assert r.is_negative is True + assert r.is_nonnegative is False + r = Rational(-5, 4) + assert r.is_negative is True + assert r.is_positive is False + assert r.is_nonpositive is True + assert r.is_nonnegative is False + r = Rational(-5, 3) + assert r.is_nonnegative is False + assert r.is_positive is False + assert r.is_negative is True + assert r.is_nonpositive is True + + +def test_pi(): + z = S.Pi + assert z.is_commutative is True + assert z.is_integer is False + assert z.is_rational is False + assert z.is_algebraic is False + assert z.is_transcendental is True + assert z.is_real is True + assert z.is_complex is True + assert z.is_noninteger is True + assert z.is_irrational is True + assert z.is_imaginary is False + assert z.is_positive is True + assert z.is_negative is False + assert z.is_nonpositive is False + assert z.is_nonnegative is True + assert z.is_even is False + assert z.is_odd is False + assert z.is_finite is True + assert z.is_infinite is False + assert z.is_comparable is True + assert z.is_prime is False + assert z.is_composite is False + + +def test_E(): + z = S.Exp1 + assert z.is_commutative is True + assert z.is_integer is False + assert z.is_rational is False + assert z.is_algebraic is False + assert z.is_transcendental is True + assert z.is_real is True + assert z.is_complex is True + assert z.is_noninteger is True + assert z.is_irrational is True + assert z.is_imaginary is False + assert z.is_positive is True + assert z.is_negative is False + assert z.is_nonpositive is False + assert z.is_nonnegative is True + assert z.is_even is False + assert z.is_odd is False + assert z.is_finite is True + assert z.is_infinite is False + assert z.is_comparable is True + assert z.is_prime is False + assert z.is_composite is False + + +def test_I(): + z = S.ImaginaryUnit + assert z.is_commutative is True + assert z.is_integer is False + assert z.is_rational is False + assert z.is_algebraic is True + assert z.is_transcendental is False + assert z.is_real is False + assert z.is_complex is True + assert z.is_noninteger is False + assert z.is_irrational is False + assert z.is_imaginary is True + assert z.is_positive is False + assert z.is_negative is False + assert z.is_nonpositive is False + assert z.is_nonnegative is False + assert z.is_even is False + assert z.is_odd is False + assert z.is_finite is True + assert z.is_infinite is False + assert z.is_comparable is False + assert z.is_prime is False + assert z.is_composite is False + + +def test_symbol_real_false(): + # issue 3848 + a = Symbol('a', real=False) + + assert a.is_real is False + assert a.is_integer is False + assert a.is_zero is False + + assert a.is_negative is False + assert a.is_positive is False + assert a.is_nonnegative is False + assert a.is_nonpositive is False + assert a.is_nonzero is False + + assert a.is_extended_negative is None + assert a.is_extended_positive is None + assert a.is_extended_nonnegative is None + assert a.is_extended_nonpositive is None + assert a.is_extended_nonzero is None + + +def test_symbol_extended_real_false(): + # issue 3848 + a = Symbol('a', extended_real=False) + + assert a.is_real is False + assert a.is_integer is False + assert a.is_zero is False + + assert a.is_negative is False + assert a.is_positive is False + assert a.is_nonnegative is False + assert a.is_nonpositive is False + assert a.is_nonzero is False + + assert a.is_extended_negative is False + assert a.is_extended_positive is False + assert a.is_extended_nonnegative is False + assert a.is_extended_nonpositive is False + assert a.is_extended_nonzero is False + + +def test_symbol_imaginary(): + a = Symbol('a', imaginary=True) + + assert a.is_real is False + assert a.is_integer is False + assert a.is_negative is False + assert a.is_positive is False + assert a.is_nonnegative is False + assert a.is_nonpositive is False + assert a.is_zero is False + assert a.is_nonzero is False # since nonzero -> real + + +def test_symbol_zero(): + x = Symbol('x', zero=True) + assert x.is_positive is False + assert x.is_nonpositive + assert x.is_negative is False + assert x.is_nonnegative + assert x.is_zero is True + # TODO Change to x.is_nonzero is None + # See https://github.com/sympy/sympy/pull/9583 + assert x.is_nonzero is False + assert x.is_finite is True + + +def test_symbol_positive(): + x = Symbol('x', positive=True) + assert x.is_positive is True + assert x.is_nonpositive is False + assert x.is_negative is False + assert x.is_nonnegative is True + assert x.is_zero is False + assert x.is_nonzero is True + + +def test_neg_symbol_positive(): + x = -Symbol('x', positive=True) + assert x.is_positive is False + assert x.is_nonpositive is True + assert x.is_negative is True + assert x.is_nonnegative is False + assert x.is_zero is False + assert x.is_nonzero is True + + +def test_symbol_nonpositive(): + x = Symbol('x', nonpositive=True) + assert x.is_positive is False + assert x.is_nonpositive is True + assert x.is_negative is None + assert x.is_nonnegative is None + assert x.is_zero is None + assert x.is_nonzero is None + + +def test_neg_symbol_nonpositive(): + x = -Symbol('x', nonpositive=True) + assert x.is_positive is None + assert x.is_nonpositive is None + assert x.is_negative is False + assert x.is_nonnegative is True + assert x.is_zero is None + assert x.is_nonzero is None + + +def test_symbol_falsepositive(): + x = Symbol('x', positive=False) + assert x.is_positive is False + assert x.is_nonpositive is None + assert x.is_negative is None + assert x.is_nonnegative is None + assert x.is_zero is None + assert x.is_nonzero is None + + +def test_symbol_falsepositive_mul(): + # To test pull request 9379 + # Explicit handling of arg.is_positive=False was added to Mul._eval_is_positive + x = 2*Symbol('x', positive=False) + assert x.is_positive is False # This was None before + assert x.is_nonpositive is None + assert x.is_negative is None + assert x.is_nonnegative is None + assert x.is_zero is None + assert x.is_nonzero is None + + +@XFAIL +def test_symbol_infinitereal_mul(): + ix = Symbol('ix', infinite=True, extended_real=True) + assert (-ix).is_extended_positive is None + + +def test_neg_symbol_falsepositive(): + x = -Symbol('x', positive=False) + assert x.is_positive is None + assert x.is_nonpositive is None + assert x.is_negative is False + assert x.is_nonnegative is None + assert x.is_zero is None + assert x.is_nonzero is None + + +def test_neg_symbol_falsenegative(): + # To test pull request 9379 + # Explicit handling of arg.is_negative=False was added to Mul._eval_is_positive + x = -Symbol('x', negative=False) + assert x.is_positive is False # This was None before + assert x.is_nonpositive is None + assert x.is_negative is None + assert x.is_nonnegative is None + assert x.is_zero is None + assert x.is_nonzero is None + + +def test_symbol_falsepositive_real(): + x = Symbol('x', positive=False, real=True) + assert x.is_positive is False + assert x.is_nonpositive is True + assert x.is_negative is None + assert x.is_nonnegative is None + assert x.is_zero is None + assert x.is_nonzero is None + + +def test_neg_symbol_falsepositive_real(): + x = -Symbol('x', positive=False, real=True) + assert x.is_positive is None + assert x.is_nonpositive is None + assert x.is_negative is False + assert x.is_nonnegative is True + assert x.is_zero is None + assert x.is_nonzero is None + + +def test_symbol_falsenonnegative(): + x = Symbol('x', nonnegative=False) + assert x.is_positive is False + assert x.is_nonpositive is None + assert x.is_negative is None + assert x.is_nonnegative is False + assert x.is_zero is False + assert x.is_nonzero is None + + +@XFAIL +def test_neg_symbol_falsenonnegative(): + x = -Symbol('x', nonnegative=False) + assert x.is_positive is None + assert x.is_nonpositive is False # this currently returns None + assert x.is_negative is False # this currently returns None + assert x.is_nonnegative is None + assert x.is_zero is False # this currently returns None + assert x.is_nonzero is True # this currently returns None + + +def test_symbol_falsenonnegative_real(): + x = Symbol('x', nonnegative=False, real=True) + assert x.is_positive is False + assert x.is_nonpositive is True + assert x.is_negative is True + assert x.is_nonnegative is False + assert x.is_zero is False + assert x.is_nonzero is True + + +def test_neg_symbol_falsenonnegative_real(): + x = -Symbol('x', nonnegative=False, real=True) + assert x.is_positive is True + assert x.is_nonpositive is False + assert x.is_negative is False + assert x.is_nonnegative is True + assert x.is_zero is False + assert x.is_nonzero is True + + +def test_prime(): + assert S.NegativeOne.is_prime is False + assert S(-2).is_prime is False + assert S(-4).is_prime is False + assert S.Zero.is_prime is False + assert S.One.is_prime is False + assert S(2).is_prime is True + assert S(17).is_prime is True + assert S(4).is_prime is False + + +def test_composite(): + assert S.NegativeOne.is_composite is False + assert S(-2).is_composite is False + assert S(-4).is_composite is False + assert S.Zero.is_composite is False + assert S(2).is_composite is False + assert S(17).is_composite is False + assert S(4).is_composite is True + x = Dummy(integer=True, positive=True, prime=False) + assert x.is_composite is None # x could be 1 + assert (x + 1).is_composite is None + x = Dummy(positive=True, even=True, prime=False) + assert x.is_integer is True + assert x.is_composite is True + + +def test_prime_symbol(): + x = Symbol('x', prime=True) + assert x.is_prime is True + assert x.is_integer is True + assert x.is_positive is True + assert x.is_negative is False + assert x.is_nonpositive is False + assert x.is_nonnegative is True + + x = Symbol('x', prime=False) + assert x.is_prime is False + assert x.is_integer is None + assert x.is_positive is None + assert x.is_negative is None + assert x.is_nonpositive is None + assert x.is_nonnegative is None + + +def test_symbol_noncommutative(): + x = Symbol('x', commutative=True) + assert x.is_complex is None + + x = Symbol('x', commutative=False) + assert x.is_integer is False + assert x.is_rational is False + assert x.is_algebraic is False + assert x.is_irrational is False + assert x.is_real is False + assert x.is_complex is False + + +def test_other_symbol(): + x = Symbol('x', integer=True) + assert x.is_integer is True + assert x.is_real is True + assert x.is_finite is True + + x = Symbol('x', integer=True, nonnegative=True) + assert x.is_integer is True + assert x.is_nonnegative is True + assert x.is_negative is False + assert x.is_positive is None + assert x.is_finite is True + + x = Symbol('x', integer=True, nonpositive=True) + assert x.is_integer is True + assert x.is_nonpositive is True + assert x.is_positive is False + assert x.is_negative is None + assert x.is_finite is True + + x = Symbol('x', odd=True) + assert x.is_odd is True + assert x.is_even is False + assert x.is_integer is True + assert x.is_finite is True + + x = Symbol('x', odd=False) + assert x.is_odd is False + assert x.is_even is None + assert x.is_integer is None + assert x.is_finite is None + + x = Symbol('x', even=True) + assert x.is_even is True + assert x.is_odd is False + assert x.is_integer is True + assert x.is_finite is True + + x = Symbol('x', even=False) + assert x.is_even is False + assert x.is_odd is None + assert x.is_integer is None + assert x.is_finite is None + + x = Symbol('x', integer=True, nonnegative=True) + assert x.is_integer is True + assert x.is_nonnegative is True + assert x.is_finite is True + + x = Symbol('x', integer=True, nonpositive=True) + assert x.is_integer is True + assert x.is_nonpositive is True + assert x.is_finite is True + + x = Symbol('x', rational=True) + assert x.is_real is True + assert x.is_finite is True + + x = Symbol('x', rational=False) + assert x.is_real is None + assert x.is_finite is None + + x = Symbol('x', irrational=True) + assert x.is_real is True + assert x.is_finite is True + + x = Symbol('x', irrational=False) + assert x.is_real is None + assert x.is_finite is None + + with raises(AttributeError): + x.is_real = False + + x = Symbol('x', algebraic=True) + assert x.is_transcendental is False + x = Symbol('x', transcendental=True) + assert x.is_algebraic is False + assert x.is_rational is False + assert x.is_integer is False + + +def test_evaluate_false(): + # Previously this failed because the assumptions query would make new + # expressions and some of the evaluation logic would fail under + # evaluate(False). + from sympy.core.parameters import evaluate + from sympy.abc import x, h + f = 2**x**7 + with evaluate(False): + fh = f.xreplace({x: x+h}) + assert fh.exp.is_rational is None + + +def test_issue_3825(): + """catch: hash instability""" + x = Symbol("x") + y = Symbol("y") + a1 = x + y + a2 = y + x + a2.is_comparable + + h1 = hash(a1) + h2 = hash(a2) + assert h1 == h2 + + +def test_issue_4822(): + z = (-1)**Rational(1, 3)*(1 - I*sqrt(3)) + assert z.is_real in [True, None] + + +def test_hash_vs_typeinfo(): + """seemingly different typeinfo, but in fact equal""" + + # the following two are semantically equal + x1 = Symbol('x', even=True) + x2 = Symbol('x', integer=True, odd=False) + + assert hash(x1) == hash(x2) + assert x1 == x2 + + +def test_hash_vs_typeinfo_2(): + """different typeinfo should mean !eq""" + # the following two are semantically different + x = Symbol('x') + x1 = Symbol('x', even=True) + + assert x != x1 + assert hash(x) != hash(x1) # This might fail with very low probability + + +def test_hash_vs_eq(): + """catch: different hash for equal objects""" + a = 1 + S.Pi # important: do not fold it into a Number instance + ha = hash(a) # it should be Add/Mul/... to trigger the bug + + a.is_positive # this uses .evalf() and deduces it is positive + assert a.is_positive is True + + # be sure that hash stayed the same + assert ha == hash(a) + + # now b should be the same expression + b = a.expand(trig=True) + hb = hash(b) + + assert a == b + assert ha == hb + + +def test_Add_is_pos_neg(): + # these cover lines not covered by the rest of tests in core + n = Symbol('n', extended_negative=True, infinite=True) + nn = Symbol('n', extended_nonnegative=True, infinite=True) + np = Symbol('n', extended_nonpositive=True, infinite=True) + p = Symbol('p', extended_positive=True, infinite=True) + r = Dummy(extended_real=True, finite=False) + x = Symbol('x') + xf = Symbol('xf', finite=True) + assert (n + p).is_extended_positive is None + assert (n + x).is_extended_positive is None + assert (p + x).is_extended_positive is None + assert (n + p).is_extended_negative is None + assert (n + x).is_extended_negative is None + assert (p + x).is_extended_negative is None + + assert (n + xf).is_extended_positive is False + assert (p + xf).is_extended_positive is True + assert (n + xf).is_extended_negative is True + assert (p + xf).is_extended_negative is False + + assert (x - S.Infinity).is_extended_negative is None # issue 7798 + # issue 8046, 16.2 + assert (p + nn).is_extended_positive + assert (n + np).is_extended_negative + assert (p + r).is_extended_positive is None + + +def test_Add_is_imaginary(): + nn = Dummy(nonnegative=True) + assert (I*nn + I).is_imaginary # issue 8046, 17 + + +def test_Add_is_algebraic(): + a = Symbol('a', algebraic=True) + b = Symbol('a', algebraic=True) + na = Symbol('na', algebraic=False) + nb = Symbol('nb', algebraic=False) + x = Symbol('x') + assert (a + b).is_algebraic + assert (na + nb).is_algebraic is None + assert (a + na).is_algebraic is False + assert (a + x).is_algebraic is None + assert (na + x).is_algebraic is None + + +def test_Mul_is_algebraic(): + a = Symbol('a', algebraic=True) + b = Symbol('b', algebraic=True) + na = Symbol('na', algebraic=False) + an = Symbol('an', algebraic=True, nonzero=True) + nb = Symbol('nb', algebraic=False) + x = Symbol('x') + assert (a*b).is_algebraic is True + assert (na*nb).is_algebraic is None + assert (a*na).is_algebraic is None + assert (an*na).is_algebraic is False + assert (a*x).is_algebraic is None + assert (na*x).is_algebraic is None + + +def test_Pow_is_algebraic(): + e = Symbol('e', algebraic=True) + + assert Pow(1, e, evaluate=False).is_algebraic + assert Pow(0, e, evaluate=False).is_algebraic + + a = Symbol('a', algebraic=True) + azf = Symbol('azf', algebraic=True, zero=False) + na = Symbol('na', algebraic=False) + ia = Symbol('ia', algebraic=True, irrational=True) + ib = Symbol('ib', algebraic=True, irrational=True) + r = Symbol('r', rational=True) + x = Symbol('x') + assert (a**2).is_algebraic is True + assert (a**r).is_algebraic is None + assert (azf**r).is_algebraic is True + assert (a**x).is_algebraic is None + assert (na**r).is_algebraic is None + assert (ia**r).is_algebraic is True + assert (ia**ib).is_algebraic is False + + assert (a**e).is_algebraic is None + + # Gelfond-Schneider constant: + assert Pow(2, sqrt(2), evaluate=False).is_algebraic is False + + assert Pow(S.GoldenRatio, sqrt(3), evaluate=False).is_algebraic is False + + # issue 8649 + t = Symbol('t', real=True, transcendental=True) + n = Symbol('n', integer=True) + assert (t**n).is_algebraic is None + assert (t**n).is_integer is None + + assert (pi**3).is_algebraic is False + r = Symbol('r', zero=True) + assert (pi**r).is_algebraic is True + + +def test_Mul_is_prime_composite(): + x = Symbol('x', positive=True, integer=True) + y = Symbol('y', positive=True, integer=True) + assert (x*y).is_prime is None + assert ( (x+1)*(y+1) ).is_prime is False + assert ( (x+1)*(y+1) ).is_composite is True + + x = Symbol('x', positive=True) + assert ( (x+1)*(y+1) ).is_prime is None + assert ( (x+1)*(y+1) ).is_composite is None + + +def test_Pow_is_pos_neg(): + z = Symbol('z', real=True) + w = Symbol('w', nonpositive=True) + + assert (S.NegativeOne**S(2)).is_positive is True + assert (S.One**z).is_positive is True + assert (S.NegativeOne**S(3)).is_positive is False + assert (S.Zero**S.Zero).is_positive is True # 0**0 is 1 + assert (w**S(3)).is_positive is False + assert (w**S(2)).is_positive is None + assert (I**2).is_positive is False + assert (I**4).is_positive is True + + # tests emerging from #16332 issue + p = Symbol('p', zero=True) + q = Symbol('q', zero=False, real=True) + j = Symbol('j', zero=False, even=True) + x = Symbol('x', zero=True) + y = Symbol('y', zero=True) + assert (p**q).is_positive is False + assert (p**q).is_negative is False + assert (p**j).is_positive is False + assert (x**y).is_positive is True # 0**0 + assert (x**y).is_negative is False + + +def test_Pow_is_prime_composite(): + x = Symbol('x', positive=True, integer=True) + y = Symbol('y', positive=True, integer=True) + assert (x**y).is_prime is None + assert ( x**(y+1) ).is_prime is False + assert ( x**(y+1) ).is_composite is None + assert ( (x+1)**(y+1) ).is_composite is True + assert ( (-x-1)**(2*y) ).is_composite is True + + x = Symbol('x', positive=True) + assert (x**y).is_prime is None + + +def test_Mul_is_infinite(): + x = Symbol('x') + f = Symbol('f', finite=True) + i = Symbol('i', infinite=True) + z = Dummy(zero=True) + nzf = Dummy(finite=True, zero=False) + from sympy.core.mul import Mul + assert (x*f).is_finite is None + assert (x*i).is_finite is None + assert (f*i).is_finite is None + assert (x*f*i).is_finite is None + assert (z*i).is_finite is None + assert (nzf*i).is_finite is False + assert (z*f).is_finite is True + assert Mul(0, f, evaluate=False).is_finite is True + assert Mul(0, i, evaluate=False).is_finite is None + + assert (x*f).is_infinite is None + assert (x*i).is_infinite is None + assert (f*i).is_infinite is None + assert (x*f*i).is_infinite is None + assert (z*i).is_infinite is S.NaN.is_infinite + assert (nzf*i).is_infinite is True + assert (z*f).is_infinite is False + assert Mul(0, f, evaluate=False).is_infinite is False + assert Mul(0, i, evaluate=False).is_infinite is S.NaN.is_infinite + + +def test_Add_is_infinite(): + x = Symbol('x') + f = Symbol('f', finite=True) + i = Symbol('i', infinite=True) + i2 = Symbol('i2', infinite=True) + z = Dummy(zero=True) + nzf = Dummy(finite=True, zero=False) + from sympy.core.add import Add + assert (x+f).is_finite is None + assert (x+i).is_finite is None + assert (f+i).is_finite is False + assert (x+f+i).is_finite is None + assert (z+i).is_finite is False + assert (nzf+i).is_finite is False + assert (z+f).is_finite is True + assert (i+i2).is_finite is None + assert Add(0, f, evaluate=False).is_finite is True + assert Add(0, i, evaluate=False).is_finite is False + + assert (x+f).is_infinite is None + assert (x+i).is_infinite is None + assert (f+i).is_infinite is True + assert (x+f+i).is_infinite is None + assert (z+i).is_infinite is True + assert (nzf+i).is_infinite is True + assert (z+f).is_infinite is False + assert (i+i2).is_infinite is None + assert Add(0, f, evaluate=False).is_infinite is False + assert Add(0, i, evaluate=False).is_infinite is True + + +def test_special_is_rational(): + i = Symbol('i', integer=True) + i2 = Symbol('i2', integer=True) + ni = Symbol('ni', integer=True, nonzero=True) + r = Symbol('r', rational=True) + rn = Symbol('r', rational=True, nonzero=True) + nr = Symbol('nr', irrational=True) + x = Symbol('x') + assert sqrt(3).is_rational is False + assert (3 + sqrt(3)).is_rational is False + assert (3*sqrt(3)).is_rational is False + assert exp(3).is_rational is False + assert exp(ni).is_rational is False + assert exp(rn).is_rational is False + assert exp(x).is_rational is None + assert exp(log(3), evaluate=False).is_rational is True + assert log(exp(3), evaluate=False).is_rational is True + assert log(3).is_rational is False + assert log(ni + 1).is_rational is False + assert log(rn + 1).is_rational is False + assert log(x).is_rational is None + assert (sqrt(3) + sqrt(5)).is_rational is None + assert (sqrt(3) + S.Pi).is_rational is False + assert (x**i).is_rational is None + assert (i**i).is_rational is True + assert (i**i2).is_rational is None + assert (r**i).is_rational is None + assert (r**r).is_rational is None + assert (r**x).is_rational is None + assert (nr**i).is_rational is None # issue 8598 + assert (nr**Symbol('z', zero=True)).is_rational + assert sin(1).is_rational is False + assert sin(ni).is_rational is False + assert sin(rn).is_rational is False + assert sin(x).is_rational is None + assert asin(r).is_rational is False + assert sin(asin(3), evaluate=False).is_rational is True + + +@XFAIL +def test_issue_6275(): + x = Symbol('x') + # both zero or both Muls...but neither "change would be very appreciated. + # This is similar to x/x => 1 even though if x = 0, it is really nan. + assert isinstance(x*0, type(0*S.Infinity)) + if 0*S.Infinity is S.NaN: + b = Symbol('b', finite=None) + assert (b*0).is_zero is None + + +def test_sanitize_assumptions(): + # issue 6666 + for cls in (Symbol, Dummy, Wild): + x = cls('x', real=1, positive=0) + assert x.is_real is True + assert x.is_positive is False + assert cls('', real=True, positive=None).is_positive is None + raises(ValueError, lambda: cls('', commutative=None)) + raises(ValueError, lambda: Symbol._sanitize({"commutative": None})) + + +def test_special_assumptions(): + e = -3 - sqrt(5) + (-sqrt(10)/2 - sqrt(2)/2)**2 + assert simplify(e < 0) is S.false + assert simplify(e > 0) is S.false + assert (e == 0) is False # it's not a literal 0 + assert e.equals(0) is True + + +def test_inconsistent(): + # cf. issues 5795 and 5545 + raises(InconsistentAssumptions, lambda: Symbol('x', real=True, + commutative=False)) + + +def test_issue_6631(): + assert ((-1)**(I)).is_real is True + assert ((-1)**(I*2)).is_real is True + assert ((-1)**(I/2)).is_real is True + assert ((-1)**(I*S.Pi)).is_real is True + assert (I**(I + 2)).is_real is True + + +def test_issue_2730(): + assert (1/(1 + I)).is_real is False + + +def test_issue_4149(): + assert (3 + I).is_complex + assert (3 + I).is_imaginary is False + assert (3*I + S.Pi*I).is_imaginary + # as Zero.is_imaginary is False, see issue 7649 + y = Symbol('y', real=True) + assert (3*I + S.Pi*I + y*I).is_imaginary is None + p = Symbol('p', positive=True) + assert (3*I + S.Pi*I + p*I).is_imaginary + n = Symbol('n', negative=True) + assert (-3*I - S.Pi*I + n*I).is_imaginary + + i = Symbol('i', imaginary=True) + assert ([(i**a).is_imaginary for a in range(4)] == + [False, True, False, True]) + + # tests from the PR #7887: + e = S("-sqrt(3)*I/2 + 0.866025403784439*I") + assert e.is_real is False + assert e.is_imaginary + + +def test_issue_2920(): + n = Symbol('n', negative=True) + assert sqrt(n).is_imaginary + + +def test_issue_7899(): + x = Symbol('x', real=True) + assert (I*x).is_real is None + assert ((x - I)*(x - 1)).is_zero is None + assert ((x - I)*(x - 1)).is_real is None + + +@XFAIL +def test_issue_7993(): + x = Dummy(integer=True) + y = Dummy(noninteger=True) + assert (x - y).is_zero is False + + +def test_issue_8075(): + raises(InconsistentAssumptions, lambda: Dummy(zero=True, finite=False)) + raises(InconsistentAssumptions, lambda: Dummy(zero=True, infinite=True)) + + +def test_issue_8642(): + x = Symbol('x', real=True, integer=False) + assert (x*2).is_integer is None, (x*2).is_integer + + +def test_issues_8632_8633_8638_8675_8992(): + p = Dummy(integer=True, positive=True) + nn = Dummy(integer=True, nonnegative=True) + assert (p - S.Half).is_positive + assert (p - 1).is_nonnegative + assert (nn + 1).is_positive + assert (-p + 1).is_nonpositive + assert (-nn - 1).is_negative + prime = Dummy(prime=True) + assert (prime - 2).is_nonnegative + assert (prime - 3).is_nonnegative is None + even = Dummy(positive=True, even=True) + assert (even - 2).is_nonnegative + + p = Dummy(positive=True) + assert (p/(p + 1) - 1).is_negative + assert ((p + 2)**3 - S.Half).is_positive + n = Dummy(negative=True) + assert (n - 3).is_nonpositive + + +def test_issue_9115_9150(): + n = Dummy('n', integer=True, nonnegative=True) + assert (factorial(n) >= 1) == True + assert (factorial(n) < 1) == False + + assert factorial(n + 1).is_even is None + assert factorial(n + 2).is_even is True + assert factorial(n + 2) >= 2 + + +def test_issue_9165(): + z = Symbol('z', zero=True) + f = Symbol('f', finite=False) + assert 0/z is S.NaN + assert 0*(1/z) is S.NaN + assert 0*f is S.NaN + + +def test_issue_10024(): + x = Dummy('x') + assert Mod(x, 2*pi).is_zero is None + + +def test_issue_10302(): + x = Symbol('x') + r = Symbol('r', real=True) + u = -(3*2**pi)**(1/pi) + 2*3**(1/pi) + i = u + u*I + + assert i.is_real is None # w/o simplification this should fail + assert (u + i).is_zero is None + assert (1 + i).is_zero is False + + a = Dummy('a', zero=True) + assert (a + I).is_zero is False + assert (a + r*I).is_zero is None + assert (a + I).is_imaginary + assert (a + x + I).is_imaginary is None + assert (a + r*I + I).is_imaginary is None + + +def test_complex_reciprocal_imaginary(): + assert (1 / (4 + 3*I)).is_imaginary is False + + +def test_issue_16313(): + x = Symbol('x', extended_real=False) + k = Symbol('k', real=True) + l = Symbol('l', real=True, zero=False) + assert (-x).is_real is False + assert (k*x).is_real is None # k can be zero also + assert (l*x).is_real is False + assert (l*x*x).is_real is None # since x*x can be a real number + assert (-x).is_positive is False + + +def test_issue_16579(): + # extended_real -> finite | infinite + x = Symbol('x', extended_real=True, infinite=False) + y = Symbol('y', extended_real=True, finite=False) + assert x.is_finite is True + assert y.is_infinite is True + + # With PR 16978, complex now implies finite + c = Symbol('c', complex=True) + assert c.is_finite is True + raises(InconsistentAssumptions, lambda: Dummy(complex=True, finite=False)) + + # Now infinite == !finite + nf = Symbol('nf', finite=False) + assert nf.is_infinite is True + + +def test_issue_17556(): + z = I*oo + assert z.is_imaginary is False + assert z.is_finite is False + + +def test_issue_21651(): + k = Symbol('k', positive=True, integer=True) + exp = 2*2**(-k) + assert exp.is_integer is None + + +def test_assumptions_copy(): + assert assumptions(Symbol('x'), {"commutative": True} + ) == {'commutative': True} + assert assumptions(Symbol('x'), ['integer']) == {} + assert assumptions(Symbol('x'), ['commutative'] + ) == {'commutative': True} + assert assumptions(Symbol('x')) == {'commutative': True} + assert assumptions(1)['positive'] + assert assumptions(3 + I) == { + 'algebraic': True, + 'commutative': True, + 'complex': True, + 'composite': False, + 'even': False, + 'extended_negative': False, + 'extended_nonnegative': False, + 'extended_nonpositive': False, + 'extended_nonzero': False, + 'extended_positive': False, + 'extended_real': False, + 'finite': True, + 'imaginary': False, + 'infinite': False, + 'integer': False, + 'irrational': False, + 'negative': False, + 'noninteger': False, + 'nonnegative': False, + 'nonpositive': False, + 'nonzero': False, + 'odd': False, + 'positive': False, + 'prime': False, + 'rational': False, + 'real': False, + 'transcendental': False, + 'zero': False} + + +def test_check_assumptions(): + assert check_assumptions(1, 0) is False + x = Symbol('x', positive=True) + assert check_assumptions(1, x) is True + assert check_assumptions(1, 1) is True + assert check_assumptions(-1, 1) is False + i = Symbol('i', integer=True) + # don't know if i is positive (or prime, etc...) + assert check_assumptions(i, 1) is None + assert check_assumptions(Dummy(integer=None), integer=True) is None + assert check_assumptions(Dummy(integer=None), integer=False) is None + assert check_assumptions(Dummy(integer=False), integer=True) is False + assert check_assumptions(Dummy(integer=True), integer=False) is False + # no T/F assumptions to check + assert check_assumptions(Dummy(integer=False), integer=None) is True + raises(ValueError, lambda: check_assumptions(2*x, x, positive=True)) + + +def test_failing_assumptions(): + x = Symbol('x', positive=True) + y = Symbol('y') + assert failing_assumptions(6*x + y, **x.assumptions0) == \ + {'real': None, 'imaginary': None, 'complex': None, 'hermitian': None, + 'positive': None, 'nonpositive': None, 'nonnegative': None, 'nonzero': None, + 'negative': None, 'zero': None, 'extended_real': None, 'finite': None, + 'infinite': None, 'extended_negative': None, 'extended_nonnegative': None, + 'extended_nonpositive': None, 'extended_nonzero': None, + 'extended_positive': None } + + +def test_common_assumptions(): + assert common_assumptions([0, 1, 2] + ) == {'algebraic': True, 'irrational': False, 'hermitian': + True, 'extended_real': True, 'real': True, 'extended_negative': + False, 'extended_nonnegative': True, 'integer': True, + 'rational': True, 'imaginary': False, 'complex': True, + 'commutative': True,'noninteger': False, 'composite': False, + 'infinite': False, 'nonnegative': True, 'finite': True, + 'transcendental': False,'negative': False} + assert common_assumptions([0, 1, 2], 'positive integer'.split() + ) == {'integer': True} + assert common_assumptions([0, 1, 2], []) == {} + assert common_assumptions([], ['integer']) == {} + assert common_assumptions([0], ['integer']) == {'integer': True} + +def test_pre_generated_assumption_rules_are_valid(): + # check the pre-generated assumptions match freshly generated assumptions + # if this check fails, consider updating the assumptions + # see sympy.core.assumptions._generate_assumption_rules + pre_generated_assumptions =_load_pre_generated_assumption_rules() + generated_assumptions =_generate_assumption_rules() + assert pre_generated_assumptions._to_python() == generated_assumptions._to_python(), "pre-generated assumptions are invalid, see sympy.core.assumptions._generate_assumption_rules" + + +def test_ask_shuffle(): + grp = PermutationGroup(Permutation(1, 0, 2), Permutation(2, 1, 3)) + + seed(123) + first = grp.random() + seed(123) + simplify(I) + second = grp.random() + seed(123) + simplify(-I) + third = grp.random() + + assert first == second == third diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_basic.py b/lib/python3.12/site-packages/sympy/core/tests/test_basic.py new file mode 100644 index 0000000000000000000000000000000000000000..44faf93162f0df8bd7d749d99f21c98d79a1ef41 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_basic.py @@ -0,0 +1,333 @@ +"""This tests sympy/core/basic.py with (ideally) no reference to subclasses +of Basic or Atom.""" +import collections +from typing import TypeVar, Generic + +from sympy.assumptions.ask import Q +from sympy.core.basic import (Basic, Atom, as_Basic, + _atomic, _aresame) +from sympy.core.containers import Tuple +from sympy.core.function import Function, Lambda +from sympy.core.numbers import I, pi, Float +from sympy.core.singleton import S +from sympy.core.symbol import symbols, Symbol, Dummy +from sympy.concrete.summations import Sum +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.functions.special.gamma_functions import gamma +from sympy.integrals.integrals import Integral +from sympy.functions.elementary.exponential import exp +from sympy.testing.pytest import raises, warns_deprecated_sympy + +b1 = Basic() +b2 = Basic(b1) +b3 = Basic(b2) +b21 = Basic(b2, b1) +T = TypeVar('T') + + +def test__aresame(): + assert not _aresame(Basic(Tuple()), Basic()) + for i, j in [(S(2), S(2.)), (1., Float(1))]: + for do in range(2): + assert not _aresame(Basic(i), Basic(j)) + assert not _aresame(i, j) + i, j = j, i + + +def test_structure(): + assert b21.args == (b2, b1) + assert b21.func(*b21.args) == b21 + assert bool(b1) + + +def test_immutable(): + assert not hasattr(b1, '__dict__') + with raises(AttributeError): + b1.x = 1 + + +def test_equality(): + instances = [b1, b2, b3, b21, Basic(b1, b1, b1), Basic] + for i, b_i in enumerate(instances): + for j, b_j in enumerate(instances): + assert (b_i == b_j) == (i == j) + assert (b_i != b_j) == (i != j) + + assert Basic() != [] + assert not(Basic() == []) + assert Basic() != 0 + assert not(Basic() == 0) + + class Foo: + """ + Class that is unaware of Basic, and relies on both classes returning + the NotImplemented singleton for equivalence to evaluate to False. + + """ + + b = Basic() + foo = Foo() + + assert b != foo + assert foo != b + assert not b == foo + assert not foo == b + + class Bar: + """ + Class that considers itself equal to any instance of Basic, and relies + on Basic returning the NotImplemented singleton in order to achieve + a symmetric equivalence relation. + + """ + def __eq__(self, other): + if isinstance(other, Basic): + return True + return NotImplemented + + def __ne__(self, other): + return not self == other + + bar = Bar() + + assert b == bar + assert bar == b + assert not b != bar + assert not bar != b + + +def test_matches_basic(): + instances = [Basic(b1, b1, b2), Basic(b1, b2, b1), Basic(b2, b1, b1), + Basic(b1, b2), Basic(b2, b1), b2, b1] + for i, b_i in enumerate(instances): + for j, b_j in enumerate(instances): + if i == j: + assert b_i.matches(b_j) == {} + else: + assert b_i.matches(b_j) is None + assert b1.match(b1) == {} + + +def test_has(): + assert b21.has(b1) + assert b21.has(b3, b1) + assert b21.has(Basic) + assert not b1.has(b21, b3) + assert not b21.has() + assert not b21.has(str) + assert not Symbol("x").has("x") + + +def test_subs(): + assert b21.subs(b2, b1) == Basic(b1, b1) + assert b21.subs(b2, b21) == Basic(b21, b1) + assert b3.subs(b2, b1) == b2 + + assert b21.subs([(b2, b1), (b1, b2)]) == Basic(b2, b2) + + assert b21.subs({b1: b2, b2: b1}) == Basic(b2, b2) + assert b21.subs(collections.ChainMap({b1: b2}, {b2: b1})) == Basic(b2, b2) + assert b21.subs(collections.OrderedDict([(b2, b1), (b1, b2)])) == Basic(b2, b2) + + raises(ValueError, lambda: b21.subs('bad arg')) + raises(ValueError, lambda: b21.subs(b1, b2, b3)) + # dict(b1=foo) creates a string 'b1' but leaves foo unchanged; subs + # will convert the first to a symbol but will raise an error if foo + # cannot be sympified; sympification is strict if foo is not string + raises(ValueError, lambda: b21.subs(b1='bad arg')) + + assert Symbol("text").subs({"text": b1}) == b1 + assert Symbol("s").subs({"s": 1}) == 1 + + +def test_subs_with_unicode_symbols(): + expr = Symbol('var1') + replaced = expr.subs('var1', 'x') + assert replaced.name == 'x' + + replaced = expr.subs('var1', 'x') + assert replaced.name == 'x' + + +def test_atoms(): + assert b21.atoms() == {Basic()} + + +def test_free_symbols_empty(): + assert b21.free_symbols == set() + + +def test_doit(): + assert b21.doit() == b21 + assert b21.doit(deep=False) == b21 + + +def test_S(): + assert repr(S) == 'S' + + +def test_xreplace(): + assert b21.xreplace({b2: b1}) == Basic(b1, b1) + assert b21.xreplace({b2: b21}) == Basic(b21, b1) + assert b3.xreplace({b2: b1}) == b2 + assert Basic(b1, b2).xreplace({b1: b2, b2: b1}) == Basic(b2, b1) + assert Atom(b1).xreplace({b1: b2}) == Atom(b1) + assert Atom(b1).xreplace({Atom(b1): b2}) == b2 + raises(TypeError, lambda: b1.xreplace()) + raises(TypeError, lambda: b1.xreplace([b1, b2])) + for f in (exp, Function('f')): + assert f.xreplace({}) == f + assert f.xreplace({}, hack2=True) == f + assert f.xreplace({f: b1}) == b1 + assert f.xreplace({f: b1}, hack2=True) == b1 + + +def test_sorted_args(): + x = symbols('x') + assert b21._sorted_args == b21.args + raises(AttributeError, lambda: x._sorted_args) + +def test_call(): + x, y = symbols('x y') + # See the long history of this in issues 5026 and 5105. + + raises(TypeError, lambda: sin(x)({ x : 1, sin(x) : 2})) + raises(TypeError, lambda: sin(x)(1)) + + # No effect as there are no callables + assert sin(x).rcall(1) == sin(x) + assert (1 + sin(x)).rcall(1) == 1 + sin(x) + + # Effect in the pressence of callables + l = Lambda(x, 2*x) + assert (l + x).rcall(y) == 2*y + x + assert (x**l).rcall(2) == x**4 + # TODO UndefinedFunction does not subclass Expr + #f = Function('f') + #assert (2*f)(x) == 2*f(x) + + assert (Q.real & Q.positive).rcall(x) == Q.real(x) & Q.positive(x) + + +def test_rewrite(): + x, y, z = symbols('x y z') + a, b = symbols('a b') + f1 = sin(x) + cos(x) + assert f1.rewrite(cos,exp) == exp(I*x)/2 + sin(x) + exp(-I*x)/2 + assert f1.rewrite([cos],sin) == sin(x) + sin(x + pi/2, evaluate=False) + f2 = sin(x) + cos(y)/gamma(z) + assert f2.rewrite(sin,exp) == -I*(exp(I*x) - exp(-I*x))/2 + cos(y)/gamma(z) + + assert f1.rewrite() == f1 + +def test_literal_evalf_is_number_is_zero_is_comparable(): + x = symbols('x') + f = Function('f') + + # issue 5033 + assert f.is_number is False + # issue 6646 + assert f(1).is_number is False + i = Integral(0, (x, x, x)) + # expressions that are symbolically 0 can be difficult to prove + # so in case there is some easy way to know if something is 0 + # it should appear in the is_zero property for that object; + # if is_zero is true evalf should always be able to compute that + # zero + assert i.n() == 0 + assert i.is_zero + assert i.is_number is False + assert i.evalf(2, strict=False) == 0 + + # issue 10268 + n = sin(1)**2 + cos(1)**2 - 1 + assert n.is_comparable is False + assert n.n(2).is_comparable is False + assert n.n(2).n(2).is_comparable + + +def test_as_Basic(): + assert as_Basic(1) is S.One + assert as_Basic(()) == Tuple() + raises(TypeError, lambda: as_Basic([])) + + +def test_atomic(): + g, h = map(Function, 'gh') + x = symbols('x') + assert _atomic(g(x + h(x))) == {g(x + h(x))} + assert _atomic(g(x + h(x)), recursive=True) == {h(x), x, g(x + h(x))} + assert _atomic(1) == set() + assert _atomic(Basic(S(1), S(2))) == set() + + +def test_as_dummy(): + u, v, x, y, z, _0, _1 = symbols('u v x y z _0 _1') + assert Lambda(x, x + 1).as_dummy() == Lambda(_0, _0 + 1) + assert Lambda(x, x + _0).as_dummy() == Lambda(_1, _0 + _1) + eq = (1 + Sum(x, (x, 1, x))) + ans = 1 + Sum(_0, (_0, 1, x)) + once = eq.as_dummy() + assert once == ans + twice = once.as_dummy() + assert twice == ans + assert Integral(x + _0, (x, x + 1), (_0, 1, 2) + ).as_dummy() == Integral(_0 + _1, (_0, x + 1), (_1, 1, 2)) + for T in (Symbol, Dummy): + d = T('x', real=True) + D = d.as_dummy() + assert D != d and D.func == Dummy and D.is_real is None + assert Dummy().as_dummy().is_commutative + assert Dummy(commutative=False).as_dummy().is_commutative is False + + +def test_canonical_variables(): + x, i0, i1 = symbols('x _:2') + assert Integral(x, (x, x + 1)).canonical_variables == {x: i0} + assert Integral(x, (x, x + 1), (i0, 1, 2)).canonical_variables == { + x: i0, i0: i1} + assert Integral(x, (x, x + i0)).canonical_variables == {x: i1} + + +def test_replace_exceptions(): + from sympy.core.symbol import Wild + x, y = symbols('x y') + e = (x**2 + x*y) + raises(TypeError, lambda: e.replace(sin, 2)) + b = Wild('b') + c = Wild('c') + raises(TypeError, lambda: e.replace(b*c, c.is_real)) + raises(TypeError, lambda: e.replace(b.is_real, 1)) + raises(TypeError, lambda: e.replace(lambda d: d.is_Number, 1)) + + +def test_ManagedProperties(): + # ManagedProperties is now deprecated. Here we do our best to check that if + # someone is using it then it does work in the way that it previously did + # but gives a deprecation warning. + from sympy.core.assumptions import ManagedProperties + + myclasses = [] + + class MyMeta(ManagedProperties): + def __init__(cls, *args, **kwargs): + myclasses.append('executed') + super().__init__(*args, **kwargs) + + code = """ +class MySubclass(Basic, metaclass=MyMeta): + pass +""" + with warns_deprecated_sympy(): + exec(code) + + assert myclasses == ['executed'] + + +def test_generic(): + # https://github.com/sympy/sympy/issues/25399 + class A(Symbol, Generic[T]): + pass + + class B(A[T]): + pass diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_cache.py b/lib/python3.12/site-packages/sympy/core/tests/test_cache.py new file mode 100644 index 0000000000000000000000000000000000000000..9124fca70718299252929a9923f335dde25256eb --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_cache.py @@ -0,0 +1,91 @@ +import sys +from sympy.core.cache import cacheit, cached_property, lazy_function +from sympy.testing.pytest import raises + +def test_cacheit_doc(): + @cacheit + def testfn(): + "test docstring" + pass + + assert testfn.__doc__ == "test docstring" + assert testfn.__name__ == "testfn" + +def test_cacheit_unhashable(): + @cacheit + def testit(x): + return x + + assert testit(1) == 1 + assert testit(1) == 1 + a = {} + assert testit(a) == {} + a[1] = 2 + assert testit(a) == {1: 2} + +def test_cachit_exception(): + # Make sure the cache doesn't call functions multiple times when they + # raise TypeError + + a = [] + + @cacheit + def testf(x): + a.append(0) + raise TypeError + + raises(TypeError, lambda: testf(1)) + assert len(a) == 1 + + a.clear() + # Unhashable type + raises(TypeError, lambda: testf([])) + assert len(a) == 1 + + @cacheit + def testf2(x): + a.append(0) + raise TypeError("Error") + + a.clear() + raises(TypeError, lambda: testf2(1)) + assert len(a) == 1 + + a.clear() + # Unhashable type + raises(TypeError, lambda: testf2([])) + assert len(a) == 1 + +def test_cached_property(): + class A: + def __init__(self, value): + self.value = value + self.calls = 0 + + @cached_property + def prop(self): + self.calls = self.calls + 1 + return self.value + + a = A(2) + assert a.calls == 0 + assert a.prop == 2 + assert a.calls == 1 + assert a.prop == 2 + assert a.calls == 1 + b = A(None) + assert b.prop == None + + +def test_lazy_function(): + module_name='xmlrpc.client' + function_name = 'gzip_decode' + lazy = lazy_function(module_name, function_name) + assert lazy(b'') == b'' + assert module_name in sys.modules + assert function_name in str(lazy) + repr_lazy = repr(lazy) + assert 'LazyFunction' in repr_lazy + assert function_name in repr_lazy + + lazy = lazy_function('sympy.core.cache', 'cheap') diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_constructor_postprocessor.py b/lib/python3.12/site-packages/sympy/core/tests/test_constructor_postprocessor.py new file mode 100644 index 0000000000000000000000000000000000000000..c199e24eddf8ef7c2a14e38d1ad2dc95e4acc0cc --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_constructor_postprocessor.py @@ -0,0 +1,87 @@ +from sympy.core.basic import Basic +from sympy.core.mul import Mul +from sympy.core.symbol import (Symbol, symbols) + +from sympy.testing.pytest import XFAIL + +class SymbolInMulOnce(Symbol): + # Test class for a symbol that can only appear once in a `Mul` expression. + pass + + +Basic._constructor_postprocessor_mapping[SymbolInMulOnce] = { + "Mul": [lambda x: x], + "Pow": [lambda x: x.base if isinstance(x.base, SymbolInMulOnce) else x], + "Add": [lambda x: x], +} + + +def _postprocess_SymbolRemovesOtherSymbols(expr): + args = tuple(i for i in expr.args if not isinstance(i, Symbol) or isinstance(i, SymbolRemovesOtherSymbols)) + if args == expr.args: + return expr + return Mul.fromiter(args) + + +class SymbolRemovesOtherSymbols(Symbol): + # Test class for a symbol that removes other symbols in `Mul`. + pass + +Basic._constructor_postprocessor_mapping[SymbolRemovesOtherSymbols] = { + "Mul": [_postprocess_SymbolRemovesOtherSymbols], +} + +class SubclassSymbolInMulOnce(SymbolInMulOnce): + pass + +class SubclassSymbolRemovesOtherSymbols(SymbolRemovesOtherSymbols): + pass + + +def test_constructor_postprocessors1(): + x = SymbolInMulOnce("x") + y = SymbolInMulOnce("y") + assert isinstance(3*x, Mul) + assert (3*x).args == (3, x) + assert x*x == x + assert 3*x*x == 3*x + assert 2*x*x + x == 3*x + assert x**3*y*y == x*y + assert x**5 + y*x**3 == x + x*y + + w = SymbolRemovesOtherSymbols("w") + assert x*w == w + assert (3*w).args == (3, w) + assert set((w + x).args) == {x, w} + +def test_constructor_postprocessors2(): + x = SubclassSymbolInMulOnce("x") + y = SubclassSymbolInMulOnce("y") + assert isinstance(3*x, Mul) + assert (3*x).args == (3, x) + assert x*x == x + assert 3*x*x == 3*x + assert 2*x*x + x == 3*x + assert x**3*y*y == x*y + assert x**5 + y*x**3 == x + x*y + + w = SubclassSymbolRemovesOtherSymbols("w") + assert x*w == w + assert (3*w).args == (3, w) + assert set((w + x).args) == {x, w} + + +@XFAIL +def test_subexpression_postprocessors(): + # The postprocessors used to work with subexpressions, but the + # functionality was removed. See #15948. + a = symbols("a") + x = SymbolInMulOnce("x") + w = SymbolRemovesOtherSymbols("w") + assert 3*a*w**2 == 3*w**2 + assert 3*a*x**3*w**2 == 3*w**2 + + x = SubclassSymbolInMulOnce("x") + w = SubclassSymbolRemovesOtherSymbols("w") + assert 3*a*w**2 == 3*w**2 + assert 3*a*x**3*w**2 == 3*w**2 diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_count_ops.py b/lib/python3.12/site-packages/sympy/core/tests/test_count_ops.py new file mode 100644 index 0000000000000000000000000000000000000000..bc95004ef5ba4421927289a049a9197d208c30d0 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_count_ops.py @@ -0,0 +1,155 @@ +from sympy.concrete.summations import Sum +from sympy.core.basic import Basic +from sympy.core.function import (Derivative, Function, count_ops) +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.relational import (Eq, Rel) +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.integrals.integrals import Integral +from sympy.logic.boolalg import (And, Equivalent, ITE, Implies, Nand, + Nor, Not, Or, Xor) +from sympy.matrices.expressions.matexpr import MatrixSymbol +from sympy.core.containers import Tuple + +x, y, z = symbols('x,y,z') +a, b, c = symbols('a,b,c') + +def test_count_ops_non_visual(): + def count(val): + return count_ops(val, visual=False) + assert count(x) == 0 + assert count(x) is not S.Zero + assert count(x + y) == 1 + assert count(x + y) is not S.One + assert count(x + y*x + 2*y) == 4 + assert count({x + y: x}) == 1 + assert count({x + y: S(2) + x}) is not S.One + assert count(x < y) == 1 + assert count(Or(x,y)) == 1 + assert count(And(x,y)) == 1 + assert count(Not(x)) == 1 + assert count(Nor(x,y)) == 2 + assert count(Nand(x,y)) == 2 + assert count(Xor(x,y)) == 1 + assert count(Implies(x,y)) == 1 + assert count(Equivalent(x,y)) == 1 + assert count(ITE(x,y,z)) == 1 + assert count(ITE(True,x,y)) == 0 + + +def test_count_ops_visual(): + ADD, MUL, POW, SIN, COS, EXP, AND, D, G, M = symbols( + 'Add Mul Pow sin cos exp And Derivative Integral Sum'.upper()) + DIV, SUB, NEG = symbols('DIV SUB NEG') + LT, LE, GT, GE, EQ, NE = symbols('LT LE GT GE EQ NE') + NOT, OR, AND, XOR, IMPLIES, EQUIVALENT, _ITE, BASIC, TUPLE = symbols( + 'Not Or And Xor Implies Equivalent ITE Basic Tuple'.upper()) + + def count(val): + return count_ops(val, visual=True) + + assert count(7) is S.Zero + assert count(S(7)) is S.Zero + assert count(-1) == NEG + assert count(-2) == NEG + assert count(S(2)/3) == DIV + assert count(Rational(2, 3)) == DIV + assert count(pi/3) == DIV + assert count(-pi/3) == DIV + NEG + assert count(I - 1) == SUB + assert count(1 - I) == SUB + assert count(1 - 2*I) == SUB + MUL + + assert count(x) is S.Zero + assert count(-x) == NEG + assert count(-2*x/3) == NEG + DIV + MUL + assert count(Rational(-2, 3)*x) == NEG + DIV + MUL + assert count(1/x) == DIV + assert count(1/(x*y)) == DIV + MUL + assert count(-1/x) == NEG + DIV + assert count(-2/x) == NEG + DIV + assert count(x/y) == DIV + assert count(-x/y) == NEG + DIV + + assert count(x**2) == POW + assert count(-x**2) == POW + NEG + assert count(-2*x**2) == POW + MUL + NEG + + assert count(x + pi/3) == ADD + DIV + assert count(x + S.One/3) == ADD + DIV + assert count(x + Rational(1, 3)) == ADD + DIV + assert count(x + y) == ADD + assert count(x - y) == SUB + assert count(y - x) == SUB + assert count(-1/(x - y)) == DIV + NEG + SUB + assert count(-1/(y - x)) == DIV + NEG + SUB + assert count(1 + x**y) == ADD + POW + assert count(1 + x + y) == 2*ADD + assert count(1 + x + y + z) == 3*ADD + assert count(1 + x**y + 2*x*y + y**2) == 3*ADD + 2*POW + 2*MUL + assert count(2*z + y + x + 1) == 3*ADD + MUL + assert count(2*z + y**17 + x + 1) == 3*ADD + MUL + POW + assert count(2*z + y**17 + x + sin(x)) == 3*ADD + POW + MUL + SIN + assert count(2*z + y**17 + x + sin(x**2)) == 3*ADD + MUL + 2*POW + SIN + assert count(2*z + y**17 + x + sin( + x**2) + exp(cos(x))) == 4*ADD + MUL + 2*POW + EXP + COS + SIN + + assert count(Derivative(x, x)) == D + assert count(Integral(x, x) + 2*x/(1 + x)) == G + DIV + MUL + 2*ADD + assert count(Sum(x, (x, 1, x + 1)) + 2*x/(1 + x)) == M + DIV + MUL + 3*ADD + assert count(Basic()) is S.Zero + + assert count({x + 1: sin(x)}) == ADD + SIN + assert count([x + 1, sin(x) + y, None]) == ADD + SIN + ADD + assert count({x + 1: sin(x), y: cos(x) + 1}) == SIN + COS + 2*ADD + assert count({}) is S.Zero + assert count([x + 1, sin(x)*y, None]) == SIN + ADD + MUL + assert count([]) is S.Zero + + assert count(Basic()) == 0 + assert count(Basic(Basic(),Basic(x,x+y))) == ADD + 2*BASIC + assert count(Basic(x, x + y)) == ADD + BASIC + assert [count(Rel(x, y, op)) for op in '< <= > >= == <> !='.split() + ] == [LT, LE, GT, GE, EQ, NE, NE] + assert count(Or(x, y)) == OR + assert count(And(x, y)) == AND + assert count(Or(x, Or(y, And(z, a)))) == AND + OR + assert count(Nor(x, y)) == NOT + OR + assert count(Nand(x, y)) == NOT + AND + assert count(Xor(x, y)) == XOR + assert count(Implies(x, y)) == IMPLIES + assert count(Equivalent(x, y)) == EQUIVALENT + assert count(ITE(x, y, z)) == _ITE + assert count([Or(x, y), And(x, y), Basic(x + y)] + ) == ADD + AND + BASIC + OR + + assert count(Basic(Tuple(x))) == BASIC + TUPLE + #It checks that TUPLE is counted as an operation. + + assert count(Eq(x + y, S(2))) == ADD + EQ + + +def test_issue_9324(): + def count(val): + return count_ops(val, visual=False) + + M = MatrixSymbol('M', 10, 10) + assert count(M[0, 0]) == 0 + assert count(2 * M[0, 0] + M[5, 7]) == 2 + P = MatrixSymbol('P', 3, 3) + Q = MatrixSymbol('Q', 3, 3) + assert count(P + Q) == 1 + m = Symbol('m', integer=True) + n = Symbol('n', integer=True) + M = MatrixSymbol('M', m + n, m * m) + assert count(M[0, 1]) == 2 + + +def test_issue_21532(): + f = Function('f') + g = Function('g') + FUNC_F, FUNC_G = symbols('FUNC_F, FUNC_G') + assert f(x).count_ops(visual=True) == FUNC_F + assert g(x).count_ops(visual=True) == FUNC_G diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_diff.py b/lib/python3.12/site-packages/sympy/core/tests/test_diff.py new file mode 100644 index 0000000000000000000000000000000000000000..effc9cd91d2e7b6f8f8e5fd04bb667ed71c0ffaf --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_diff.py @@ -0,0 +1,160 @@ +from sympy.concrete.summations import Sum +from sympy.core.expr import Expr +from sympy.core.function import (Derivative, Function, diff, Subs) +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.complexes import (im, re) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import Max +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (cos, cot, sin, tan) +from sympy.tensor.array.ndim_array import NDimArray +from sympy.testing.pytest import raises +from sympy.abc import a, b, c, x, y, z + +def test_diff(): + assert Rational(1, 3).diff(x) is S.Zero + assert I.diff(x) is S.Zero + assert pi.diff(x) is S.Zero + assert x.diff(x, 0) == x + assert (x**2).diff(x, 2, x) == 0 + assert (x**2).diff((x, 2), x) == 0 + assert (x**2).diff((x, 1), x) == 2 + assert (x**2).diff((x, 1), (x, 1)) == 2 + assert (x**2).diff((x, 2)) == 2 + assert (x**2).diff(x, y, 0) == 2*x + assert (x**2).diff(x, (y, 0)) == 2*x + assert (x**2).diff(x, y) == 0 + raises(ValueError, lambda: x.diff(1, x)) + + p = Rational(5) + e = a*b + b**p + assert e.diff(a) == b + assert e.diff(b) == a + 5*b**4 + assert e.diff(b).diff(a) == Rational(1) + e = a*(b + c) + assert e.diff(a) == b + c + assert e.diff(b) == a + assert e.diff(b).diff(a) == Rational(1) + e = c**p + assert e.diff(c, 6) == Rational(0) + assert e.diff(c, 5) == Rational(120) + e = c**Rational(2) + assert e.diff(c) == 2*c + e = a*b*c + assert e.diff(c) == a*b + + +def test_diff2(): + n3 = Rational(3) + n2 = Rational(2) + n6 = Rational(6) + + e = n3*(-n2 + x**n2)*cos(x) + x*(-n6 + x**n2)*sin(x) + assert e == 3*(-2 + x**2)*cos(x) + x*(-6 + x**2)*sin(x) + assert e.diff(x).expand() == x**3*cos(x) + + e = (x + 1)**3 + assert e.diff(x) == 3*(x + 1)**2 + e = x*(x + 1)**3 + assert e.diff(x) == (x + 1)**3 + 3*x*(x + 1)**2 + e = 2*exp(x*x)*x + assert e.diff(x) == 2*exp(x**2) + 4*x**2*exp(x**2) + + +def test_diff3(): + p = Rational(5) + e = a*b + sin(b**p) + assert e == a*b + sin(b**5) + assert e.diff(a) == b + assert e.diff(b) == a + 5*b**4*cos(b**5) + e = tan(c) + assert e == tan(c) + assert e.diff(c) in [cos(c)**(-2), 1 + sin(c)**2/cos(c)**2, 1 + tan(c)**2] + e = c*log(c) - c + assert e == -c + c*log(c) + assert e.diff(c) == log(c) + e = log(sin(c)) + assert e == log(sin(c)) + assert e.diff(c) in [sin(c)**(-1)*cos(c), cot(c)] + e = (Rational(2)**a/log(Rational(2))) + assert e == 2**a*log(Rational(2))**(-1) + assert e.diff(a) == 2**a + + +def test_diff_no_eval_derivative(): + class My(Expr): + def __new__(cls, x): + return Expr.__new__(cls, x) + + # My doesn't have its own _eval_derivative method + assert My(x).diff(x).func is Derivative + assert My(x).diff(x, 3).func is Derivative + assert re(x).diff(x, 2) == Derivative(re(x), (x, 2)) # issue 15518 + assert diff(NDimArray([re(x), im(x)]), (x, 2)) == NDimArray( + [Derivative(re(x), (x, 2)), Derivative(im(x), (x, 2))]) + # it doesn't have y so it shouldn't need a method for this case + assert My(x).diff(y) == 0 + + +def test_speed(): + # this should return in 0.0s. If it takes forever, it's wrong. + assert x.diff(x, 10**8) == 0 + + +def test_deriv_noncommutative(): + A = Symbol("A", commutative=False) + f = Function("f") + assert A*f(x)*A == f(x)*A**2 + assert A*f(x).diff(x)*A == f(x).diff(x) * A**2 + + +def test_diff_nth_derivative(): + f = Function("f") + n = Symbol("n", integer=True) + + expr = diff(sin(x), (x, n)) + expr2 = diff(f(x), (x, 2)) + expr3 = diff(f(x), (x, n)) + + assert expr.subs(sin(x), cos(-x)) == Derivative(cos(-x), (x, n)) + assert expr.subs(n, 1).doit() == cos(x) + assert expr.subs(n, 2).doit() == -sin(x) + + assert expr2.subs(Derivative(f(x), x), y) == Derivative(y, x) + # Currently not supported (cannot determine if `n > 1`): + #assert expr3.subs(Derivative(f(x), x), y) == Derivative(y, (x, n-1)) + assert expr3 == Derivative(f(x), (x, n)) + + assert diff(x, (x, n)) == Piecewise((x, Eq(n, 0)), (1, Eq(n, 1)), (0, True)) + assert diff(2*x, (x, n)).dummy_eq( + Sum(Piecewise((2*x*factorial(n)/(factorial(y)*factorial(-y + n)), + Eq(y, 0) & Eq(Max(0, -y + n), 0)), + (2*factorial(n)/(factorial(y)*factorial(-y + n)), Eq(y, 0) & Eq(Max(0, + -y + n), 1)), (0, True)), (y, 0, n))) + # TODO: assert diff(x**2, (x, n)) == x**(2-n)*ff(2, n) + exprm = x*sin(x) + mul_diff = diff(exprm, (x, n)) + assert isinstance(mul_diff, Sum) + for i in range(5): + assert mul_diff.subs(n, i).doit() == exprm.diff((x, i)).expand() + + exprm2 = 2*y*x*sin(x)*cos(x)*log(x)*exp(x) + dex = exprm2.diff((x, n)) + assert isinstance(dex, Sum) + for i in range(7): + assert dex.subs(n, i).doit().expand() == \ + exprm2.diff((x, i)).expand() + + assert (cos(x)*sin(y)).diff([[x, y, z]]) == NDimArray([ + -sin(x)*sin(y), cos(x)*cos(y), 0]) + + +def test_issue_16160(): + assert Derivative(x**3, (x, x)).subs(x, 2) == Subs( + Derivative(x**3, (x, 2)), x, 2) + assert Derivative(1 + x**3, (x, x)).subs(x, 0 + ) == Derivative(1 + y**3, (y, 0)).subs(y, 0) diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_function.py b/lib/python3.12/site-packages/sympy/core/tests/test_function.py new file mode 100644 index 0000000000000000000000000000000000000000..7cc5df95f2399a96329f1111d3ef2cc9aa1342f8 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_function.py @@ -0,0 +1,1455 @@ +from sympy.concrete.summations import Sum +from sympy.core.basic import Basic, _aresame +from sympy.core.cache import clear_cache +from sympy.core.containers import Dict, Tuple +from sympy.core.expr import Expr, unchanged +from sympy.core.function import (Subs, Function, diff, Lambda, expand, + nfloat, Derivative) +from sympy.core.numbers import E, Float, zoo, Rational, pi, I, oo, nan +from sympy.core.power import Pow +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import symbols, Dummy, Symbol +from sympy.functions.elementary.complexes import im, re +from sympy.functions.elementary.exponential import log, exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import sin, cos, acos +from sympy.functions.special.error_functions import expint +from sympy.functions.special.gamma_functions import loggamma, polygamma +from sympy.matrices.dense import Matrix +from sympy.printing.str import sstr +from sympy.series.order import O +from sympy.tensor.indexed import Indexed +from sympy.core.function import (PoleError, _mexpand, arity, + BadSignatureError, BadArgumentsError) +from sympy.core.parameters import _exp_is_pow +from sympy.core.sympify import sympify, SympifyError +from sympy.matrices import MutableMatrix, ImmutableMatrix +from sympy.sets.sets import FiniteSet +from sympy.solvers.solveset import solveset +from sympy.tensor.array import NDimArray +from sympy.utilities.iterables import subsets, variations +from sympy.testing.pytest import XFAIL, raises, warns_deprecated_sympy, _both_exp_pow + +from sympy.abc import t, w, x, y, z +f, g, h = symbols('f g h', cls=Function) +_xi_1, _xi_2, _xi_3 = [Dummy() for i in range(3)] + +def test_f_expand_complex(): + x = Symbol('x', real=True) + + assert f(x).expand(complex=True) == I*im(f(x)) + re(f(x)) + assert exp(x).expand(complex=True) == exp(x) + assert exp(I*x).expand(complex=True) == cos(x) + I*sin(x) + assert exp(z).expand(complex=True) == cos(im(z))*exp(re(z)) + \ + I*sin(im(z))*exp(re(z)) + + +def test_bug1(): + e = sqrt(-log(w)) + assert e.subs(log(w), -x) == sqrt(x) + + e = sqrt(-5*log(w)) + assert e.subs(log(w), -x) == sqrt(5*x) + + +def test_general_function(): + nu = Function('nu') + + e = nu(x) + edx = e.diff(x) + edy = e.diff(y) + edxdx = e.diff(x).diff(x) + edxdy = e.diff(x).diff(y) + assert e == nu(x) + assert edx != nu(x) + assert edx == diff(nu(x), x) + assert edy == 0 + assert edxdx == diff(diff(nu(x), x), x) + assert edxdy == 0 + +def test_general_function_nullary(): + nu = Function('nu') + + e = nu() + edx = e.diff(x) + edxdx = e.diff(x).diff(x) + assert e == nu() + assert edx != nu() + assert edx == 0 + assert edxdx == 0 + + +def test_derivative_subs_bug(): + e = diff(g(x), x) + assert e.subs(g(x), f(x)) != e + assert e.subs(g(x), f(x)) == Derivative(f(x), x) + assert e.subs(g(x), -f(x)) == Derivative(-f(x), x) + + assert e.subs(x, y) == Derivative(g(y), y) + + +def test_derivative_subs_self_bug(): + d = diff(f(x), x) + + assert d.subs(d, y) == y + + +def test_derivative_linearity(): + assert diff(-f(x), x) == -diff(f(x), x) + assert diff(8*f(x), x) == 8*diff(f(x), x) + assert diff(8*f(x), x) != 7*diff(f(x), x) + assert diff(8*f(x)*x, x) == 8*f(x) + 8*x*diff(f(x), x) + assert diff(8*f(x)*y*x, x).expand() == 8*y*f(x) + 8*y*x*diff(f(x), x) + + +def test_derivative_evaluate(): + assert Derivative(sin(x), x) != diff(sin(x), x) + assert Derivative(sin(x), x).doit() == diff(sin(x), x) + + assert Derivative(Derivative(f(x), x), x) == diff(f(x), x, x) + assert Derivative(sin(x), x, 0) == sin(x) + assert Derivative(sin(x), (x, y), (x, -y)) == sin(x) + + +def test_diff_symbols(): + assert diff(f(x, y, z), x, y, z) == Derivative(f(x, y, z), x, y, z) + assert diff(f(x, y, z), x, x, x) == Derivative(f(x, y, z), x, x, x) == Derivative(f(x, y, z), (x, 3)) + assert diff(f(x, y, z), x, 3) == Derivative(f(x, y, z), x, 3) + + # issue 5028 + assert [diff(-z + x/y, sym) for sym in (z, x, y)] == [-1, 1/y, -x/y**2] + assert diff(f(x, y, z), x, y, z, 2) == Derivative(f(x, y, z), x, y, z, z) + assert diff(f(x, y, z), x, y, z, 2, evaluate=False) == \ + Derivative(f(x, y, z), x, y, z, z) + assert Derivative(f(x, y, z), x, y, z)._eval_derivative(z) == \ + Derivative(f(x, y, z), x, y, z, z) + assert Derivative(Derivative(f(x, y, z), x), y)._eval_derivative(z) == \ + Derivative(f(x, y, z), x, y, z) + + raises(TypeError, lambda: cos(x).diff((x, y)).variables) + assert cos(x).diff((x, y))._wrt_variables == [x] + + # issue 23222 + assert sympify("a*x+b").diff("x") == sympify("a") + +def test_Function(): + class myfunc(Function): + @classmethod + def eval(cls): # zero args + return + + assert myfunc.nargs == FiniteSet(0) + assert myfunc().nargs == FiniteSet(0) + raises(TypeError, lambda: myfunc(x).nargs) + + class myfunc(Function): + @classmethod + def eval(cls, x): # one arg + return + + assert myfunc.nargs == FiniteSet(1) + assert myfunc(x).nargs == FiniteSet(1) + raises(TypeError, lambda: myfunc(x, y).nargs) + + class myfunc(Function): + @classmethod + def eval(cls, *x): # star args + return + + assert myfunc.nargs == S.Naturals0 + assert myfunc(x).nargs == S.Naturals0 + + +def test_nargs(): + f = Function('f') + assert f.nargs == S.Naturals0 + assert f(1).nargs == S.Naturals0 + assert Function('f', nargs=2)(1, 2).nargs == FiniteSet(2) + assert sin.nargs == FiniteSet(1) + assert sin(2).nargs == FiniteSet(1) + assert log.nargs == FiniteSet(1, 2) + assert log(2).nargs == FiniteSet(1, 2) + assert Function('f', nargs=2).nargs == FiniteSet(2) + assert Function('f', nargs=0).nargs == FiniteSet(0) + assert Function('f', nargs=(0, 1)).nargs == FiniteSet(0, 1) + assert Function('f', nargs=None).nargs == S.Naturals0 + raises(ValueError, lambda: Function('f', nargs=())) + +def test_nargs_inheritance(): + class f1(Function): + nargs = 2 + class f2(f1): + pass + class f3(f2): + pass + class f4(f3): + nargs = 1,2 + class f5(f4): + pass + class f6(f5): + pass + class f7(f6): + nargs=None + class f8(f7): + pass + class f9(f8): + pass + class f10(f9): + nargs = 1 + class f11(f10): + pass + assert f1.nargs == FiniteSet(2) + assert f2.nargs == FiniteSet(2) + assert f3.nargs == FiniteSet(2) + assert f4.nargs == FiniteSet(1, 2) + assert f5.nargs == FiniteSet(1, 2) + assert f6.nargs == FiniteSet(1, 2) + assert f7.nargs == S.Naturals0 + assert f8.nargs == S.Naturals0 + assert f9.nargs == S.Naturals0 + assert f10.nargs == FiniteSet(1) + assert f11.nargs == FiniteSet(1) + +def test_arity(): + f = lambda x, y: 1 + assert arity(f) == 2 + def f(x, y, z=None): + pass + assert arity(f) == (2, 3) + assert arity(lambda *x: x) is None + assert arity(log) == (1, 2) + + +def test_Lambda(): + e = Lambda(x, x**2) + assert e(4) == 16 + assert e(x) == x**2 + assert e(y) == y**2 + + assert Lambda((), 42)() == 42 + assert unchanged(Lambda, (), 42) + assert Lambda((), 42) != Lambda((), 43) + assert Lambda((), f(x))() == f(x) + assert Lambda((), 42).nargs == FiniteSet(0) + + assert unchanged(Lambda, (x,), x**2) + assert Lambda(x, x**2) == Lambda((x,), x**2) + assert Lambda(x, x**2) != Lambda(x, x**2 + 1) + assert Lambda((x, y), x**y) != Lambda((y, x), y**x) + assert Lambda((x, y), x**y) != Lambda((x, y), y**x) + + assert Lambda((x, y), x**y)(x, y) == x**y + assert Lambda((x, y), x**y)(3, 3) == 3**3 + assert Lambda((x, y), x**y)(x, 3) == x**3 + assert Lambda((x, y), x**y)(3, y) == 3**y + assert Lambda(x, f(x))(x) == f(x) + assert Lambda(x, x**2)(e(x)) == x**4 + assert e(e(x)) == x**4 + + x1, x2 = (Indexed('x', i) for i in (1, 2)) + assert Lambda((x1, x2), x1 + x2)(x, y) == x + y + + assert Lambda((x, y), x + y).nargs == FiniteSet(2) + + p = x, y, z, t + assert Lambda(p, t*(x + y + z))(*p) == t * (x + y + z) + + eq = Lambda(x, 2*x) + Lambda(y, 2*y) + assert eq != 2*Lambda(x, 2*x) + assert eq.as_dummy() == 2*Lambda(x, 2*x).as_dummy() + assert Lambda(x, 2*x) not in [ Lambda(x, x) ] + raises(BadSignatureError, lambda: Lambda(1, x)) + assert Lambda(x, 1)(1) is S.One + + raises(BadSignatureError, lambda: Lambda((x, x), x + 2)) + raises(BadSignatureError, lambda: Lambda(((x, x), y), x)) + raises(BadSignatureError, lambda: Lambda(((y, x), x), x)) + raises(BadSignatureError, lambda: Lambda(((y, 1), 2), x)) + + with warns_deprecated_sympy(): + assert Lambda([x, y], x+y) == Lambda((x, y), x+y) + + flam = Lambda(((x, y),), x + y) + assert flam((2, 3)) == 5 + flam = Lambda(((x, y), z), x + y + z) + assert flam((2, 3), 1) == 6 + flam = Lambda((((x, y), z),), x + y + z) + assert flam(((2, 3), 1)) == 6 + raises(BadArgumentsError, lambda: flam(1, 2, 3)) + flam = Lambda( (x,), (x, x)) + assert flam(1,) == (1, 1) + assert flam((1,)) == ((1,), (1,)) + flam = Lambda( ((x,),), (x, x)) + raises(BadArgumentsError, lambda: flam(1)) + assert flam((1,)) == (1, 1) + + # Previously TypeError was raised so this is potentially needed for + # backwards compatibility. + assert issubclass(BadSignatureError, TypeError) + assert issubclass(BadArgumentsError, TypeError) + + # These are tested to see they don't raise: + hash(Lambda(x, 2*x)) + hash(Lambda(x, x)) # IdentityFunction subclass + + +def test_IdentityFunction(): + assert Lambda(x, x) is Lambda(y, y) is S.IdentityFunction + assert Lambda(x, 2*x) is not S.IdentityFunction + assert Lambda((x, y), x) is not S.IdentityFunction + + +def test_Lambda_symbols(): + assert Lambda(x, 2*x).free_symbols == set() + assert Lambda(x, x*y).free_symbols == {y} + assert Lambda((), 42).free_symbols == set() + assert Lambda((), x*y).free_symbols == {x,y} + + +def test_functionclas_symbols(): + assert f.free_symbols == set() + + +def test_Lambda_arguments(): + raises(TypeError, lambda: Lambda(x, 2*x)(x, y)) + raises(TypeError, lambda: Lambda((x, y), x + y)(x)) + raises(TypeError, lambda: Lambda((), 42)(x)) + + +def test_Lambda_equality(): + assert Lambda((x, y), 2*x) == Lambda((x, y), 2*x) + # these, of course, should never be equal + assert Lambda(x, 2*x) != Lambda((x, y), 2*x) + assert Lambda(x, 2*x) != 2*x + # But it is tempting to want expressions that differ only + # in bound symbols to compare the same. But this is not what + # Python's `==` is intended to do; two objects that compare + # as equal means that they are indistibguishable and cache to the + # same value. We wouldn't want to expression that are + # mathematically the same but written in different variables to be + # interchanged else what is the point of allowing for different + # variable names? + assert Lambda(x, 2*x) != Lambda(y, 2*y) + + +def test_Subs(): + assert Subs(1, (), ()) is S.One + # check null subs influence on hashing + assert Subs(x, y, z) != Subs(x, y, 1) + # neutral subs works + assert Subs(x, x, 1).subs(x, y).has(y) + # self mapping var/point + assert Subs(Derivative(f(x), (x, 2)), x, x).doit() == f(x).diff(x, x) + assert Subs(x, x, 0).has(x) # it's a structural answer + assert not Subs(x, x, 0).free_symbols + assert Subs(Subs(x + y, x, 2), y, 1) == Subs(x + y, (x, y), (2, 1)) + assert Subs(x, (x,), (0,)) == Subs(x, x, 0) + assert Subs(x, x, 0) == Subs(y, y, 0) + assert Subs(x, x, 0).subs(x, 1) == Subs(x, x, 0) + assert Subs(y, x, 0).subs(y, 1) == Subs(1, x, 0) + assert Subs(f(x), x, 0).doit() == f(0) + assert Subs(f(x**2), x**2, 0).doit() == f(0) + assert Subs(f(x, y, z), (x, y, z), (0, 1, 1)) != \ + Subs(f(x, y, z), (x, y, z), (0, 0, 1)) + assert Subs(x, y, 2).subs(x, y).doit() == 2 + assert Subs(f(x, y), (x, y, z), (0, 1, 1)) != \ + Subs(f(x, y) + z, (x, y, z), (0, 1, 0)) + assert Subs(f(x, y), (x, y), (0, 1)).doit() == f(0, 1) + assert Subs(Subs(f(x, y), x, 0), y, 1).doit() == f(0, 1) + raises(ValueError, lambda: Subs(f(x, y), (x, y), (0, 0, 1))) + raises(ValueError, lambda: Subs(f(x, y), (x, x, y), (0, 0, 1))) + + assert len(Subs(f(x, y), (x, y), (0, 1)).variables) == 2 + assert Subs(f(x, y), (x, y), (0, 1)).point == Tuple(0, 1) + + assert Subs(f(x), x, 0) == Subs(f(y), y, 0) + assert Subs(f(x, y), (x, y), (0, 1)) == Subs(f(x, y), (y, x), (1, 0)) + assert Subs(f(x)*y, (x, y), (0, 1)) == Subs(f(y)*x, (y, x), (0, 1)) + assert Subs(f(x)*y, (x, y), (1, 1)) == Subs(f(y)*x, (x, y), (1, 1)) + + assert Subs(f(x), x, 0).subs(x, 1).doit() == f(0) + assert Subs(f(x), x, y).subs(y, 0) == Subs(f(x), x, 0) + assert Subs(y*f(x), x, y).subs(y, 2) == Subs(2*f(x), x, 2) + assert (2 * Subs(f(x), x, 0)).subs(Subs(f(x), x, 0), y) == 2*y + + assert Subs(f(x), x, 0).free_symbols == set() + assert Subs(f(x, y), x, z).free_symbols == {y, z} + + assert Subs(f(x).diff(x), x, 0).doit(), Subs(f(x).diff(x), x, 0) + assert Subs(1 + f(x).diff(x), x, 0).doit(), 1 + Subs(f(x).diff(x), x, 0) + assert Subs(y*f(x, y).diff(x), (x, y), (0, 2)).doit() == \ + 2*Subs(Derivative(f(x, 2), x), x, 0) + assert Subs(y**2*f(x), x, 0).diff(y) == 2*y*f(0) + + e = Subs(y**2*f(x), x, y) + assert e.diff(y) == e.doit().diff(y) == y**2*Derivative(f(y), y) + 2*y*f(y) + + assert Subs(f(x), x, 0) + Subs(f(x), x, 0) == 2*Subs(f(x), x, 0) + e1 = Subs(z*f(x), x, 1) + e2 = Subs(z*f(y), y, 1) + assert e1 + e2 == 2*e1 + assert e1.__hash__() == e2.__hash__() + assert Subs(z*f(x + 1), x, 1) not in [ e1, e2 ] + assert Derivative(f(x), x).subs(x, g(x)) == Derivative(f(g(x)), g(x)) + assert Derivative(f(x), x).subs(x, x + y) == Subs(Derivative(f(x), x), + x, x + y) + assert Subs(f(x)*cos(y) + z, (x, y), (0, pi/3)).n(2) == \ + Subs(f(x)*cos(y) + z, (x, y), (0, pi/3)).evalf(2) == \ + z + Rational('1/2').n(2)*f(0) + + assert f(x).diff(x).subs(x, 0).subs(x, y) == f(x).diff(x).subs(x, 0) + assert (x*f(x).diff(x).subs(x, 0)).subs(x, y) == y*f(x).diff(x).subs(x, 0) + assert Subs(Derivative(g(x)**2, g(x), x), g(x), exp(x) + ).doit() == 2*exp(x) + assert Subs(Derivative(g(x)**2, g(x), x), g(x), exp(x) + ).doit(deep=False) == 2*Derivative(exp(x), x) + assert Derivative(f(x, g(x)), x).doit() == Derivative( + f(x, g(x)), g(x))*Derivative(g(x), x) + Subs(Derivative( + f(y, g(x)), y), y, x) + +def test_doitdoit(): + done = Derivative(f(x, g(x)), x, g(x)).doit() + assert done == done.doit() + + +@XFAIL +def test_Subs2(): + # this reflects a limitation of subs(), probably won't fix + assert Subs(f(x), x**2, x).doit() == f(sqrt(x)) + + +def test_expand_function(): + assert expand(x + y) == x + y + assert expand(x + y, complex=True) == I*im(x) + I*im(y) + re(x) + re(y) + assert expand((x + y)**11, modulus=11) == x**11 + y**11 + + +def test_function_comparable(): + assert sin(x).is_comparable is False + assert cos(x).is_comparable is False + + assert sin(Float('0.1')).is_comparable is True + assert cos(Float('0.1')).is_comparable is True + + assert sin(E).is_comparable is True + assert cos(E).is_comparable is True + + assert sin(Rational(1, 3)).is_comparable is True + assert cos(Rational(1, 3)).is_comparable is True + + +def test_function_comparable_infinities(): + assert sin(oo).is_comparable is False + assert sin(-oo).is_comparable is False + assert sin(zoo).is_comparable is False + assert sin(nan).is_comparable is False + + +def test_deriv1(): + # These all require derivatives evaluated at a point (issue 4719) to work. + # See issue 4624 + assert f(2*x).diff(x) == 2*Subs(Derivative(f(x), x), x, 2*x) + assert (f(x)**3).diff(x) == 3*f(x)**2*f(x).diff(x) + assert (f(2*x)**3).diff(x) == 6*f(2*x)**2*Subs( + Derivative(f(x), x), x, 2*x) + + assert f(2 + x).diff(x) == Subs(Derivative(f(x), x), x, x + 2) + assert f(2 + 3*x).diff(x) == 3*Subs( + Derivative(f(x), x), x, 3*x + 2) + assert f(3*sin(x)).diff(x) == 3*cos(x)*Subs( + Derivative(f(x), x), x, 3*sin(x)) + + # See issue 8510 + assert f(x, x + z).diff(x) == ( + Subs(Derivative(f(y, x + z), y), y, x) + + Subs(Derivative(f(x, y), y), y, x + z)) + assert f(x, x**2).diff(x) == ( + 2*x*Subs(Derivative(f(x, y), y), y, x**2) + + Subs(Derivative(f(y, x**2), y), y, x)) + # but Subs is not always necessary + assert f(x, g(y)).diff(g(y)) == Derivative(f(x, g(y)), g(y)) + + +def test_deriv2(): + assert (x**3).diff(x) == 3*x**2 + assert (x**3).diff(x, evaluate=False) != 3*x**2 + assert (x**3).diff(x, evaluate=False) == Derivative(x**3, x) + + assert diff(x**3, x) == 3*x**2 + assert diff(x**3, x, evaluate=False) != 3*x**2 + assert diff(x**3, x, evaluate=False) == Derivative(x**3, x) + + +def test_func_deriv(): + assert f(x).diff(x) == Derivative(f(x), x) + # issue 4534 + assert f(x, y).diff(x, y) - f(x, y).diff(y, x) == 0 + assert Derivative(f(x, y), x, y).args[1:] == ((x, 1), (y, 1)) + assert Derivative(f(x, y), y, x).args[1:] == ((y, 1), (x, 1)) + assert (Derivative(f(x, y), x, y) - Derivative(f(x, y), y, x)).doit() == 0 + + +def test_suppressed_evaluation(): + a = sin(0, evaluate=False) + assert a != 0 + assert a.func is sin + assert a.args == (0,) + + +def test_function_evalf(): + def eq(a, b, eps): + return abs(a - b) < eps + assert eq(sin(1).evalf(15), Float("0.841470984807897"), 1e-13) + assert eq( + sin(2).evalf(25), Float("0.9092974268256816953960199", 25), 1e-23) + assert eq(sin(1 + I).evalf( + 15), Float("1.29845758141598") + Float("0.634963914784736")*I, 1e-13) + assert eq(exp(1 + I).evalf(15), Float( + "1.46869393991588") + Float("2.28735528717884239")*I, 1e-13) + assert eq(exp(-0.5 + 1.5*I).evalf(15), Float( + "0.0429042815937374") + Float("0.605011292285002")*I, 1e-13) + assert eq(log(pi + sqrt(2)*I).evalf( + 15), Float("1.23699044022052") + Float("0.422985442737893")*I, 1e-13) + assert eq(cos(100).evalf(15), Float("0.86231887228768"), 1e-13) + + +def test_extensibility_eval(): + class MyFunc(Function): + @classmethod + def eval(cls, *args): + return (0, 0, 0) + assert MyFunc(0) == (0, 0, 0) + + +@_both_exp_pow +def test_function_non_commutative(): + x = Symbol('x', commutative=False) + assert f(x).is_commutative is False + assert sin(x).is_commutative is False + assert exp(x).is_commutative is False + assert log(x).is_commutative is False + assert f(x).is_complex is False + assert sin(x).is_complex is False + assert exp(x).is_complex is False + assert log(x).is_complex is False + + +def test_function_complex(): + x = Symbol('x', complex=True) + xzf = Symbol('x', complex=True, zero=False) + assert f(x).is_commutative is True + assert sin(x).is_commutative is True + assert exp(x).is_commutative is True + assert log(x).is_commutative is True + assert f(x).is_complex is None + assert sin(x).is_complex is True + assert exp(x).is_complex is True + assert log(x).is_complex is None + assert log(xzf).is_complex is True + + +def test_function__eval_nseries(): + n = Symbol('n') + + assert sin(x)._eval_nseries(x, 2, None) == x + O(x**2) + assert sin(x + 1)._eval_nseries(x, 2, None) == x*cos(1) + sin(1) + O(x**2) + assert sin(pi*(1 - x))._eval_nseries(x, 2, None) == pi*x + O(x**2) + assert acos(1 - x**2)._eval_nseries(x, 2, None) == sqrt(2)*sqrt(x**2) + O(x**2) + assert polygamma(n, x + 1)._eval_nseries(x, 2, None) == \ + polygamma(n, 1) + polygamma(n + 1, 1)*x + O(x**2) + raises(PoleError, lambda: sin(1/x)._eval_nseries(x, 2, None)) + assert acos(1 - x)._eval_nseries(x, 2, None) == sqrt(2)*sqrt(x) + sqrt(2)*x**(S(3)/2)/12 + O(x**2) + assert acos(1 + x)._eval_nseries(x, 2, None) == sqrt(2)*sqrt(-x) + sqrt(2)*(-x)**(S(3)/2)/12 + O(x**2) + assert loggamma(1/x)._eval_nseries(x, 0, None) == \ + log(x)/2 - log(x)/x - 1/x + O(1, x) + assert loggamma(log(1/x)).nseries(x, n=1, logx=y) == loggamma(-y) + + # issue 6725: + assert expint(Rational(3, 2), -x)._eval_nseries(x, 5, None) == \ + 2 - 2*sqrt(pi)*sqrt(-x) - 2*x + x**2 + x**3/3 + x**4/12 + 4*I*x**(S(3)/2)*sqrt(-x)/3 + \ + 2*I*x**(S(5)/2)*sqrt(-x)/5 + 2*I*x**(S(7)/2)*sqrt(-x)/21 + O(x**5) + assert sin(sqrt(x))._eval_nseries(x, 3, None) == \ + sqrt(x) - x**Rational(3, 2)/6 + x**Rational(5, 2)/120 + O(x**3) + + # issue 19065: + s1 = f(x,y).series(y, n=2) + assert {i.name for i in s1.atoms(Symbol)} == {'x', 'xi', 'y'} + xi = Symbol('xi') + s2 = f(xi, y).series(y, n=2) + assert {i.name for i in s2.atoms(Symbol)} == {'xi', 'xi0', 'y'} + +def test_doit(): + n = Symbol('n', integer=True) + f = Sum(2 * n * x, (n, 1, 3)) + d = Derivative(f, x) + assert d.doit() == 12 + assert d.doit(deep=False) == Sum(2*n, (n, 1, 3)) + + +def test_evalf_default(): + from sympy.functions.special.gamma_functions import polygamma + assert type(sin(4.0)) == Float + assert type(re(sin(I + 1.0))) == Float + assert type(im(sin(I + 1.0))) == Float + assert type(sin(4)) == sin + assert type(polygamma(2.0, 4.0)) == Float + assert type(sin(Rational(1, 4))) == sin + + +def test_issue_5399(): + args = [x, y, S(2), S.Half] + + def ok(a): + """Return True if the input args for diff are ok""" + if not a: + return False + if a[0].is_Symbol is False: + return False + s_at = [i for i in range(len(a)) if a[i].is_Symbol] + n_at = [i for i in range(len(a)) if not a[i].is_Symbol] + # every symbol is followed by symbol or int + # every number is followed by a symbol + return (all(a[i + 1].is_Symbol or a[i + 1].is_Integer + for i in s_at if i + 1 < len(a)) and + all(a[i + 1].is_Symbol + for i in n_at if i + 1 < len(a))) + eq = x**10*y**8 + for a in subsets(args): + for v in variations(a, len(a)): + if ok(v): + eq.diff(*v) # does not raise + else: + raises(ValueError, lambda: eq.diff(*v)) + + +def test_derivative_numerically(): + z0 = x._random() + assert abs(Derivative(sin(x), x).doit_numerically(z0) - cos(z0)) < 1e-15 + + +def test_fdiff_argument_index_error(): + from sympy.core.function import ArgumentIndexError + + class myfunc(Function): + nargs = 1 # define since there is no eval routine + + def fdiff(self, idx): + raise ArgumentIndexError + mf = myfunc(x) + assert mf.diff(x) == Derivative(mf, x) + raises(TypeError, lambda: myfunc(x, x)) + + +def test_deriv_wrt_function(): + x = f(t) + xd = diff(x, t) + xdd = diff(xd, t) + y = g(t) + yd = diff(y, t) + + assert diff(x, t) == xd + assert diff(2 * x + 4, t) == 2 * xd + assert diff(2 * x + 4 + y, t) == 2 * xd + yd + assert diff(2 * x + 4 + y * x, t) == 2 * xd + x * yd + xd * y + assert diff(2 * x + 4 + y * x, x) == 2 + y + assert (diff(4 * x**2 + 3 * x + x * y, t) == 3 * xd + x * yd + xd * y + + 8 * x * xd) + assert (diff(4 * x**2 + 3 * xd + x * y, t) == 3 * xdd + x * yd + xd * y + + 8 * x * xd) + assert diff(4 * x**2 + 3 * xd + x * y, xd) == 3 + assert diff(4 * x**2 + 3 * xd + x * y, xdd) == 0 + assert diff(sin(x), t) == xd * cos(x) + assert diff(exp(x), t) == xd * exp(x) + assert diff(sqrt(x), t) == xd / (2 * sqrt(x)) + + +def test_diff_wrt_value(): + assert Expr()._diff_wrt is False + assert x._diff_wrt is True + assert f(x)._diff_wrt is True + assert Derivative(f(x), x)._diff_wrt is True + assert Derivative(x**2, x)._diff_wrt is False + + +def test_diff_wrt(): + fx = f(x) + dfx = diff(f(x), x) + ddfx = diff(f(x), x, x) + + assert diff(sin(fx) + fx**2, fx) == cos(fx) + 2*fx + assert diff(sin(dfx) + dfx**2, dfx) == cos(dfx) + 2*dfx + assert diff(sin(ddfx) + ddfx**2, ddfx) == cos(ddfx) + 2*ddfx + assert diff(fx**2, dfx) == 0 + assert diff(fx**2, ddfx) == 0 + assert diff(dfx**2, fx) == 0 + assert diff(dfx**2, ddfx) == 0 + assert diff(ddfx**2, dfx) == 0 + + assert diff(fx*dfx*ddfx, fx) == dfx*ddfx + assert diff(fx*dfx*ddfx, dfx) == fx*ddfx + assert diff(fx*dfx*ddfx, ddfx) == fx*dfx + + assert diff(f(x), x).diff(f(x)) == 0 + assert (sin(f(x)) - cos(diff(f(x), x))).diff(f(x)) == cos(f(x)) + + assert diff(sin(fx), fx, x) == diff(sin(fx), x, fx) + + # Chain rule cases + assert f(g(x)).diff(x) == \ + Derivative(g(x), x)*Derivative(f(g(x)), g(x)) + assert diff(f(g(x), h(y)), x) == \ + Derivative(g(x), x)*Derivative(f(g(x), h(y)), g(x)) + assert diff(f(g(x), h(x)), x) == ( + Derivative(f(g(x), h(x)), g(x))*Derivative(g(x), x) + + Derivative(f(g(x), h(x)), h(x))*Derivative(h(x), x)) + assert f( + sin(x)).diff(x) == cos(x)*Subs(Derivative(f(x), x), x, sin(x)) + + assert diff(f(g(x)), g(x)) == Derivative(f(g(x)), g(x)) + + +def test_diff_wrt_func_subs(): + assert f(g(x)).diff(x).subs(g, Lambda(x, 2*x)).doit() == f(2*x).diff(x) + + +def test_subs_in_derivative(): + expr = sin(x*exp(y)) + u = Function('u') + v = Function('v') + assert Derivative(expr, y).subs(expr, y) == Derivative(y, y) + assert Derivative(expr, y).subs(y, x).doit() == \ + Derivative(expr, y).doit().subs(y, x) + assert Derivative(f(x, y), y).subs(y, x) == Subs(Derivative(f(x, y), y), y, x) + assert Derivative(f(x, y), y).subs(x, y) == Subs(Derivative(f(x, y), y), x, y) + assert Derivative(f(x, y), y).subs(y, g(x, y)) == Subs(Derivative(f(x, y), y), y, g(x, y)).doit() + assert Derivative(f(x, y), y).subs(x, g(x, y)) == Subs(Derivative(f(x, y), y), x, g(x, y)) + assert Derivative(f(x, y), g(y)).subs(x, g(x, y)) == Derivative(f(g(x, y), y), g(y)) + assert Derivative(f(u(x), h(y)), h(y)).subs(h(y), g(x, y)) == \ + Subs(Derivative(f(u(x), h(y)), h(y)), h(y), g(x, y)).doit() + assert Derivative(f(x, y), y).subs(y, z) == Derivative(f(x, z), z) + assert Derivative(f(x, y), y).subs(y, g(y)) == Derivative(f(x, g(y)), g(y)) + assert Derivative(f(g(x), h(y)), h(y)).subs(h(y), u(y)) == \ + Derivative(f(g(x), u(y)), u(y)) + assert Derivative(f(x, f(x, x)), f(x, x)).subs( + f, Lambda((x, y), x + y)) == Subs( + Derivative(z + x, z), z, 2*x) + assert Subs(Derivative(f(f(x)), x), f, cos).doit() == sin(x)*sin(cos(x)) + assert Subs(Derivative(f(f(x)), f(x)), f, cos).doit() == -sin(cos(x)) + # Issue 13791. No comparison (it's a long formula) but this used to raise an exception. + assert isinstance(v(x, y, u(x, y)).diff(y).diff(x).diff(y), Expr) + # This is also related to issues 13791 and 13795; issue 15190 + F = Lambda((x, y), exp(2*x + 3*y)) + abstract = f(x, f(x, x)).diff(x, 2) + concrete = F(x, F(x, x)).diff(x, 2) + assert (abstract.subs(f, F).doit() - concrete).simplify() == 0 + # don't introduce a new symbol if not necessary + assert x in f(x).diff(x).subs(x, 0).atoms() + # case (4) + assert Derivative(f(x,f(x,y)), x, y).subs(x, g(y) + ) == Subs(Derivative(f(x, f(x, y)), x, y), x, g(y)) + + assert Derivative(f(x, x), x).subs(x, 0 + ) == Subs(Derivative(f(x, x), x), x, 0) + # issue 15194 + assert Derivative(f(y, g(x)), (x, z)).subs(z, x + ) == Derivative(f(y, g(x)), (x, x)) + + df = f(x).diff(x) + assert df.subs(df, 1) is S.One + assert df.diff(df) is S.One + dxy = Derivative(f(x, y), x, y) + dyx = Derivative(f(x, y), y, x) + assert dxy.subs(Derivative(f(x, y), y, x), 1) is S.One + assert dxy.diff(dyx) is S.One + assert Derivative(f(x, y), x, 2, y, 3).subs( + dyx, g(x, y)) == Derivative(g(x, y), x, 1, y, 2) + assert Derivative(f(x, x - y), y).subs(x, x + y) == Subs( + Derivative(f(x, x - y), y), x, x + y) + + +def test_diff_wrt_not_allowed(): + # issue 7027 included + for wrt in ( + cos(x), re(x), x**2, x*y, 1 + x, + Derivative(cos(x), x), Derivative(f(f(x)), x)): + raises(ValueError, lambda: diff(f(x), wrt)) + # if we don't differentiate wrt then don't raise error + assert diff(exp(x*y), x*y, 0) == exp(x*y) + + +def test_diff_wrt_intlike(): + class Two: + def __int__(self): + return 2 + + assert cos(x).diff(x, Two()) == -cos(x) + + +def test_klein_gordon_lagrangian(): + m = Symbol('m') + phi = f(x, t) + + L = -(diff(phi, t)**2 - diff(phi, x)**2 - m**2*phi**2)/2 + eqna = Eq( + diff(L, phi) - diff(L, diff(phi, x), x) - diff(L, diff(phi, t), t), 0) + eqnb = Eq(diff(phi, t, t) - diff(phi, x, x) + m**2*phi, 0) + assert eqna == eqnb + + +def test_sho_lagrangian(): + m = Symbol('m') + k = Symbol('k') + x = f(t) + + L = m*diff(x, t)**2/2 - k*x**2/2 + eqna = Eq(diff(L, x), diff(L, diff(x, t), t)) + eqnb = Eq(-k*x, m*diff(x, t, t)) + assert eqna == eqnb + + assert diff(L, x, t) == diff(L, t, x) + assert diff(L, diff(x, t), t) == m*diff(x, t, 2) + assert diff(L, t, diff(x, t)) == -k*x + m*diff(x, t, 2) + + +def test_straight_line(): + F = f(x) + Fd = F.diff(x) + L = sqrt(1 + Fd**2) + assert diff(L, F) == 0 + assert diff(L, Fd) == Fd/sqrt(1 + Fd**2) + + +def test_sort_variable(): + vsort = Derivative._sort_variable_count + def vsort0(*v, reverse=False): + return [i[0] for i in vsort([(i, 0) for i in ( + reversed(v) if reverse else v)])] + + for R in range(2): + assert vsort0(y, x, reverse=R) == [x, y] + assert vsort0(f(x), x, reverse=R) == [x, f(x)] + assert vsort0(f(y), f(x), reverse=R) == [f(x), f(y)] + assert vsort0(g(x), f(y), reverse=R) == [f(y), g(x)] + assert vsort0(f(x, y), f(x), reverse=R) == [f(x), f(x, y)] + fx = f(x).diff(x) + assert vsort0(fx, y, reverse=R) == [y, fx] + fy = f(y).diff(y) + assert vsort0(fy, fx, reverse=R) == [fx, fy] + fxx = fx.diff(x) + assert vsort0(fxx, fx, reverse=R) == [fx, fxx] + assert vsort0(Basic(x), f(x), reverse=R) == [f(x), Basic(x)] + assert vsort0(Basic(y), Basic(x), reverse=R) == [Basic(x), Basic(y)] + assert vsort0(Basic(y, z), Basic(x), reverse=R) == [ + Basic(x), Basic(y, z)] + assert vsort0(fx, x, reverse=R) == [ + x, fx] if R else [fx, x] + assert vsort0(Basic(x), x, reverse=R) == [ + x, Basic(x)] if R else [Basic(x), x] + assert vsort0(Basic(f(x)), f(x), reverse=R) == [ + f(x), Basic(f(x))] if R else [Basic(f(x)), f(x)] + assert vsort0(Basic(x, z), Basic(x), reverse=R) == [ + Basic(x), Basic(x, z)] if R else [Basic(x, z), Basic(x)] + assert vsort([]) == [] + assert _aresame(vsort([(x, 1)]), [Tuple(x, 1)]) + assert vsort([(x, y), (x, z)]) == [(x, y + z)] + assert vsort([(y, 1), (x, 1 + y)]) == [(x, 1 + y), (y, 1)] + # coverage complete; legacy tests below + assert vsort([(x, 3), (y, 2), (z, 1)]) == [(x, 3), (y, 2), (z, 1)] + assert vsort([(h(x), 1), (g(x), 1), (f(x), 1)]) == [ + (f(x), 1), (g(x), 1), (h(x), 1)] + assert vsort([(z, 1), (y, 2), (x, 3), (h(x), 1), (g(x), 1), + (f(x), 1)]) == [(x, 3), (y, 2), (z, 1), (f(x), 1), (g(x), 1), + (h(x), 1)] + assert vsort([(x, 1), (f(x), 1), (y, 1), (f(y), 1)]) == [(x, 1), + (y, 1), (f(x), 1), (f(y), 1)] + assert vsort([(y, 1), (x, 2), (g(x), 1), (f(x), 1), (z, 1), + (h(x), 1), (y, 2), (x, 1)]) == [(x, 3), (y, 3), (z, 1), + (f(x), 1), (g(x), 1), (h(x), 1)] + assert vsort([(z, 1), (y, 1), (f(x), 1), (x, 1), (f(x), 1), + (g(x), 1)]) == [(x, 1), (y, 1), (z, 1), (f(x), 2), (g(x), 1)] + assert vsort([(z, 1), (y, 2), (f(x), 1), (x, 2), (f(x), 2), + (g(x), 1), (z, 2), (z, 1), (y, 1), (x, 1)]) == [(x, 3), (y, 3), + (z, 4), (f(x), 3), (g(x), 1)] + assert vsort(((y, 2), (x, 1), (y, 1), (x, 1))) == [(x, 2), (y, 3)] + assert isinstance(vsort([(x, 3), (y, 2), (z, 1)])[0], Tuple) + assert vsort([(x, 1), (f(x), 1), (x, 1)]) == [(x, 2), (f(x), 1)] + assert vsort([(y, 2), (x, 3), (z, 1)]) == [(x, 3), (y, 2), (z, 1)] + assert vsort([(h(y), 1), (g(x), 1), (f(x), 1)]) == [ + (f(x), 1), (g(x), 1), (h(y), 1)] + assert vsort([(x, 1), (y, 1), (x, 1)]) == [(x, 2), (y, 1)] + assert vsort([(f(x), 1), (f(y), 1), (f(x), 1)]) == [ + (f(x), 2), (f(y), 1)] + dfx = f(x).diff(x) + self = [(dfx, 1), (x, 1)] + assert vsort(self) == self + assert vsort([ + (dfx, 1), (y, 1), (f(x), 1), (x, 1), (f(y), 1), (x, 1)]) == [ + (y, 1), (f(x), 1), (f(y), 1), (dfx, 1), (x, 2)] + dfy = f(y).diff(y) + assert vsort([(dfy, 1), (dfx, 1)]) == [(dfx, 1), (dfy, 1)] + d2fx = dfx.diff(x) + assert vsort([(d2fx, 1), (dfx, 1)]) == [(dfx, 1), (d2fx, 1)] + + +def test_multiple_derivative(): + # Issue #15007 + assert f(x, y).diff(y, y, x, y, x + ) == Derivative(f(x, y), (x, 2), (y, 3)) + + +def test_unhandled(): + class MyExpr(Expr): + def _eval_derivative(self, s): + if not s.name.startswith('xi'): + return self + else: + return None + + eq = MyExpr(f(x), y, z) + assert diff(eq, x, y, f(x), z) == Derivative(eq, f(x)) + assert diff(eq, f(x), x) == Derivative(eq, f(x)) + assert f(x, y).diff(x,(y, z)) == Derivative(f(x, y), x, (y, z)) + assert f(x, y).diff(x,(y, 0)) == Derivative(f(x, y), x) + + +def test_nfloat(): + from sympy.core.basic import _aresame + from sympy.polys.rootoftools import rootof + + x = Symbol("x") + eq = x**Rational(4, 3) + 4*x**(S.One/3)/3 + assert _aresame(nfloat(eq), x**Rational(4, 3) + (4.0/3)*x**(S.One/3)) + assert _aresame(nfloat(eq, exponent=True), x**(4.0/3) + (4.0/3)*x**(1.0/3)) + eq = x**Rational(4, 3) + 4*x**(x/3)/3 + assert _aresame(nfloat(eq), x**Rational(4, 3) + (4.0/3)*x**(x/3)) + big = 12345678901234567890 + # specify precision to match value used in nfloat + Float_big = Float(big, 15) + assert _aresame(nfloat(big), Float_big) + assert _aresame(nfloat(big*x), Float_big*x) + assert _aresame(nfloat(x**big, exponent=True), x**Float_big) + assert nfloat(cos(x + sqrt(2))) == cos(x + nfloat(sqrt(2))) + + # issue 6342 + f = S('x*lamda + lamda**3*(x/2 + 1/2) + lamda**2 + 1/4') + assert not any(a.free_symbols for a in solveset(f.subs(x, -0.139))) + + # issue 6632 + assert nfloat(-100000*sqrt(2500000001) + 5000000001) == \ + 9.99999999800000e-11 + + # issue 7122 + eq = cos(3*x**4 + y)*rootof(x**5 + 3*x**3 + 1, 0) + assert str(nfloat(eq, exponent=False, n=1)) == '-0.7*cos(3.0*x**4 + y)' + + # issue 10933 + for ti in (dict, Dict): + d = ti({S.Half: S.Half}) + n = nfloat(d) + assert isinstance(n, ti) + assert _aresame(list(n.items()).pop(), (S.Half, Float(.5))) + for ti in (dict, Dict): + d = ti({S.Half: S.Half}) + n = nfloat(d, dkeys=True) + assert isinstance(n, ti) + assert _aresame(list(n.items()).pop(), (Float(.5), Float(.5))) + d = [S.Half] + n = nfloat(d) + assert type(n) is list + assert _aresame(n[0], Float(.5)) + assert _aresame(nfloat(Eq(x, S.Half)).rhs, Float(.5)) + assert _aresame(nfloat(S(True)), S(True)) + assert _aresame(nfloat(Tuple(S.Half))[0], Float(.5)) + assert nfloat(Eq((3 - I)**2/2 + I, 0)) == S.false + # pass along kwargs + assert nfloat([{S.Half: x}], dkeys=True) == [{Float(0.5): x}] + + # Issue 17706 + A = MutableMatrix([[1, 2], [3, 4]]) + B = MutableMatrix( + [[Float('1.0', precision=53), Float('2.0', precision=53)], + [Float('3.0', precision=53), Float('4.0', precision=53)]]) + assert _aresame(nfloat(A), B) + A = ImmutableMatrix([[1, 2], [3, 4]]) + B = ImmutableMatrix( + [[Float('1.0', precision=53), Float('2.0', precision=53)], + [Float('3.0', precision=53), Float('4.0', precision=53)]]) + assert _aresame(nfloat(A), B) + + # issue 22524 + f = Function('f') + assert not nfloat(f(2)).atoms(Float) + + +def test_issue_7068(): + from sympy.abc import a, b + f = Function('f') + y1 = Dummy('y') + y2 = Dummy('y') + func1 = f(a + y1 * b) + func2 = f(a + y2 * b) + func1_y = func1.diff(y1) + func2_y = func2.diff(y2) + assert func1_y != func2_y + z1 = Subs(f(a), a, y1) + z2 = Subs(f(a), a, y2) + assert z1 != z2 + + +def test_issue_7231(): + from sympy.abc import a + ans1 = f(x).series(x, a) + res = (f(a) + (-a + x)*Subs(Derivative(f(y), y), y, a) + + (-a + x)**2*Subs(Derivative(f(y), y, y), y, a)/2 + + (-a + x)**3*Subs(Derivative(f(y), y, y, y), + y, a)/6 + + (-a + x)**4*Subs(Derivative(f(y), y, y, y, y), + y, a)/24 + + (-a + x)**5*Subs(Derivative(f(y), y, y, y, y, y), + y, a)/120 + O((-a + x)**6, (x, a))) + assert res == ans1 + ans2 = f(x).series(x, a) + assert res == ans2 + + +def test_issue_7687(): + from sympy.core.function import Function + from sympy.abc import x + f = Function('f')(x) + ff = Function('f')(x) + match_with_cache = ff.matches(f) + assert isinstance(f, type(ff)) + clear_cache() + ff = Function('f')(x) + assert isinstance(f, type(ff)) + assert match_with_cache == ff.matches(f) + + +def test_issue_7688(): + from sympy.core.function import Function, UndefinedFunction + + f = Function('f') # actually an UndefinedFunction + clear_cache() + class A(UndefinedFunction): + pass + a = A('f') + assert isinstance(a, type(f)) + + +def test_mexpand(): + from sympy.abc import x + assert _mexpand(None) is None + assert _mexpand(1) is S.One + assert _mexpand(x*(x + 1)**2) == (x*(x + 1)**2).expand() + + +def test_issue_8469(): + # This should not take forever to run + N = 40 + def g(w, theta): + return 1/(1+exp(w-theta)) + + ws = symbols(['w%i'%i for i in range(N)]) + import functools + expr = functools.reduce(g, ws) + assert isinstance(expr, Pow) + + +def test_issue_12996(): + # foo=True imitates the sort of arguments that Derivative can get + # from Integral when it passes doit to the expression + assert Derivative(im(x), x).doit(foo=True) == Derivative(im(x), x) + + +def test_should_evalf(): + # This should not take forever to run (see #8506) + assert isinstance(sin((1.0 + 1.0*I)**10000 + 1), sin) + + +def test_Derivative_as_finite_difference(): + # Central 1st derivative at gridpoint + x, h = symbols('x h', real=True) + dfdx = f(x).diff(x) + assert (dfdx.as_finite_difference([x-2, x-1, x, x+1, x+2]) - + (S.One/12*(f(x-2)-f(x+2)) + Rational(2, 3)*(f(x+1)-f(x-1)))).simplify() == 0 + + # Central 1st derivative "half-way" + assert (dfdx.as_finite_difference() - + (f(x + S.Half)-f(x - S.Half))).simplify() == 0 + assert (dfdx.as_finite_difference(h) - + (f(x + h/S(2))-f(x - h/S(2)))/h).simplify() == 0 + assert (dfdx.as_finite_difference([x - 3*h, x-h, x+h, x + 3*h]) - + (S(9)/(8*2*h)*(f(x+h) - f(x-h)) + + S.One/(24*2*h)*(f(x - 3*h) - f(x + 3*h)))).simplify() == 0 + + # One sided 1st derivative at gridpoint + assert (dfdx.as_finite_difference([0, 1, 2], 0) - + (Rational(-3, 2)*f(0) + 2*f(1) - f(2)/2)).simplify() == 0 + assert (dfdx.as_finite_difference([x, x+h], x) - + (f(x+h) - f(x))/h).simplify() == 0 + assert (dfdx.as_finite_difference([x-h, x, x+h], x-h) - + (-S(3)/(2*h)*f(x-h) + 2/h*f(x) - + S.One/(2*h)*f(x+h))).simplify() == 0 + + # One sided 1st derivative "half-way" + assert (dfdx.as_finite_difference([x-h, x+h, x + 3*h, x + 5*h, x + 7*h]) + - 1/(2*h)*(-S(11)/(12)*f(x-h) + S(17)/(24)*f(x+h) + + Rational(3, 8)*f(x + 3*h) - Rational(5, 24)*f(x + 5*h) + + S.One/24*f(x + 7*h))).simplify() == 0 + + d2fdx2 = f(x).diff(x, 2) + # Central 2nd derivative at gridpoint + assert (d2fdx2.as_finite_difference([x-h, x, x+h]) - + h**-2 * (f(x-h) + f(x+h) - 2*f(x))).simplify() == 0 + + assert (d2fdx2.as_finite_difference([x - 2*h, x-h, x, x+h, x + 2*h]) - + h**-2 * (Rational(-1, 12)*(f(x - 2*h) + f(x + 2*h)) + + Rational(4, 3)*(f(x+h) + f(x-h)) - Rational(5, 2)*f(x))).simplify() == 0 + + # Central 2nd derivative "half-way" + assert (d2fdx2.as_finite_difference([x - 3*h, x-h, x+h, x + 3*h]) - + (2*h)**-2 * (S.Half*(f(x - 3*h) + f(x + 3*h)) - + S.Half*(f(x+h) + f(x-h)))).simplify() == 0 + + # One sided 2nd derivative at gridpoint + assert (d2fdx2.as_finite_difference([x, x+h, x + 2*h, x + 3*h]) - + h**-2 * (2*f(x) - 5*f(x+h) + + 4*f(x+2*h) - f(x+3*h))).simplify() == 0 + + # One sided 2nd derivative at "half-way" + assert (d2fdx2.as_finite_difference([x-h, x+h, x + 3*h, x + 5*h]) - + (2*h)**-2 * (Rational(3, 2)*f(x-h) - Rational(7, 2)*f(x+h) + Rational(5, 2)*f(x + 3*h) - + S.Half*f(x + 5*h))).simplify() == 0 + + d3fdx3 = f(x).diff(x, 3) + # Central 3rd derivative at gridpoint + assert (d3fdx3.as_finite_difference() - + (-f(x - Rational(3, 2)) + 3*f(x - S.Half) - + 3*f(x + S.Half) + f(x + Rational(3, 2)))).simplify() == 0 + + assert (d3fdx3.as_finite_difference( + [x - 3*h, x - 2*h, x-h, x, x+h, x + 2*h, x + 3*h]) - + h**-3 * (S.One/8*(f(x - 3*h) - f(x + 3*h)) - f(x - 2*h) + + f(x + 2*h) + Rational(13, 8)*(f(x-h) - f(x+h)))).simplify() == 0 + + # Central 3rd derivative at "half-way" + assert (d3fdx3.as_finite_difference([x - 3*h, x-h, x+h, x + 3*h]) - + (2*h)**-3 * (f(x + 3*h)-f(x - 3*h) + + 3*(f(x-h)-f(x+h)))).simplify() == 0 + + # One sided 3rd derivative at gridpoint + assert (d3fdx3.as_finite_difference([x, x+h, x + 2*h, x + 3*h]) - + h**-3 * (f(x + 3*h)-f(x) + 3*(f(x+h)-f(x + 2*h)))).simplify() == 0 + + # One sided 3rd derivative at "half-way" + assert (d3fdx3.as_finite_difference([x-h, x+h, x + 3*h, x + 5*h]) - + (2*h)**-3 * (f(x + 5*h)-f(x-h) + + 3*(f(x+h)-f(x + 3*h)))).simplify() == 0 + + # issue 11007 + y = Symbol('y', real=True) + d2fdxdy = f(x, y).diff(x, y) + + ref0 = Derivative(f(x + S.Half, y), y) - Derivative(f(x - S.Half, y), y) + assert (d2fdxdy.as_finite_difference(wrt=x) - ref0).simplify() == 0 + + half = S.Half + xm, xp, ym, yp = x-half, x+half, y-half, y+half + ref2 = f(xm, ym) + f(xp, yp) - f(xp, ym) - f(xm, yp) + assert (d2fdxdy.as_finite_difference() - ref2).simplify() == 0 + + +def test_issue_11159(): + # Tests Application._eval_subs + with _exp_is_pow(False): + expr1 = E + expr0 = expr1 * expr1 + expr1 = expr0.subs(expr1,expr0) + assert expr0 == expr1 + with _exp_is_pow(True): + expr1 = E + expr0 = expr1 * expr1 + expr2 = expr0.subs(expr1, expr0) + assert expr2 == E ** 4 + + +def test_issue_12005(): + e1 = Subs(Derivative(f(x), x), x, x) + assert e1.diff(x) == Derivative(f(x), x, x) + e2 = Subs(Derivative(f(x), x), x, x**2 + 1) + assert e2.diff(x) == 2*x*Subs(Derivative(f(x), x, x), x, x**2 + 1) + e3 = Subs(Derivative(f(x) + y**2 - y, y), y, y**2) + assert e3.diff(y) == 4*y + e4 = Subs(Derivative(f(x + y), y), y, (x**2)) + assert e4.diff(y) is S.Zero + e5 = Subs(Derivative(f(x), x), (y, z), (y, z)) + assert e5.diff(x) == Derivative(f(x), x, x) + assert f(g(x)).diff(g(x), g(x)) == Derivative(f(g(x)), g(x), g(x)) + + +def test_issue_13843(): + x = symbols('x') + f = Function('f') + m, n = symbols('m n', integer=True) + assert Derivative(Derivative(f(x), (x, m)), (x, n)) == Derivative(f(x), (x, m + n)) + assert Derivative(Derivative(f(x), (x, m+5)), (x, n+3)) == Derivative(f(x), (x, m + n + 8)) + + assert Derivative(f(x), (x, n)).doit() == Derivative(f(x), (x, n)) + + +def test_order_could_be_zero(): + x, y = symbols('x, y') + n = symbols('n', integer=True, nonnegative=True) + m = symbols('m', integer=True, positive=True) + assert diff(y, (x, n)) == Piecewise((y, Eq(n, 0)), (0, True)) + assert diff(y, (x, n + 1)) is S.Zero + assert diff(y, (x, m)) is S.Zero + + +def test_undefined_function_eq(): + f = Function('f') + f2 = Function('f') + g = Function('g') + f_real = Function('f', is_real=True) + + # This test may only be meaningful if the cache is turned off + assert f == f2 + assert hash(f) == hash(f2) + assert f == f + + assert f != g + + assert f != f_real + + +def test_function_assumptions(): + x = Symbol('x') + f = Function('f') + f_real = Function('f', real=True) + f_real1 = Function('f', real=1) + f_real_inherit = Function(Symbol('f', real=True)) + + assert f_real == f_real1 # assumptions are sanitized + assert f != f_real + assert f(x) != f_real(x) + + assert f(x).is_real is None + assert f_real(x).is_real is True + assert f_real_inherit(x).is_real is True and f_real_inherit.name == 'f' + + # Can also do it this way, but it won't be equal to f_real because of the + # way UndefinedFunction.__new__ works. Any non-recognized assumptions + # are just added literally as something which is used in the hash + f_real2 = Function('f', is_real=True) + assert f_real2(x).is_real is True + + +def test_undef_fcn_float_issue_6938(): + f = Function('ceil') + assert not f(0.3).is_number + f = Function('sin') + assert not f(0.3).is_number + assert not f(pi).evalf().is_number + x = Symbol('x') + assert not f(x).evalf(subs={x:1.2}).is_number + + +def test_undefined_function_eval(): + # Issue 15170. Make sure UndefinedFunction with eval defined works + # properly. + + fdiff = lambda self, argindex=1: cos(self.args[argindex - 1]) + eval = classmethod(lambda cls, t: None) + _imp_ = classmethod(lambda cls, t: sin(t)) + + temp = Function('temp', fdiff=fdiff, eval=eval, _imp_=_imp_) + + expr = temp(t) + assert sympify(expr) == expr + assert type(sympify(expr)).fdiff.__name__ == "" + assert expr.diff(t) == cos(t) + + +def test_issue_15241(): + F = f(x) + Fx = F.diff(x) + assert (F + x*Fx).diff(x, Fx) == 2 + assert (F + x*Fx).diff(Fx, x) == 1 + assert (x*F + x*Fx*F).diff(F, x) == x*Fx.diff(x) + Fx + 1 + assert (x*F + x*Fx*F).diff(x, F) == x*Fx.diff(x) + Fx + 1 + y = f(x) + G = f(y) + Gy = G.diff(y) + assert (G + y*Gy).diff(y, Gy) == 2 + assert (G + y*Gy).diff(Gy, y) == 1 + assert (y*G + y*Gy*G).diff(G, y) == y*Gy.diff(y) + Gy + 1 + assert (y*G + y*Gy*G).diff(y, G) == y*Gy.diff(y) + Gy + 1 + + +def test_issue_15226(): + assert Subs(Derivative(f(y), x, y), y, g(x)).doit() != 0 + + +def test_issue_7027(): + for wrt in (cos(x), re(x), Derivative(cos(x), x)): + raises(ValueError, lambda: diff(f(x), wrt)) + + +def test_derivative_quick_exit(): + assert f(x).diff(y) == 0 + assert f(x).diff(y, f(x)) == 0 + assert f(x).diff(x, f(y)) == 0 + assert f(f(x)).diff(x, f(x), f(y)) == 0 + assert f(f(x)).diff(x, f(x), y) == 0 + assert f(x).diff(g(x)) == 0 + assert f(x).diff(x, f(x).diff(x)) == 1 + df = f(x).diff(x) + assert f(x).diff(df) == 0 + dg = g(x).diff(x) + assert dg.diff(df).doit() == 0 + + +def test_issue_15084_13166(): + eq = f(x, g(x)) + assert eq.diff((g(x), y)) == Derivative(f(x, g(x)), (g(x), y)) + # issue 13166 + assert eq.diff(x, 2).doit() == ( + (Derivative(f(x, g(x)), (g(x), 2))*Derivative(g(x), x) + + Subs(Derivative(f(x, _xi_2), _xi_2, x), _xi_2, g(x)))*Derivative(g(x), + x) + Derivative(f(x, g(x)), g(x))*Derivative(g(x), (x, 2)) + + Derivative(g(x), x)*Subs(Derivative(f(_xi_1, g(x)), _xi_1, g(x)), + _xi_1, x) + Subs(Derivative(f(_xi_1, g(x)), (_xi_1, 2)), _xi_1, x)) + # issue 6681 + assert diff(f(x, t, g(x, t)), x).doit() == ( + Derivative(f(x, t, g(x, t)), g(x, t))*Derivative(g(x, t), x) + + Subs(Derivative(f(_xi_1, t, g(x, t)), _xi_1), _xi_1, x)) + # make sure the order doesn't matter when using diff + assert eq.diff(x, g(x)) == eq.diff(g(x), x) + + +def test_negative_counts(): + # issue 13873 + raises(ValueError, lambda: sin(x).diff(x, -1)) + + +def test_Derivative__new__(): + raises(TypeError, lambda: f(x).diff((x, 2), 0)) + assert f(x, y).diff([(x, y), 0]) == f(x, y) + assert f(x, y).diff([(x, y), 1]) == NDimArray([ + Derivative(f(x, y), x), Derivative(f(x, y), y)]) + assert f(x,y).diff(y, (x, z), y, x) == Derivative( + f(x, y), (x, z + 1), (y, 2)) + assert Matrix([x]).diff(x, 2) == Matrix([0]) # is_zero exit + + +def test_issue_14719_10150(): + class V(Expr): + _diff_wrt = True + is_scalar = False + assert V().diff(V()) == Derivative(V(), V()) + assert (2*V()).diff(V()) == 2*Derivative(V(), V()) + class X(Expr): + _diff_wrt = True + assert X().diff(X()) == 1 + assert (2*X()).diff(X()) == 2 + + +def test_noncommutative_issue_15131(): + x = Symbol('x', commutative=False) + t = Symbol('t', commutative=False) + fx = Function('Fx', commutative=False)(x) + ft = Function('Ft', commutative=False)(t) + A = Symbol('A', commutative=False) + eq = fx * A * ft + eqdt = eq.diff(t) + assert eqdt.args[-1] == ft.diff(t) + + +def test_Subs_Derivative(): + a = Derivative(f(g(x), h(x)), g(x), h(x),x) + b = Derivative(Derivative(f(g(x), h(x)), g(x), h(x)),x) + c = f(g(x), h(x)).diff(g(x), h(x), x) + d = f(g(x), h(x)).diff(g(x), h(x)).diff(x) + e = Derivative(f(g(x), h(x)), x) + eqs = (a, b, c, d, e) + subs = lambda arg: arg.subs(f, Lambda((x, y), exp(x + y)) + ).subs(g(x), 1/x).subs(h(x), x**3) + ans = 3*x**2*exp(1/x)*exp(x**3) - exp(1/x)*exp(x**3)/x**2 + assert all(subs(i).doit().expand() == ans for i in eqs) + assert all(subs(i.doit()).doit().expand() == ans for i in eqs) + +def test_issue_15360(): + f = Function('f') + assert f.name == 'f' + + +def test_issue_15947(): + assert f._diff_wrt is False + raises(TypeError, lambda: f(f)) + raises(TypeError, lambda: f(x).diff(f)) + + +def test_Derivative_free_symbols(): + f = Function('f') + n = Symbol('n', integer=True, positive=True) + assert diff(f(x), (x, n)).free_symbols == {n, x} + + +def test_issue_20683(): + x = Symbol('x') + y = Symbol('y') + z = Symbol('z') + y = Derivative(z, x).subs(x,0) + assert y.doit() == 0 + y = Derivative(8, x).subs(x,0) + assert y.doit() == 0 + + +def test_issue_10503(): + f = exp(x**3)*cos(x**6) + assert f.series(x, 0, 14) == 1 + x**3 + x**6/2 + x**9/6 - 11*x**12/24 + O(x**14) + + +def test_issue_17382(): + # copied from sympy/core/tests/test_evalf.py + def NS(e, n=15, **options): + return sstr(sympify(e).evalf(n, **options), full_prec=True) + + x = Symbol('x') + expr = solveset(2 * cos(x) * cos(2 * x) - 1, x, S.Reals) + expected = "Union(" \ + "ImageSet(Lambda(_n, 6.28318530717959*_n + 5.79812359592087), Integers), " \ + "ImageSet(Lambda(_n, 6.28318530717959*_n + 0.485061711258717), Integers))" + assert NS(expr) == expected + +def test_eval_sympified(): + # Check both arguments and return types from eval are sympified + + class F(Function): + @classmethod + def eval(cls, x): + assert x is S.One + return 1 + + assert F(1) is S.One + + # String arguments are not allowed + class F2(Function): + @classmethod + def eval(cls, x): + if x == 0: + return '1' + + raises(SympifyError, lambda: F2(0)) + F2(1) # Doesn't raise + + # TODO: Disable string inputs (https://github.com/sympy/sympy/issues/11003) + # raises(SympifyError, lambda: F2('2')) + +def test_eval_classmethod_check(): + with raises(TypeError): + class F(Function): + def eval(self, x): + pass diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_logic.py b/lib/python3.12/site-packages/sympy/core/tests/test_logic.py new file mode 100644 index 0000000000000000000000000000000000000000..df5647f32ea7c4e326eb4e3aec6a7b2987f32aee --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_logic.py @@ -0,0 +1,198 @@ +from sympy.core.logic import (fuzzy_not, Logic, And, Or, Not, fuzzy_and, + fuzzy_or, _fuzzy_group, _torf, fuzzy_nand, fuzzy_xor) +from sympy.testing.pytest import raises + +from itertools import product + +T = True +F = False +U = None + + + +def test_torf(): + v = [T, F, U] + for i in product(*[v]*3): + assert _torf(i) is (True if all(j for j in i) else + (False if all(j is False for j in i) else None)) + + +def test_fuzzy_group(): + v = [T, F, U] + for i in product(*[v]*3): + assert _fuzzy_group(i) is (None if None in i else + (True if all(j for j in i) else False)) + assert _fuzzy_group(i, quick_exit=True) is \ + (None if (i.count(False) > 1) else + (None if None in i else (True if all(j for j in i) else False))) + it = (True if (i == 0) else None for i in range(2)) + assert _torf(it) is None + it = (True if (i == 1) else None for i in range(2)) + assert _torf(it) is None + + +def test_fuzzy_not(): + assert fuzzy_not(T) == F + assert fuzzy_not(F) == T + assert fuzzy_not(U) == U + + +def test_fuzzy_and(): + assert fuzzy_and([T, T]) == T + assert fuzzy_and([T, F]) == F + assert fuzzy_and([T, U]) == U + assert fuzzy_and([F, F]) == F + assert fuzzy_and([F, U]) == F + assert fuzzy_and([U, U]) == U + assert [fuzzy_and([w]) for w in [U, T, F]] == [U, T, F] + assert fuzzy_and([T, F, U]) == F + assert fuzzy_and([]) == T + raises(TypeError, lambda: fuzzy_and()) + + +def test_fuzzy_or(): + assert fuzzy_or([T, T]) == T + assert fuzzy_or([T, F]) == T + assert fuzzy_or([T, U]) == T + assert fuzzy_or([F, F]) == F + assert fuzzy_or([F, U]) == U + assert fuzzy_or([U, U]) == U + assert [fuzzy_or([w]) for w in [U, T, F]] == [U, T, F] + assert fuzzy_or([T, F, U]) == T + assert fuzzy_or([]) == F + raises(TypeError, lambda: fuzzy_or()) + + +def test_logic_cmp(): + l1 = And('a', Not('b')) + l2 = And('a', Not('b')) + + assert hash(l1) == hash(l2) + assert (l1 == l2) == T + assert (l1 != l2) == F + + assert And('a', 'b', 'c') == And('b', 'a', 'c') + assert And('a', 'b', 'c') == And('c', 'b', 'a') + assert And('a', 'b', 'c') == And('c', 'a', 'b') + + assert Not('a') < Not('b') + assert (Not('b') < Not('a')) is False + assert (Not('a') < 2) is False + + +def test_logic_onearg(): + assert And() is True + assert Or() is False + + assert And(T) == T + assert And(F) == F + assert Or(T) == T + assert Or(F) == F + + assert And('a') == 'a' + assert Or('a') == 'a' + + +def test_logic_xnotx(): + assert And('a', Not('a')) == F + assert Or('a', Not('a')) == T + + +def test_logic_eval_TF(): + assert And(F, F) == F + assert And(F, T) == F + assert And(T, F) == F + assert And(T, T) == T + + assert Or(F, F) == F + assert Or(F, T) == T + assert Or(T, F) == T + assert Or(T, T) == T + + assert And('a', T) == 'a' + assert And('a', F) == F + assert Or('a', T) == T + assert Or('a', F) == 'a' + + +def test_logic_combine_args(): + assert And('a', 'b', 'a') == And('a', 'b') + assert Or('a', 'b', 'a') == Or('a', 'b') + + assert And(And('a', 'b'), And('c', 'd')) == And('a', 'b', 'c', 'd') + assert Or(Or('a', 'b'), Or('c', 'd')) == Or('a', 'b', 'c', 'd') + + assert Or('t', And('n', 'p', 'r'), And('n', 'r'), And('n', 'p', 'r'), 't', + And('n', 'r')) == Or('t', And('n', 'p', 'r'), And('n', 'r')) + + +def test_logic_expand(): + t = And(Or('a', 'b'), 'c') + assert t.expand() == Or(And('a', 'c'), And('b', 'c')) + + t = And(Or('a', Not('b')), 'b') + assert t.expand() == And('a', 'b') + + t = And(Or('a', 'b'), Or('c', 'd')) + assert t.expand() == \ + Or(And('a', 'c'), And('a', 'd'), And('b', 'c'), And('b', 'd')) + + +def test_logic_fromstring(): + S = Logic.fromstring + + assert S('a') == 'a' + assert S('!a') == Not('a') + assert S('a & b') == And('a', 'b') + assert S('a | b') == Or('a', 'b') + assert S('a | b & c') == And(Or('a', 'b'), 'c') + assert S('a & b | c') == Or(And('a', 'b'), 'c') + assert S('a & b & c') == And('a', 'b', 'c') + assert S('a | b | c') == Or('a', 'b', 'c') + + raises(ValueError, lambda: S('| a')) + raises(ValueError, lambda: S('& a')) + raises(ValueError, lambda: S('a | | b')) + raises(ValueError, lambda: S('a | & b')) + raises(ValueError, lambda: S('a & & b')) + raises(ValueError, lambda: S('a |')) + raises(ValueError, lambda: S('a|b')) + raises(ValueError, lambda: S('!')) + raises(ValueError, lambda: S('! a')) + raises(ValueError, lambda: S('!(a + 1)')) + raises(ValueError, lambda: S('')) + + +def test_logic_not(): + assert Not('a') != '!a' + assert Not('!a') != 'a' + assert Not(True) == False + assert Not(False) == True + + # NOTE: we may want to change default Not behaviour and put this + # functionality into some method. + assert Not(And('a', 'b')) == Or(Not('a'), Not('b')) + assert Not(Or('a', 'b')) == And(Not('a'), Not('b')) + + raises(ValueError, lambda: Not(1)) + + +def test_formatting(): + S = Logic.fromstring + raises(ValueError, lambda: S('a&b')) + raises(ValueError, lambda: S('a|b')) + raises(ValueError, lambda: S('! a')) + + +def test_fuzzy_xor(): + assert fuzzy_xor((None,)) is None + assert fuzzy_xor((None, True)) is None + assert fuzzy_xor((None, False)) is None + assert fuzzy_xor((True, False)) is True + assert fuzzy_xor((True, True)) is False + assert fuzzy_xor((True, True, False)) is False + assert fuzzy_xor((True, True, False, True)) is True + +def test_fuzzy_nand(): + for args in [(1, 0), (1, 1), (0, 0)]: + assert fuzzy_nand(args) == fuzzy_not(fuzzy_and(args)) diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_multidimensional.py b/lib/python3.12/site-packages/sympy/core/tests/test_multidimensional.py new file mode 100644 index 0000000000000000000000000000000000000000..765c78adf8dbed2ead43721ca4ab9510dbeeb282 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_multidimensional.py @@ -0,0 +1,24 @@ +from sympy.core.function import (Derivative, Function, diff) +from sympy.core.symbol import symbols +from sympy.functions.elementary.trigonometric import sin +from sympy.core.multidimensional import vectorize +x, y, z = symbols('x y z') +f, g, h = list(map(Function, 'fgh')) + + +def test_vectorize(): + @vectorize(0) + def vsin(x): + return sin(x) + + assert vsin([1, x, y]) == [sin(1), sin(x), sin(y)] + + @vectorize(0, 1) + def vdiff(f, y): + return diff(f, y) + + assert vdiff([f(x, y, z), g(x, y, z), h(x, y, z)], [x, y, z]) == \ + [[Derivative(f(x, y, z), x), Derivative(f(x, y, z), y), + Derivative(f(x, y, z), z)], [Derivative(g(x, y, z), x), + Derivative(g(x, y, z), y), Derivative(g(x, y, z), z)], + [Derivative(h(x, y, z), x), Derivative(h(x, y, z), y), Derivative(h(x, y, z), z)]] diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_noncommutative.py b/lib/python3.12/site-packages/sympy/core/tests/test_noncommutative.py new file mode 100644 index 0000000000000000000000000000000000000000..b3d3a3cec2ef64aa500aad08b438c90cc8987581 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_noncommutative.py @@ -0,0 +1,140 @@ +"""Tests for noncommutative symbols and expressions.""" + +from sympy.core.function import expand +from sympy.core.numbers import I +from sympy.core.symbol import symbols +from sympy.functions.elementary.complexes import (adjoint, conjugate, transpose) +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.polys.polytools import (cancel, factor) +from sympy.simplify.combsimp import combsimp +from sympy.simplify.gammasimp import gammasimp +from sympy.simplify.radsimp import (collect, radsimp, rcollect) +from sympy.simplify.ratsimp import ratsimp +from sympy.simplify.simplify import (posify, simplify) +from sympy.simplify.trigsimp import trigsimp +from sympy.abc import x, y, z +from sympy.testing.pytest import XFAIL + +A, B, C = symbols("A B C", commutative=False) +X = symbols("X", commutative=False, hermitian=True) +Y = symbols("Y", commutative=False, antihermitian=True) + + +def test_adjoint(): + assert adjoint(A).is_commutative is False + assert adjoint(A*A) == adjoint(A)**2 + assert adjoint(A*B) == adjoint(B)*adjoint(A) + assert adjoint(A*B**2) == adjoint(B)**2*adjoint(A) + assert adjoint(A*B - B*A) == adjoint(B)*adjoint(A) - adjoint(A)*adjoint(B) + assert adjoint(A + I*B) == adjoint(A) - I*adjoint(B) + + assert adjoint(X) == X + assert adjoint(-I*X) == I*X + assert adjoint(Y) == -Y + assert adjoint(-I*Y) == -I*Y + + assert adjoint(X) == conjugate(transpose(X)) + assert adjoint(Y) == conjugate(transpose(Y)) + assert adjoint(X) == transpose(conjugate(X)) + assert adjoint(Y) == transpose(conjugate(Y)) + + +def test_cancel(): + assert cancel(A*B - B*A) == A*B - B*A + assert cancel(A*B*(x - 1)) == A*B*(x - 1) + assert cancel(A*B*(x**2 - 1)/(x + 1)) == A*B*(x - 1) + assert cancel(A*B*(x**2 - 1)/(x + 1) - B*A*(x - 1)) == A*B*(x - 1) + (1 - x)*B*A + + +@XFAIL +def test_collect(): + assert collect(A*B - B*A, A) == A*B - B*A + assert collect(A*B - B*A, B) == A*B - B*A + assert collect(A*B - B*A, x) == A*B - B*A + + +def test_combsimp(): + assert combsimp(A*B - B*A) == A*B - B*A + + +def test_gammasimp(): + assert gammasimp(A*B - B*A) == A*B - B*A + + +def test_conjugate(): + assert conjugate(A).is_commutative is False + assert (A*A).conjugate() == conjugate(A)**2 + assert (A*B).conjugate() == conjugate(A)*conjugate(B) + assert (A*B**2).conjugate() == conjugate(A)*conjugate(B)**2 + assert (A*B - B*A).conjugate() == \ + conjugate(A)*conjugate(B) - conjugate(B)*conjugate(A) + assert (A*B).conjugate() - (B*A).conjugate() == \ + conjugate(A)*conjugate(B) - conjugate(B)*conjugate(A) + assert (A + I*B).conjugate() == conjugate(A) - I*conjugate(B) + + +def test_expand(): + assert expand((A*B)**2) == A*B*A*B + assert expand(A*B - B*A) == A*B - B*A + assert expand((A*B/A)**2) == A*B*B/A + assert expand(B*A*(A + B)*B) == B*A**2*B + B*A*B**2 + assert expand(B*A*(A + C)*B) == B*A**2*B + B*A*C*B + + +def test_factor(): + assert factor(A*B - B*A) == A*B - B*A + + +def test_posify(): + assert posify(A)[0].is_commutative is False + for q in (A*B/A, (A*B/A)**2, (A*B)**2, A*B - B*A): + p = posify(q) + assert p[0].subs(p[1]) == q + + +def test_radsimp(): + assert radsimp(A*B - B*A) == A*B - B*A + + +@XFAIL +def test_ratsimp(): + assert ratsimp(A*B - B*A) == A*B - B*A + + +@XFAIL +def test_rcollect(): + assert rcollect(A*B - B*A, A) == A*B - B*A + assert rcollect(A*B - B*A, B) == A*B - B*A + assert rcollect(A*B - B*A, x) == A*B - B*A + + +def test_simplify(): + assert simplify(A*B - B*A) == A*B - B*A + + +def test_subs(): + assert (x*y*A).subs(x*y, z) == A*z + assert (x*A*B).subs(x*A, C) == C*B + assert (x*A*x*x).subs(x**2*A, C) == x*C + assert (x*A*x*B).subs(x**2*A, C) == C*B + assert (A**2*B**2).subs(A*B**2, C) == A*C + assert (A*A*A + A*B*A).subs(A*A*A, C) == C + A*B*A + + +def test_transpose(): + assert transpose(A).is_commutative is False + assert transpose(A*A) == transpose(A)**2 + assert transpose(A*B) == transpose(B)*transpose(A) + assert transpose(A*B**2) == transpose(B)**2*transpose(A) + assert transpose(A*B - B*A) == \ + transpose(B)*transpose(A) - transpose(A)*transpose(B) + assert transpose(A + I*B) == transpose(A) + I*transpose(B) + + assert transpose(X) == conjugate(X) + assert transpose(-I*X) == -I*conjugate(X) + assert transpose(Y) == -conjugate(Y) + assert transpose(-I*Y) == I*conjugate(Y) + + +def test_trigsimp(): + assert trigsimp(A*sin(x)**2 + A*cos(x)**2) == A diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_parameters.py b/lib/python3.12/site-packages/sympy/core/tests/test_parameters.py new file mode 100644 index 0000000000000000000000000000000000000000..21e03d717872a9a8165ceeebf7ef58e9842702c0 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_parameters.py @@ -0,0 +1,126 @@ +from sympy.abc import x, y +from sympy.core.parameters import evaluate +from sympy.core import Mul, Add, Pow, S +from sympy.core.numbers import oo +from sympy.functions.elementary.miscellaneous import sqrt + +def test_add(): + with evaluate(False): + p = oo - oo + assert isinstance(p, Add) and p.args == (oo, -oo) + p = 5 - oo + assert isinstance(p, Add) and p.args == (-oo, 5) + p = oo - 5 + assert isinstance(p, Add) and p.args == (oo, -5) + p = oo + 5 + assert isinstance(p, Add) and p.args == (oo, 5) + p = 5 + oo + assert isinstance(p, Add) and p.args == (oo, 5) + p = -oo + 5 + assert isinstance(p, Add) and p.args == (-oo, 5) + p = -5 - oo + assert isinstance(p, Add) and p.args == (-oo, -5) + + with evaluate(False): + expr = x + x + assert isinstance(expr, Add) + assert expr.args == (x, x) + + with evaluate(True): + assert (x + x).args == (2, x) + + assert (x + x).args == (x, x) + + assert isinstance(x + x, Mul) + + with evaluate(False): + assert S.One + 1 == Add(1, 1) + assert 1 + S.One == Add(1, 1) + + assert S(4) - 3 == Add(4, -3) + assert -3 + S(4) == Add(4, -3) + + assert S(2) * 4 == Mul(2, 4) + assert 4 * S(2) == Mul(2, 4) + + assert S(6) / 3 == Mul(6, Pow(3, -1)) + assert S.One / 3 * 6 == Mul(S.One / 3, 6) + + assert 9 ** S(2) == Pow(9, 2) + assert S(2) ** 9 == Pow(2, 9) + + assert S(2) / 2 == Mul(2, Pow(2, -1)) + assert S.One / 2 * 2 == Mul(S.One / 2, 2) + + assert S(2) / 3 + 1 == Add(S(2) / 3, 1) + assert 1 + S(2) / 3 == Add(1, S(2) / 3) + + assert S(4) / 7 - 3 == Add(S(4) / 7, -3) + assert -3 + S(4) / 7 == Add(-3, S(4) / 7) + + assert S(2) / 4 * 4 == Mul(S(2) / 4, 4) + assert 4 * (S(2) / 4) == Mul(4, S(2) / 4) + + assert S(6) / 3 == Mul(6, Pow(3, -1)) + assert S.One / 3 * 6 == Mul(S.One / 3, 6) + + assert S.One / 3 + sqrt(3) == Add(S.One / 3, sqrt(3)) + assert sqrt(3) + S.One / 3 == Add(sqrt(3), S.One / 3) + + assert S.One / 2 * 10.333 == Mul(S.One / 2, 10.333) + assert 10.333 * (S.One / 2) == Mul(10.333, S.One / 2) + + assert sqrt(2) * sqrt(2) == Mul(sqrt(2), sqrt(2)) + + assert S.One / 2 + x == Add(S.One / 2, x) + assert x + S.One / 2 == Add(x, S.One / 2) + + assert S.One / x * x == Mul(S.One / x, x) + assert x * (S.One / x) == Mul(x, Pow(x, -1)) + + assert S.One / 3 == Pow(3, -1) + assert S.One / x == Pow(x, -1) + assert 1 / S(3) == Pow(3, -1) + assert 1 / x == Pow(x, -1) + +def test_nested(): + with evaluate(False): + expr = (x + x) + (y + y) + assert expr.args == ((x + x), (y + y)) + assert expr.args[0].args == (x, x) + +def test_reentrantcy(): + with evaluate(False): + expr = x + x + assert expr.args == (x, x) + with evaluate(True): + expr = x + x + assert expr.args == (2, x) + expr = x + x + assert expr.args == (x, x) + +def test_reusability(): + f = evaluate(False) + + with f: + expr = x + x + assert expr.args == (x, x) + + expr = x + x + assert expr.args == (2, x) + + with f: + expr = x + x + assert expr.args == (x, x) + + # Assure reentrancy with reusability + ctx = evaluate(False) + with ctx: + expr = x + x + assert expr.args == (x, x) + with ctx: + expr = x + x + assert expr.args == (x, x) + + expr = x + x + assert expr.args == (2, x) diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_random.py b/lib/python3.12/site-packages/sympy/core/tests/test_random.py new file mode 100644 index 0000000000000000000000000000000000000000..01c677126285eb66349253368b94b3270fb97793 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_random.py @@ -0,0 +1,61 @@ +import random +from sympy.core.random import random as rand, seed, shuffle, _assumptions_shuffle +from sympy.core.symbol import Symbol, symbols +from sympy.functions.elementary.trigonometric import sin, acos +from sympy.abc import x + + +def test_random(): + random.seed(42) + a = random.random() + random.seed(42) + Symbol('z').is_finite + b = random.random() + assert a == b + + got = set() + for i in range(2): + random.seed(28) + m0, m1 = symbols('m_0 m_1', real=True) + _ = acos(-m0/m1) + got.add(random.uniform(0,1)) + assert len(got) == 1 + + random.seed(10) + y = 0 + for i in range(4): + y += sin(random.uniform(-10,10) * x) + random.seed(10) + z = 0 + for i in range(4): + z += sin(random.uniform(-10,10) * x) + assert y == z + + +def test_seed(): + assert rand() < 1 + seed(1) + a = rand() + b = rand() + seed(1) + c = rand() + d = rand() + assert a == c + if not c == d: + assert a != b + else: + assert a == b + + abc = 'abc' + first = list(abc) + second = list(abc) + third = list(abc) + + seed(123) + shuffle(first) + + seed(123) + shuffle(second) + _assumptions_shuffle(third) + + assert first == second == third diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_rules.py b/lib/python3.12/site-packages/sympy/core/tests/test_rules.py new file mode 100644 index 0000000000000000000000000000000000000000..31cb88b52db21f39653033b4567526e992be99f0 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_rules.py @@ -0,0 +1,14 @@ +from sympy.core.rules import Transform + +from sympy.testing.pytest import raises + + +def test_Transform(): + add1 = Transform(lambda x: x + 1, lambda x: x % 2 == 1) + assert add1[1] == 2 + assert (1 in add1) is True + assert add1.get(1) == 2 + + raises(KeyError, lambda: add1[2]) + assert (2 in add1) is False + assert add1.get(2) is None diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_subs.py b/lib/python3.12/site-packages/sympy/core/tests/test_subs.py new file mode 100644 index 0000000000000000000000000000000000000000..0803a4b1b5e93b8a35f43516ccef3ab9a16f08ec --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_subs.py @@ -0,0 +1,895 @@ +from sympy.calculus.accumulationbounds import AccumBounds +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.containers import (Dict, Tuple) +from sympy.core.function import (Derivative, Function, Lambda, Subs) +from sympy.core.mul import Mul +from sympy.core.numbers import (Float, I, Integer, Rational, oo, pi, zoo) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, Wild, symbols) +from sympy.core.sympify import SympifyError +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (atan2, cos, cot, sin, tan) +from sympy.matrices.dense import (Matrix, zeros) +from sympy.matrices.expressions.special import ZeroMatrix +from sympy.polys.polytools import factor +from sympy.polys.rootoftools import RootOf +from sympy.simplify.cse_main import cse +from sympy.simplify.simplify import nsimplify +from sympy.core.basic import _aresame +from sympy.testing.pytest import XFAIL, raises +from sympy.abc import a, x, y, z, t + + +def test_subs(): + n3 = Rational(3) + e = x + e = e.subs(x, n3) + assert e == Rational(3) + + e = 2*x + assert e == 2*x + e = e.subs(x, n3) + assert e == Rational(6) + + +def test_subs_Matrix(): + z = zeros(2) + z1 = ZeroMatrix(2, 2) + assert (x*y).subs({x:z, y:0}) in [z, z1] + assert (x*y).subs({y:z, x:0}) == 0 + assert (x*y).subs({y:z, x:0}, simultaneous=True) in [z, z1] + assert (x + y).subs({x: z, y: z}, simultaneous=True) in [z, z1] + assert (x + y).subs({x: z, y: z}) in [z, z1] + + # Issue #15528 + assert Mul(Matrix([[3]]), x).subs(x, 2.0) == Matrix([[6.0]]) + # Does not raise a TypeError, see comment on the MatAdd postprocessor + assert Add(Matrix([[3]]), x).subs(x, 2.0) == Add(Matrix([[3]]), 2.0) + + +def test_subs_AccumBounds(): + e = x + e = e.subs(x, AccumBounds(1, 3)) + assert e == AccumBounds(1, 3) + + e = 2*x + e = e.subs(x, AccumBounds(1, 3)) + assert e == AccumBounds(2, 6) + + e = x + x**2 + e = e.subs(x, AccumBounds(-1, 1)) + assert e == AccumBounds(-1, 2) + + +def test_trigonometric(): + n3 = Rational(3) + e = (sin(x)**2).diff(x) + assert e == 2*sin(x)*cos(x) + e = e.subs(x, n3) + assert e == 2*cos(n3)*sin(n3) + + e = (sin(x)**2).diff(x) + assert e == 2*sin(x)*cos(x) + e = e.subs(sin(x), cos(x)) + assert e == 2*cos(x)**2 + + assert exp(pi).subs(exp, sin) == 0 + assert cos(exp(pi)).subs(exp, sin) == 1 + + i = Symbol('i', integer=True) + zoo = S.ComplexInfinity + assert tan(x).subs(x, pi/2) is zoo + assert cot(x).subs(x, pi) is zoo + assert cot(i*x).subs(x, pi) is zoo + assert tan(i*x).subs(x, pi/2) == tan(i*pi/2) + assert tan(i*x).subs(x, pi/2).subs(i, 1) is zoo + o = Symbol('o', odd=True) + assert tan(o*x).subs(x, pi/2) == tan(o*pi/2) + + +def test_powers(): + assert sqrt(1 - sqrt(x)).subs(x, 4) == I + assert (sqrt(1 - x**2)**3).subs(x, 2) == - 3*I*sqrt(3) + assert (x**Rational(1, 3)).subs(x, 27) == 3 + assert (x**Rational(1, 3)).subs(x, -27) == 3*(-1)**Rational(1, 3) + assert ((-x)**Rational(1, 3)).subs(x, 27) == 3*(-1)**Rational(1, 3) + n = Symbol('n', negative=True) + assert (x**n).subs(x, 0) is S.ComplexInfinity + assert exp(-1).subs(S.Exp1, 0) is S.ComplexInfinity + assert (x**(4.0*y)).subs(x**(2.0*y), n) == n**2.0 + assert (2**(x + 2)).subs(2, 3) == 3**(x + 3) + + +def test_logexppow(): # no eval() + x = Symbol('x', real=True) + w = Symbol('w') + e = (3**(1 + x) + 2**(1 + x))/(3**x + 2**x) + assert e.subs(2**x, w) != e + assert e.subs(exp(x*log(Rational(2))), w) != e + + +def test_bug(): + x1 = Symbol('x1') + x2 = Symbol('x2') + y = x1*x2 + assert y.subs(x1, Float(3.0)) == Float(3.0)*x2 + + +def test_subbug1(): + # see that they don't fail + (x**x).subs(x, 1) + (x**x).subs(x, 1.0) + + +def test_subbug2(): + # Ensure this does not cause infinite recursion + assert Float(7.7).epsilon_eq(abs(x).subs(x, -7.7)) + + +def test_dict_set(): + a, b, c = map(Wild, 'abc') + + f = 3*cos(4*x) + r = f.match(a*cos(b*x)) + assert r == {a: 3, b: 4} + e = a/b*sin(b*x) + assert e.subs(r) == r[a]/r[b]*sin(r[b]*x) + assert e.subs(r) == 3*sin(4*x) / 4 + s = set(r.items()) + assert e.subs(s) == r[a]/r[b]*sin(r[b]*x) + assert e.subs(s) == 3*sin(4*x) / 4 + + assert e.subs(r) == r[a]/r[b]*sin(r[b]*x) + assert e.subs(r) == 3*sin(4*x) / 4 + assert x.subs(Dict((x, 1))) == 1 + + +def test_dict_ambigous(): # see issue 3566 + f = x*exp(x) + g = z*exp(z) + + df = {x: y, exp(x): y} + dg = {z: y, exp(z): y} + + assert f.subs(df) == y**2 + assert g.subs(dg) == y**2 + + # and this is how order can affect the result + assert f.subs(x, y).subs(exp(x), y) == y*exp(y) + assert f.subs(exp(x), y).subs(x, y) == y**2 + + # length of args and count_ops are the same so + # default_sort_key resolves ordering...if one + # doesn't want this result then an unordered + # sequence should not be used. + e = 1 + x*y + assert e.subs({x: y, y: 2}) == 5 + # here, there are no obviously clashing keys or values + # but the results depend on the order + assert exp(x/2 + y).subs({exp(y + 1): 2, x: 2}) == exp(y + 1) + + +def test_deriv_sub_bug3(): + f = Function('f') + pat = Derivative(f(x), x, x) + assert pat.subs(y, y**2) == Derivative(f(x), x, x) + assert pat.subs(y, y**2) != Derivative(f(x), x) + + +def test_equality_subs1(): + f = Function('f') + eq = Eq(f(x)**2, x) + res = Eq(Integer(16), x) + assert eq.subs(f(x), 4) == res + + +def test_equality_subs2(): + f = Function('f') + eq = Eq(f(x)**2, 16) + assert bool(eq.subs(f(x), 3)) is False + assert bool(eq.subs(f(x), 4)) is True + + +def test_issue_3742(): + e = sqrt(x)*exp(y) + assert e.subs(sqrt(x), 1) == exp(y) + + +def test_subs_dict1(): + assert (1 + x*y).subs(x, pi) == 1 + pi*y + assert (1 + x*y).subs({x: pi, y: 2}) == 1 + 2*pi + + c2, c3, q1p, q2p, c1, s1, s2, s3 = symbols('c2 c3 q1p q2p c1 s1 s2 s3') + test = (c2**2*q2p*c3 + c1**2*s2**2*q2p*c3 + s1**2*s2**2*q2p*c3 + - c1**2*q1p*c2*s3 - s1**2*q1p*c2*s3) + assert (test.subs({c1**2: 1 - s1**2, c2**2: 1 - s2**2, c3**3: 1 - s3**2}) + == c3*q2p*(1 - s2**2) + c3*q2p*s2**2*(1 - s1**2) + - c2*q1p*s3*(1 - s1**2) + c3*q2p*s1**2*s2**2 - c2*q1p*s3*s1**2) + + +def test_mul(): + x, y, z, a, b, c = symbols('x y z a b c') + A, B, C = symbols('A B C', commutative=0) + assert (x*y*z).subs(z*x, y) == y**2 + assert (z*x).subs(1/x, z) == 1 + assert (x*y/z).subs(1/z, a) == a*x*y + assert (x*y/z).subs(x/z, a) == a*y + assert (x*y/z).subs(y/z, a) == a*x + assert (x*y/z).subs(x/z, 1/a) == y/a + assert (x*y/z).subs(x, 1/a) == y/(z*a) + assert (2*x*y).subs(5*x*y, z) != z*Rational(2, 5) + assert (x*y*A).subs(x*y, a) == a*A + assert (x**2*y**(x*Rational(3, 2))).subs(x*y**(x/2), 2) == 4*y**(x/2) + assert (x*exp(x*2)).subs(x*exp(x), 2) == 2*exp(x) + assert ((x**(2*y))**3).subs(x**y, 2) == 64 + assert (x*A*B).subs(x*A, y) == y*B + assert (x*y*(1 + x)*(1 + x*y)).subs(x*y, 2) == 6*(1 + x) + assert ((1 + A*B)*A*B).subs(A*B, x*A*B) + assert (x*a/z).subs(x/z, A) == a*A + assert (x**3*A).subs(x**2*A, a) == a*x + assert (x**2*A*B).subs(x**2*B, a) == a*A + assert (x**2*A*B).subs(x**2*A, a) == a*B + assert (b*A**3/(a**3*c**3)).subs(a**4*c**3*A**3/b**4, z) == \ + b*A**3/(a**3*c**3) + assert (6*x).subs(2*x, y) == 3*y + assert (y*exp(x*Rational(3, 2))).subs(y*exp(x), 2) == 2*exp(x/2) + assert (y*exp(x*Rational(3, 2))).subs(y*exp(x), 2) == 2*exp(x/2) + assert (A**2*B*A**2*B*A**2).subs(A*B*A, C) == A*C**2*A + assert (x*A**3).subs(x*A, y) == y*A**2 + assert (x**2*A**3).subs(x*A, y) == y**2*A + assert (x*A**3).subs(x*A, B) == B*A**2 + assert (x*A*B*A*exp(x*A*B)).subs(x*A, B) == B**2*A*exp(B*B) + assert (x**2*A*B*A*exp(x*A*B)).subs(x*A, B) == B**3*exp(B**2) + assert (x**3*A*exp(x*A*B)*A*exp(x*A*B)).subs(x*A, B) == \ + x*B*exp(B**2)*B*exp(B**2) + assert (x*A*B*C*A*B).subs(x*A*B, C) == C**2*A*B + assert (-I*a*b).subs(a*b, 2) == -2*I + + # issue 6361 + assert (-8*I*a).subs(-2*a, 1) == 4*I + assert (-I*a).subs(-a, 1) == I + + # issue 6441 + assert (4*x**2).subs(2*x, y) == y**2 + assert (2*4*x**2).subs(2*x, y) == 2*y**2 + assert (-x**3/9).subs(-x/3, z) == -z**2*x + assert (-x**3/9).subs(x/3, z) == -z**2*x + assert (-2*x**3/9).subs(x/3, z) == -2*x*z**2 + assert (-2*x**3/9).subs(-x/3, z) == -2*x*z**2 + assert (-2*x**3/9).subs(-2*x, z) == z*x**2/9 + assert (-2*x**3/9).subs(2*x, z) == -z*x**2/9 + assert (2*(3*x/5/7)**2).subs(3*x/5, z) == 2*(Rational(1, 7))**2*z**2 + assert (4*x).subs(-2*x, z) == 4*x # try keep subs literal + + +def test_subs_simple(): + a = symbols('a', commutative=True) + x = symbols('x', commutative=False) + + assert (2*a).subs(1, 3) == 2*a + assert (2*a).subs(2, 3) == 3*a + assert (2*a).subs(a, 3) == 6 + assert sin(2).subs(1, 3) == sin(2) + assert sin(2).subs(2, 3) == sin(3) + assert sin(a).subs(a, 3) == sin(3) + + assert (2*x).subs(1, 3) == 2*x + assert (2*x).subs(2, 3) == 3*x + assert (2*x).subs(x, 3) == 6 + assert sin(x).subs(x, 3) == sin(3) + + +def test_subs_constants(): + a, b = symbols('a b', commutative=True) + x, y = symbols('x y', commutative=False) + + assert (a*b).subs(2*a, 1) == a*b + assert (1.5*a*b).subs(a, 1) == 1.5*b + assert (2*a*b).subs(2*a, 1) == b + assert (2*a*b).subs(4*a, 1) == 2*a*b + + assert (x*y).subs(2*x, 1) == x*y + assert (1.5*x*y).subs(x, 1) == 1.5*y + assert (2*x*y).subs(2*x, 1) == y + assert (2*x*y).subs(4*x, 1) == 2*x*y + + +def test_subs_commutative(): + a, b, c, d, K = symbols('a b c d K', commutative=True) + + assert (a*b).subs(a*b, K) == K + assert (a*b*a*b).subs(a*b, K) == K**2 + assert (a*a*b*b).subs(a*b, K) == K**2 + assert (a*b*c*d).subs(a*b*c, K) == d*K + assert (a*b**c).subs(a, K) == K*b**c + assert (a*b**c).subs(b, K) == a*K**c + assert (a*b**c).subs(c, K) == a*b**K + assert (a*b*c*b*a).subs(a*b, K) == c*K**2 + assert (a**3*b**2*a).subs(a*b, K) == a**2*K**2 + + +def test_subs_noncommutative(): + w, x, y, z, L = symbols('w x y z L', commutative=False) + alpha = symbols('alpha', commutative=True) + someint = symbols('someint', commutative=True, integer=True) + + assert (x*y).subs(x*y, L) == L + assert (w*y*x).subs(x*y, L) == w*y*x + assert (w*x*y*z).subs(x*y, L) == w*L*z + assert (x*y*x*y).subs(x*y, L) == L**2 + assert (x*x*y).subs(x*y, L) == x*L + assert (x*x*y*y).subs(x*y, L) == x*L*y + assert (w*x*y).subs(x*y*z, L) == w*x*y + assert (x*y**z).subs(x, L) == L*y**z + assert (x*y**z).subs(y, L) == x*L**z + assert (x*y**z).subs(z, L) == x*y**L + assert (w*x*y*z*x*y).subs(x*y*z, L) == w*L*x*y + assert (w*x*y*y*w*x*x*y*x*y*y*x*y).subs(x*y, L) == w*L*y*w*x*L**2*y*L + + # Check fractional power substitutions. It should not do + # substitutions that choose a value for noncommutative log, + # or inverses that don't already appear in the expressions. + assert (x*x*x).subs(x*x, L) == L*x + assert (x*x*x*y*x*x*x*x).subs(x*x, L) == L*x*y*L**2 + for p in range(1, 5): + for k in range(10): + assert (y * x**k).subs(x**p, L) == y * L**(k//p) * x**(k % p) + assert (x**Rational(3, 2)).subs(x**S.Half, L) == x**Rational(3, 2) + assert (x**S.Half).subs(x**S.Half, L) == L + assert (x**Rational(-1, 2)).subs(x**S.Half, L) == x**Rational(-1, 2) + assert (x**Rational(-1, 2)).subs(x**Rational(-1, 2), L) == L + + assert (x**(2*someint)).subs(x**someint, L) == L**2 + assert (x**(2*someint + 3)).subs(x**someint, L) == L**2*x**3 + assert (x**(3*someint + 3)).subs(x**someint, L) == L**3*x**3 + assert (x**(3*someint)).subs(x**(2*someint), L) == L * x**someint + assert (x**(4*someint)).subs(x**(2*someint), L) == L**2 + assert (x**(4*someint + 1)).subs(x**(2*someint), L) == L**2 * x + assert (x**(4*someint)).subs(x**(3*someint), L) == L * x**someint + assert (x**(4*someint + 1)).subs(x**(3*someint), L) == L * x**(someint + 1) + + assert (x**(2*alpha)).subs(x**alpha, L) == x**(2*alpha) + assert (x**(2*alpha + 2)).subs(x**2, L) == x**(2*alpha + 2) + assert ((2*z)**alpha).subs(z**alpha, y) == (2*z)**alpha + assert (x**(2*someint*alpha)).subs(x**someint, L) == x**(2*someint*alpha) + assert (x**(2*someint + alpha)).subs(x**someint, L) == x**(2*someint + alpha) + + # This could in principle be substituted, but is not currently + # because it requires recognizing that someint**2 is divisible by + # someint. + assert (x**(someint**2 + 3)).subs(x**someint, L) == x**(someint**2 + 3) + + # alpha**z := exp(log(alpha) z) is usually well-defined + assert (4**z).subs(2**z, y) == y**2 + + # Negative powers + assert (x**(-1)).subs(x**3, L) == x**(-1) + assert (x**(-2)).subs(x**3, L) == x**(-2) + assert (x**(-3)).subs(x**3, L) == L**(-1) + assert (x**(-4)).subs(x**3, L) == L**(-1) * x**(-1) + assert (x**(-5)).subs(x**3, L) == L**(-1) * x**(-2) + + assert (x**(-1)).subs(x**(-3), L) == x**(-1) + assert (x**(-2)).subs(x**(-3), L) == x**(-2) + assert (x**(-3)).subs(x**(-3), L) == L + assert (x**(-4)).subs(x**(-3), L) == L * x**(-1) + assert (x**(-5)).subs(x**(-3), L) == L * x**(-2) + + assert (x**1).subs(x**(-3), L) == x + assert (x**2).subs(x**(-3), L) == x**2 + assert (x**3).subs(x**(-3), L) == L**(-1) + assert (x**4).subs(x**(-3), L) == L**(-1) * x + assert (x**5).subs(x**(-3), L) == L**(-1) * x**2 + + +def test_subs_basic_funcs(): + a, b, c, d, K = symbols('a b c d K', commutative=True) + w, x, y, z, L = symbols('w x y z L', commutative=False) + + assert (x + y).subs(x + y, L) == L + assert (x - y).subs(x - y, L) == L + assert (x/y).subs(x, L) == L/y + assert (x**y).subs(x, L) == L**y + assert (x**y).subs(y, L) == x**L + assert ((a - c)/b).subs(b, K) == (a - c)/K + assert (exp(x*y - z)).subs(x*y, L) == exp(L - z) + assert (a*exp(x*y - w*z) + b*exp(x*y + w*z)).subs(z, 0) == \ + a*exp(x*y) + b*exp(x*y) + assert ((a - b)/(c*d - a*b)).subs(c*d - a*b, K) == (a - b)/K + assert (w*exp(a*b - c)*x*y/4).subs(x*y, L) == w*exp(a*b - c)*L/4 + + +def test_subs_wild(): + R, S, T, U = symbols('R S T U', cls=Wild) + + assert (R*S).subs(R*S, T) == T + assert (S*R).subs(R*S, T) == T + assert (R + S).subs(R + S, T) == T + assert (R**S).subs(R, T) == T**S + assert (R**S).subs(S, T) == R**T + assert (R*S**T).subs(R, U) == U*S**T + assert (R*S**T).subs(S, U) == R*U**T + assert (R*S**T).subs(T, U) == R*S**U + + +def test_subs_mixed(): + a, b, c, d, K = symbols('a b c d K', commutative=True) + w, x, y, z, L = symbols('w x y z L', commutative=False) + R, S, T, U = symbols('R S T U', cls=Wild) + + assert (a*x*y).subs(x*y, L) == a*L + assert (a*b*x*y*x).subs(x*y, L) == a*b*L*x + assert (R*x*y*exp(x*y)).subs(x*y, L) == R*L*exp(L) + assert (a*x*y*y*x - x*y*z*exp(a*b)).subs(x*y, L) == a*L*y*x - L*z*exp(a*b) + e = c*y*x*y*x**(R*S - a*b) - T*(a*R*b*S) + assert e.subs(x*y, L).subs(a*b, K).subs(R*S, U) == \ + c*y*L*x**(U - K) - T*(U*K) + + +def test_division(): + a, b, c = symbols('a b c', commutative=True) + x, y, z = symbols('x y z', commutative=True) + + assert (1/a).subs(a, c) == 1/c + assert (1/a**2).subs(a, c) == 1/c**2 + assert (1/a**2).subs(a, -2) == Rational(1, 4) + assert (-(1/a**2)).subs(a, -2) == Rational(-1, 4) + + assert (1/x).subs(x, z) == 1/z + assert (1/x**2).subs(x, z) == 1/z**2 + assert (1/x**2).subs(x, -2) == Rational(1, 4) + assert (-(1/x**2)).subs(x, -2) == Rational(-1, 4) + + #issue 5360 + assert (1/x).subs(x, 0) == 1/S.Zero + + +def test_add(): + a, b, c, d, x, y, t = symbols('a b c d x y t') + + assert (a**2 - b - c).subs(a**2 - b, d) in [d - c, a**2 - b - c] + assert (a**2 - c).subs(a**2 - c, d) == d + assert (a**2 - b - c).subs(a**2 - c, d) in [d - b, a**2 - b - c] + assert (a**2 - x - c).subs(a**2 - c, d) in [d - x, a**2 - x - c] + assert (a**2 - b - sqrt(a)).subs(a**2 - sqrt(a), c) == c - b + assert (a + b + exp(a + b)).subs(a + b, c) == c + exp(c) + assert (c + b + exp(c + b)).subs(c + b, a) == a + exp(a) + assert (a + b + c + d).subs(b + c, x) == a + d + x + assert (a + b + c + d).subs(-b - c, x) == a + d - x + assert ((x + 1)*y).subs(x + 1, t) == t*y + assert ((-x - 1)*y).subs(x + 1, t) == -t*y + assert ((x - 1)*y).subs(x + 1, t) == y*(t - 2) + assert ((-x + 1)*y).subs(x + 1, t) == y*(-t + 2) + + # this should work every time: + e = a**2 - b - c + assert e.subs(Add(*e.args[:2]), d) == d + e.args[2] + assert e.subs(a**2 - c, d) == d - b + + # the fallback should recognize when a change has + # been made; while .1 == Rational(1, 10) they are not the same + # and the change should be made + assert (0.1 + a).subs(0.1, Rational(1, 10)) == Rational(1, 10) + a + + e = (-x*(-y + 1) - y*(y - 1)) + ans = (-x*(x) - y*(-x)).expand() + assert e.subs(-y + 1, x) == ans + + #Test issue 18747 + assert (exp(x) + cos(x)).subs(x, oo) == oo + assert Add(*[AccumBounds(-1, 1), oo]) == oo + assert Add(*[oo, AccumBounds(-1, 1)]) == oo + + +def test_subs_issue_4009(): + assert (I*Symbol('a')).subs(1, 2) == I*Symbol('a') + + +def test_functions_subs(): + f, g = symbols('f g', cls=Function) + l = Lambda((x, y), sin(x) + y) + assert (g(y, x) + cos(x)).subs(g, l) == sin(y) + x + cos(x) + assert (f(x)**2).subs(f, sin) == sin(x)**2 + assert (f(x, y)).subs(f, log) == log(x, y) + assert (f(x, y)).subs(f, sin) == f(x, y) + assert (sin(x) + atan2(x, y)).subs([[atan2, f], [sin, g]]) == \ + f(x, y) + g(x) + assert (g(f(x + y, x))).subs([[f, l], [g, exp]]) == exp(x + sin(x + y)) + + +def test_derivative_subs(): + f = Function('f') + g = Function('g') + assert Derivative(f(x), x).subs(f(x), y) != 0 + # need xreplace to put the function back, see #13803 + assert Derivative(f(x), x).subs(f(x), y).xreplace({y: f(x)}) == \ + Derivative(f(x), x) + # issues 5085, 5037 + assert cse(Derivative(f(x), x) + f(x))[1][0].has(Derivative) + assert cse(Derivative(f(x, y), x) + + Derivative(f(x, y), y))[1][0].has(Derivative) + eq = Derivative(g(x), g(x)) + assert eq.subs(g, f) == Derivative(f(x), f(x)) + assert eq.subs(g(x), f(x)) == Derivative(f(x), f(x)) + assert eq.subs(g, cos) == Subs(Derivative(y, y), y, cos(x)) + + +def test_derivative_subs2(): + f_func, g_func = symbols('f g', cls=Function) + f, g = f_func(x, y, z), g_func(x, y, z) + assert Derivative(f, x, y).subs(Derivative(f, x, y), g) == g + assert Derivative(f, y, x).subs(Derivative(f, x, y), g) == g + assert Derivative(f, x, y).subs(Derivative(f, x), g) == Derivative(g, y) + assert Derivative(f, x, y).subs(Derivative(f, y), g) == Derivative(g, x) + assert (Derivative(f, x, y, z).subs( + Derivative(f, x, z), g) == Derivative(g, y)) + assert (Derivative(f, x, y, z).subs( + Derivative(f, z, y), g) == Derivative(g, x)) + assert (Derivative(f, x, y, z).subs( + Derivative(f, z, y, x), g) == g) + + # Issue 9135 + assert (Derivative(f, x, x, y).subs( + Derivative(f, y, y), g) == Derivative(f, x, x, y)) + assert (Derivative(f, x, y, y, z).subs( + Derivative(f, x, y, y, y), g) == Derivative(f, x, y, y, z)) + + assert Derivative(f, x, y).subs(Derivative(f_func(x), x, y), g) == Derivative(f, x, y) + + +def test_derivative_subs3(): + dex = Derivative(exp(x), x) + assert Derivative(dex, x).subs(dex, exp(x)) == dex + assert dex.subs(exp(x), dex) == Derivative(exp(x), x, x) + + +def test_issue_5284(): + A, B = symbols('A B', commutative=False) + assert (x*A).subs(x**2*A, B) == x*A + assert (A**2).subs(A**3, B) == A**2 + assert (A**6).subs(A**3, B) == B**2 + + +def test_subs_iter(): + assert x.subs(reversed([[x, y]])) == y + it = iter([[x, y]]) + assert x.subs(it) == y + assert x.subs(Tuple((x, y))) == y + + +def test_subs_dict(): + a, b, c, d, e = symbols('a b c d e') + + assert (2*x + y + z).subs({"x": 1, "y": 2}) == 4 + z + + l = [(sin(x), 2), (x, 1)] + assert (sin(x)).subs(l) == \ + (sin(x)).subs(dict(l)) == 2 + assert sin(x).subs(reversed(l)) == sin(1) + + expr = sin(2*x) + sqrt(sin(2*x))*cos(2*x)*sin(exp(x)*x) + reps = {sin(2*x): c, + sqrt(sin(2*x)): a, + cos(2*x): b, + exp(x): e, + x: d,} + assert expr.subs(reps) == c + a*b*sin(d*e) + + l = [(x, 3), (y, x**2)] + assert (x + y).subs(l) == 3 + x**2 + assert (x + y).subs(reversed(l)) == 12 + + # If changes are made to convert lists into dictionaries and do + # a dictionary-lookup replacement, these tests will help to catch + # some logical errors that might occur + l = [(y, z + 2), (1 + z, 5), (z, 2)] + assert (y - 1 + 3*x).subs(l) == 5 + 3*x + l = [(y, z + 2), (z, 3)] + assert (y - 2).subs(l) == 3 + + +def test_no_arith_subs_on_floats(): + assert (x + 3).subs(x + 3, a) == a + assert (x + 3).subs(x + 2, a) == a + 1 + + assert (x + y + 3).subs(x + 3, a) == a + y + assert (x + y + 3).subs(x + 2, a) == a + y + 1 + + assert (x + 3.0).subs(x + 3.0, a) == a + assert (x + 3.0).subs(x + 2.0, a) == x + 3.0 + + assert (x + y + 3.0).subs(x + 3.0, a) == a + y + assert (x + y + 3.0).subs(x + 2.0, a) == x + y + 3.0 + + +def test_issue_5651(): + a, b, c, K = symbols('a b c K', commutative=True) + assert (a/(b*c)).subs(b*c, K) == a/K + assert (a/(b**2*c**3)).subs(b*c, K) == a/(c*K**2) + assert (1/(x*y)).subs(x*y, 2) == S.Half + assert ((1 + x*y)/(x*y)).subs(x*y, 1) == 2 + assert (x*y*z).subs(x*y, 2) == 2*z + assert ((1 + x*y)/(x*y)/z).subs(x*y, 1) == 2/z + + +def test_issue_6075(): + assert Tuple(1, True).subs(1, 2) == Tuple(2, True) + + +def test_issue_6079(): + # since x + 2.0 == x + 2 we can't do a simple equality test + assert _aresame((x + 2.0).subs(2, 3), x + 2.0) + assert _aresame((x + 2.0).subs(2.0, 3), x + 3) + assert not _aresame(x + 2, x + 2.0) + assert not _aresame(Basic(cos(x), S(1)), Basic(cos(x), S(1.))) + assert _aresame(cos, cos) + assert not _aresame(1, S.One) + assert not _aresame(x, symbols('x', positive=True)) + + +def test_issue_4680(): + N = Symbol('N') + assert N.subs({"N": 3}) == 3 + + +def test_issue_6158(): + assert (x - 1).subs(1, y) == x - y + assert (x - 1).subs(-1, y) == x + y + assert (x - oo).subs(oo, y) == x - y + assert (x - oo).subs(-oo, y) == x + y + + +def test_Function_subs(): + f, g, h, i = symbols('f g h i', cls=Function) + p = Piecewise((g(f(x, y)), x < -1), (g(x), x <= 1)) + assert p.subs(g, h) == Piecewise((h(f(x, y)), x < -1), (h(x), x <= 1)) + assert (f(y) + g(x)).subs({f: h, g: i}) == i(x) + h(y) + + +def test_simultaneous_subs(): + reps = {x: 0, y: 0} + assert (x/y).subs(reps) != (y/x).subs(reps) + assert (x/y).subs(reps, simultaneous=True) == \ + (y/x).subs(reps, simultaneous=True) + reps = reps.items() + assert (x/y).subs(reps) != (y/x).subs(reps) + assert (x/y).subs(reps, simultaneous=True) == \ + (y/x).subs(reps, simultaneous=True) + assert Derivative(x, y, z).subs(reps, simultaneous=True) == \ + Subs(Derivative(0, y, z), y, 0) + + +def test_issue_6419_6421(): + assert (1/(1 + x/y)).subs(x/y, x) == 1/(1 + x) + assert (-2*I).subs(2*I, x) == -x + assert (-I*x).subs(I*x, x) == -x + assert (-3*I*y**4).subs(3*I*y**2, x) == -x*y**2 + + +def test_issue_6559(): + assert (-12*x + y).subs(-x, 1) == 12 + y + # though this involves cse it generated a failure in Mul._eval_subs + x0, x1 = symbols('x0 x1') + e = -log(-12*sqrt(2) + 17)/24 - log(-2*sqrt(2) + 3)/12 + sqrt(2)/3 + # XXX modify cse so x1 is eliminated and x0 = -sqrt(2)? + assert cse(e) == ( + [(x0, sqrt(2))], [x0/3 - log(-12*x0 + 17)/24 - log(-2*x0 + 3)/12]) + + +def test_issue_5261(): + x = symbols('x', real=True) + e = I*x + assert exp(e).subs(exp(x), y) == y**I + assert (2**e).subs(2**x, y) == y**I + eq = (-2)**e + assert eq.subs((-2)**x, y) == eq + + +def test_issue_6923(): + assert (-2*x*sqrt(2)).subs(2*x, y) == -sqrt(2)*y + + +def test_2arg_hack(): + N = Symbol('N', commutative=False) + ans = Mul(2, y + 1, evaluate=False) + assert (2*x*(y + 1)).subs(x, 1, hack2=True) == ans + assert (2*(y + 1 + N)).subs(N, 0, hack2=True) == ans + + +@XFAIL +def test_mul2(): + """When this fails, remove things labelled "2-arg hack" + 1) remove special handling in the fallback of subs that + was added in the same commit as this test + 2) remove the special handling in Mul.flatten + """ + assert (2*(x + 1)).is_Mul + + +def test_noncommutative_subs(): + x,y = symbols('x,y', commutative=False) + assert (x*y*x).subs([(x, x*y), (y, x)], simultaneous=True) == (x*y*x**2*y) + + +def test_issue_2877(): + f = Float(2.0) + assert (x + f).subs({f: 2}) == x + 2 + + def r(a, b, c): + return factor(a*x**2 + b*x + c) + e = r(5.0/6, 10, 5) + assert nsimplify(e) == 5*x**2/6 + 10*x + 5 + + +def test_issue_5910(): + t = Symbol('t') + assert (1/(1 - t)).subs(t, 1) is zoo + n = t + d = t - 1 + assert (n/d).subs(t, 1) is zoo + assert (-n/-d).subs(t, 1) is zoo + + +def test_issue_5217(): + s = Symbol('s') + z = (1 - 2*x*x) + w = (1 + 2*x*x) + q = 2*x*x*2*y*y + sub = {2*x*x: s} + assert w.subs(sub) == 1 + s + assert z.subs(sub) == 1 - s + assert q == 4*x**2*y**2 + assert q.subs(sub) == 2*y**2*s + + +def test_issue_10829(): + assert (4**x).subs(2**x, y) == y**2 + assert (9**x).subs(3**x, y) == y**2 + + +def test_pow_eval_subs_no_cache(): + # Tests pull request 9376 is working + from sympy.core.cache import clear_cache + + s = 1/sqrt(x**2) + # This bug only appeared when the cache was turned off. + # We need to approximate running this test without the cache. + # This creates approximately the same situation. + clear_cache() + + # This used to fail with a wrong result. + # It incorrectly returned 1/sqrt(x**2) before this pull request. + result = s.subs(sqrt(x**2), y) + assert result == 1/y + + +def test_RootOf_issue_10092(): + x = Symbol('x', real=True) + eq = x**3 - 17*x**2 + 81*x - 118 + r = RootOf(eq, 0) + assert (x < r).subs(x, r) is S.false + + +def test_issue_8886(): + from sympy.physics.mechanics import ReferenceFrame as R + # if something can't be sympified we assume that it + # doesn't play well with SymPy and disallow the + # substitution + v = R('A').x + raises(SympifyError, lambda: x.subs(x, v)) + raises(SympifyError, lambda: v.subs(v, x)) + assert v.__eq__(x) is False + + +def test_issue_12657(): + # treat -oo like the atom that it is + reps = [(-oo, 1), (oo, 2)] + assert (x < -oo).subs(reps) == (x < 1) + assert (x < -oo).subs(list(reversed(reps))) == (x < 1) + reps = [(-oo, 2), (oo, 1)] + assert (x < oo).subs(reps) == (x < 1) + assert (x < oo).subs(list(reversed(reps))) == (x < 1) + + +def test_recurse_Application_args(): + F = Lambda((x, y), exp(2*x + 3*y)) + f = Function('f') + A = f(x, f(x, x)) + C = F(x, F(x, x)) + assert A.subs(f, F) == A.replace(f, F) == C + + +def test_Subs_subs(): + assert Subs(x*y, x, x).subs(x, y) == Subs(x*y, x, y) + assert Subs(x*y, x, x + 1).subs(x, y) == \ + Subs(x*y, x, y + 1) + assert Subs(x*y, y, x + 1).subs(x, y) == \ + Subs(y**2, y, y + 1) + a = Subs(x*y*z, (y, x, z), (x + 1, x + z, x)) + b = Subs(x*y*z, (y, x, z), (x + 1, y + z, y)) + assert a.subs(x, y) == b and \ + a.doit().subs(x, y) == a.subs(x, y).doit() + f = Function('f') + g = Function('g') + assert Subs(2*f(x, y) + g(x), f(x, y), 1).subs(y, 2) == Subs( + 2*f(x, y) + g(x), (f(x, y), y), (1, 2)) + + +def test_issue_13333(): + eq = 1/x + assert eq.subs({"x": '1/2'}) == 2 + assert eq.subs({"x": '(1/2)'}) == 2 + + +def test_issue_15234(): + x, y = symbols('x y', real=True) + p = 6*x**5 + x**4 - 4*x**3 + 4*x**2 - 2*x + 3 + p_subbed = 6*x**5 - 4*x**3 - 2*x + y**4 + 4*y**2 + 3 + assert p.subs([(x**i, y**i) for i in [2, 4]]) == p_subbed + x, y = symbols('x y', complex=True) + p = 6*x**5 + x**4 - 4*x**3 + 4*x**2 - 2*x + 3 + p_subbed = 6*x**5 - 4*x**3 - 2*x + y**4 + 4*y**2 + 3 + assert p.subs([(x**i, y**i) for i in [2, 4]]) == p_subbed + + +def test_issue_6976(): + x, y = symbols('x y') + assert (sqrt(x)**3 + sqrt(x) + x + x**2).subs(sqrt(x), y) == \ + y**4 + y**3 + y**2 + y + assert (x**4 + x**3 + x**2 + x + sqrt(x)).subs(x**2, y) == \ + sqrt(x) + x**3 + x + y**2 + y + assert x.subs(x**3, y) == x + assert x.subs(x**Rational(1, 3), y) == y**3 + + # More substitutions are possible with nonnegative symbols + x, y = symbols('x y', nonnegative=True) + assert (x**4 + x**3 + x**2 + x + sqrt(x)).subs(x**2, y) == \ + y**Rational(1, 4) + y**Rational(3, 2) + sqrt(y) + y**2 + y + assert x.subs(x**3, y) == y**Rational(1, 3) + + +def test_issue_11746(): + assert (1/x).subs(x**2, 1) == 1/x + assert (1/(x**3)).subs(x**2, 1) == x**(-3) + assert (1/(x**4)).subs(x**2, 1) == 1 + assert (1/(x**3)).subs(x**4, 1) == x**(-3) + assert (1/(y**5)).subs(x**5, 1) == y**(-5) + + +def test_issue_17823(): + from sympy.physics.mechanics import dynamicsymbols + q1, q2 = dynamicsymbols('q1, q2') + expr = q1.diff().diff()**2*q1 + q1.diff()*q2.diff() + reps={q1: a, q1.diff(): a*x*y, q1.diff().diff(): z} + assert expr.subs(reps) == a*x*y*Derivative(q2, t) + a*z**2 + + +def test_issue_19326(): + x, y = [i(t) for i in map(Function, 'xy')] + assert (x*y).subs({x: 1 + x, y: x}) == (1 + x)*x + + +def test_issue_19558(): + e = (7*x*cos(x) - 12*log(x)**3)*(-log(x)**4 + 2*sin(x) + 1)**2/ \ + (2*(x*cos(x) - 2*log(x)**3)*(3*log(x)**4 - 7*sin(x) + 3)**2) + + assert e.subs(x, oo) == AccumBounds(-oo, oo) + assert (sin(x) + cos(x)).subs(x, oo) == AccumBounds(-2, 2) + + +def test_issue_22033(): + xr = Symbol('xr', real=True) + e = (1/xr) + assert e.subs(xr**2, y) == e + + +def test_guard_against_indeterminate_evaluation(): + eq = x**y + assert eq.subs([(x, 1), (y, oo)]) == 1 # because 1**y == 1 + assert eq.subs([(y, oo), (x, 1)]) is S.NaN + assert eq.subs({x: 1, y: oo}) is S.NaN + assert eq.subs([(x, 1), (y, oo)], simultaneous=True) is S.NaN diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_symbol.py b/lib/python3.12/site-packages/sympy/core/tests/test_symbol.py new file mode 100644 index 0000000000000000000000000000000000000000..acf27700825c4822456207afe95108480505ce2c --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_symbol.py @@ -0,0 +1,421 @@ +import threading + +from sympy.core.function import Function, UndefinedFunction +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.relational import (GreaterThan, LessThan, StrictGreaterThan, StrictLessThan) +from sympy.core.symbol import (Dummy, Symbol, Wild, symbols) +from sympy.core.sympify import sympify # can't import as S yet +from sympy.core.symbol import uniquely_named_symbol, _symbol, Str + +from sympy.testing.pytest import raises, skip_under_pyodide +from sympy.core.symbol import disambiguate + + +def test_Str(): + a1 = Str('a') + a2 = Str('a') + b = Str('b') + assert a1 == a2 != b + raises(TypeError, lambda: Str()) + + +def test_Symbol(): + a = Symbol("a") + x1 = Symbol("x") + x2 = Symbol("x") + xdummy1 = Dummy("x") + xdummy2 = Dummy("x") + + assert a != x1 + assert a != x2 + assert x1 == x2 + assert x1 != xdummy1 + assert xdummy1 != xdummy2 + + assert Symbol("x") == Symbol("x") + assert Dummy("x") != Dummy("x") + d = symbols('d', cls=Dummy) + assert isinstance(d, Dummy) + c, d = symbols('c,d', cls=Dummy) + assert isinstance(c, Dummy) + assert isinstance(d, Dummy) + raises(TypeError, lambda: Symbol()) + + +def test_Dummy(): + assert Dummy() != Dummy() + + +def test_Dummy_force_dummy_index(): + raises(AssertionError, lambda: Dummy(dummy_index=1)) + assert Dummy('d', dummy_index=2) == Dummy('d', dummy_index=2) + assert Dummy('d1', dummy_index=2) != Dummy('d2', dummy_index=2) + d1 = Dummy('d', dummy_index=3) + d2 = Dummy('d') + # might fail if d1 were created with dummy_index >= 10**6 + assert d1 != d2 + d3 = Dummy('d', dummy_index=3) + assert d1 == d3 + assert Dummy()._count == Dummy('d', dummy_index=3)._count + + +def test_lt_gt(): + S = sympify + x, y = Symbol('x'), Symbol('y') + + assert (x >= y) == GreaterThan(x, y) + assert (x >= 0) == GreaterThan(x, 0) + assert (x <= y) == LessThan(x, y) + assert (x <= 0) == LessThan(x, 0) + + assert (0 <= x) == GreaterThan(x, 0) + assert (0 >= x) == LessThan(x, 0) + assert (S(0) >= x) == GreaterThan(0, x) + assert (S(0) <= x) == LessThan(0, x) + + assert (x > y) == StrictGreaterThan(x, y) + assert (x > 0) == StrictGreaterThan(x, 0) + assert (x < y) == StrictLessThan(x, y) + assert (x < 0) == StrictLessThan(x, 0) + + assert (0 < x) == StrictGreaterThan(x, 0) + assert (0 > x) == StrictLessThan(x, 0) + assert (S(0) > x) == StrictGreaterThan(0, x) + assert (S(0) < x) == StrictLessThan(0, x) + + e = x**2 + 4*x + 1 + assert (e >= 0) == GreaterThan(e, 0) + assert (0 <= e) == GreaterThan(e, 0) + assert (e > 0) == StrictGreaterThan(e, 0) + assert (0 < e) == StrictGreaterThan(e, 0) + + assert (e <= 0) == LessThan(e, 0) + assert (0 >= e) == LessThan(e, 0) + assert (e < 0) == StrictLessThan(e, 0) + assert (0 > e) == StrictLessThan(e, 0) + + assert (S(0) >= e) == GreaterThan(0, e) + assert (S(0) <= e) == LessThan(0, e) + assert (S(0) < e) == StrictLessThan(0, e) + assert (S(0) > e) == StrictGreaterThan(0, e) + + +def test_no_len(): + # there should be no len for numbers + x = Symbol('x') + raises(TypeError, lambda: len(x)) + + +def test_ineq_unequal(): + S = sympify + x, y, z = symbols('x,y,z') + + e = ( + S(-1) >= x, S(-1) >= y, S(-1) >= z, + S(-1) > x, S(-1) > y, S(-1) > z, + S(-1) <= x, S(-1) <= y, S(-1) <= z, + S(-1) < x, S(-1) < y, S(-1) < z, + S(0) >= x, S(0) >= y, S(0) >= z, + S(0) > x, S(0) > y, S(0) > z, + S(0) <= x, S(0) <= y, S(0) <= z, + S(0) < x, S(0) < y, S(0) < z, + S('3/7') >= x, S('3/7') >= y, S('3/7') >= z, + S('3/7') > x, S('3/7') > y, S('3/7') > z, + S('3/7') <= x, S('3/7') <= y, S('3/7') <= z, + S('3/7') < x, S('3/7') < y, S('3/7') < z, + S(1.5) >= x, S(1.5) >= y, S(1.5) >= z, + S(1.5) > x, S(1.5) > y, S(1.5) > z, + S(1.5) <= x, S(1.5) <= y, S(1.5) <= z, + S(1.5) < x, S(1.5) < y, S(1.5) < z, + S(2) >= x, S(2) >= y, S(2) >= z, + S(2) > x, S(2) > y, S(2) > z, + S(2) <= x, S(2) <= y, S(2) <= z, + S(2) < x, S(2) < y, S(2) < z, + x >= -1, y >= -1, z >= -1, + x > -1, y > -1, z > -1, + x <= -1, y <= -1, z <= -1, + x < -1, y < -1, z < -1, + x >= 0, y >= 0, z >= 0, + x > 0, y > 0, z > 0, + x <= 0, y <= 0, z <= 0, + x < 0, y < 0, z < 0, + x >= 1.5, y >= 1.5, z >= 1.5, + x > 1.5, y > 1.5, z > 1.5, + x <= 1.5, y <= 1.5, z <= 1.5, + x < 1.5, y < 1.5, z < 1.5, + x >= 2, y >= 2, z >= 2, + x > 2, y > 2, z > 2, + x <= 2, y <= 2, z <= 2, + x < 2, y < 2, z < 2, + + x >= y, x >= z, y >= x, y >= z, z >= x, z >= y, + x > y, x > z, y > x, y > z, z > x, z > y, + x <= y, x <= z, y <= x, y <= z, z <= x, z <= y, + x < y, x < z, y < x, y < z, z < x, z < y, + + x - pi >= y + z, y - pi >= x + z, z - pi >= x + y, + x - pi > y + z, y - pi > x + z, z - pi > x + y, + x - pi <= y + z, y - pi <= x + z, z - pi <= x + y, + x - pi < y + z, y - pi < x + z, z - pi < x + y, + True, False + ) + + left_e = e[:-1] + for i, e1 in enumerate( left_e ): + for e2 in e[i + 1:]: + assert e1 != e2 + + +def test_Wild_properties(): + S = sympify + # these tests only include Atoms + x = Symbol("x") + y = Symbol("y") + p = Symbol("p", positive=True) + k = Symbol("k", integer=True) + n = Symbol("n", integer=True, positive=True) + + given_patterns = [ x, y, p, k, -k, n, -n, S(-3), S(3), + pi, Rational(3, 2), I ] + + integerp = lambda k: k.is_integer + positivep = lambda k: k.is_positive + symbolp = lambda k: k.is_Symbol + realp = lambda k: k.is_extended_real + + S = Wild("S", properties=[symbolp]) + R = Wild("R", properties=[realp]) + Y = Wild("Y", exclude=[x, p, k, n]) + P = Wild("P", properties=[positivep]) + K = Wild("K", properties=[integerp]) + N = Wild("N", properties=[positivep, integerp]) + + given_wildcards = [ S, R, Y, P, K, N ] + + goodmatch = { + S: (x, y, p, k, n), + R: (p, k, -k, n, -n, -3, 3, pi, Rational(3, 2)), + Y: (y, -3, 3, pi, Rational(3, 2), I ), + P: (p, n, 3, pi, Rational(3, 2)), + K: (k, -k, n, -n, -3, 3), + N: (n, 3)} + + for A in given_wildcards: + for pat in given_patterns: + d = pat.match(A) + if pat in goodmatch[A]: + assert d[A] in goodmatch[A] + else: + assert d is None + + +def test_symbols(): + x = Symbol('x') + y = Symbol('y') + z = Symbol('z') + + assert symbols('x') == x + assert symbols('x ') == x + assert symbols(' x ') == x + assert symbols('x,') == (x,) + assert symbols('x, ') == (x,) + assert symbols('x ,') == (x,) + + assert symbols('x , y') == (x, y) + + assert symbols('x,y,z') == (x, y, z) + assert symbols('x y z') == (x, y, z) + + assert symbols('x,y,z,') == (x, y, z) + assert symbols('x y z ') == (x, y, z) + + xyz = Symbol('xyz') + abc = Symbol('abc') + + assert symbols('xyz') == xyz + assert symbols('xyz,') == (xyz,) + assert symbols('xyz,abc') == (xyz, abc) + + assert symbols(('xyz',)) == (xyz,) + assert symbols(('xyz,',)) == ((xyz,),) + assert symbols(('x,y,z,',)) == ((x, y, z),) + assert symbols(('xyz', 'abc')) == (xyz, abc) + assert symbols(('xyz,abc',)) == ((xyz, abc),) + assert symbols(('xyz,abc', 'x,y,z')) == ((xyz, abc), (x, y, z)) + + assert symbols(('x', 'y', 'z')) == (x, y, z) + assert symbols(['x', 'y', 'z']) == [x, y, z] + assert symbols({'x', 'y', 'z'}) == {x, y, z} + + raises(ValueError, lambda: symbols('')) + raises(ValueError, lambda: symbols(',')) + raises(ValueError, lambda: symbols('x,,y,,z')) + raises(ValueError, lambda: symbols(('x', '', 'y', '', 'z'))) + + a, b = symbols('x,y', real=True) + assert a.is_real and b.is_real + + x0 = Symbol('x0') + x1 = Symbol('x1') + x2 = Symbol('x2') + + y0 = Symbol('y0') + y1 = Symbol('y1') + + assert symbols('x0:0') == () + assert symbols('x0:1') == (x0,) + assert symbols('x0:2') == (x0, x1) + assert symbols('x0:3') == (x0, x1, x2) + + assert symbols('x:0') == () + assert symbols('x:1') == (x0,) + assert symbols('x:2') == (x0, x1) + assert symbols('x:3') == (x0, x1, x2) + + assert symbols('x1:1') == () + assert symbols('x1:2') == (x1,) + assert symbols('x1:3') == (x1, x2) + + assert symbols('x1:3,x,y,z') == (x1, x2, x, y, z) + + assert symbols('x:3,y:2') == (x0, x1, x2, y0, y1) + assert symbols(('x:3', 'y:2')) == ((x0, x1, x2), (y0, y1)) + + a = Symbol('a') + b = Symbol('b') + c = Symbol('c') + d = Symbol('d') + + assert symbols('x:z') == (x, y, z) + assert symbols('a:d,x:z') == (a, b, c, d, x, y, z) + assert symbols(('a:d', 'x:z')) == ((a, b, c, d), (x, y, z)) + + aa = Symbol('aa') + ab = Symbol('ab') + ac = Symbol('ac') + ad = Symbol('ad') + + assert symbols('aa:d') == (aa, ab, ac, ad) + assert symbols('aa:d,x:z') == (aa, ab, ac, ad, x, y, z) + assert symbols(('aa:d','x:z')) == ((aa, ab, ac, ad), (x, y, z)) + + assert type(symbols(('q:2', 'u:2'), cls=Function)[0][0]) == UndefinedFunction # issue 23532 + + # issue 6675 + def sym(s): + return str(symbols(s)) + assert sym('a0:4') == '(a0, a1, a2, a3)' + assert sym('a2:4,b1:3') == '(a2, a3, b1, b2)' + assert sym('a1(2:4)') == '(a12, a13)' + assert sym('a0:2.0:2') == '(a0.0, a0.1, a1.0, a1.1)' + assert sym('aa:cz') == '(aaz, abz, acz)' + assert sym('aa:c0:2') == '(aa0, aa1, ab0, ab1, ac0, ac1)' + assert sym('aa:ba:b') == '(aaa, aab, aba, abb)' + assert sym('a:3b') == '(a0b, a1b, a2b)' + assert sym('a-1:3b') == '(a-1b, a-2b)' + assert sym(r'a:2\,:2' + chr(0)) == '(a0,0%s, a0,1%s, a1,0%s, a1,1%s)' % ( + (chr(0),)*4) + assert sym('x(:a:3)') == '(x(a0), x(a1), x(a2))' + assert sym('x(:c):1') == '(xa0, xb0, xc0)' + assert sym('x((:a)):3') == '(x(a)0, x(a)1, x(a)2)' + assert sym('x(:a:3') == '(x(a0, x(a1, x(a2)' + assert sym(':2') == '(0, 1)' + assert sym(':b') == '(a, b)' + assert sym(':b:2') == '(a0, a1, b0, b1)' + assert sym(':2:2') == '(00, 01, 10, 11)' + assert sym(':b:b') == '(aa, ab, ba, bb)' + + raises(ValueError, lambda: symbols(':')) + raises(ValueError, lambda: symbols('a:')) + raises(ValueError, lambda: symbols('::')) + raises(ValueError, lambda: symbols('a::')) + raises(ValueError, lambda: symbols(':a:')) + raises(ValueError, lambda: symbols('::a')) + + +def test_symbols_become_functions_issue_3539(): + from sympy.abc import alpha, phi, beta, t + raises(TypeError, lambda: beta(2)) + raises(TypeError, lambda: beta(2.5)) + raises(TypeError, lambda: phi(2.5)) + raises(TypeError, lambda: alpha(2.5)) + raises(TypeError, lambda: phi(t)) + + +def test_unicode(): + xu = Symbol('x') + x = Symbol('x') + assert x == xu + + raises(TypeError, lambda: Symbol(1)) + + +def test_uniquely_named_symbol_and_Symbol(): + F = uniquely_named_symbol + x = Symbol('x') + assert F(x) == x + assert F('x') == x + assert str(F('x', x)) == 'x0' + assert str(F('x', (x + 1, 1/x))) == 'x0' + _x = Symbol('x', real=True) + assert F(('x', _x)) == _x + assert F((x, _x)) == _x + assert F('x', real=True).is_real + y = Symbol('y') + assert F(('x', y), real=True).is_real + r = Symbol('x', real=True) + assert F(('x', r)).is_real + assert F(('x', r), real=False).is_real + assert F('x1', Symbol('x1'), + compare=lambda i: str(i).rstrip('1')).name == 'x0' + assert F('x1', Symbol('x1'), + modify=lambda i: i + '_').name == 'x1_' + assert _symbol(x, _x) == x + + +def test_disambiguate(): + x, y, y_1, _x, x_1, x_2 = symbols('x y y_1 _x x_1 x_2') + t1 = Dummy('y'), _x, Dummy('x'), Dummy('x') + t2 = Dummy('x'), Dummy('x') + t3 = Dummy('x'), Dummy('y') + t4 = x, Dummy('x') + t5 = Symbol('x', integer=True), x, Symbol('x_1') + + assert disambiguate(*t1) == (y, x_2, x, x_1) + assert disambiguate(*t2) == (x, x_1) + assert disambiguate(*t3) == (x, y) + assert disambiguate(*t4) == (x_1, x) + assert disambiguate(*t5) == (t5[0], x_2, x_1) + assert disambiguate(*t5)[0] != x # assumptions are retained + + t6 = _x, Dummy('x')/y + t7 = y*Dummy('y'), y + + assert disambiguate(*t6) == (x_1, x/y) + assert disambiguate(*t7) == (y*y_1, y_1) + assert disambiguate(Dummy('x_1'), Dummy('x_1') + ) == (x_1, Symbol('x_1_1')) + + +@skip_under_pyodide("Cannot create threads under pyodide.") +def test_issue_gh_16734(): + # https://github.com/sympy/sympy/issues/16734 + + syms = list(symbols('x, y')) + + def thread1(): + for n in range(1000): + syms[0], syms[1] = symbols(f'x{n}, y{n}') + syms[0].is_positive # Check an assumption in this thread. + syms[0] = None + + def thread2(): + while syms[0] is not None: + # Compare the symbol in this thread. + result = (syms[0] == syms[1]) # noqa + + # Previously this would be very likely to raise an exception: + thread = threading.Thread(target=thread1) + thread.start() + thread2() + thread.join() diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_sympify.py b/lib/python3.12/site-packages/sympy/core/tests/test_sympify.py new file mode 100644 index 0000000000000000000000000000000000000000..8cd0787bc6f5320c6be1cf3279c82cbff3ab97ce --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_sympify.py @@ -0,0 +1,885 @@ +from sympy.core.add import Add +from sympy.core.containers import Tuple +from sympy.core.function import (Function, Lambda) +from sympy.core.mul import Mul +from sympy.core.numbers import (Float, I, Integer, Rational, pi, oo) +from sympy.core.power import Pow +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.logic.boolalg import (false, Or, true, Xor) +from sympy.matrices.dense import Matrix +from sympy.parsing.sympy_parser import null +from sympy.polys.polytools import Poly +from sympy.printing.repr import srepr +from sympy.sets.fancysets import Range +from sympy.sets.sets import Interval +from sympy.abc import x, y +from sympy.core.sympify import (sympify, _sympify, SympifyError, kernS, + CantSympify, converter) +from sympy.core.decorators import _sympifyit +from sympy.external import import_module +from sympy.testing.pytest import raises, XFAIL, skip +from sympy.utilities.decorator import conserve_mpmath_dps +from sympy.geometry import Point, Line +from sympy.functions.combinatorial.factorials import factorial, factorial2 +from sympy.abc import _clash, _clash1, _clash2 +from sympy.external.gmpy import gmpy as _gmpy, flint as _flint +from sympy.sets import FiniteSet, EmptySet +from sympy.tensor.array.dense_ndim_array import ImmutableDenseNDimArray + +import mpmath +from collections import defaultdict, OrderedDict + + +numpy = import_module('numpy') + + +def test_issue_3538(): + v = sympify("exp(x)") + assert v == exp(x) + assert type(v) == type(exp(x)) + assert str(type(v)) == str(type(exp(x))) + + +def test_sympify1(): + assert sympify("x") == Symbol("x") + assert sympify(" x") == Symbol("x") + assert sympify(" x ") == Symbol("x") + # issue 4877 + assert sympify('--.5') == 0.5 + assert sympify('-1/2') == -S.Half + assert sympify('-+--.5') == -0.5 + assert sympify('-.[3]') == Rational(-1, 3) + assert sympify('.[3]') == Rational(1, 3) + assert sympify('+.[3]') == Rational(1, 3) + assert sympify('+0.[3]*10**-2') == Rational(1, 300) + assert sympify('.[052631578947368421]') == Rational(1, 19) + assert sympify('.0[526315789473684210]') == Rational(1, 19) + assert sympify('.034[56]') == Rational(1711, 49500) + # options to make reals into rationals + assert sympify('1.22[345]', rational=True) == \ + 1 + Rational(22, 100) + Rational(345, 99900) + assert sympify('2/2.6', rational=True) == Rational(10, 13) + assert sympify('2.6/2', rational=True) == Rational(13, 10) + assert sympify('2.6e2/17', rational=True) == Rational(260, 17) + assert sympify('2.6e+2/17', rational=True) == Rational(260, 17) + assert sympify('2.6e-2/17', rational=True) == Rational(26, 17000) + assert sympify('2.1+3/4', rational=True) == \ + Rational(21, 10) + Rational(3, 4) + assert sympify('2.234456', rational=True) == Rational(279307, 125000) + assert sympify('2.234456e23', rational=True) == 223445600000000000000000 + assert sympify('2.234456e-23', rational=True) == \ + Rational(279307, 12500000000000000000000000000) + assert sympify('-2.234456e-23', rational=True) == \ + Rational(-279307, 12500000000000000000000000000) + assert sympify('12345678901/17', rational=True) == \ + Rational(12345678901, 17) + assert sympify('1/.3 + x', rational=True) == Rational(10, 3) + x + # make sure longs in fractions work + assert sympify('222222222222/11111111111') == \ + Rational(222222222222, 11111111111) + # ... even if they come from repetend notation + assert sympify('1/.2[123456789012]') == Rational(333333333333, 70781892967) + # ... or from high precision reals + assert sympify('.1234567890123456', rational=True) == \ + Rational(19290123283179, 156250000000000) + + +def test_sympify_Fraction(): + try: + import fractions + except ImportError: + pass + else: + value = sympify(fractions.Fraction(101, 127)) + assert value == Rational(101, 127) and type(value) is Rational + + +def test_sympify_gmpy(): + if _gmpy is not None: + import gmpy2 + + value = sympify(gmpy2.mpz(1000001)) + assert value == Integer(1000001) and type(value) is Integer + + value = sympify(gmpy2.mpq(101, 127)) + assert value == Rational(101, 127) and type(value) is Rational + + +def test_sympify_flint(): + if _flint is not None: + import flint + + value = sympify(flint.fmpz(1000001)) + assert value == Integer(1000001) and type(value) is Integer + + value = sympify(flint.fmpq(101, 127)) + assert value == Rational(101, 127) and type(value) is Rational + + +@conserve_mpmath_dps +def test_sympify_mpmath(): + value = sympify(mpmath.mpf(1.0)) + assert value == Float(1.0) and type(value) is Float + + mpmath.mp.dps = 12 + assert sympify( + mpmath.pi).epsilon_eq(Float("3.14159265359"), Float("1e-12")) == True + assert sympify( + mpmath.pi).epsilon_eq(Float("3.14159265359"), Float("1e-13")) == False + + mpmath.mp.dps = 6 + assert sympify( + mpmath.pi).epsilon_eq(Float("3.14159"), Float("1e-5")) == True + assert sympify( + mpmath.pi).epsilon_eq(Float("3.14159"), Float("1e-6")) == False + + mpmath.mp.dps = 15 + assert sympify(mpmath.mpc(1.0 + 2.0j)) == Float(1.0) + Float(2.0)*I + + +def test_sympify2(): + class A: + def _sympy_(self): + return Symbol("x")**3 + + a = A() + + assert _sympify(a) == x**3 + assert sympify(a) == x**3 + assert a == x**3 + + +def test_sympify3(): + assert sympify("x**3") == x**3 + assert sympify("x^3") == x**3 + assert sympify("1/2") == Integer(1)/2 + + raises(SympifyError, lambda: _sympify('x**3')) + raises(SympifyError, lambda: _sympify('1/2')) + + +def test_sympify_keywords(): + raises(SympifyError, lambda: sympify('if')) + raises(SympifyError, lambda: sympify('for')) + raises(SympifyError, lambda: sympify('while')) + raises(SympifyError, lambda: sympify('lambda')) + + +def test_sympify_float(): + assert sympify("1e-64") != 0 + assert sympify("1e-20000") != 0 + + +def test_sympify_bool(): + assert sympify(True) is true + assert sympify(False) is false + + +def test_sympyify_iterables(): + ans = [Rational(3, 10), Rational(1, 5)] + assert sympify(['.3', '.2'], rational=True) == ans + assert sympify({"x": 0, "y": 1}) == {x: 0, y: 1} + assert sympify(['1', '2', ['3', '4']]) == [S(1), S(2), [S(3), S(4)]] + + +@XFAIL +def test_issue_16772(): + # because there is a converter for tuple, the + # args are only sympified without the flags being passed + # along; list, on the other hand, is not converted + # with a converter so its args are traversed later + ans = [Rational(3, 10), Rational(1, 5)] + assert sympify(('.3', '.2'), rational=True) == Tuple(*ans) + + +def test_issue_16859(): + class no(float, CantSympify): + pass + raises(SympifyError, lambda: sympify(no(1.2))) + + +def test_sympify4(): + class A: + def _sympy_(self): + return Symbol("x") + + a = A() + + assert _sympify(a)**3 == x**3 + assert sympify(a)**3 == x**3 + assert a == x + + +def test_sympify_text(): + assert sympify('some') == Symbol('some') + assert sympify('core') == Symbol('core') + + assert sympify('True') is True + assert sympify('False') is False + + assert sympify('Poly') == Poly + assert sympify('sin') == sin + + +def test_sympify_function(): + assert sympify('factor(x**2-1, x)') == -(1 - x)*(x + 1) + assert sympify('sin(pi/2)*cos(pi)') == -Integer(1) + + +def test_sympify_poly(): + p = Poly(x**2 + x + 1, x) + + assert _sympify(p) is p + assert sympify(p) is p + + +def test_sympify_factorial(): + assert sympify('x!') == factorial(x) + assert sympify('(x+1)!') == factorial(x + 1) + assert sympify('(1 + y*(x + 1))!') == factorial(1 + y*(x + 1)) + assert sympify('(1 + y*(x + 1)!)^2') == (1 + y*factorial(x + 1))**2 + assert sympify('y*x!') == y*factorial(x) + assert sympify('x!!') == factorial2(x) + assert sympify('(x+1)!!') == factorial2(x + 1) + assert sympify('(1 + y*(x + 1))!!') == factorial2(1 + y*(x + 1)) + assert sympify('(1 + y*(x + 1)!!)^2') == (1 + y*factorial2(x + 1))**2 + assert sympify('y*x!!') == y*factorial2(x) + assert sympify('factorial2(x)!') == factorial(factorial2(x)) + + raises(SympifyError, lambda: sympify("+!!")) + raises(SympifyError, lambda: sympify(")!!")) + raises(SympifyError, lambda: sympify("!")) + raises(SympifyError, lambda: sympify("(!)")) + raises(SympifyError, lambda: sympify("x!!!")) + + +def test_issue_3595(): + assert sympify("a_") == Symbol("a_") + assert sympify("_a") == Symbol("_a") + + +def test_lambda(): + x = Symbol('x') + assert sympify('lambda: 1') == Lambda((), 1) + assert sympify('lambda x: x') == Lambda(x, x) + assert sympify('lambda x: 2*x') == Lambda(x, 2*x) + assert sympify('lambda x, y: 2*x+y') == Lambda((x, y), 2*x + y) + + +def test_lambda_raises(): + raises(SympifyError, lambda: sympify("lambda *args: args")) # args argument error + raises(SympifyError, lambda: sympify("lambda **kwargs: kwargs[0]")) # kwargs argument error + raises(SympifyError, lambda: sympify("lambda x = 1: x")) # Keyword argument error + with raises(SympifyError): + _sympify('lambda: 1') + + +def test_sympify_raises(): + raises(SympifyError, lambda: sympify("fx)")) + + class A: + def __str__(self): + return 'x' + + raises(SympifyError, lambda: sympify(A())) + + +def test__sympify(): + x = Symbol('x') + f = Function('f') + + # positive _sympify + assert _sympify(x) is x + assert _sympify(1) == Integer(1) + assert _sympify(0.5) == Float("0.5") + assert _sympify(1 + 1j) == 1.0 + I*1.0 + + # Function f is not Basic and can't sympify to Basic. We allow it to pass + # with sympify but not with _sympify. + # https://github.com/sympy/sympy/issues/20124 + assert sympify(f) is f + raises(SympifyError, lambda: _sympify(f)) + + class A: + def _sympy_(self): + return Integer(5) + + a = A() + assert _sympify(a) == Integer(5) + + # negative _sympify + raises(SympifyError, lambda: _sympify('1')) + raises(SympifyError, lambda: _sympify([1, 2, 3])) + + +def test_sympifyit(): + x = Symbol('x') + y = Symbol('y') + + @_sympifyit('b', NotImplemented) + def add(a, b): + return a + b + + assert add(x, 1) == x + 1 + assert add(x, 0.5) == x + Float('0.5') + assert add(x, y) == x + y + + assert add(x, '1') == NotImplemented + + @_sympifyit('b') + def add_raises(a, b): + return a + b + + assert add_raises(x, 1) == x + 1 + assert add_raises(x, 0.5) == x + Float('0.5') + assert add_raises(x, y) == x + y + + raises(SympifyError, lambda: add_raises(x, '1')) + + +def test_int_float(): + class F1_1: + def __float__(self): + return 1.1 + + class F1_1b: + """ + This class is still a float, even though it also implements __int__(). + """ + def __float__(self): + return 1.1 + + def __int__(self): + return 1 + + class F1_1c: + """ + This class is still a float, because it implements _sympy_() + """ + def __float__(self): + return 1.1 + + def __int__(self): + return 1 + + def _sympy_(self): + return Float(1.1) + + class I5: + def __int__(self): + return 5 + + class I5b: + """ + This class implements both __int__() and __float__(), so it will be + treated as Float in SymPy. One could change this behavior, by using + float(a) == int(a), but deciding that integer-valued floats represent + exact numbers is arbitrary and often not correct, so we do not do it. + If, in the future, we decide to do it anyway, the tests for I5b need to + be changed. + """ + def __float__(self): + return 5.0 + + def __int__(self): + return 5 + + class I5c: + """ + This class implements both __int__() and __float__(), but also + a _sympy_() method, so it will be Integer. + """ + def __float__(self): + return 5.0 + + def __int__(self): + return 5 + + def _sympy_(self): + return Integer(5) + + i5 = I5() + i5b = I5b() + i5c = I5c() + f1_1 = F1_1() + f1_1b = F1_1b() + f1_1c = F1_1c() + assert sympify(i5) == 5 + assert isinstance(sympify(i5), Integer) + assert sympify(i5b) == 5.0 + assert isinstance(sympify(i5b), Float) + assert sympify(i5c) == 5 + assert isinstance(sympify(i5c), Integer) + assert abs(sympify(f1_1) - 1.1) < 1e-5 + assert abs(sympify(f1_1b) - 1.1) < 1e-5 + assert abs(sympify(f1_1c) - 1.1) < 1e-5 + + assert _sympify(i5) == 5 + assert isinstance(_sympify(i5), Integer) + assert _sympify(i5b) == 5.0 + assert isinstance(_sympify(i5b), Float) + assert _sympify(i5c) == 5 + assert isinstance(_sympify(i5c), Integer) + assert abs(_sympify(f1_1) - 1.1) < 1e-5 + assert abs(_sympify(f1_1b) - 1.1) < 1e-5 + assert abs(_sympify(f1_1c) - 1.1) < 1e-5 + + +def test_evaluate_false(): + cases = { + '2 + 3': Add(2, 3, evaluate=False), + '2**2 / 3': Mul(Pow(2, 2, evaluate=False), Pow(3, -1, evaluate=False), evaluate=False), + '2 + 3 * 5': Add(2, Mul(3, 5, evaluate=False), evaluate=False), + '2 - 3 * 5': Add(2, Mul(-1, Mul(3, 5,evaluate=False), evaluate=False), evaluate=False), + '1 / 3': Mul(1, Pow(3, -1, evaluate=False), evaluate=False), + 'True | False': Or(True, False, evaluate=False), + '1 + 2 + 3 + 5*3 + integrate(x)': Add(1, 2, 3, Mul(5, 3, evaluate=False), x**2/2, evaluate=False), + '2 * 4 * 6 + 8': Add(Mul(2, 4, 6, evaluate=False), 8, evaluate=False), + '2 - 8 / 4': Add(2, Mul(-1, Mul(8, Pow(4, -1, evaluate=False), evaluate=False), evaluate=False), evaluate=False), + '2 - 2**2': Add(2, Mul(-1, Pow(2, 2, evaluate=False), evaluate=False), evaluate=False), + } + for case, result in cases.items(): + assert sympify(case, evaluate=False) == result + + +def test_issue_4133(): + a = sympify('Integer(4)') + + assert a == Integer(4) + assert a.is_Integer + + +def test_issue_3982(): + a = [3, 2.0] + assert sympify(a) == [Integer(3), Float(2.0)] + assert sympify(tuple(a)) == Tuple(Integer(3), Float(2.0)) + assert sympify(set(a)) == FiniteSet(Integer(3), Float(2.0)) + + +def test_S_sympify(): + assert S(1)/2 == sympify(1)/2 == S.Half + assert (-2)**(S(1)/2) == sqrt(2)*I + + +def test_issue_4788(): + assert srepr(S(1.0 + 0J)) == srepr(S(1.0)) == srepr(Float(1.0)) + + +def test_issue_4798_None(): + assert S(None) is None + + +def test_issue_3218(): + assert sympify("x+\ny") == x + y + +def test_issue_19399(): + if not numpy: + skip("numpy not installed.") + + a = numpy.array(Rational(1, 2)) + b = Rational(1, 3) + assert (a * b, type(a * b)) == (b * a, type(b * a)) + + +def test_issue_4988_builtins(): + C = Symbol('C') + vars = {'C': C} + exp1 = sympify('C') + assert exp1 == C # Make sure it did not get mixed up with sympy.C + + exp2 = sympify('C', vars) + assert exp2 == C # Make sure it did not get mixed up with sympy.C + + +def test_geometry(): + p = sympify(Point(0, 1)) + assert p == Point(0, 1) and isinstance(p, Point) + L = sympify(Line(p, (1, 0))) + assert L == Line((0, 1), (1, 0)) and isinstance(L, Line) + + +def test_kernS(): + s = '-1 - 2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x)))' + # when 1497 is fixed, this no longer should pass: the expression + # should be unchanged + assert -1 - 2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x))) == -1 + # sympification should not allow the constant to enter a Mul + # or else the structure can change dramatically + ss = kernS(s) + assert ss != -1 and ss.simplify() == -1 + s = '-1 - 2*(-(-x + 1/x)/(x*(x - 1/x)**2) - 1/(x*(x - 1/x)))'.replace( + 'x', '_kern') + ss = kernS(s) + assert ss != -1 and ss.simplify() == -1 + # issue 6687 + assert (kernS('Interval(-1,-2 - 4*(-3))') + == Interval(-1, Add(-2, Mul(12, 1, evaluate=False), evaluate=False))) + assert kernS('_kern') == Symbol('_kern') + assert kernS('E**-(x)') == exp(-x) + e = 2*(x + y)*y + assert kernS(['2*(x + y)*y', ('2*(x + y)*y',)]) == [e, (e,)] + assert kernS('-(2*sin(x)**2 + 2*sin(x)*cos(x))*y/2') == \ + -y*(2*sin(x)**2 + 2*sin(x)*cos(x))/2 + # issue 15132 + assert kernS('(1 - x)/(1 - x*(1-y))') == kernS('(1-x)/(1-(1-y)*x)') + assert kernS('(1-2**-(4+1)*(1-y)*x)') == (1 - x*(1 - y)/32) + assert kernS('(1-2**(4+1)*(1-y)*x)') == (1 - 32*x*(1 - y)) + assert kernS('(1-2.*(1-y)*x)') == 1 - 2.*x*(1 - y) + one = kernS('x - (x - 1)') + assert one != 1 and one.expand() == 1 + assert kernS("(2*x)/(x-1)") == 2*x/(x-1) + + +def test_issue_6540_6552(): + assert S('[[1/3,2], (2/5,)]') == [[Rational(1, 3), 2], (Rational(2, 5),)] + assert S('[[2/6,2], (2/4,)]') == [[Rational(1, 3), 2], (S.Half,)] + assert S('[[[2*(1)]]]') == [[[2]]] + assert S('Matrix([2*(1)])') == Matrix([2]) + + +def test_issue_6046(): + assert str(S("Q & C", locals=_clash1)) == 'C & Q' + assert str(S('pi(x)', locals=_clash2)) == 'pi(x)' + locals = {} + exec("from sympy.abc import Q, C", locals) + assert str(S('C&Q', locals)) == 'C & Q' + # clash can act as Symbol or Function + assert str(S('pi(C, Q)', locals=_clash)) == 'pi(C, Q)' + assert len(S('pi + x', locals=_clash2).free_symbols) == 2 + # but not both + raises(TypeError, lambda: S('pi + pi(x)', locals=_clash2)) + assert all(set(i.values()) == {null} for i in ( + _clash, _clash1, _clash2)) + + +def test_issue_8821_highprec_from_str(): + s = str(pi.evalf(128)) + p = sympify(s) + assert Abs(sin(p)) < 1e-127 + + +def test_issue_10295(): + if not numpy: + skip("numpy not installed.") + + A = numpy.array([[1, 3, -1], + [0, 1, 7]]) + sA = S(A) + assert sA.shape == (2, 3) + for (ri, ci), val in numpy.ndenumerate(A): + assert sA[ri, ci] == val + + B = numpy.array([-7, x, 3*y**2]) + sB = S(B) + assert sB.shape == (3,) + assert B[0] == sB[0] == -7 + assert B[1] == sB[1] == x + assert B[2] == sB[2] == 3*y**2 + + C = numpy.arange(0, 24) + C.resize(2,3,4) + sC = S(C) + assert sC[0, 0, 0].is_integer + assert sC[0, 0, 0] == 0 + + a1 = numpy.array([1, 2, 3]) + a2 = numpy.array(list(range(24))) + a2.resize(2, 4, 3) + assert sympify(a1) == ImmutableDenseNDimArray([1, 2, 3]) + assert sympify(a2) == ImmutableDenseNDimArray(list(range(24)), (2, 4, 3)) + + +def test_Range(): + # Only works in Python 3 where range returns a range type + assert sympify(range(10)) == Range(10) + assert _sympify(range(10)) == Range(10) + + +def test_sympify_set(): + n = Symbol('n') + assert sympify({n}) == FiniteSet(n) + assert sympify(set()) == EmptySet + + +def test_sympify_numpy(): + if not numpy: + skip('numpy not installed. Abort numpy tests.') + np = numpy + + def equal(x, y): + return x == y and type(x) == type(y) + + assert sympify(np.bool_(1)) is S(True) + try: + assert equal( + sympify(np.int_(1234567891234567891)), S(1234567891234567891)) + assert equal( + sympify(np.intp(1234567891234567891)), S(1234567891234567891)) + except OverflowError: + # May fail on 32-bit systems: Python int too large to convert to C long + pass + assert equal(sympify(np.intc(1234567891)), S(1234567891)) + assert equal(sympify(np.int8(-123)), S(-123)) + assert equal(sympify(np.int16(-12345)), S(-12345)) + assert equal(sympify(np.int32(-1234567891)), S(-1234567891)) + assert equal( + sympify(np.int64(-1234567891234567891)), S(-1234567891234567891)) + assert equal(sympify(np.uint8(123)), S(123)) + assert equal(sympify(np.uint16(12345)), S(12345)) + assert equal(sympify(np.uint32(1234567891)), S(1234567891)) + assert equal( + sympify(np.uint64(1234567891234567891)), S(1234567891234567891)) + assert equal(sympify(np.float32(1.123456)), Float(1.123456, precision=24)) + assert equal(sympify(np.float64(1.1234567891234)), + Float(1.1234567891234, precision=53)) + + # The exact precision of np.longdouble, npfloat128 and other extended + # precision dtypes is platform dependent. + ldprec = np.finfo(np.longdouble(1)).nmant + 1 + assert equal(sympify(np.longdouble(1.123456789)), + Float(1.123456789, precision=ldprec)) + + assert equal(sympify(np.complex64(1 + 2j)), S(1.0 + 2.0*I)) + assert equal(sympify(np.complex128(1 + 2j)), S(1.0 + 2.0*I)) + + lcprec = np.finfo(np.clongdouble(1)).nmant + 1 + assert equal(sympify(np.clongdouble(1 + 2j)), + Float(1.0, precision=lcprec) + Float(2.0, precision=lcprec)*I) + + #float96 does not exist on all platforms + if hasattr(np, 'float96'): + f96prec = np.finfo(np.float96(1)).nmant + 1 + assert equal(sympify(np.float96(1.123456789)), + Float(1.123456789, precision=f96prec)) + + #float128 does not exist on all platforms + if hasattr(np, 'float128'): + f128prec = np.finfo(np.float128(1)).nmant + 1 + assert equal(sympify(np.float128(1.123456789123)), + Float(1.123456789123, precision=f128prec)) + + +@XFAIL +def test_sympify_rational_numbers_set(): + ans = [Rational(3, 10), Rational(1, 5)] + assert sympify({'.3', '.2'}, rational=True) == FiniteSet(*ans) + + +def test_sympify_mro(): + """Tests the resolution order for classes that implement _sympy_""" + class a: + def _sympy_(self): + return Integer(1) + class b(a): + def _sympy_(self): + return Integer(2) + class c(a): + pass + + assert sympify(a()) == Integer(1) + assert sympify(b()) == Integer(2) + assert sympify(c()) == Integer(1) + + +def test_sympify_converter(): + """Tests the resolution order for classes in converter""" + class a: + pass + class b(a): + pass + class c(a): + pass + + converter[a] = lambda x: Integer(1) + converter[b] = lambda x: Integer(2) + + assert sympify(a()) == Integer(1) + assert sympify(b()) == Integer(2) + assert sympify(c()) == Integer(1) + + class MyInteger(Integer): + pass + + if int in converter: + int_converter = converter[int] + else: + int_converter = None + + try: + converter[int] = MyInteger + assert sympify(1) == MyInteger(1) + finally: + if int_converter is None: + del converter[int] + else: + converter[int] = int_converter + + +def test_issue_13924(): + if not numpy: + skip("numpy not installed.") + + a = sympify(numpy.array([1])) + assert isinstance(a, ImmutableDenseNDimArray) + assert a[0] == 1 + + +def test_numpy_sympify_args(): + # Issue 15098. Make sure sympify args work with numpy types (like numpy.str_) + if not numpy: + skip("numpy not installed.") + + a = sympify(numpy.str_('a')) + assert type(a) is Symbol + assert a == Symbol('a') + + class CustomSymbol(Symbol): + pass + + a = sympify(numpy.str_('a'), {"Symbol": CustomSymbol}) + assert isinstance(a, CustomSymbol) + + a = sympify(numpy.str_('x^y')) + assert a == x**y + a = sympify(numpy.str_('x^y'), convert_xor=False) + assert a == Xor(x, y) + + raises(SympifyError, lambda: sympify(numpy.str_('x'), strict=True)) + + a = sympify(numpy.str_('1.1')) + assert isinstance(a, Float) + assert a == 1.1 + + a = sympify(numpy.str_('1.1'), rational=True) + assert isinstance(a, Rational) + assert a == Rational(11, 10) + + a = sympify(numpy.str_('x + x')) + assert isinstance(a, Mul) + assert a == 2*x + + a = sympify(numpy.str_('x + x'), evaluate=False) + assert isinstance(a, Add) + assert a == Add(x, x, evaluate=False) + + +def test_issue_5939(): + a = Symbol('a') + b = Symbol('b') + assert sympify('''a+\nb''') == a + b + + +def test_issue_16759(): + d = sympify({.5: 1}) + assert S.Half not in d + assert Float(.5) in d + assert d[.5] is S.One + d = sympify(OrderedDict({.5: 1})) + assert S.Half not in d + assert Float(.5) in d + assert d[.5] is S.One + d = sympify(defaultdict(int, {.5: 1})) + assert S.Half not in d + assert Float(.5) in d + assert d[.5] is S.One + + +def test_issue_17811(): + a = Function('a') + assert sympify('a(x)*5', evaluate=False) == Mul(a(x), 5, evaluate=False) + + +def test_issue_8439(): + assert sympify(float('inf')) == oo + assert x + float('inf') == x + oo + assert S(float('inf')) == oo + + +def test_issue_14706(): + if not numpy: + skip("numpy not installed.") + + z1 = numpy.zeros((1, 1), dtype=numpy.float64) + z2 = numpy.zeros((2, 2), dtype=numpy.float64) + z3 = numpy.zeros((), dtype=numpy.float64) + + y1 = numpy.ones((1, 1), dtype=numpy.float64) + y2 = numpy.ones((2, 2), dtype=numpy.float64) + y3 = numpy.ones((), dtype=numpy.float64) + + assert numpy.all(x + z1 == numpy.full((1, 1), x)) + assert numpy.all(x + z2 == numpy.full((2, 2), x)) + assert numpy.all(z1 + x == numpy.full((1, 1), x)) + assert numpy.all(z2 + x == numpy.full((2, 2), x)) + for z in [z3, + numpy.int64(0), + numpy.float64(0), + numpy.complex64(0)]: + assert x + z == x + assert z + x == x + assert isinstance(x + z, Symbol) + assert isinstance(z + x, Symbol) + + # If these tests fail, then it means that numpy has finally + # fixed the issue of scalar conversion for rank>0 arrays + # which is mentioned in numpy/numpy#10404. In that case, + # some changes have to be made in sympify.py. + # Note: For future reference, for anyone who takes up this + # issue when numpy has finally fixed their side of the problem, + # the changes for this temporary fix were introduced in PR 18651 + assert numpy.all(x + y1 == numpy.full((1, 1), x + 1.0)) + assert numpy.all(x + y2 == numpy.full((2, 2), x + 1.0)) + assert numpy.all(y1 + x == numpy.full((1, 1), x + 1.0)) + assert numpy.all(y2 + x == numpy.full((2, 2), x + 1.0)) + for y_ in [y3, + numpy.int64(1), + numpy.float64(1), + numpy.complex64(1)]: + assert x + y_ == y_ + x + assert isinstance(x + y_, Add) + assert isinstance(y_ + x, Add) + + assert x + numpy.array(x) == 2 * x + assert x + numpy.array([x]) == numpy.array([2*x], dtype=object) + + assert sympify(numpy.array([1])) == ImmutableDenseNDimArray([1], 1) + assert sympify(numpy.array([[[1]]])) == ImmutableDenseNDimArray([1], (1, 1, 1)) + assert sympify(z1) == ImmutableDenseNDimArray([0.0], (1, 1)) + assert sympify(z2) == ImmutableDenseNDimArray([0.0, 0.0, 0.0, 0.0], (2, 2)) + assert sympify(z3) == ImmutableDenseNDimArray([0.0], ()) + assert sympify(z3, strict=True) == 0.0 + + raises(SympifyError, lambda: sympify(numpy.array([1]), strict=True)) + raises(SympifyError, lambda: sympify(z1, strict=True)) + raises(SympifyError, lambda: sympify(z2, strict=True)) + + +def test_issue_21536(): + #test to check evaluate=False in case of iterable input + u = sympify("x+3*x+2", evaluate=False) + v = sympify("2*x+4*x+2+4", evaluate=False) + + assert u.is_Add and set(u.args) == {x, 3*x, 2} + assert v.is_Add and set(v.args) == {2*x, 4*x, 2, 4} + assert sympify(["x+3*x+2", "2*x+4*x+2+4"], evaluate=False) == [u, v] + + #test to check evaluate=True in case of iterable input + u = sympify("x+3*x+2", evaluate=True) + v = sympify("2*x+4*x+2+4", evaluate=True) + + assert u.is_Add and set(u.args) == {4*x, 2} + assert v.is_Add and set(v.args) == {6*x, 6} + assert sympify(["x+3*x+2", "2*x+4*x+2+4"], evaluate=True) == [u, v] + + #test to check evaluate with no input in case of iterable input + u = sympify("x+3*x+2") + v = sympify("2*x+4*x+2+4") + + assert u.is_Add and set(u.args) == {4*x, 2} + assert v.is_Add and set(v.args) == {6*x, 6} + assert sympify(["x+3*x+2", "2*x+4*x+2+4"]) == [u, v] diff --git a/lib/python3.12/site-packages/sympy/core/tests/test_var.py b/lib/python3.12/site-packages/sympy/core/tests/test_var.py new file mode 100644 index 0000000000000000000000000000000000000000..a02709464c9878082fecaf70fa47067ac8838ac6 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/core/tests/test_var.py @@ -0,0 +1,62 @@ +from sympy.core.function import (Function, FunctionClass) +from sympy.core.symbol import (Symbol, var) +from sympy.testing.pytest import raises + +def test_var(): + ns = {"var": var, "raises": raises} + eval("var('a')", ns) + assert ns["a"] == Symbol("a") + + eval("var('b bb cc zz _x')", ns) + assert ns["b"] == Symbol("b") + assert ns["bb"] == Symbol("bb") + assert ns["cc"] == Symbol("cc") + assert ns["zz"] == Symbol("zz") + assert ns["_x"] == Symbol("_x") + + v = eval("var(['d', 'e', 'fg'])", ns) + assert ns['d'] == Symbol('d') + assert ns['e'] == Symbol('e') + assert ns['fg'] == Symbol('fg') + +# check return value + assert v != ['d', 'e', 'fg'] + assert v == [Symbol('d'), Symbol('e'), Symbol('fg')] + + +def test_var_return(): + ns = {"var": var, "raises": raises} + "raises(ValueError, lambda: var(''))" + v2 = eval("var('q')", ns) + v3 = eval("var('q p')", ns) + + assert v2 == Symbol('q') + assert v3 == (Symbol('q'), Symbol('p')) + + +def test_var_accepts_comma(): + ns = {"var": var} + v1 = eval("var('x y z')", ns) + v2 = eval("var('x,y,z')", ns) + v3 = eval("var('x,y z')", ns) + + assert v1 == v2 + assert v1 == v3 + + +def test_var_keywords(): + ns = {"var": var} + eval("var('x y', real=True)", ns) + assert ns['x'].is_real and ns['y'].is_real + + +def test_var_cls(): + ns = {"var": var, "Function": Function} + eval("var('f', cls=Function)", ns) + + assert isinstance(ns['f'], FunctionClass) + + eval("var('g,h', cls=Function)", ns) + + assert isinstance(ns['g'], FunctionClass) + assert isinstance(ns['h'], FunctionClass) diff --git a/lib/python3.12/site-packages/sympy/integrals/__init__.py b/lib/python3.12/site-packages/sympy/integrals/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e78fe96f84d68e9b119571eb22dedb7033811b23 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/__init__.py @@ -0,0 +1,45 @@ +"""Integration functions that integrate a SymPy expression. + + Examples + ======== + + >>> from sympy import integrate, sin + >>> from sympy.abc import x + >>> integrate(1/x,x) + log(x) + >>> integrate(sin(x),x) + -cos(x) +""" +from .integrals import integrate, Integral, line_integrate +from .transforms import (mellin_transform, inverse_mellin_transform, + MellinTransform, InverseMellinTransform, + laplace_transform, inverse_laplace_transform, + laplace_correspondence, laplace_initial_conds, + LaplaceTransform, InverseLaplaceTransform, + fourier_transform, inverse_fourier_transform, + FourierTransform, InverseFourierTransform, + sine_transform, inverse_sine_transform, + SineTransform, InverseSineTransform, + cosine_transform, inverse_cosine_transform, + CosineTransform, InverseCosineTransform, + hankel_transform, inverse_hankel_transform, + HankelTransform, InverseHankelTransform) +from .singularityfunctions import singularityintegrate + +__all__ = [ + 'integrate', 'Integral', 'line_integrate', + + 'mellin_transform', 'inverse_mellin_transform', 'MellinTransform', + 'InverseMellinTransform', 'laplace_transform', + 'inverse_laplace_transform', 'LaplaceTransform', + 'laplace_correspondence', 'laplace_initial_conds', + 'InverseLaplaceTransform', 'fourier_transform', + 'inverse_fourier_transform', 'FourierTransform', + 'InverseFourierTransform', 'sine_transform', 'inverse_sine_transform', + 'SineTransform', 'InverseSineTransform', 'cosine_transform', + 'inverse_cosine_transform', 'CosineTransform', 'InverseCosineTransform', + 'hankel_transform', 'inverse_hankel_transform', 'HankelTransform', + 'InverseHankelTransform', + + 'singularityintegrate', +] diff --git a/lib/python3.12/site-packages/sympy/integrals/deltafunctions.py b/lib/python3.12/site-packages/sympy/integrals/deltafunctions.py new file mode 100644 index 0000000000000000000000000000000000000000..ae9fef0b0010a313e0866a54d978024dd475f882 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/deltafunctions.py @@ -0,0 +1,201 @@ +from sympy.core.mul import Mul +from sympy.core.singleton import S +from sympy.core.sorting import default_sort_key +from sympy.functions import DiracDelta, Heaviside +from .integrals import Integral, integrate + + +def change_mul(node, x): + """change_mul(node, x) + + Rearranges the operands of a product, bringing to front any simple + DiracDelta expression. + + Explanation + =========== + + If no simple DiracDelta expression was found, then all the DiracDelta + expressions are simplified (using DiracDelta.expand(diracdelta=True, wrt=x)). + + Return: (dirac, new node) + Where: + o dirac is either a simple DiracDelta expression or None (if no simple + expression was found); + o new node is either a simplified DiracDelta expressions or None (if it + could not be simplified). + + Examples + ======== + + >>> from sympy import DiracDelta, cos + >>> from sympy.integrals.deltafunctions import change_mul + >>> from sympy.abc import x, y + >>> change_mul(x*y*DiracDelta(x)*cos(x), x) + (DiracDelta(x), x*y*cos(x)) + >>> change_mul(x*y*DiracDelta(x**2 - 1)*cos(x), x) + (None, x*y*cos(x)*DiracDelta(x - 1)/2 + x*y*cos(x)*DiracDelta(x + 1)/2) + >>> change_mul(x*y*DiracDelta(cos(x))*cos(x), x) + (None, None) + + See Also + ======== + + sympy.functions.special.delta_functions.DiracDelta + deltaintegrate + """ + + new_args = [] + dirac = None + + #Sorting is needed so that we consistently collapse the same delta; + #However, we must preserve the ordering of non-commutative terms + c, nc = node.args_cnc() + sorted_args = sorted(c, key=default_sort_key) + sorted_args.extend(nc) + + for arg in sorted_args: + if arg.is_Pow and isinstance(arg.base, DiracDelta): + new_args.append(arg.func(arg.base, arg.exp - 1)) + arg = arg.base + if dirac is None and (isinstance(arg, DiracDelta) and arg.is_simple(x)): + dirac = arg + else: + new_args.append(arg) + if not dirac: # there was no simple dirac + new_args = [] + for arg in sorted_args: + if isinstance(arg, DiracDelta): + new_args.append(arg.expand(diracdelta=True, wrt=x)) + elif arg.is_Pow and isinstance(arg.base, DiracDelta): + new_args.append(arg.func(arg.base.expand(diracdelta=True, wrt=x), arg.exp)) + else: + new_args.append(arg) + if new_args != sorted_args: + nnode = Mul(*new_args).expand() + else: # if the node didn't change there is nothing to do + nnode = None + return (None, nnode) + return (dirac, Mul(*new_args)) + + +def deltaintegrate(f, x): + """ + deltaintegrate(f, x) + + Explanation + =========== + + The idea for integration is the following: + + - If we are dealing with a DiracDelta expression, i.e. DiracDelta(g(x)), + we try to simplify it. + + If we could simplify it, then we integrate the resulting expression. + We already know we can integrate a simplified expression, because only + simple DiracDelta expressions are involved. + + If we couldn't simplify it, there are two cases: + + 1) The expression is a simple expression: we return the integral, + taking care if we are dealing with a Derivative or with a proper + DiracDelta. + + 2) The expression is not simple (i.e. DiracDelta(cos(x))): we can do + nothing at all. + + - If the node is a multiplication node having a DiracDelta term: + + First we expand it. + + If the expansion did work, then we try to integrate the expansion. + + If not, we try to extract a simple DiracDelta term, then we have two + cases: + + 1) We have a simple DiracDelta term, so we return the integral. + + 2) We didn't have a simple term, but we do have an expression with + simplified DiracDelta terms, so we integrate this expression. + + Examples + ======== + + >>> from sympy.abc import x, y, z + >>> from sympy.integrals.deltafunctions import deltaintegrate + >>> from sympy import sin, cos, DiracDelta + >>> deltaintegrate(x*sin(x)*cos(x)*DiracDelta(x - 1), x) + sin(1)*cos(1)*Heaviside(x - 1) + >>> deltaintegrate(y**2*DiracDelta(x - z)*DiracDelta(y - z), y) + z**2*DiracDelta(x - z)*Heaviside(y - z) + + See Also + ======== + + sympy.functions.special.delta_functions.DiracDelta + sympy.integrals.integrals.Integral + """ + if not f.has(DiracDelta): + return None + + # g(x) = DiracDelta(h(x)) + if f.func == DiracDelta: + h = f.expand(diracdelta=True, wrt=x) + if h == f: # can't simplify the expression + #FIXME: the second term tells whether is DeltaDirac or Derivative + #For integrating derivatives of DiracDelta we need the chain rule + if f.is_simple(x): + if (len(f.args) <= 1 or f.args[1] == 0): + return Heaviside(f.args[0]) + else: + return (DiracDelta(f.args[0], f.args[1] - 1) / + f.args[0].as_poly().LC()) + else: # let's try to integrate the simplified expression + fh = integrate(h, x) + return fh + elif f.is_Mul or f.is_Pow: # g(x) = a*b*c*f(DiracDelta(h(x)))*d*e + g = f.expand() + if f != g: # the expansion worked + fh = integrate(g, x) + if fh is not None and not isinstance(fh, Integral): + return fh + else: + # no expansion performed, try to extract a simple DiracDelta term + deltaterm, rest_mult = change_mul(f, x) + + if not deltaterm: + if rest_mult: + fh = integrate(rest_mult, x) + return fh + else: + from sympy.solvers import solve + deltaterm = deltaterm.expand(diracdelta=True, wrt=x) + if deltaterm.is_Mul: # Take out any extracted factors + deltaterm, rest_mult_2 = change_mul(deltaterm, x) + rest_mult = rest_mult*rest_mult_2 + point = solve(deltaterm.args[0], x)[0] + + # Return the largest hyperreal term left after + # repeated integration by parts. For example, + # + # integrate(y*DiracDelta(x, 1),x) == y*DiracDelta(x,0), not 0 + # + # This is so Integral(y*DiracDelta(x).diff(x),x).doit() + # will return y*DiracDelta(x) instead of 0 or DiracDelta(x), + # both of which are correct everywhere the value is defined + # but give wrong answers for nested integration. + n = (0 if len(deltaterm.args)==1 else deltaterm.args[1]) + m = 0 + while n >= 0: + r = S.NegativeOne**n*rest_mult.diff(x, n).subs(x, point) + if r.is_zero: + n -= 1 + m += 1 + else: + if m == 0: + return r*Heaviside(x - point) + else: + return r*DiracDelta(x,m-1) + # In some very weak sense, x=0 is still a singularity, + # but we hope will not be of any practical consequence. + return S.Zero + return None diff --git a/lib/python3.12/site-packages/sympy/integrals/heurisch.py b/lib/python3.12/site-packages/sympy/integrals/heurisch.py new file mode 100644 index 0000000000000000000000000000000000000000..5b9ce143bda946fc3db26011f3d3e2ef73c81aa2 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/heurisch.py @@ -0,0 +1,780 @@ +from __future__ import annotations + +from collections import defaultdict +from functools import reduce +from itertools import permutations + +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.mul import Mul +from sympy.core.symbol import Wild, Dummy, Symbol +from sympy.core.basic import sympify +from sympy.core.numbers import Rational, pi, I +from sympy.core.relational import Eq, Ne +from sympy.core.singleton import S +from sympy.core.sorting import ordered +from sympy.core.traversal import iterfreeargs + +from sympy.functions import exp, sin, cos, tan, cot, asin, atan +from sympy.functions import log, sinh, cosh, tanh, coth, asinh +from sympy.functions import sqrt, erf, erfi, li, Ei +from sympy.functions import besselj, bessely, besseli, besselk +from sympy.functions import hankel1, hankel2, jn, yn +from sympy.functions.elementary.complexes import Abs, re, im, sign, arg +from sympy.functions.elementary.exponential import LambertW +from sympy.functions.elementary.integers import floor, ceiling +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.special.delta_functions import Heaviside, DiracDelta + +from sympy.simplify.radsimp import collect + +from sympy.logic.boolalg import And, Or +from sympy.utilities.iterables import uniq + +from sympy.polys import quo, gcd, lcm, factor_list, cancel, PolynomialError +from sympy.polys.monomials import itermonomials +from sympy.polys.polyroots import root_factors + +from sympy.polys.rings import PolyRing +from sympy.polys.solvers import solve_lin_sys +from sympy.polys.constructor import construct_domain + +from sympy.integrals.integrals import integrate + + +def components(f, x): + """ + Returns a set of all functional components of the given expression + which includes symbols, function applications and compositions and + non-integer powers. Fractional powers are collected with + minimal, positive exponents. + + Examples + ======== + + >>> from sympy import cos, sin + >>> from sympy.abc import x + >>> from sympy.integrals.heurisch import components + + >>> components(sin(x)*cos(x)**2, x) + {x, sin(x), cos(x)} + + See Also + ======== + + heurisch + """ + result = set() + + if f.has_free(x): + if f.is_symbol and f.is_commutative: + result.add(f) + elif f.is_Function or f.is_Derivative: + for g in f.args: + result |= components(g, x) + + result.add(f) + elif f.is_Pow: + result |= components(f.base, x) + + if not f.exp.is_Integer: + if f.exp.is_Rational: + result.add(f.base**Rational(1, f.exp.q)) + else: + result |= components(f.exp, x) | {f} + else: + for g in f.args: + result |= components(g, x) + + return result + +# name -> [] of symbols +_symbols_cache: dict[str, list[Dummy]] = {} + + +# NB @cacheit is not convenient here +def _symbols(name, n): + """get vector of symbols local to this module""" + try: + lsyms = _symbols_cache[name] + except KeyError: + lsyms = [] + _symbols_cache[name] = lsyms + + while len(lsyms) < n: + lsyms.append( Dummy('%s%i' % (name, len(lsyms))) ) + + return lsyms[:n] + + +def heurisch_wrapper(f, x, rewrite=False, hints=None, mappings=None, retries=3, + degree_offset=0, unnecessary_permutations=None, + _try_heurisch=None): + """ + A wrapper around the heurisch integration algorithm. + + Explanation + =========== + + This method takes the result from heurisch and checks for poles in the + denominator. For each of these poles, the integral is reevaluated, and + the final integration result is given in terms of a Piecewise. + + Examples + ======== + + >>> from sympy import cos, symbols + >>> from sympy.integrals.heurisch import heurisch, heurisch_wrapper + >>> n, x = symbols('n x') + >>> heurisch(cos(n*x), x) + sin(n*x)/n + >>> heurisch_wrapper(cos(n*x), x) + Piecewise((sin(n*x)/n, Ne(n, 0)), (x, True)) + + See Also + ======== + + heurisch + """ + from sympy.solvers.solvers import solve, denoms + f = sympify(f) + if not f.has_free(x): + return f*x + + res = heurisch(f, x, rewrite, hints, mappings, retries, degree_offset, + unnecessary_permutations, _try_heurisch) + if not isinstance(res, Basic): + return res + + # We consider each denominator in the expression, and try to find + # cases where one or more symbolic denominator might be zero. The + # conditions for these cases are stored in the list slns. + # + # Since denoms returns a set we use ordered. This is important because the + # ordering of slns determines the order of the resulting Piecewise so we + # need a deterministic order here to make the output deterministic. + slns = [] + for d in ordered(denoms(res)): + try: + slns += solve([d], dict=True, exclude=(x,)) + except NotImplementedError: + pass + if not slns: + return res + slns = list(uniq(slns)) + # Remove the solutions corresponding to poles in the original expression. + slns0 = [] + for d in denoms(f): + try: + slns0 += solve([d], dict=True, exclude=(x,)) + except NotImplementedError: + pass + slns = [s for s in slns if s not in slns0] + if not slns: + return res + if len(slns) > 1: + eqs = [] + for sub_dict in slns: + eqs.extend([Eq(key, value) for key, value in sub_dict.items()]) + slns = solve(eqs, dict=True, exclude=(x,)) + slns + # For each case listed in the list slns, we reevaluate the integral. + pairs = [] + for sub_dict in slns: + expr = heurisch(f.subs(sub_dict), x, rewrite, hints, mappings, retries, + degree_offset, unnecessary_permutations, + _try_heurisch) + cond = And(*[Eq(key, value) for key, value in sub_dict.items()]) + generic = Or(*[Ne(key, value) for key, value in sub_dict.items()]) + if expr is None: + expr = integrate(f.subs(sub_dict),x) + pairs.append((expr, cond)) + # If there is one condition, put the generic case first. Otherwise, + # doing so may lead to longer Piecewise formulas + if len(pairs) == 1: + pairs = [(heurisch(f, x, rewrite, hints, mappings, retries, + degree_offset, unnecessary_permutations, + _try_heurisch), + generic), + (pairs[0][0], True)] + else: + pairs.append((heurisch(f, x, rewrite, hints, mappings, retries, + degree_offset, unnecessary_permutations, + _try_heurisch), + True)) + return Piecewise(*pairs) + +class BesselTable: + """ + Derivatives of Bessel functions of orders n and n-1 + in terms of each other. + + See the docstring of DiffCache. + """ + + def __init__(self): + self.table = {} + self.n = Dummy('n') + self.z = Dummy('z') + self._create_table() + + def _create_table(t): + table, n, z = t.table, t.n, t.z + for f in (besselj, bessely, hankel1, hankel2): + table[f] = (f(n-1, z) - n*f(n, z)/z, + (n-1)*f(n-1, z)/z - f(n, z)) + + f = besseli + table[f] = (f(n-1, z) - n*f(n, z)/z, + (n-1)*f(n-1, z)/z + f(n, z)) + f = besselk + table[f] = (-f(n-1, z) - n*f(n, z)/z, + (n-1)*f(n-1, z)/z - f(n, z)) + + for f in (jn, yn): + table[f] = (f(n-1, z) - (n+1)*f(n, z)/z, + (n-1)*f(n-1, z)/z - f(n, z)) + + def diffs(t, f, n, z): + if f in t.table: + diff0, diff1 = t.table[f] + repl = [(t.n, n), (t.z, z)] + return (diff0.subs(repl), diff1.subs(repl)) + + def has(t, f): + return f in t.table + +_bessel_table = None + +class DiffCache: + """ + Store for derivatives of expressions. + + Explanation + =========== + + The standard form of the derivative of a Bessel function of order n + contains two Bessel functions of orders n-1 and n+1, respectively. + Such forms cannot be used in parallel Risch algorithm, because + there is a linear recurrence relation between the three functions + while the algorithm expects that functions and derivatives are + represented in terms of algebraically independent transcendentals. + + The solution is to take two of the functions, e.g., those of orders + n and n-1, and to express the derivatives in terms of the pair. + To guarantee that the proper form is used the two derivatives are + cached as soon as one is encountered. + + Derivatives of other functions are also cached at no extra cost. + All derivatives are with respect to the same variable `x`. + """ + + def __init__(self, x): + self.cache = {} + self.x = x + + global _bessel_table + if not _bessel_table: + _bessel_table = BesselTable() + + def get_diff(self, f): + cache = self.cache + + if f in cache: + pass + elif (not hasattr(f, 'func') or + not _bessel_table.has(f.func)): + cache[f] = cancel(f.diff(self.x)) + else: + n, z = f.args + d0, d1 = _bessel_table.diffs(f.func, n, z) + dz = self.get_diff(z) + cache[f] = d0*dz + cache[f.func(n-1, z)] = d1*dz + + return cache[f] + +def heurisch(f, x, rewrite=False, hints=None, mappings=None, retries=3, + degree_offset=0, unnecessary_permutations=None, + _try_heurisch=None): + """ + Compute indefinite integral using heuristic Risch algorithm. + + Explanation + =========== + + This is a heuristic approach to indefinite integration in finite + terms using the extended heuristic (parallel) Risch algorithm, based + on Manuel Bronstein's "Poor Man's Integrator". + + The algorithm supports various classes of functions including + transcendental elementary or special functions like Airy, + Bessel, Whittaker and Lambert. + + Note that this algorithm is not a decision procedure. If it isn't + able to compute the antiderivative for a given function, then this is + not a proof that such a functions does not exist. One should use + recursive Risch algorithm in such case. It's an open question if + this algorithm can be made a full decision procedure. + + This is an internal integrator procedure. You should use top level + 'integrate' function in most cases, as this procedure needs some + preprocessing steps and otherwise may fail. + + Specification + ============= + + heurisch(f, x, rewrite=False, hints=None) + + where + f : expression + x : symbol + + rewrite -> force rewrite 'f' in terms of 'tan' and 'tanh' + hints -> a list of functions that may appear in anti-derivate + + - hints = None --> no suggestions at all + - hints = [ ] --> try to figure out + - hints = [f1, ..., fn] --> we know better + + Examples + ======== + + >>> from sympy import tan + >>> from sympy.integrals.heurisch import heurisch + >>> from sympy.abc import x, y + + >>> heurisch(y*tan(x), x) + y*log(tan(x)**2 + 1)/2 + + See Manuel Bronstein's "Poor Man's Integrator": + + References + ========== + + .. [1] https://www-sop.inria.fr/cafe/Manuel.Bronstein/pmint/index.html + + For more information on the implemented algorithm refer to: + + .. [2] K. Geddes, L. Stefanus, On the Risch-Norman Integration + Method and its Implementation in Maple, Proceedings of + ISSAC'89, ACM Press, 212-217. + + .. [3] J. H. Davenport, On the Parallel Risch Algorithm (I), + Proceedings of EUROCAM'82, LNCS 144, Springer, 144-157. + + .. [4] J. H. Davenport, On the Parallel Risch Algorithm (III): + Use of Tangents, SIGSAM Bulletin 16 (1982), 3-6. + + .. [5] J. H. Davenport, B. M. Trager, On the Parallel Risch + Algorithm (II), ACM Transactions on Mathematical + Software 11 (1985), 356-362. + + See Also + ======== + + sympy.integrals.integrals.Integral.doit + sympy.integrals.integrals.Integral + sympy.integrals.heurisch.components + """ + f = sympify(f) + + # There are some functions that Heurisch cannot currently handle, + # so do not even try. + # Set _try_heurisch=True to skip this check + if _try_heurisch is not True: + if f.has(Abs, re, im, sign, Heaviside, DiracDelta, floor, ceiling, arg): + return + + if not f.has_free(x): + return f*x + + if not f.is_Add: + indep, f = f.as_independent(x) + else: + indep = S.One + + rewritables = { + (sin, cos, cot): tan, + (sinh, cosh, coth): tanh, + } + + if rewrite: + for candidates, rule in rewritables.items(): + f = f.rewrite(candidates, rule) + else: + for candidates in rewritables.keys(): + if f.has(*candidates): + break + else: + rewrite = True + + terms = components(f, x) + dcache = DiffCache(x) + + if hints is not None: + if not hints: + a = Wild('a', exclude=[x]) + b = Wild('b', exclude=[x]) + c = Wild('c', exclude=[x]) + + for g in set(terms): # using copy of terms + if g.is_Function: + if isinstance(g, li): + M = g.args[0].match(a*x**b) + + if M is not None: + terms.add( x*(li(M[a]*x**M[b]) - (M[a]*x**M[b])**(-1/M[b])*Ei((M[b]+1)*log(M[a]*x**M[b])/M[b])) ) + #terms.add( x*(li(M[a]*x**M[b]) - (x**M[b])**(-1/M[b])*Ei((M[b]+1)*log(M[a]*x**M[b])/M[b])) ) + #terms.add( x*(li(M[a]*x**M[b]) - x*Ei((M[b]+1)*log(M[a]*x**M[b])/M[b])) ) + #terms.add( li(M[a]*x**M[b]) - Ei((M[b]+1)*log(M[a]*x**M[b])/M[b]) ) + + elif isinstance(g, exp): + M = g.args[0].match(a*x**2) + + if M is not None: + if M[a].is_positive: + terms.add(erfi(sqrt(M[a])*x)) + else: # M[a].is_negative or unknown + terms.add(erf(sqrt(-M[a])*x)) + + M = g.args[0].match(a*x**2 + b*x + c) + + if M is not None: + if M[a].is_positive: + terms.add(sqrt(pi/4*(-M[a]))*exp(M[c] - M[b]**2/(4*M[a]))* + erfi(sqrt(M[a])*x + M[b]/(2*sqrt(M[a])))) + elif M[a].is_negative: + terms.add(sqrt(pi/4*(-M[a]))*exp(M[c] - M[b]**2/(4*M[a]))* + erf(sqrt(-M[a])*x - M[b]/(2*sqrt(-M[a])))) + + M = g.args[0].match(a*log(x)**2) + + if M is not None: + if M[a].is_positive: + terms.add(erfi(sqrt(M[a])*log(x) + 1/(2*sqrt(M[a])))) + if M[a].is_negative: + terms.add(erf(sqrt(-M[a])*log(x) - 1/(2*sqrt(-M[a])))) + + elif g.is_Pow: + if g.exp.is_Rational and g.exp.q == 2: + M = g.base.match(a*x**2 + b) + + if M is not None and M[b].is_positive: + if M[a].is_positive: + terms.add(asinh(sqrt(M[a]/M[b])*x)) + elif M[a].is_negative: + terms.add(asin(sqrt(-M[a]/M[b])*x)) + + M = g.base.match(a*x**2 - b) + + if M is not None and M[b].is_positive: + if M[a].is_positive: + dF = 1/sqrt(M[a]*x**2 - M[b]) + F = log(2*sqrt(M[a])*sqrt(M[a]*x**2 - M[b]) + 2*M[a]*x)/sqrt(M[a]) + dcache.cache[F] = dF # hack: F.diff(x) doesn't automatically simplify to f + terms.add(F) + elif M[a].is_negative: + terms.add(-M[b]/2*sqrt(-M[a])* + atan(sqrt(-M[a])*x/sqrt(M[a]*x**2 - M[b]))) + + else: + terms |= set(hints) + + for g in set(terms): # using copy of terms + terms |= components(dcache.get_diff(g), x) + + # XXX: The commented line below makes heurisch more deterministic wrt + # PYTHONHASHSEED and the iteration order of sets. There are other places + # where sets are iterated over but this one is possibly the most important. + # Theoretically the order here should not matter but different orderings + # can expose potential bugs in the different code paths so potentially it + # is better to keep the non-determinism. + # + # terms = list(ordered(terms)) + + # TODO: caching is significant factor for why permutations work at all. Change this. + V = _symbols('x', len(terms)) + + + # sort mapping expressions from largest to smallest (last is always x). + mapping = list(reversed(list(zip(*ordered( # + [(a[0].as_independent(x)[1], a) for a in zip(terms, V)])))[1])) # + rev_mapping = {v: k for k, v in mapping} # + if mappings is None: # + # optimizing the number of permutations of mapping # + assert mapping[-1][0] == x # if not, find it and correct this comment + unnecessary_permutations = [mapping.pop(-1)] + # only permute types of objects and let the ordering + # of types take care of the order of replacement + types = defaultdict(list) + for i in mapping: + types[type(i)].append(i) + mapping = [types[i] for i in types] + def _iter_mappings(): + for i in permutations(mapping): + yield [j for i in i for j in i] + mappings = _iter_mappings() + else: + unnecessary_permutations = unnecessary_permutations or [] + + def _substitute(expr): + return expr.subs(mapping) + + for mapping in mappings: + mapping = list(mapping) + mapping = mapping + unnecessary_permutations + diffs = [ _substitute(dcache.get_diff(g)) for g in terms ] + denoms = [ g.as_numer_denom()[1] for g in diffs ] + if all(h.is_polynomial(*V) for h in denoms) and _substitute(f).is_rational_function(*V): + denom = reduce(lambda p, q: lcm(p, q, *V), denoms) + break + else: + if not rewrite: + result = heurisch(f, x, rewrite=True, hints=hints, + unnecessary_permutations=unnecessary_permutations) + + if result is not None: + return indep*result + return None + + numers = [ cancel(denom*g) for g in diffs ] + def _derivation(h): + return Add(*[ d * h.diff(v) for d, v in zip(numers, V) ]) + + def _deflation(p): + for y in V: + if not p.has(y): + continue + + if _derivation(p) is not S.Zero: + c, q = p.as_poly(y).primitive() + return _deflation(c)*gcd(q, q.diff(y)).as_expr() + + return p + + def _splitter(p): + for y in V: + if not p.has(y): + continue + + if _derivation(y) is not S.Zero: + c, q = p.as_poly(y).primitive() + + q = q.as_expr() + + h = gcd(q, _derivation(q), y) + s = quo(h, gcd(q, q.diff(y), y), y) + + c_split = _splitter(c) + + if s.as_poly(y).degree() == 0: + return (c_split[0], q * c_split[1]) + + q_split = _splitter(cancel(q / s)) + + return (c_split[0]*q_split[0]*s, c_split[1]*q_split[1]) + + return (S.One, p) + + special = {} + + for term in terms: + if term.is_Function: + if isinstance(term, tan): + special[1 + _substitute(term)**2] = False + elif isinstance(term, tanh): + special[1 + _substitute(term)] = False + special[1 - _substitute(term)] = False + elif isinstance(term, LambertW): + special[_substitute(term)] = True + + F = _substitute(f) + + P, Q = F.as_numer_denom() + + u_split = _splitter(denom) + v_split = _splitter(Q) + + polys = set(list(v_split) + [ u_split[0] ] + list(special.keys())) + + s = u_split[0] * Mul(*[ k for k, v in special.items() if v ]) + polified = [ p.as_poly(*V) for p in [s, P, Q] ] + + if None in polified: + return None + + #--- definitions for _integrate + a, b, c = [ p.total_degree() for p in polified ] + + poly_denom = (s * v_split[0] * _deflation(v_split[1])).as_expr() + + def _exponent(g): + if g.is_Pow: + if g.exp.is_Rational and g.exp.q != 1: + if g.exp.p > 0: + return g.exp.p + g.exp.q - 1 + else: + return abs(g.exp.p + g.exp.q) + else: + return 1 + elif not g.is_Atom and g.args: + return max(_exponent(h) for h in g.args) + else: + return 1 + + A, B = _exponent(f), a + max(b, c) + + if A > 1 and B > 1: + monoms = tuple(ordered(itermonomials(V, A + B - 1 + degree_offset))) + else: + monoms = tuple(ordered(itermonomials(V, A + B + degree_offset))) + + poly_coeffs = _symbols('A', len(monoms)) + + poly_part = Add(*[ poly_coeffs[i]*monomial + for i, monomial in enumerate(monoms) ]) + + reducibles = set() + + for poly in ordered(polys): + coeff, factors = factor_list(poly, *V) + reducibles.add(coeff) + reducibles.update(fact for fact, mul in factors) + + def _integrate(field=None): + atans = set() + pairs = set() + + if field == 'Q': + irreducibles = set(reducibles) + else: + setV = set(V) + irreducibles = set() + for poly in ordered(reducibles): + zV = setV & set(iterfreeargs(poly)) + for z in ordered(zV): + s = set(root_factors(poly, z, filter=field)) + irreducibles |= s + break + + log_part, atan_part = [], [] + + for poly in ordered(irreducibles): + m = collect(poly, I, evaluate=False) + y = m.get(I, S.Zero) + if y: + x = m.get(S.One, S.Zero) + if x.has(I) or y.has(I): + continue # nontrivial x + I*y + pairs.add((x, y)) + irreducibles.remove(poly) + + while pairs: + x, y = pairs.pop() + if (x, -y) in pairs: + pairs.remove((x, -y)) + # Choosing b with no minus sign + if y.could_extract_minus_sign(): + y = -y + irreducibles.add(x*x + y*y) + atans.add(atan(x/y)) + else: + irreducibles.add(x + I*y) + + + B = _symbols('B', len(irreducibles)) + C = _symbols('C', len(atans)) + + # Note: the ordering matters here + for poly, b in reversed(list(zip(ordered(irreducibles), B))): + if poly.has(*V): + poly_coeffs.append(b) + log_part.append(b * log(poly)) + + for poly, c in reversed(list(zip(ordered(atans), C))): + if poly.has(*V): + poly_coeffs.append(c) + atan_part.append(c * poly) + + # TODO: Currently it's better to use symbolic expressions here instead + # of rational functions, because it's simpler and FracElement doesn't + # give big speed improvement yet. This is because cancellation is slow + # due to slow polynomial GCD algorithms. If this gets improved then + # revise this code. + candidate = poly_part/poly_denom + Add(*log_part) + Add(*atan_part) + h = F - _derivation(candidate) / denom + raw_numer = h.as_numer_denom()[0] + + # Rewrite raw_numer as a polynomial in K[coeffs][V] where K is a field + # that we have to determine. We can't use simply atoms() because log(3), + # sqrt(y) and similar expressions can appear, leading to non-trivial + # domains. + syms = set(poly_coeffs) | set(V) + non_syms = set() + + def find_non_syms(expr): + if expr.is_Integer or expr.is_Rational: + pass # ignore trivial numbers + elif expr in syms: + pass # ignore variables + elif not expr.has_free(*syms): + non_syms.add(expr) + elif expr.is_Add or expr.is_Mul or expr.is_Pow: + list(map(find_non_syms, expr.args)) + else: + # TODO: Non-polynomial expression. This should have been + # filtered out at an earlier stage. + raise PolynomialError + + try: + find_non_syms(raw_numer) + except PolynomialError: + return None + else: + ground, _ = construct_domain(non_syms, field=True) + + coeff_ring = PolyRing(poly_coeffs, ground) + ring = PolyRing(V, coeff_ring) + try: + numer = ring.from_expr(raw_numer) + except ValueError: + raise PolynomialError + solution = solve_lin_sys(numer.coeffs(), coeff_ring, _raw=False) + + if solution is None: + return None + else: + return candidate.xreplace(solution).xreplace( + dict(zip(poly_coeffs, [S.Zero]*len(poly_coeffs)))) + + if all(isinstance(_, Symbol) for _ in V): + more_free = F.free_symbols - set(V) + else: + Fd = F.as_dummy() + more_free = Fd.xreplace(dict(zip(V, (Dummy() for _ in V))) + ).free_symbols & Fd.free_symbols + if not more_free: + # all free generators are identified in V + solution = _integrate('Q') + + if solution is None: + solution = _integrate() + else: + solution = _integrate() + + if solution is not None: + antideriv = solution.subs(rev_mapping) + antideriv = cancel(antideriv).expand() + + if antideriv.is_Add: + antideriv = antideriv.as_independent(x)[1] + + return indep*antideriv + else: + if retries >= 0: + result = heurisch(f, x, mappings=mappings, rewrite=rewrite, hints=hints, retries=retries - 1, unnecessary_permutations=unnecessary_permutations) + + if result is not None: + return indep*result + + return None diff --git a/lib/python3.12/site-packages/sympy/integrals/integrals.py b/lib/python3.12/site-packages/sympy/integrals/integrals.py new file mode 100644 index 0000000000000000000000000000000000000000..0a54b5712ad65ad1c49a021950689578b6575106 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/integrals.py @@ -0,0 +1,1640 @@ +from typing import Tuple as tTuple + +from sympy.concrete.expr_with_limits import AddWithLimits +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.containers import Tuple +from sympy.core.expr import Expr +from sympy.core.exprtools import factor_terms +from sympy.core.function import diff +from sympy.core.logic import fuzzy_bool +from sympy.core.mul import Mul +from sympy.core.numbers import oo, pi +from sympy.core.relational import Ne +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, Wild) +from sympy.core.sympify import sympify +from sympy.functions import Piecewise, sqrt, piecewise_fold, tan, cot, atan +from sympy.functions.elementary.exponential import log +from sympy.functions.elementary.integers import floor +from sympy.functions.elementary.complexes import Abs, sign +from sympy.functions.elementary.miscellaneous import Min, Max +from sympy.functions.special.singularity_functions import Heaviside +from .rationaltools import ratint +from sympy.matrices import MatrixBase +from sympy.polys import Poly, PolynomialError +from sympy.series.formal import FormalPowerSeries +from sympy.series.limits import limit +from sympy.series.order import Order +from sympy.tensor.functions import shape +from sympy.utilities.exceptions import sympy_deprecation_warning +from sympy.utilities.iterables import is_sequence +from sympy.utilities.misc import filldedent + + +class Integral(AddWithLimits): + """Represents unevaluated integral.""" + + __slots__ = () + + args: tTuple[Expr, Tuple] + + def __new__(cls, function, *symbols, **assumptions): + """Create an unevaluated integral. + + Explanation + =========== + + Arguments are an integrand followed by one or more limits. + + If no limits are given and there is only one free symbol in the + expression, that symbol will be used, otherwise an error will be + raised. + + >>> from sympy import Integral + >>> from sympy.abc import x, y + >>> Integral(x) + Integral(x, x) + >>> Integral(y) + Integral(y, y) + + When limits are provided, they are interpreted as follows (using + ``x`` as though it were the variable of integration): + + (x,) or x - indefinite integral + (x, a) - "evaluate at" integral is an abstract antiderivative + (x, a, b) - definite integral + + The ``as_dummy`` method can be used to see which symbols cannot be + targeted by subs: those with a prepended underscore cannot be + changed with ``subs``. (Also, the integration variables themselves -- + the first element of a limit -- can never be changed by subs.) + + >>> i = Integral(x, x) + >>> at = Integral(x, (x, x)) + >>> i.as_dummy() + Integral(x, x) + >>> at.as_dummy() + Integral(_0, (_0, x)) + + """ + + #This will help other classes define their own definitions + #of behaviour with Integral. + if hasattr(function, '_eval_Integral'): + return function._eval_Integral(*symbols, **assumptions) + + if isinstance(function, Poly): + sympy_deprecation_warning( + """ + integrate(Poly) and Integral(Poly) are deprecated. Instead, + use the Poly.integrate() method, or convert the Poly to an + Expr first with the Poly.as_expr() method. + """, + deprecated_since_version="1.6", + active_deprecations_target="deprecated-integrate-poly") + + obj = AddWithLimits.__new__(cls, function, *symbols, **assumptions) + return obj + + def __getnewargs__(self): + return (self.function,) + tuple([tuple(xab) for xab in self.limits]) + + @property + def free_symbols(self): + """ + This method returns the symbols that will exist when the + integral is evaluated. This is useful if one is trying to + determine whether an integral depends on a certain + symbol or not. + + Examples + ======== + + >>> from sympy import Integral + >>> from sympy.abc import x, y + >>> Integral(x, (x, y, 1)).free_symbols + {y} + + See Also + ======== + + sympy.concrete.expr_with_limits.ExprWithLimits.function + sympy.concrete.expr_with_limits.ExprWithLimits.limits + sympy.concrete.expr_with_limits.ExprWithLimits.variables + """ + return super().free_symbols + + def _eval_is_zero(self): + # This is a very naive and quick test, not intended to do the integral to + # answer whether it is zero or not, e.g. Integral(sin(x), (x, 0, 2*pi)) + # is zero but this routine should return None for that case. But, like + # Mul, there are trivial situations for which the integral will be + # zero so we check for those. + if self.function.is_zero: + return True + got_none = False + for l in self.limits: + if len(l) == 3: + z = (l[1] == l[2]) or (l[1] - l[2]).is_zero + if z: + return True + elif z is None: + got_none = True + free = self.function.free_symbols + for xab in self.limits: + if len(xab) == 1: + free.add(xab[0]) + continue + if len(xab) == 2 and xab[0] not in free: + if xab[1].is_zero: + return True + elif xab[1].is_zero is None: + got_none = True + # take integration symbol out of free since it will be replaced + # with the free symbols in the limits + free.discard(xab[0]) + # add in the new symbols + for i in xab[1:]: + free.update(i.free_symbols) + if self.function.is_zero is False and got_none is False: + return False + + def transform(self, x, u): + r""" + Performs a change of variables from `x` to `u` using the relationship + given by `x` and `u` which will define the transformations `f` and `F` + (which are inverses of each other) as follows: + + 1) If `x` is a Symbol (which is a variable of integration) then `u` + will be interpreted as some function, f(u), with inverse F(u). + This, in effect, just makes the substitution of x with f(x). + + 2) If `u` is a Symbol then `x` will be interpreted as some function, + F(x), with inverse f(u). This is commonly referred to as + u-substitution. + + Once f and F have been identified, the transformation is made as + follows: + + .. math:: \int_a^b x \mathrm{d}x \rightarrow \int_{F(a)}^{F(b)} f(x) + \frac{\mathrm{d}}{\mathrm{d}x} + + where `F(x)` is the inverse of `f(x)` and the limits and integrand have + been corrected so as to retain the same value after integration. + + Notes + ===== + + The mappings, F(x) or f(u), must lead to a unique integral. Linear + or rational linear expression, ``2*x``, ``1/x`` and ``sqrt(x)``, will + always work; quadratic expressions like ``x**2 - 1`` are acceptable + as long as the resulting integrand does not depend on the sign of + the solutions (see examples). + + The integral will be returned unchanged if ``x`` is not a variable of + integration. + + ``x`` must be (or contain) only one of of the integration variables. If + ``u`` has more than one free symbol then it should be sent as a tuple + (``u``, ``uvar``) where ``uvar`` identifies which variable is replacing + the integration variable. + XXX can it contain another integration variable? + + Examples + ======== + + >>> from sympy.abc import a, x, u + >>> from sympy import Integral, cos, sqrt + + >>> i = Integral(x*cos(x**2 - 1), (x, 0, 1)) + + transform can change the variable of integration + + >>> i.transform(x, u) + Integral(u*cos(u**2 - 1), (u, 0, 1)) + + transform can perform u-substitution as long as a unique + integrand is obtained: + + >>> ui = i.transform(x**2 - 1, u) + >>> ui + Integral(cos(u)/2, (u, -1, 0)) + + This attempt fails because x = +/-sqrt(u + 1) and the + sign does not cancel out of the integrand: + + >>> Integral(cos(x**2 - 1), (x, 0, 1)).transform(x**2 - 1, u) + Traceback (most recent call last): + ... + ValueError: + The mapping between F(x) and f(u) did not give a unique integrand. + + transform can do a substitution. Here, the previous + result is transformed back into the original expression + using "u-substitution": + + >>> ui.transform(sqrt(u + 1), x) == i + True + + We can accomplish the same with a regular substitution: + + >>> ui.transform(u, x**2 - 1) == i + True + + If the `x` does not contain a symbol of integration then + the integral will be returned unchanged. Integral `i` does + not have an integration variable `a` so no change is made: + + >>> i.transform(a, x) == i + True + + When `u` has more than one free symbol the symbol that is + replacing `x` must be identified by passing `u` as a tuple: + + >>> Integral(x, (x, 0, 1)).transform(x, (u + a, u)) + Integral(a + u, (u, -a, 1 - a)) + >>> Integral(x, (x, 0, 1)).transform(x, (u + a, a)) + Integral(a + u, (a, -u, 1 - u)) + + See Also + ======== + + sympy.concrete.expr_with_limits.ExprWithLimits.variables : Lists the integration variables + as_dummy : Replace integration variables with dummy ones + """ + d = Dummy('d') + + xfree = x.free_symbols.intersection(self.variables) + if len(xfree) > 1: + raise ValueError( + 'F(x) can only contain one of: %s' % self.variables) + xvar = xfree.pop() if xfree else d + + if xvar not in self.variables: + return self + + u = sympify(u) + if isinstance(u, Expr): + ufree = u.free_symbols + if len(ufree) == 0: + raise ValueError(filldedent(''' + f(u) cannot be a constant''')) + if len(ufree) > 1: + raise ValueError(filldedent(''' + When f(u) has more than one free symbol, the one replacing x + must be identified: pass f(u) as (f(u), u)''')) + uvar = ufree.pop() + else: + u, uvar = u + if uvar not in u.free_symbols: + raise ValueError(filldedent(''' + Expecting a tuple (expr, symbol) where symbol identified + a free symbol in expr, but symbol is not in expr's free + symbols.''')) + if not isinstance(uvar, Symbol): + # This probably never evaluates to True + raise ValueError(filldedent(''' + Expecting a tuple (expr, symbol) but didn't get + a symbol; got %s''' % uvar)) + + if x.is_Symbol and u.is_Symbol: + return self.xreplace({x: u}) + + if not x.is_Symbol and not u.is_Symbol: + raise ValueError('either x or u must be a symbol') + + if uvar == xvar: + return self.transform(x, (u.subs(uvar, d), d)).xreplace({d: uvar}) + + if uvar in self.limits: + raise ValueError(filldedent(''' + u must contain the same variable as in x + or a variable that is not already an integration variable''')) + + from sympy.solvers.solvers import solve + if not x.is_Symbol: + F = [x.subs(xvar, d)] + soln = solve(u - x, xvar, check=False) + if not soln: + raise ValueError('no solution for solve(F(x) - f(u), x)') + f = [fi.subs(uvar, d) for fi in soln] + else: + f = [u.subs(uvar, d)] + from sympy.simplify.simplify import posify + pdiff, reps = posify(u - x) + puvar = uvar.subs([(v, k) for k, v in reps.items()]) + soln = [s.subs(reps) for s in solve(pdiff, puvar)] + if not soln: + raise ValueError('no solution for solve(F(x) - f(u), u)') + F = [fi.subs(xvar, d) for fi in soln] + + newfuncs = {(self.function.subs(xvar, fi)*fi.diff(d) + ).subs(d, uvar) for fi in f} + if len(newfuncs) > 1: + raise ValueError(filldedent(''' + The mapping between F(x) and f(u) did not give + a unique integrand.''')) + newfunc = newfuncs.pop() + + def _calc_limit_1(F, a, b): + """ + replace d with a, using subs if possible, otherwise limit + where sign of b is considered + """ + wok = F.subs(d, a) + if wok is S.NaN or wok.is_finite is False and a.is_finite: + return limit(sign(b)*F, d, a) + return wok + + def _calc_limit(a, b): + """ + replace d with a, using subs if possible, otherwise limit + where sign of b is considered + """ + avals = list({_calc_limit_1(Fi, a, b) for Fi in F}) + if len(avals) > 1: + raise ValueError(filldedent(''' + The mapping between F(x) and f(u) did not + give a unique limit.''')) + return avals[0] + + newlimits = [] + for xab in self.limits: + sym = xab[0] + if sym == xvar: + if len(xab) == 3: + a, b = xab[1:] + a, b = _calc_limit(a, b), _calc_limit(b, a) + if fuzzy_bool(a - b > 0): + a, b = b, a + newfunc = -newfunc + newlimits.append((uvar, a, b)) + elif len(xab) == 2: + a = _calc_limit(xab[1], 1) + newlimits.append((uvar, a)) + else: + newlimits.append(uvar) + else: + newlimits.append(xab) + + return self.func(newfunc, *newlimits) + + def doit(self, **hints): + """ + Perform the integration using any hints given. + + Examples + ======== + + >>> from sympy import Piecewise, S + >>> from sympy.abc import x, t + >>> p = x**2 + Piecewise((0, x/t < 0), (1, True)) + >>> p.integrate((t, S(4)/5, 1), (x, -1, 1)) + 1/3 + + See Also + ======== + + sympy.integrals.trigonometry.trigintegrate + sympy.integrals.heurisch.heurisch + sympy.integrals.rationaltools.ratint + as_sum : Approximate the integral using a sum + """ + if not hints.get('integrals', True): + return self + + deep = hints.get('deep', True) + meijerg = hints.get('meijerg', None) + conds = hints.get('conds', 'piecewise') + risch = hints.get('risch', None) + heurisch = hints.get('heurisch', None) + manual = hints.get('manual', None) + if len(list(filter(None, (manual, meijerg, risch, heurisch)))) > 1: + raise ValueError("At most one of manual, meijerg, risch, heurisch can be True") + elif manual: + meijerg = risch = heurisch = False + elif meijerg: + manual = risch = heurisch = False + elif risch: + manual = meijerg = heurisch = False + elif heurisch: + manual = meijerg = risch = False + eval_kwargs = {"meijerg": meijerg, "risch": risch, "manual": manual, "heurisch": heurisch, + "conds": conds} + + if conds not in ('separate', 'piecewise', 'none'): + raise ValueError('conds must be one of "separate", "piecewise", ' + '"none", got: %s' % conds) + + if risch and any(len(xab) > 1 for xab in self.limits): + raise ValueError('risch=True is only allowed for indefinite integrals.') + + # check for the trivial zero + if self.is_zero: + return S.Zero + + # hacks to handle integrals of + # nested summations + from sympy.concrete.summations import Sum + if isinstance(self.function, Sum): + if any(v in self.function.limits[0] for v in self.variables): + raise ValueError('Limit of the sum cannot be an integration variable.') + if any(l.is_infinite for l in self.function.limits[0][1:]): + return self + _i = self + _sum = self.function + return _sum.func(_i.func(_sum.function, *_i.limits).doit(), *_sum.limits).doit() + + # now compute and check the function + function = self.function + + # hack to use a consistent Heaviside(x, 1/2) + function = function.replace( + lambda x: isinstance(x, Heaviside) and x.args[1]*2 != 1, + lambda x: Heaviside(x.args[0])) + + if deep: + function = function.doit(**hints) + if function.is_zero: + return S.Zero + + # hacks to handle special cases + if isinstance(function, MatrixBase): + return function.applyfunc( + lambda f: self.func(f, *self.limits).doit(**hints)) + + if isinstance(function, FormalPowerSeries): + if len(self.limits) > 1: + raise NotImplementedError + xab = self.limits[0] + if len(xab) > 1: + return function.integrate(xab, **eval_kwargs) + else: + return function.integrate(xab[0], **eval_kwargs) + + # There is no trivial answer and special handling + # is done so continue + + # first make sure any definite limits have integration + # variables with matching assumptions + reps = {} + for xab in self.limits: + if len(xab) != 3: + # it makes sense to just make + # all x real but in practice with the + # current state of integration...this + # doesn't work out well + # x = xab[0] + # if x not in reps and not x.is_real: + # reps[x] = Dummy(real=True) + continue + x, a, b = xab + l = (a, b) + if all(i.is_nonnegative for i in l) and not x.is_nonnegative: + d = Dummy(positive=True) + elif all(i.is_nonpositive for i in l) and not x.is_nonpositive: + d = Dummy(negative=True) + elif all(i.is_real for i in l) and not x.is_real: + d = Dummy(real=True) + else: + d = None + if d: + reps[x] = d + if reps: + undo = {v: k for k, v in reps.items()} + did = self.xreplace(reps).doit(**hints) + if isinstance(did, tuple): # when separate=True + did = tuple([i.xreplace(undo) for i in did]) + else: + did = did.xreplace(undo) + return did + + # continue with existing assumptions + undone_limits = [] + # ulj = free symbols of any undone limits' upper and lower limits + ulj = set() + for xab in self.limits: + # compute uli, the free symbols in the + # Upper and Lower limits of limit I + if len(xab) == 1: + uli = set(xab[:1]) + elif len(xab) == 2: + uli = xab[1].free_symbols + elif len(xab) == 3: + uli = xab[1].free_symbols.union(xab[2].free_symbols) + # this integral can be done as long as there is no blocking + # limit that has been undone. An undone limit is blocking if + # it contains an integration variable that is in this limit's + # upper or lower free symbols or vice versa + if xab[0] in ulj or any(v[0] in uli for v in undone_limits): + undone_limits.append(xab) + ulj.update(uli) + function = self.func(*([function] + [xab])) + factored_function = function.factor() + if not isinstance(factored_function, Integral): + function = factored_function + continue + + if function.has(Abs, sign) and ( + (len(xab) < 3 and all(x.is_extended_real for x in xab)) or + (len(xab) == 3 and all(x.is_extended_real and not x.is_infinite for + x in xab[1:]))): + # some improper integrals are better off with Abs + xr = Dummy("xr", real=True) + function = (function.xreplace({xab[0]: xr}) + .rewrite(Piecewise).xreplace({xr: xab[0]})) + elif function.has(Min, Max): + function = function.rewrite(Piecewise) + if (function.has(Piecewise) and + not isinstance(function, Piecewise)): + function = piecewise_fold(function) + if isinstance(function, Piecewise): + if len(xab) == 1: + antideriv = function._eval_integral(xab[0], + **eval_kwargs) + else: + antideriv = self._eval_integral( + function, xab[0], **eval_kwargs) + else: + # There are a number of tradeoffs in using the + # Meijer G method. It can sometimes be a lot faster + # than other methods, and sometimes slower. And + # there are certain types of integrals for which it + # is more likely to work than others. These + # heuristics are incorporated in deciding what + # integration methods to try, in what order. See the + # integrate() docstring for details. + def try_meijerg(function, xab): + ret = None + if len(xab) == 3 and meijerg is not False: + x, a, b = xab + try: + res = meijerint_definite(function, x, a, b) + except NotImplementedError: + _debug('NotImplementedError ' + 'from meijerint_definite') + res = None + if res is not None: + f, cond = res + if conds == 'piecewise': + u = self.func(function, (x, a, b)) + # if Piecewise modifies cond too + # much it may not be recognized by + # _condsimp pattern matching so just + # turn off all evaluation + return Piecewise((f, cond), (u, True), + evaluate=False) + elif conds == 'separate': + if len(self.limits) != 1: + raise ValueError(filldedent(''' + conds=separate not supported in + multiple integrals''')) + ret = f, cond + else: + ret = f + return ret + + meijerg1 = meijerg + if (meijerg is not False and + len(xab) == 3 and xab[1].is_extended_real and xab[2].is_extended_real + and not function.is_Poly and + (xab[1].has(oo, -oo) or xab[2].has(oo, -oo))): + ret = try_meijerg(function, xab) + if ret is not None: + function = ret + continue + meijerg1 = False + # If the special meijerg code did not succeed in + # finding a definite integral, then the code using + # meijerint_indefinite will not either (it might + # find an antiderivative, but the answer is likely + # to be nonsensical). Thus if we are requested to + # only use Meijer G-function methods, we give up at + # this stage. Otherwise we just disable G-function + # methods. + if meijerg1 is False and meijerg is True: + antideriv = None + else: + antideriv = self._eval_integral( + function, xab[0], **eval_kwargs) + if antideriv is None and meijerg is True: + ret = try_meijerg(function, xab) + if ret is not None: + function = ret + continue + + final = hints.get('final', True) + # dotit may be iterated but floor terms making atan and acot + # continuous should only be added in the final round + if (final and not isinstance(antideriv, Integral) and + antideriv is not None): + for atan_term in antideriv.atoms(atan): + atan_arg = atan_term.args[0] + # Checking `atan_arg` to be linear combination of `tan` or `cot` + for tan_part in atan_arg.atoms(tan): + x1 = Dummy('x1') + tan_exp1 = atan_arg.subs(tan_part, x1) + # The coefficient of `tan` should be constant + coeff = tan_exp1.diff(x1) + if x1 not in coeff.free_symbols: + a = tan_part.args[0] + antideriv = antideriv.subs(atan_term, Add(atan_term, + sign(coeff)*pi*floor((a-pi/2)/pi))) + for cot_part in atan_arg.atoms(cot): + x1 = Dummy('x1') + cot_exp1 = atan_arg.subs(cot_part, x1) + # The coefficient of `cot` should be constant + coeff = cot_exp1.diff(x1) + if x1 not in coeff.free_symbols: + a = cot_part.args[0] + antideriv = antideriv.subs(atan_term, Add(atan_term, + sign(coeff)*pi*floor((a)/pi))) + + if antideriv is None: + undone_limits.append(xab) + function = self.func(*([function] + [xab])).factor() + factored_function = function.factor() + if not isinstance(factored_function, Integral): + function = factored_function + continue + else: + if len(xab) == 1: + function = antideriv + else: + if len(xab) == 3: + x, a, b = xab + elif len(xab) == 2: + x, b = xab + a = None + else: + raise NotImplementedError + + if deep: + if isinstance(a, Basic): + a = a.doit(**hints) + if isinstance(b, Basic): + b = b.doit(**hints) + + if antideriv.is_Poly: + gens = list(antideriv.gens) + gens.remove(x) + + antideriv = antideriv.as_expr() + + function = antideriv._eval_interval(x, a, b) + function = Poly(function, *gens) + else: + def is_indef_int(g, x): + return (isinstance(g, Integral) and + any(i == (x,) for i in g.limits)) + + def eval_factored(f, x, a, b): + # _eval_interval for integrals with + # (constant) factors + # a single indefinite integral is assumed + args = [] + for g in Mul.make_args(f): + if is_indef_int(g, x): + args.append(g._eval_interval(x, a, b)) + else: + args.append(g) + return Mul(*args) + + integrals, others, piecewises = [], [], [] + for f in Add.make_args(antideriv): + if any(is_indef_int(g, x) + for g in Mul.make_args(f)): + integrals.append(f) + elif any(isinstance(g, Piecewise) + for g in Mul.make_args(f)): + piecewises.append(piecewise_fold(f)) + else: + others.append(f) + uneval = Add(*[eval_factored(f, x, a, b) + for f in integrals]) + try: + evalued = Add(*others)._eval_interval(x, a, b) + evalued_pw = piecewise_fold(Add(*piecewises))._eval_interval(x, a, b) + function = uneval + evalued + evalued_pw + except NotImplementedError: + # This can happen if _eval_interval depends in a + # complicated way on limits that cannot be computed + undone_limits.append(xab) + function = self.func(*([function] + [xab])) + factored_function = function.factor() + if not isinstance(factored_function, Integral): + function = factored_function + return function + + def _eval_derivative(self, sym): + """Evaluate the derivative of the current Integral object by + differentiating under the integral sign [1], using the Fundamental + Theorem of Calculus [2] when possible. + + Explanation + =========== + + Whenever an Integral is encountered that is equivalent to zero or + has an integrand that is independent of the variable of integration + those integrals are performed. All others are returned as Integral + instances which can be resolved with doit() (provided they are integrable). + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Differentiation_under_the_integral_sign + .. [2] https://en.wikipedia.org/wiki/Fundamental_theorem_of_calculus + + Examples + ======== + + >>> from sympy import Integral + >>> from sympy.abc import x, y + >>> i = Integral(x + y, y, (y, 1, x)) + >>> i.diff(x) + Integral(x + y, (y, x)) + Integral(1, y, (y, 1, x)) + >>> i.doit().diff(x) == i.diff(x).doit() + True + >>> i.diff(y) + 0 + + The previous must be true since there is no y in the evaluated integral: + + >>> i.free_symbols + {x} + >>> i.doit() + 2*x**3/3 - x/2 - 1/6 + + """ + + # differentiate under the integral sign; we do not + # check for regularity conditions (TODO), see issue 4215 + + # get limits and the function + f, limits = self.function, list(self.limits) + + # the order matters if variables of integration appear in the limits + # so work our way in from the outside to the inside. + limit = limits.pop(-1) + if len(limit) == 3: + x, a, b = limit + elif len(limit) == 2: + x, b = limit + a = None + else: + a = b = None + x = limit[0] + + if limits: # f is the argument to an integral + f = self.func(f, *tuple(limits)) + + # assemble the pieces + def _do(f, ab): + dab_dsym = diff(ab, sym) + if not dab_dsym: + return S.Zero + if isinstance(f, Integral): + limits = [(x, x) if (len(l) == 1 and l[0] == x) else l + for l in f.limits] + f = self.func(f.function, *limits) + return f.subs(x, ab)*dab_dsym + + rv = S.Zero + if b is not None: + rv += _do(f, b) + if a is not None: + rv -= _do(f, a) + if len(limit) == 1 and sym == x: + # the dummy variable *is* also the real-world variable + arg = f + rv += arg + else: + # the dummy variable might match sym but it's + # only a dummy and the actual variable is determined + # by the limits, so mask off the variable of integration + # while differentiating + u = Dummy('u') + arg = f.subs(x, u).diff(sym).subs(u, x) + if arg: + rv += self.func(arg, (x, a, b)) + return rv + + def _eval_integral(self, f, x, meijerg=None, risch=None, manual=None, + heurisch=None, conds='piecewise',final=None): + """ + Calculate the anti-derivative to the function f(x). + + Explanation + =========== + + The following algorithms are applied (roughly in this order): + + 1. Simple heuristics (based on pattern matching and integral table): + + - most frequently used functions (e.g. polynomials, products of + trig functions) + + 2. Integration of rational functions: + + - A complete algorithm for integrating rational functions is + implemented (the Lazard-Rioboo-Trager algorithm). The algorithm + also uses the partial fraction decomposition algorithm + implemented in apart() as a preprocessor to make this process + faster. Note that the integral of a rational function is always + elementary, but in general, it may include a RootSum. + + 3. Full Risch algorithm: + + - The Risch algorithm is a complete decision + procedure for integrating elementary functions, which means that + given any elementary function, it will either compute an + elementary antiderivative, or else prove that none exists. + Currently, part of transcendental case is implemented, meaning + elementary integrals containing exponentials, logarithms, and + (soon!) trigonometric functions can be computed. The algebraic + case, e.g., functions containing roots, is much more difficult + and is not implemented yet. + + - If the routine fails (because the integrand is not elementary, or + because a case is not implemented yet), it continues on to the + next algorithms below. If the routine proves that the integrals + is nonelementary, it still moves on to the algorithms below, + because we might be able to find a closed-form solution in terms + of special functions. If risch=True, however, it will stop here. + + 4. The Meijer G-Function algorithm: + + - This algorithm works by first rewriting the integrand in terms of + very general Meijer G-Function (meijerg in SymPy), integrating + it, and then rewriting the result back, if possible. This + algorithm is particularly powerful for definite integrals (which + is actually part of a different method of Integral), since it can + compute closed-form solutions of definite integrals even when no + closed-form indefinite integral exists. But it also is capable + of computing many indefinite integrals as well. + + - Another advantage of this method is that it can use some results + about the Meijer G-Function to give a result in terms of a + Piecewise expression, which allows to express conditionally + convergent integrals. + + - Setting meijerg=True will cause integrate() to use only this + method. + + 5. The "manual integration" algorithm: + + - This algorithm tries to mimic how a person would find an + antiderivative by hand, for example by looking for a + substitution or applying integration by parts. This algorithm + does not handle as many integrands but can return results in a + more familiar form. + + - Sometimes this algorithm can evaluate parts of an integral; in + this case integrate() will try to evaluate the rest of the + integrand using the other methods here. + + - Setting manual=True will cause integrate() to use only this + method. + + 6. The Heuristic Risch algorithm: + + - This is a heuristic version of the Risch algorithm, meaning that + it is not deterministic. This is tried as a last resort because + it can be very slow. It is still used because not enough of the + full Risch algorithm is implemented, so that there are still some + integrals that can only be computed using this method. The goal + is to implement enough of the Risch and Meijer G-function methods + so that this can be deleted. + + Setting heurisch=True will cause integrate() to use only this + method. Set heurisch=False to not use it. + + """ + + from sympy.integrals.risch import risch_integrate, NonElementaryIntegral + from sympy.integrals.manualintegrate import manualintegrate + + if risch: + try: + return risch_integrate(f, x, conds=conds) + except NotImplementedError: + return None + + if manual: + try: + result = manualintegrate(f, x) + if result is not None and result.func != Integral: + return result + except (ValueError, PolynomialError): + pass + + eval_kwargs = {"meijerg": meijerg, "risch": risch, "manual": manual, + "heurisch": heurisch, "conds": conds} + + # if it is a poly(x) then let the polynomial integrate itself (fast) + # + # It is important to make this check first, otherwise the other code + # will return a SymPy expression instead of a Polynomial. + # + # see Polynomial for details. + if isinstance(f, Poly) and not (manual or meijerg or risch): + # Note: this is deprecated, but the deprecation warning is already + # issued in the Integral constructor. + return f.integrate(x) + + # Piecewise antiderivatives need to call special integrate. + if isinstance(f, Piecewise): + return f.piecewise_integrate(x, **eval_kwargs) + + # let's cut it short if `f` does not depend on `x`; if + # x is only a dummy, that will be handled below + if not f.has(x): + return f*x + + # try to convert to poly(x) and then integrate if successful (fast) + poly = f.as_poly(x) + if poly is not None and not (manual or meijerg or risch): + return poly.integrate().as_expr() + + if risch is not False: + try: + result, i = risch_integrate(f, x, separate_integral=True, + conds=conds) + except NotImplementedError: + pass + else: + if i: + # There was a nonelementary integral. Try integrating it. + + # if no part of the NonElementaryIntegral is integrated by + # the Risch algorithm, then use the original function to + # integrate, instead of re-written one + if result == 0: + return NonElementaryIntegral(f, x).doit(risch=False) + else: + return result + i.doit(risch=False) + else: + return result + + # since Integral(f=g1+g2+...) == Integral(g1) + Integral(g2) + ... + # we are going to handle Add terms separately, + # if `f` is not Add -- we only have one term + + # Note that in general, this is a bad idea, because Integral(g1) + + # Integral(g2) might not be computable, even if Integral(g1 + g2) is. + # For example, Integral(x**x + x**x*log(x)). But many heuristics only + # work term-wise. So we compute this step last, after trying + # risch_integrate. We also try risch_integrate again in this loop, + # because maybe the integral is a sum of an elementary part and a + # nonelementary part (like erf(x) + exp(x)). risch_integrate() is + # quite fast, so this is acceptable. + from sympy.simplify.fu import sincos_to_sum + parts = [] + args = Add.make_args(f) + for g in args: + coeff, g = g.as_independent(x) + + # g(x) = const + if g is S.One and not meijerg: + parts.append(coeff*x) + continue + + # g(x) = expr + O(x**n) + order_term = g.getO() + + if order_term is not None: + h = self._eval_integral(g.removeO(), x, **eval_kwargs) + + if h is not None: + h_order_expr = self._eval_integral(order_term.expr, x, **eval_kwargs) + + if h_order_expr is not None: + h_order_term = order_term.func( + h_order_expr, *order_term.variables) + parts.append(coeff*(h + h_order_term)) + continue + + # NOTE: if there is O(x**n) and we fail to integrate then + # there is no point in trying other methods because they + # will fail, too. + return None + + # c + # g(x) = (a*x+b) + if g.is_Pow and not g.exp.has(x) and not meijerg: + a = Wild('a', exclude=[x]) + b = Wild('b', exclude=[x]) + + M = g.base.match(a*x + b) + + if M is not None: + if g.exp == -1: + h = log(g.base) + elif conds != 'piecewise': + h = g.base**(g.exp + 1) / (g.exp + 1) + else: + h1 = log(g.base) + h2 = g.base**(g.exp + 1) / (g.exp + 1) + h = Piecewise((h2, Ne(g.exp, -1)), (h1, True)) + + parts.append(coeff * h / M[a]) + continue + + # poly(x) + # g(x) = ------- + # poly(x) + if g.is_rational_function(x) and not (manual or meijerg or risch): + parts.append(coeff * ratint(g, x)) + continue + + if not (manual or meijerg or risch): + # g(x) = Mul(trig) + h = trigintegrate(g, x, conds=conds) + if h is not None: + parts.append(coeff * h) + continue + + # g(x) has at least a DiracDelta term + h = deltaintegrate(g, x) + if h is not None: + parts.append(coeff * h) + continue + + from .singularityfunctions import singularityintegrate + # g(x) has at least a Singularity Function term + h = singularityintegrate(g, x) + if h is not None: + parts.append(coeff * h) + continue + + # Try risch again. + if risch is not False: + try: + h, i = risch_integrate(g, x, + separate_integral=True, conds=conds) + except NotImplementedError: + h = None + else: + if i: + h = h + i.doit(risch=False) + + parts.append(coeff*h) + continue + + # fall back to heurisch + if heurisch is not False: + from sympy.integrals.heurisch import (heurisch as heurisch_, + heurisch_wrapper) + try: + if conds == 'piecewise': + h = heurisch_wrapper(g, x, hints=[]) + else: + h = heurisch_(g, x, hints=[]) + except PolynomialError: + # XXX: this exception means there is a bug in the + # implementation of heuristic Risch integration + # algorithm. + h = None + else: + h = None + + if meijerg is not False and h is None: + # rewrite using G functions + try: + h = meijerint_indefinite(g, x) + except NotImplementedError: + _debug('NotImplementedError from meijerint_definite') + if h is not None: + parts.append(coeff * h) + continue + + if h is None and manual is not False: + try: + result = manualintegrate(g, x) + if result is not None and not isinstance(result, Integral): + if result.has(Integral) and not manual: + # Try to have other algorithms do the integrals + # manualintegrate can't handle, + # unless we were asked to use manual only. + # Keep the rest of eval_kwargs in case another + # method was set to False already + new_eval_kwargs = eval_kwargs + new_eval_kwargs["manual"] = False + new_eval_kwargs["final"] = False + result = result.func(*[ + arg.doit(**new_eval_kwargs) if + arg.has(Integral) else arg + for arg in result.args + ]).expand(multinomial=False, + log=False, + power_exp=False, + power_base=False) + if not result.has(Integral): + parts.append(coeff * result) + continue + except (ValueError, PolynomialError): + # can't handle some SymPy expressions + pass + + # if we failed maybe it was because we had + # a product that could have been expanded, + # so let's try an expansion of the whole + # thing before giving up; we don't try this + # at the outset because there are things + # that cannot be solved unless they are + # NOT expanded e.g., x**x*(1+log(x)). There + # should probably be a checker somewhere in this + # routine to look for such cases and try to do + # collection on the expressions if they are already + # in an expanded form + if not h and len(args) == 1: + f = sincos_to_sum(f).expand(mul=True, deep=False) + if f.is_Add: + # Note: risch will be identical on the expanded + # expression, but maybe it will be able to pick out parts, + # like x*(exp(x) + erf(x)). + return self._eval_integral(f, x, **eval_kwargs) + + if h is not None: + parts.append(coeff * h) + else: + return None + + return Add(*parts) + + def _eval_lseries(self, x, logx=None, cdir=0): + expr = self.as_dummy() + symb = x + for l in expr.limits: + if x in l[1:]: + symb = l[0] + break + for term in expr.function.lseries(symb, logx): + yield integrate(term, *expr.limits) + + def _eval_nseries(self, x, n, logx=None, cdir=0): + symb = x + for l in self.limits: + if x in l[1:]: + symb = l[0] + break + terms, order = self.function.nseries( + x=symb, n=n, logx=logx).as_coeff_add(Order) + order = [o.subs(symb, x) for o in order] + return integrate(terms, *self.limits) + Add(*order)*x + + def _eval_as_leading_term(self, x, logx=None, cdir=0): + series_gen = self.args[0].lseries(x) + for leading_term in series_gen: + if leading_term != 0: + break + return integrate(leading_term, *self.args[1:]) + + def _eval_simplify(self, **kwargs): + expr = factor_terms(self) + if isinstance(expr, Integral): + from sympy.simplify.simplify import simplify + return expr.func(*[simplify(i, **kwargs) for i in expr.args]) + return expr.simplify(**kwargs) + + def as_sum(self, n=None, method="midpoint", evaluate=True): + """ + Approximates a definite integral by a sum. + + Parameters + ========== + + n : + The number of subintervals to use, optional. + method : + One of: 'left', 'right', 'midpoint', 'trapezoid'. + evaluate : bool + If False, returns an unevaluated Sum expression. The default + is True, evaluate the sum. + + Notes + ===== + + These methods of approximate integration are described in [1]. + + Examples + ======== + + >>> from sympy import Integral, sin, sqrt + >>> from sympy.abc import x, n + >>> e = Integral(sin(x), (x, 3, 7)) + >>> e + Integral(sin(x), (x, 3, 7)) + + For demonstration purposes, this interval will only be split into 2 + regions, bounded by [3, 5] and [5, 7]. + + The left-hand rule uses function evaluations at the left of each + interval: + + >>> e.as_sum(2, 'left') + 2*sin(5) + 2*sin(3) + + The midpoint rule uses evaluations at the center of each interval: + + >>> e.as_sum(2, 'midpoint') + 2*sin(4) + 2*sin(6) + + The right-hand rule uses function evaluations at the right of each + interval: + + >>> e.as_sum(2, 'right') + 2*sin(5) + 2*sin(7) + + The trapezoid rule uses function evaluations on both sides of the + intervals. This is equivalent to taking the average of the left and + right hand rule results: + + >>> s = e.as_sum(2, 'trapezoid') + >>> s + 2*sin(5) + sin(3) + sin(7) + >>> (e.as_sum(2, 'left') + e.as_sum(2, 'right'))/2 == s + True + + Here, the discontinuity at x = 0 can be avoided by using the + midpoint or right-hand method: + + >>> e = Integral(1/sqrt(x), (x, 0, 1)) + >>> e.as_sum(5).n(4) + 1.730 + >>> e.as_sum(10).n(4) + 1.809 + >>> e.doit().n(4) # the actual value is 2 + 2.000 + + The left- or trapezoid method will encounter the discontinuity and + return infinity: + + >>> e.as_sum(5, 'left') + zoo + + The number of intervals can be symbolic. If omitted, a dummy symbol + will be used for it. + + >>> e = Integral(x**2, (x, 0, 2)) + >>> e.as_sum(n, 'right').expand() + 8/3 + 4/n + 4/(3*n**2) + + This shows that the midpoint rule is more accurate, as its error + term decays as the square of n: + + >>> e.as_sum(method='midpoint').expand() + 8/3 - 2/(3*_n**2) + + A symbolic sum is returned with evaluate=False: + + >>> e.as_sum(n, 'midpoint', evaluate=False) + 2*Sum((2*_k/n - 1/n)**2, (_k, 1, n))/n + + See Also + ======== + + Integral.doit : Perform the integration using any hints + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Riemann_sum#Riemann_summation_methods + """ + + from sympy.concrete.summations import Sum + limits = self.limits + if len(limits) > 1: + raise NotImplementedError( + "Multidimensional midpoint rule not implemented yet") + else: + limit = limits[0] + if (len(limit) != 3 or limit[1].is_finite is False or + limit[2].is_finite is False): + raise ValueError("Expecting a definite integral over " + "a finite interval.") + if n is None: + n = Dummy('n', integer=True, positive=True) + else: + n = sympify(n) + if (n.is_positive is False or n.is_integer is False or + n.is_finite is False): + raise ValueError("n must be a positive integer, got %s" % n) + x, a, b = limit + dx = (b - a)/n + k = Dummy('k', integer=True, positive=True) + f = self.function + + if method == "left": + result = dx*Sum(f.subs(x, a + (k-1)*dx), (k, 1, n)) + elif method == "right": + result = dx*Sum(f.subs(x, a + k*dx), (k, 1, n)) + elif method == "midpoint": + result = dx*Sum(f.subs(x, a + k*dx - dx/2), (k, 1, n)) + elif method == "trapezoid": + result = dx*((f.subs(x, a) + f.subs(x, b))/2 + + Sum(f.subs(x, a + k*dx), (k, 1, n - 1))) + else: + raise ValueError("Unknown method %s" % method) + return result.doit() if evaluate else result + + def principal_value(self, **kwargs): + """ + Compute the Cauchy Principal Value of the definite integral of a real function in the given interval + on the real axis. + + Explanation + =========== + + In mathematics, the Cauchy principal value, is a method for assigning values to certain improper + integrals which would otherwise be undefined. + + Examples + ======== + + >>> from sympy import Integral, oo + >>> from sympy.abc import x + >>> Integral(x+1, (x, -oo, oo)).principal_value() + oo + >>> f = 1 / (x**3) + >>> Integral(f, (x, -oo, oo)).principal_value() + 0 + >>> Integral(f, (x, -10, 10)).principal_value() + 0 + >>> Integral(f, (x, -10, oo)).principal_value() + Integral(f, (x, -oo, 10)).principal_value() + 0 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Cauchy_principal_value + .. [2] https://mathworld.wolfram.com/CauchyPrincipalValue.html + """ + if len(self.limits) != 1 or len(list(self.limits[0])) != 3: + raise ValueError("You need to insert a variable, lower_limit, and upper_limit correctly to calculate " + "cauchy's principal value") + x, a, b = self.limits[0] + if not (a.is_comparable and b.is_comparable and a <= b): + raise ValueError("The lower_limit must be smaller than or equal to the upper_limit to calculate " + "cauchy's principal value. Also, a and b need to be comparable.") + if a == b: + return S.Zero + + from sympy.calculus.singularities import singularities + + r = Dummy('r') + f = self.function + singularities_list = [s for s in singularities(f, x) if s.is_comparable and a <= s <= b] + for i in singularities_list: + if i in (a, b): + raise ValueError( + 'The principal value is not defined in the given interval due to singularity at %d.' % (i)) + F = integrate(f, x, **kwargs) + if F.has(Integral): + return self + if a is -oo and b is oo: + I = limit(F - F.subs(x, -x), x, oo) + else: + I = limit(F, x, b, '-') - limit(F, x, a, '+') + for s in singularities_list: + I += limit(((F.subs(x, s - r)) - F.subs(x, s + r)), r, 0, '+') + return I + + + +def integrate(*args, meijerg=None, conds='piecewise', risch=None, heurisch=None, manual=None, **kwargs): + """integrate(f, var, ...) + + .. deprecated:: 1.6 + + Using ``integrate()`` with :class:`~.Poly` is deprecated. Use + :meth:`.Poly.integrate` instead. See :ref:`deprecated-integrate-poly`. + + Explanation + =========== + + Compute definite or indefinite integral of one or more variables + using Risch-Norman algorithm and table lookup. This procedure is + able to handle elementary algebraic and transcendental functions + and also a huge class of special functions, including Airy, + Bessel, Whittaker and Lambert. + + var can be: + + - a symbol -- indefinite integration + - a tuple (symbol, a) -- indefinite integration with result + given with ``a`` replacing ``symbol`` + - a tuple (symbol, a, b) -- definite integration + + Several variables can be specified, in which case the result is + multiple integration. (If var is omitted and the integrand is + univariate, the indefinite integral in that variable will be performed.) + + Indefinite integrals are returned without terms that are independent + of the integration variables. (see examples) + + Definite improper integrals often entail delicate convergence + conditions. Pass conds='piecewise', 'separate' or 'none' to have + these returned, respectively, as a Piecewise function, as a separate + result (i.e. result will be a tuple), or not at all (default is + 'piecewise'). + + **Strategy** + + SymPy uses various approaches to definite integration. One method is to + find an antiderivative for the integrand, and then use the fundamental + theorem of calculus. Various functions are implemented to integrate + polynomial, rational and trigonometric functions, and integrands + containing DiracDelta terms. + + SymPy also implements the part of the Risch algorithm, which is a decision + procedure for integrating elementary functions, i.e., the algorithm can + either find an elementary antiderivative, or prove that one does not + exist. There is also a (very successful, albeit somewhat slow) general + implementation of the heuristic Risch algorithm. This algorithm will + eventually be phased out as more of the full Risch algorithm is + implemented. See the docstring of Integral._eval_integral() for more + details on computing the antiderivative using algebraic methods. + + The option risch=True can be used to use only the (full) Risch algorithm. + This is useful if you want to know if an elementary function has an + elementary antiderivative. If the indefinite Integral returned by this + function is an instance of NonElementaryIntegral, that means that the + Risch algorithm has proven that integral to be non-elementary. Note that + by default, additional methods (such as the Meijer G method outlined + below) are tried on these integrals, as they may be expressible in terms + of special functions, so if you only care about elementary answers, use + risch=True. Also note that an unevaluated Integral returned by this + function is not necessarily a NonElementaryIntegral, even with risch=True, + as it may just be an indication that the particular part of the Risch + algorithm needed to integrate that function is not yet implemented. + + Another family of strategies comes from re-writing the integrand in + terms of so-called Meijer G-functions. Indefinite integrals of a + single G-function can always be computed, and the definite integral + of a product of two G-functions can be computed from zero to + infinity. Various strategies are implemented to rewrite integrands + as G-functions, and use this information to compute integrals (see + the ``meijerint`` module). + + The option manual=True can be used to use only an algorithm that tries + to mimic integration by hand. This algorithm does not handle as many + integrands as the other algorithms implemented but may return results in + a more familiar form. The ``manualintegrate`` module has functions that + return the steps used (see the module docstring for more information). + + In general, the algebraic methods work best for computing + antiderivatives of (possibly complicated) combinations of elementary + functions. The G-function methods work best for computing definite + integrals from zero to infinity of moderately complicated + combinations of special functions, or indefinite integrals of very + simple combinations of special functions. + + The strategy employed by the integration code is as follows: + + - If computing a definite integral, and both limits are real, + and at least one limit is +- oo, try the G-function method of + definite integration first. + + - Try to find an antiderivative, using all available methods, ordered + by performance (that is try fastest method first, slowest last; in + particular polynomial integration is tried first, Meijer + G-functions second to last, and heuristic Risch last). + + - If still not successful, try G-functions irrespective of the + limits. + + The option meijerg=True, False, None can be used to, respectively: + always use G-function methods and no others, never use G-function + methods, or use all available methods (in order as described above). + It defaults to None. + + Examples + ======== + + >>> from sympy import integrate, log, exp, oo + >>> from sympy.abc import a, x, y + + >>> integrate(x*y, x) + x**2*y/2 + + >>> integrate(log(x), x) + x*log(x) - x + + >>> integrate(log(x), (x, 1, a)) + a*log(a) - a + 1 + + >>> integrate(x) + x**2/2 + + Terms that are independent of x are dropped by indefinite integration: + + >>> from sympy import sqrt + >>> integrate(sqrt(1 + x), (x, 0, x)) + 2*(x + 1)**(3/2)/3 - 2/3 + >>> integrate(sqrt(1 + x), x) + 2*(x + 1)**(3/2)/3 + + >>> integrate(x*y) + Traceback (most recent call last): + ... + ValueError: specify integration variables to integrate x*y + + Note that ``integrate(x)`` syntax is meant only for convenience + in interactive sessions and should be avoided in library code. + + >>> integrate(x**a*exp(-x), (x, 0, oo)) # same as conds='piecewise' + Piecewise((gamma(a + 1), re(a) > -1), + (Integral(x**a*exp(-x), (x, 0, oo)), True)) + + >>> integrate(x**a*exp(-x), (x, 0, oo), conds='none') + gamma(a + 1) + + >>> integrate(x**a*exp(-x), (x, 0, oo), conds='separate') + (gamma(a + 1), re(a) > -1) + + See Also + ======== + + Integral, Integral.doit + + """ + doit_flags = { + 'deep': False, + 'meijerg': meijerg, + 'conds': conds, + 'risch': risch, + 'heurisch': heurisch, + 'manual': manual + } + integral = Integral(*args, **kwargs) + + if isinstance(integral, Integral): + return integral.doit(**doit_flags) + else: + new_args = [a.doit(**doit_flags) if isinstance(a, Integral) else a + for a in integral.args] + return integral.func(*new_args) + + +def line_integrate(field, curve, vars): + """line_integrate(field, Curve, variables) + + Compute the line integral. + + Examples + ======== + + >>> from sympy import Curve, line_integrate, E, ln + >>> from sympy.abc import x, y, t + >>> C = Curve([E**t + 1, E**t - 1], (t, 0, ln(2))) + >>> line_integrate(x + y, C, [x, y]) + 3*sqrt(2) + + See Also + ======== + + sympy.integrals.integrals.integrate, Integral + """ + from sympy.geometry import Curve + F = sympify(field) + if not F: + raise ValueError( + "Expecting function specifying field as first argument.") + if not isinstance(curve, Curve): + raise ValueError("Expecting Curve entity as second argument.") + if not is_sequence(vars): + raise ValueError("Expecting ordered iterable for variables.") + if len(curve.functions) != len(vars): + raise ValueError("Field variable size does not match curve dimension.") + + if curve.parameter in vars: + raise ValueError("Curve parameter clashes with field parameters.") + + # Calculate derivatives for line parameter functions + # F(r) -> F(r(t)) and finally F(r(t)*r'(t)) + Ft = F + dldt = 0 + for i, var in enumerate(vars): + _f = curve.functions[i] + _dn = diff(_f, curve.parameter) + # ...arc length + dldt = dldt + (_dn * _dn) + Ft = Ft.subs(var, _f) + Ft = Ft * sqrt(dldt) + + integral = Integral(Ft, curve.limits).doit(deep=False) + return integral + + +### Property function dispatching ### + +@shape.register(Integral) +def _(expr): + return shape(expr.function) + +# Delayed imports +from .deltafunctions import deltaintegrate +from .meijerint import meijerint_definite, meijerint_indefinite, _debug +from .trigonometry import trigintegrate diff --git a/lib/python3.12/site-packages/sympy/integrals/intpoly.py b/lib/python3.12/site-packages/sympy/integrals/intpoly.py new file mode 100644 index 0000000000000000000000000000000000000000..38fd071183fb2192f4c1443d04c8f0ecfb6cc4ea --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/intpoly.py @@ -0,0 +1,1302 @@ +""" +Module to implement integration of uni/bivariate polynomials over +2D Polytopes and uni/bi/trivariate polynomials over 3D Polytopes. + +Uses evaluation techniques as described in Chin et al. (2015) [1]. + + +References +=========== + +.. [1] Chin, Eric B., Jean B. Lasserre, and N. Sukumar. "Numerical integration +of homogeneous functions on convex and nonconvex polygons and polyhedra." +Computational Mechanics 56.6 (2015): 967-981 + +PDF link : http://dilbert.engr.ucdavis.edu/~suku/quadrature/cls-integration.pdf +""" + +from functools import cmp_to_key + +from sympy.abc import x, y, z +from sympy.core import S, diff, Expr, Symbol +from sympy.core.sympify import _sympify +from sympy.geometry import Segment2D, Polygon, Point, Point2D +from sympy.polys.polytools import LC, gcd_list, degree_list, Poly +from sympy.simplify.simplify import nsimplify + + +def polytope_integrate(poly, expr=None, *, clockwise=False, max_degree=None): + """Integrates polynomials over 2/3-Polytopes. + + Explanation + =========== + + This function accepts the polytope in ``poly`` and the function in ``expr`` + (uni/bi/trivariate polynomials are implemented) and returns + the exact integral of ``expr`` over ``poly``. + + Parameters + ========== + + poly : The input Polygon. + + expr : The input polynomial. + + clockwise : Binary value to sort input points of 2-Polytope clockwise.(Optional) + + max_degree : The maximum degree of any monomial of the input polynomial.(Optional) + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy import Point, Polygon + >>> from sympy.integrals.intpoly import polytope_integrate + >>> polygon = Polygon(Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0)) + >>> polys = [1, x, y, x*y, x**2*y, x*y**2] + >>> expr = x*y + >>> polytope_integrate(polygon, expr) + 1/4 + >>> polytope_integrate(polygon, polys, max_degree=3) + {1: 1, x: 1/2, y: 1/2, x*y: 1/4, x*y**2: 1/6, x**2*y: 1/6} + """ + if clockwise: + if isinstance(poly, Polygon): + poly = Polygon(*point_sort(poly.vertices), evaluate=False) + else: + raise TypeError("clockwise=True works for only 2-Polytope" + "V-representation input") + + if isinstance(poly, Polygon): + # For Vertex Representation(2D case) + hp_params = hyperplane_parameters(poly) + facets = poly.sides + elif len(poly[0]) == 2: + # For Hyperplane Representation(2D case) + plen = len(poly) + if len(poly[0][0]) == 2: + intersections = [intersection(poly[(i - 1) % plen], poly[i], + "plane2D") + for i in range(0, plen)] + hp_params = poly + lints = len(intersections) + facets = [Segment2D(intersections[i], + intersections[(i + 1) % lints]) + for i in range(lints)] + else: + raise NotImplementedError("Integration for H-representation 3D" + "case not implemented yet.") + else: + # For Vertex Representation(3D case) + vertices = poly[0] + facets = poly[1:] + hp_params = hyperplane_parameters(facets, vertices) + + if max_degree is None: + if expr is None: + raise TypeError('Input expression must be a valid SymPy expression') + return main_integrate3d(expr, facets, vertices, hp_params) + + if max_degree is not None: + result = {} + if expr is not None: + f_expr = [] + for e in expr: + _ = decompose(e) + if len(_) == 1 and not _.popitem()[0]: + f_expr.append(e) + elif Poly(e).total_degree() <= max_degree: + f_expr.append(e) + expr = f_expr + + if not isinstance(expr, list) and expr is not None: + raise TypeError('Input polynomials must be list of expressions') + + if len(hp_params[0][0]) == 3: + result_dict = main_integrate3d(0, facets, vertices, hp_params, + max_degree) + else: + result_dict = main_integrate(0, facets, hp_params, max_degree) + + if expr is None: + return result_dict + + for poly in expr: + poly = _sympify(poly) + if poly not in result: + if poly.is_zero: + result[S.Zero] = S.Zero + continue + integral_value = S.Zero + monoms = decompose(poly, separate=True) + for monom in monoms: + monom = nsimplify(monom) + coeff, m = strip(monom) + integral_value += result_dict[m] * coeff + result[poly] = integral_value + return result + + if expr is None: + raise TypeError('Input expression must be a valid SymPy expression') + + return main_integrate(expr, facets, hp_params) + + +def strip(monom): + if monom.is_zero: + return S.Zero, S.Zero + elif monom.is_number: + return monom, S.One + else: + coeff = LC(monom) + return coeff, monom / coeff + +def _polynomial_integrate(polynomials, facets, hp_params): + dims = (x, y) + dim_length = len(dims) + integral_value = S.Zero + for deg in polynomials: + poly_contribute = S.Zero + facet_count = 0 + for hp in hp_params: + value_over_boundary = integration_reduction(facets, + facet_count, + hp[0], hp[1], + polynomials[deg], + dims, deg) + poly_contribute += value_over_boundary * (hp[1] / norm(hp[0])) + facet_count += 1 + poly_contribute /= (dim_length + deg) + integral_value += poly_contribute + + return integral_value + + +def main_integrate3d(expr, facets, vertices, hp_params, max_degree=None): + """Function to translate the problem of integrating uni/bi/tri-variate + polynomials over a 3-Polytope to integrating over its faces. + This is done using Generalized Stokes' Theorem and Euler's Theorem. + + Parameters + ========== + + expr : + The input polynomial. + facets : + Faces of the 3-Polytope(expressed as indices of `vertices`). + vertices : + Vertices that constitute the Polytope. + hp_params : + Hyperplane Parameters of the facets. + max_degree : optional + Max degree of constituent monomial in given list of polynomial. + + Examples + ======== + + >>> from sympy.integrals.intpoly import main_integrate3d, \ + hyperplane_parameters + >>> cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\ + (5, 0, 5), (5, 5, 0), (5, 5, 5)],\ + [2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\ + [3, 1, 0, 2], [0, 4, 6, 2]] + >>> vertices = cube[0] + >>> faces = cube[1:] + >>> hp_params = hyperplane_parameters(faces, vertices) + >>> main_integrate3d(1, faces, vertices, hp_params) + -125 + """ + result = {} + dims = (x, y, z) + dim_length = len(dims) + if max_degree: + grad_terms = gradient_terms(max_degree, 3) + flat_list = [term for z_terms in grad_terms + for x_term in z_terms + for term in x_term] + + for term in flat_list: + result[term[0]] = 0 + + for facet_count, hp in enumerate(hp_params): + a, b = hp[0], hp[1] + x0 = vertices[facets[facet_count][0]] + + for i, monom in enumerate(flat_list): + # Every monomial is a tuple : + # (term, x_degree, y_degree, z_degree, value over boundary) + expr, x_d, y_d, z_d, z_index, y_index, x_index, _ = monom + degree = x_d + y_d + z_d + if b.is_zero: + value_over_face = S.Zero + else: + value_over_face = \ + integration_reduction_dynamic(facets, facet_count, a, + b, expr, degree, dims, + x_index, y_index, + z_index, x0, grad_terms, + i, vertices, hp) + monom[7] = value_over_face + result[expr] += value_over_face * \ + (b / norm(a)) / (dim_length + x_d + y_d + z_d) + return result + else: + integral_value = S.Zero + polynomials = decompose(expr) + for deg in polynomials: + poly_contribute = S.Zero + facet_count = 0 + for i, facet in enumerate(facets): + hp = hp_params[i] + if hp[1].is_zero: + continue + pi = polygon_integrate(facet, hp, i, facets, vertices, expr, deg) + poly_contribute += pi *\ + (hp[1] / norm(tuple(hp[0]))) + facet_count += 1 + poly_contribute /= (dim_length + deg) + integral_value += poly_contribute + return integral_value + + +def main_integrate(expr, facets, hp_params, max_degree=None): + """Function to translate the problem of integrating univariate/bivariate + polynomials over a 2-Polytope to integrating over its boundary facets. + This is done using Generalized Stokes's Theorem and Euler's Theorem. + + Parameters + ========== + + expr : + The input polynomial. + facets : + Facets(Line Segments) of the 2-Polytope. + hp_params : + Hyperplane Parameters of the facets. + max_degree : optional + The maximum degree of any monomial of the input polynomial. + + >>> from sympy.abc import x, y + >>> from sympy.integrals.intpoly import main_integrate,\ + hyperplane_parameters + >>> from sympy import Point, Polygon + >>> triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1)) + >>> facets = triangle.sides + >>> hp_params = hyperplane_parameters(triangle) + >>> main_integrate(x**2 + y**2, facets, hp_params) + 325/6 + """ + dims = (x, y) + dim_length = len(dims) + result = {} + + if max_degree: + grad_terms = [[0, 0, 0, 0]] + gradient_terms(max_degree) + + for facet_count, hp in enumerate(hp_params): + a, b = hp[0], hp[1] + x0 = facets[facet_count].points[0] + + for i, monom in enumerate(grad_terms): + # Every monomial is a tuple : + # (term, x_degree, y_degree, value over boundary) + m, x_d, y_d, _ = monom + value = result.get(m, None) + degree = S.Zero + if b.is_zero: + value_over_boundary = S.Zero + else: + degree = x_d + y_d + value_over_boundary = \ + integration_reduction_dynamic(facets, facet_count, a, + b, m, degree, dims, x_d, + y_d, max_degree, x0, + grad_terms, i) + monom[3] = value_over_boundary + if value is not None: + result[m] += value_over_boundary * \ + (b / norm(a)) / (dim_length + degree) + else: + result[m] = value_over_boundary * \ + (b / norm(a)) / (dim_length + degree) + return result + else: + if not isinstance(expr, list): + polynomials = decompose(expr) + return _polynomial_integrate(polynomials, facets, hp_params) + else: + return {e: _polynomial_integrate(decompose(e), facets, hp_params) for e in expr} + + +def polygon_integrate(facet, hp_param, index, facets, vertices, expr, degree): + """Helper function to integrate the input uni/bi/trivariate polynomial + over a certain face of the 3-Polytope. + + Parameters + ========== + + facet : + Particular face of the 3-Polytope over which ``expr`` is integrated. + index : + The index of ``facet`` in ``facets``. + facets : + Faces of the 3-Polytope(expressed as indices of `vertices`). + vertices : + Vertices that constitute the facet. + expr : + The input polynomial. + degree : + Degree of ``expr``. + + Examples + ======== + + >>> from sympy.integrals.intpoly import polygon_integrate + >>> cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\ + (5, 0, 5), (5, 5, 0), (5, 5, 5)],\ + [2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\ + [3, 1, 0, 2], [0, 4, 6, 2]] + >>> facet = cube[1] + >>> facets = cube[1:] + >>> vertices = cube[0] + >>> polygon_integrate(facet, [(0, 1, 0), 5], 0, facets, vertices, 1, 0) + -25 + """ + expr = S(expr) + if expr.is_zero: + return S.Zero + result = S.Zero + x0 = vertices[facet[0]] + facet_len = len(facet) + for i, fac in enumerate(facet): + side = (vertices[fac], vertices[facet[(i + 1) % facet_len]]) + result += distance_to_side(x0, side, hp_param[0]) *\ + lineseg_integrate(facet, i, side, expr, degree) + if not expr.is_number: + expr = diff(expr, x) * x0[0] + diff(expr, y) * x0[1] +\ + diff(expr, z) * x0[2] + result += polygon_integrate(facet, hp_param, index, facets, vertices, + expr, degree - 1) + result /= (degree + 2) + return result + + +def distance_to_side(point, line_seg, A): + """Helper function to compute the signed distance between given 3D point + and a line segment. + + Parameters + ========== + + point : 3D Point + line_seg : Line Segment + + Examples + ======== + + >>> from sympy.integrals.intpoly import distance_to_side + >>> point = (0, 0, 0) + >>> distance_to_side(point, [(0, 0, 1), (0, 1, 0)], (1, 0, 0)) + -sqrt(2)/2 + """ + x1, x2 = line_seg + rev_normal = [-1 * S(i)/norm(A) for i in A] + vector = [x2[i] - x1[i] for i in range(0, 3)] + vector = [vector[i]/norm(vector) for i in range(0, 3)] + + n_side = cross_product((0, 0, 0), rev_normal, vector) + vectorx0 = [line_seg[0][i] - point[i] for i in range(0, 3)] + dot_product = sum(vectorx0[i] * n_side[i] for i in range(0, 3)) + + return dot_product + + +def lineseg_integrate(polygon, index, line_seg, expr, degree): + """Helper function to compute the line integral of ``expr`` over ``line_seg``. + + Parameters + =========== + + polygon : + Face of a 3-Polytope. + index : + Index of line_seg in polygon. + line_seg : + Line Segment. + + Examples + ======== + + >>> from sympy.integrals.intpoly import lineseg_integrate + >>> polygon = [(0, 5, 0), (5, 5, 0), (5, 5, 5), (0, 5, 5)] + >>> line_seg = [(0, 5, 0), (5, 5, 0)] + >>> lineseg_integrate(polygon, 0, line_seg, 1, 0) + 5 + """ + expr = _sympify(expr) + if expr.is_zero: + return S.Zero + result = S.Zero + x0 = line_seg[0] + distance = norm(tuple([line_seg[1][i] - line_seg[0][i] for i in + range(3)])) + if isinstance(expr, Expr): + expr_dict = {x: line_seg[1][0], + y: line_seg[1][1], + z: line_seg[1][2]} + result += distance * expr.subs(expr_dict) + else: + result += distance * expr + + expr = diff(expr, x) * x0[0] + diff(expr, y) * x0[1] +\ + diff(expr, z) * x0[2] + + result += lineseg_integrate(polygon, index, line_seg, expr, degree - 1) + result /= (degree + 1) + return result + + +def integration_reduction(facets, index, a, b, expr, dims, degree): + """Helper method for main_integrate. Returns the value of the input + expression evaluated over the polytope facet referenced by a given index. + + Parameters + =========== + + facets : + List of facets of the polytope. + index : + Index referencing the facet to integrate the expression over. + a : + Hyperplane parameter denoting direction. + b : + Hyperplane parameter denoting distance. + expr : + The expression to integrate over the facet. + dims : + List of symbols denoting axes. + degree : + Degree of the homogeneous polynomial. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy.integrals.intpoly import integration_reduction,\ + hyperplane_parameters + >>> from sympy import Point, Polygon + >>> triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1)) + >>> facets = triangle.sides + >>> a, b = hyperplane_parameters(triangle)[0] + >>> integration_reduction(facets, 0, a, b, 1, (x, y), 0) + 5 + """ + expr = _sympify(expr) + if expr.is_zero: + return expr + + value = S.Zero + x0 = facets[index].points[0] + m = len(facets) + gens = (x, y) + + inner_product = diff(expr, gens[0]) * x0[0] + diff(expr, gens[1]) * x0[1] + + if inner_product != 0: + value += integration_reduction(facets, index, a, b, + inner_product, dims, degree - 1) + + value += left_integral2D(m, index, facets, x0, expr, gens) + + return value/(len(dims) + degree - 1) + + +def left_integral2D(m, index, facets, x0, expr, gens): + """Computes the left integral of Eq 10 in Chin et al. + For the 2D case, the integral is just an evaluation of the polynomial + at the intersection of two facets which is multiplied by the distance + between the first point of facet and that intersection. + + Parameters + ========== + + m : + No. of hyperplanes. + index : + Index of facet to find intersections with. + facets : + List of facets(Line Segments in 2D case). + x0 : + First point on facet referenced by index. + expr : + Input polynomial + gens : + Generators which generate the polynomial + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy.integrals.intpoly import left_integral2D + >>> from sympy import Point, Polygon + >>> triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1)) + >>> facets = triangle.sides + >>> left_integral2D(3, 0, facets, facets[0].points[0], 1, (x, y)) + 5 + """ + value = S.Zero + for j in range(m): + intersect = () + if j in ((index - 1) % m, (index + 1) % m): + intersect = intersection(facets[index], facets[j], "segment2D") + if intersect: + distance_origin = norm(tuple(map(lambda x, y: x - y, + intersect, x0))) + if is_vertex(intersect): + if isinstance(expr, Expr): + if len(gens) == 3: + expr_dict = {gens[0]: intersect[0], + gens[1]: intersect[1], + gens[2]: intersect[2]} + else: + expr_dict = {gens[0]: intersect[0], + gens[1]: intersect[1]} + value += distance_origin * expr.subs(expr_dict) + else: + value += distance_origin * expr + return value + + +def integration_reduction_dynamic(facets, index, a, b, expr, degree, dims, + x_index, y_index, max_index, x0, + monomial_values, monom_index, vertices=None, + hp_param=None): + """The same integration_reduction function which uses a dynamic + programming approach to compute terms by using the values of the integral + of previously computed terms. + + Parameters + ========== + + facets : + Facets of the Polytope. + index : + Index of facet to find intersections with.(Used in left_integral()). + a, b : + Hyperplane parameters. + expr : + Input monomial. + degree : + Total degree of ``expr``. + dims : + Tuple denoting axes variables. + x_index : + Exponent of 'x' in ``expr``. + y_index : + Exponent of 'y' in ``expr``. + max_index : + Maximum exponent of any monomial in ``monomial_values``. + x0 : + First point on ``facets[index]``. + monomial_values : + List of monomial values constituting the polynomial. + monom_index : + Index of monomial whose integration is being found. + vertices : optional + Coordinates of vertices constituting the 3-Polytope. + hp_param : optional + Hyperplane Parameter of the face of the facets[index]. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy.integrals.intpoly import (integration_reduction_dynamic, \ + hyperplane_parameters) + >>> from sympy import Point, Polygon + >>> triangle = Polygon(Point(0, 3), Point(5, 3), Point(1, 1)) + >>> facets = triangle.sides + >>> a, b = hyperplane_parameters(triangle)[0] + >>> x0 = facets[0].points[0] + >>> monomial_values = [[0, 0, 0, 0], [1, 0, 0, 5],\ + [y, 0, 1, 15], [x, 1, 0, None]] + >>> integration_reduction_dynamic(facets, 0, a, b, x, 1, (x, y), 1, 0, 1,\ + x0, monomial_values, 3) + 25/2 + """ + value = S.Zero + m = len(facets) + + if expr == S.Zero: + return expr + + if len(dims) == 2: + if not expr.is_number: + _, x_degree, y_degree, _ = monomial_values[monom_index] + x_index = monom_index - max_index + \ + x_index - 2 if x_degree > 0 else 0 + y_index = monom_index - 1 if y_degree > 0 else 0 + x_value, y_value =\ + monomial_values[x_index][3], monomial_values[y_index][3] + + value += x_degree * x_value * x0[0] + y_degree * y_value * x0[1] + + value += left_integral2D(m, index, facets, x0, expr, dims) + else: + # For 3D use case the max_index contains the z_degree of the term + z_index = max_index + if not expr.is_number: + x_degree, y_degree, z_degree = y_index,\ + z_index - x_index - y_index, x_index + x_value = monomial_values[z_index - 1][y_index - 1][x_index][7]\ + if x_degree > 0 else 0 + y_value = monomial_values[z_index - 1][y_index][x_index][7]\ + if y_degree > 0 else 0 + z_value = monomial_values[z_index - 1][y_index][x_index - 1][7]\ + if z_degree > 0 else 0 + + value += x_degree * x_value * x0[0] + y_degree * y_value * x0[1] \ + + z_degree * z_value * x0[2] + + value += left_integral3D(facets, index, expr, + vertices, hp_param, degree) + return value / (len(dims) + degree - 1) + + +def left_integral3D(facets, index, expr, vertices, hp_param, degree): + """Computes the left integral of Eq 10 in Chin et al. + + Explanation + =========== + + For the 3D case, this is the sum of the integral values over constituting + line segments of the face (which is accessed by facets[index]) multiplied + by the distance between the first point of facet and that line segment. + + Parameters + ========== + + facets : + List of faces of the 3-Polytope. + index : + Index of face over which integral is to be calculated. + expr : + Input polynomial. + vertices : + List of vertices that constitute the 3-Polytope. + hp_param : + The hyperplane parameters of the face. + degree : + Degree of the ``expr``. + + Examples + ======== + + >>> from sympy.integrals.intpoly import left_integral3D + >>> cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\ + (5, 0, 5), (5, 5, 0), (5, 5, 5)],\ + [2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\ + [3, 1, 0, 2], [0, 4, 6, 2]] + >>> facets = cube[1:] + >>> vertices = cube[0] + >>> left_integral3D(facets, 3, 1, vertices, ([0, -1, 0], -5), 0) + -50 + """ + value = S.Zero + facet = facets[index] + x0 = vertices[facet[0]] + facet_len = len(facet) + for i, fac in enumerate(facet): + side = (vertices[fac], vertices[facet[(i + 1) % facet_len]]) + value += distance_to_side(x0, side, hp_param[0]) * \ + lineseg_integrate(facet, i, side, expr, degree) + return value + + +def gradient_terms(binomial_power=0, no_of_gens=2): + """Returns a list of all the possible monomials between + 0 and y**binomial_power for 2D case and z**binomial_power + for 3D case. + + Parameters + ========== + + binomial_power : + Power upto which terms are generated. + no_of_gens : + Denotes whether terms are being generated for 2D or 3D case. + + Examples + ======== + + >>> from sympy.integrals.intpoly import gradient_terms + >>> gradient_terms(2) + [[1, 0, 0, 0], [y, 0, 1, 0], [y**2, 0, 2, 0], [x, 1, 0, 0], + [x*y, 1, 1, 0], [x**2, 2, 0, 0]] + >>> gradient_terms(2, 3) + [[[[1, 0, 0, 0, 0, 0, 0, 0]]], [[[y, 0, 1, 0, 1, 0, 0, 0], + [z, 0, 0, 1, 1, 0, 1, 0]], [[x, 1, 0, 0, 1, 1, 0, 0]]], + [[[y**2, 0, 2, 0, 2, 0, 0, 0], [y*z, 0, 1, 1, 2, 0, 1, 0], + [z**2, 0, 0, 2, 2, 0, 2, 0]], [[x*y, 1, 1, 0, 2, 1, 0, 0], + [x*z, 1, 0, 1, 2, 1, 1, 0]], [[x**2, 2, 0, 0, 2, 2, 0, 0]]]] + """ + if no_of_gens == 2: + count = 0 + terms = [None] * int((binomial_power ** 2 + 3 * binomial_power + 2) / 2) + for x_count in range(0, binomial_power + 1): + for y_count in range(0, binomial_power - x_count + 1): + terms[count] = [x**x_count*y**y_count, + x_count, y_count, 0] + count += 1 + else: + terms = [[[[x ** x_count * y ** y_count * + z ** (z_count - y_count - x_count), + x_count, y_count, z_count - y_count - x_count, + z_count, x_count, z_count - y_count - x_count, 0] + for y_count in range(z_count - x_count, -1, -1)] + for x_count in range(0, z_count + 1)] + for z_count in range(0, binomial_power + 1)] + return terms + + +def hyperplane_parameters(poly, vertices=None): + """A helper function to return the hyperplane parameters + of which the facets of the polytope are a part of. + + Parameters + ========== + + poly : + The input 2/3-Polytope. + vertices : + Vertex indices of 3-Polytope. + + Examples + ======== + + >>> from sympy import Point, Polygon + >>> from sympy.integrals.intpoly import hyperplane_parameters + >>> hyperplane_parameters(Polygon(Point(0, 3), Point(5, 3), Point(1, 1))) + [((0, 1), 3), ((1, -2), -1), ((-2, -1), -3)] + >>> cube = [[(0, 0, 0), (0, 0, 5), (0, 5, 0), (0, 5, 5), (5, 0, 0),\ + (5, 0, 5), (5, 5, 0), (5, 5, 5)],\ + [2, 6, 7, 3], [3, 7, 5, 1], [7, 6, 4, 5], [1, 5, 4, 0],\ + [3, 1, 0, 2], [0, 4, 6, 2]] + >>> hyperplane_parameters(cube[1:], cube[0]) + [([0, -1, 0], -5), ([0, 0, -1], -5), ([-1, 0, 0], -5), + ([0, 1, 0], 0), ([1, 0, 0], 0), ([0, 0, 1], 0)] + """ + if isinstance(poly, Polygon): + vertices = list(poly.vertices) + [poly.vertices[0]] # Close the polygon + params = [None] * (len(vertices) - 1) + + for i in range(len(vertices) - 1): + v1 = vertices[i] + v2 = vertices[i + 1] + + a1 = v1[1] - v2[1] + a2 = v2[0] - v1[0] + b = v2[0] * v1[1] - v2[1] * v1[0] + + factor = gcd_list([a1, a2, b]) + + b = S(b) / factor + a = (S(a1) / factor, S(a2) / factor) + params[i] = (a, b) + else: + params = [None] * len(poly) + for i, polygon in enumerate(poly): + v1, v2, v3 = [vertices[vertex] for vertex in polygon[:3]] + normal = cross_product(v1, v2, v3) + b = sum(normal[j] * v1[j] for j in range(0, 3)) + fac = gcd_list(normal) + if fac.is_zero: + fac = 1 + normal = [j / fac for j in normal] + b = b / fac + params[i] = (normal, b) + return params + + +def cross_product(v1, v2, v3): + """Returns the cross-product of vectors (v2 - v1) and (v3 - v1) + That is : (v2 - v1) X (v3 - v1) + """ + v2 = [v2[j] - v1[j] for j in range(0, 3)] + v3 = [v3[j] - v1[j] for j in range(0, 3)] + return [v3[2] * v2[1] - v3[1] * v2[2], + v3[0] * v2[2] - v3[2] * v2[0], + v3[1] * v2[0] - v3[0] * v2[1]] + + +def best_origin(a, b, lineseg, expr): + """Helper method for polytope_integrate. Currently not used in the main + algorithm. + + Explanation + =========== + + Returns a point on the lineseg whose vector inner product with the + divergence of `expr` yields an expression with the least maximum + total power. + + Parameters + ========== + + a : + Hyperplane parameter denoting direction. + b : + Hyperplane parameter denoting distance. + lineseg : + Line segment on which to find the origin. + expr : + The expression which determines the best point. + + Algorithm(currently works only for 2D use case) + =============================================== + + 1 > Firstly, check for edge cases. Here that would refer to vertical + or horizontal lines. + + 2 > If input expression is a polynomial containing more than one generator + then find out the total power of each of the generators. + + x**2 + 3 + x*y + x**4*y**5 ---> {x: 7, y: 6} + + If expression is a constant value then pick the first boundary point + of the line segment. + + 3 > First check if a point exists on the line segment where the value of + the highest power generator becomes 0. If not check if the value of + the next highest becomes 0. If none becomes 0 within line segment + constraints then pick the first boundary point of the line segment. + Actually, any point lying on the segment can be picked as best origin + in the last case. + + Examples + ======== + + >>> from sympy.integrals.intpoly import best_origin + >>> from sympy.abc import x, y + >>> from sympy import Point, Segment2D + >>> l = Segment2D(Point(0, 3), Point(1, 1)) + >>> expr = x**3*y**7 + >>> best_origin((2, 1), 3, l, expr) + (0, 3.0) + """ + a1, b1 = lineseg.points[0] + + def x_axis_cut(ls): + """Returns the point where the input line segment + intersects the x-axis. + + Parameters + ========== + + ls : + Line segment + """ + p, q = ls.points + if p.y.is_zero: + return tuple(p) + elif q.y.is_zero: + return tuple(q) + elif p.y/q.y < S.Zero: + return p.y * (p.x - q.x)/(q.y - p.y) + p.x, S.Zero + else: + return () + + def y_axis_cut(ls): + """Returns the point where the input line segment + intersects the y-axis. + + Parameters + ========== + + ls : + Line segment + """ + p, q = ls.points + if p.x.is_zero: + return tuple(p) + elif q.x.is_zero: + return tuple(q) + elif p.x/q.x < S.Zero: + return S.Zero, p.x * (p.y - q.y)/(q.x - p.x) + p.y + else: + return () + + gens = (x, y) + power_gens = {} + + for i in gens: + power_gens[i] = S.Zero + + if len(gens) > 1: + # Special case for vertical and horizontal lines + if len(gens) == 2: + if a[0] == 0: + if y_axis_cut(lineseg): + return S.Zero, b/a[1] + else: + return a1, b1 + elif a[1] == 0: + if x_axis_cut(lineseg): + return b/a[0], S.Zero + else: + return a1, b1 + + if isinstance(expr, Expr): # Find the sum total of power of each + if expr.is_Add: # generator and store in a dictionary. + for monomial in expr.args: + if monomial.is_Pow: + if monomial.args[0] in gens: + power_gens[monomial.args[0]] += monomial.args[1] + else: + for univariate in monomial.args: + term_type = len(univariate.args) + if term_type == 0 and univariate in gens: + power_gens[univariate] += 1 + elif term_type == 2 and univariate.args[0] in gens: + power_gens[univariate.args[0]] +=\ + univariate.args[1] + elif expr.is_Mul: + for term in expr.args: + term_type = len(term.args) + if term_type == 0 and term in gens: + power_gens[term] += 1 + elif term_type == 2 and term.args[0] in gens: + power_gens[term.args[0]] += term.args[1] + elif expr.is_Pow: + power_gens[expr.args[0]] = expr.args[1] + elif expr.is_Symbol: + power_gens[expr] += 1 + else: # If `expr` is a constant take first vertex of the line segment. + return a1, b1 + + # TODO : This part is quite hacky. Should be made more robust with + # TODO : respect to symbol names and scalable w.r.t higher dimensions. + power_gens = sorted(power_gens.items(), key=lambda k: str(k[0])) + if power_gens[0][1] >= power_gens[1][1]: + if y_axis_cut(lineseg): + x0 = (S.Zero, b / a[1]) + elif x_axis_cut(lineseg): + x0 = (b / a[0], S.Zero) + else: + x0 = (a1, b1) + else: + if x_axis_cut(lineseg): + x0 = (b/a[0], S.Zero) + elif y_axis_cut(lineseg): + x0 = (S.Zero, b/a[1]) + else: + x0 = (a1, b1) + else: + x0 = (b/a[0]) + return x0 + + +def decompose(expr, separate=False): + """Decomposes an input polynomial into homogeneous ones of + smaller or equal degree. + + Explanation + =========== + + Returns a dictionary with keys as the degree of the smaller + constituting polynomials. Values are the constituting polynomials. + + Parameters + ========== + + expr : Expr + Polynomial(SymPy expression). + separate : bool + If True then simply return a list of the constituent monomials + If not then break up the polynomial into constituent homogeneous + polynomials. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy.integrals.intpoly import decompose + >>> decompose(x**2 + x*y + x + y + x**3*y**2 + y**5) + {1: x + y, 2: x**2 + x*y, 5: x**3*y**2 + y**5} + >>> decompose(x**2 + x*y + x + y + x**3*y**2 + y**5, True) + {x, x**2, y, y**5, x*y, x**3*y**2} + """ + poly_dict = {} + + if isinstance(expr, Expr) and not expr.is_number: + if expr.is_Symbol: + poly_dict[1] = expr + elif expr.is_Add: + symbols = expr.atoms(Symbol) + degrees = [(sum(degree_list(monom, *symbols)), monom) + for monom in expr.args] + if separate: + return {monom[1] for monom in degrees} + else: + for monom in degrees: + degree, term = monom + if poly_dict.get(degree): + poly_dict[degree] += term + else: + poly_dict[degree] = term + elif expr.is_Pow: + _, degree = expr.args + poly_dict[degree] = expr + else: # Now expr can only be of `Mul` type + degree = 0 + for term in expr.args: + term_type = len(term.args) + if term_type == 0 and term.is_Symbol: + degree += 1 + elif term_type == 2: + degree += term.args[1] + poly_dict[degree] = expr + else: + poly_dict[0] = expr + + if separate: + return set(poly_dict.values()) + return poly_dict + + +def point_sort(poly, normal=None, clockwise=True): + """Returns the same polygon with points sorted in clockwise or + anti-clockwise order. + + Note that it's necessary for input points to be sorted in some order + (clockwise or anti-clockwise) for the integration algorithm to work. + As a convention algorithm has been implemented keeping clockwise + orientation in mind. + + Parameters + ========== + + poly: + 2D or 3D Polygon. + normal : optional + The normal of the plane which the 3-Polytope is a part of. + clockwise : bool, optional + Returns points sorted in clockwise order if True and + anti-clockwise if False. + + Examples + ======== + + >>> from sympy.integrals.intpoly import point_sort + >>> from sympy import Point + >>> point_sort([Point(0, 0), Point(1, 0), Point(1, 1)]) + [Point2D(1, 1), Point2D(1, 0), Point2D(0, 0)] + """ + pts = poly.vertices if isinstance(poly, Polygon) else poly + n = len(pts) + if n < 2: + return list(pts) + + order = S.One if clockwise else S.NegativeOne + dim = len(pts[0]) + if dim == 2: + center = Point(sum((vertex.x for vertex in pts)) / n, + sum((vertex.y for vertex in pts)) / n) + else: + center = Point(sum((vertex.x for vertex in pts)) / n, + sum((vertex.y for vertex in pts)) / n, + sum((vertex.z for vertex in pts)) / n) + + def compare(a, b): + if a.x - center.x >= S.Zero and b.x - center.x < S.Zero: + return -order + elif a.x - center.x < 0 and b.x - center.x >= 0: + return order + elif a.x - center.x == 0 and b.x - center.x == 0: + if a.y - center.y >= 0 or b.y - center.y >= 0: + return -order if a.y > b.y else order + return -order if b.y > a.y else order + + det = (a.x - center.x) * (b.y - center.y) -\ + (b.x - center.x) * (a.y - center.y) + if det < 0: + return -order + elif det > 0: + return order + + first = (a.x - center.x) * (a.x - center.x) +\ + (a.y - center.y) * (a.y - center.y) + second = (b.x - center.x) * (b.x - center.x) +\ + (b.y - center.y) * (b.y - center.y) + return -order if first > second else order + + def compare3d(a, b): + det = cross_product(center, a, b) + dot_product = sum(det[i] * normal[i] for i in range(0, 3)) + if dot_product < 0: + return -order + elif dot_product > 0: + return order + + return sorted(pts, key=cmp_to_key(compare if dim==2 else compare3d)) + + +def norm(point): + """Returns the Euclidean norm of a point from origin. + + Parameters + ========== + + point: + This denotes a point in the dimension_al spac_e. + + Examples + ======== + + >>> from sympy.integrals.intpoly import norm + >>> from sympy import Point + >>> norm(Point(2, 7)) + sqrt(53) + """ + half = S.Half + if isinstance(point, (list, tuple)): + return sum(coord ** 2 for coord in point) ** half + elif isinstance(point, Point): + if isinstance(point, Point2D): + return (point.x ** 2 + point.y ** 2) ** half + else: + return (point.x ** 2 + point.y ** 2 + point.z) ** half + elif isinstance(point, dict): + return sum(i**2 for i in point.values()) ** half + + +def intersection(geom_1, geom_2, intersection_type): + """Returns intersection between geometric objects. + + Explanation + =========== + + Note that this function is meant for use in integration_reduction and + at that point in the calling function the lines denoted by the segments + surely intersect within segment boundaries. Coincident lines are taken + to be non-intersecting. Also, the hyperplane intersection for 2D case is + also implemented. + + Parameters + ========== + + geom_1, geom_2: + The input line segments. + + Examples + ======== + + >>> from sympy.integrals.intpoly import intersection + >>> from sympy import Point, Segment2D + >>> l1 = Segment2D(Point(1, 1), Point(3, 5)) + >>> l2 = Segment2D(Point(2, 0), Point(2, 5)) + >>> intersection(l1, l2, "segment2D") + (2, 3) + >>> p1 = ((-1, 0), 0) + >>> p2 = ((0, 1), 1) + >>> intersection(p1, p2, "plane2D") + (0, 1) + """ + if intersection_type[:-2] == "segment": + if intersection_type == "segment2D": + x1, y1 = geom_1.points[0] + x2, y2 = geom_1.points[1] + x3, y3 = geom_2.points[0] + x4, y4 = geom_2.points[1] + elif intersection_type == "segment3D": + x1, y1, z1 = geom_1.points[0] + x2, y2, z2 = geom_1.points[1] + x3, y3, z3 = geom_2.points[0] + x4, y4, z4 = geom_2.points[1] + + denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) + if denom: + t1 = x1 * y2 - y1 * x2 + t2 = x3 * y4 - x4 * y3 + return (S(t1 * (x3 - x4) - t2 * (x1 - x2)) / denom, + S(t1 * (y3 - y4) - t2 * (y1 - y2)) / denom) + if intersection_type[:-2] == "plane": + if intersection_type == "plane2D": # Intersection of hyperplanes + a1x, a1y = geom_1[0] + a2x, a2y = geom_2[0] + b1, b2 = geom_1[1], geom_2[1] + + denom = a1x * a2y - a2x * a1y + if denom: + return (S(b1 * a2y - b2 * a1y) / denom, + S(b2 * a1x - b1 * a2x) / denom) + + +def is_vertex(ent): + """If the input entity is a vertex return True. + + Parameter + ========= + + ent : + Denotes a geometric entity representing a point. + + Examples + ======== + + >>> from sympy import Point + >>> from sympy.integrals.intpoly import is_vertex + >>> is_vertex((2, 3)) + True + >>> is_vertex((2, 3, 6)) + True + >>> is_vertex(Point(2, 3)) + True + """ + if isinstance(ent, tuple): + if len(ent) in [2, 3]: + return True + elif isinstance(ent, Point): + return True + return False + + +def plot_polytope(poly): + """Plots the 2D polytope using the functions written in plotting + module which in turn uses matplotlib backend. + + Parameter + ========= + + poly: + Denotes a 2-Polytope. + """ + from sympy.plotting.plot import Plot, List2DSeries + + xl = [vertex.x for vertex in poly.vertices] + yl = [vertex.y for vertex in poly.vertices] + + xl.append(poly.vertices[0].x) # Closing the polygon + yl.append(poly.vertices[0].y) + + l2ds = List2DSeries(xl, yl) + p = Plot(l2ds, axes='label_axes=True') + p.show() + + +def plot_polynomial(expr): + """Plots the polynomial using the functions written in + plotting module which in turn uses matplotlib backend. + + Parameter + ========= + + expr: + Denotes a polynomial(SymPy expression). + """ + from sympy.plotting.plot import plot3d, plot + gens = expr.free_symbols + if len(gens) == 2: + plot3d(expr) + else: + plot(expr) diff --git a/lib/python3.12/site-packages/sympy/integrals/laplace.py b/lib/python3.12/site-packages/sympy/integrals/laplace.py new file mode 100644 index 0000000000000000000000000000000000000000..3c3ddfce3367e98a0b5c121dd8a7b1faf662735f --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/laplace.py @@ -0,0 +1,2373 @@ +"""Laplace Transforms""" +import sys +import sympy +from sympy.core import S, pi, I +from sympy.core.add import Add +from sympy.core.cache import cacheit +from sympy.core.expr import Expr +from sympy.core.function import ( + AppliedUndef, Derivative, expand, expand_complex, expand_mul, expand_trig, + Lambda, WildFunction, diff, Subs) +from sympy.core.mul import Mul, prod +from sympy.core.relational import ( + _canonical, Ge, Gt, Lt, Unequality, Eq, Ne, Relational) +from sympy.core.sorting import ordered +from sympy.core.symbol import Dummy, symbols, Wild +from sympy.functions.elementary.complexes import ( + re, im, arg, Abs, polar_lift, periodic_argument) +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.elementary.hyperbolic import cosh, coth, sinh, asinh +from sympy.functions.elementary.miscellaneous import Max, Min, sqrt +from sympy.functions.elementary.piecewise import ( + Piecewise, piecewise_exclusive) +from sympy.functions.elementary.trigonometric import cos, sin, atan, sinc +from sympy.functions.special.bessel import besseli, besselj, besselk, bessely +from sympy.functions.special.delta_functions import DiracDelta, Heaviside +from sympy.functions.special.error_functions import erf, erfc, Ei +from sympy.functions.special.gamma_functions import ( + digamma, gamma, lowergamma, uppergamma) +from sympy.functions.special.singularity_functions import SingularityFunction +from sympy.integrals import integrate, Integral +from sympy.integrals.transforms import ( + _simplify, IntegralTransform, IntegralTransformError) +from sympy.logic.boolalg import to_cnf, conjuncts, disjuncts, Or, And +from sympy.matrices.matrixbase import MatrixBase +from sympy.polys.matrices.linsolve import _lin_eq2dict +from sympy.polys.polyerrors import PolynomialError +from sympy.polys.polyroots import roots +from sympy.polys.polytools import Poly +from sympy.polys.rationaltools import together +from sympy.polys.rootoftools import RootSum +from sympy.utilities.exceptions import ( + sympy_deprecation_warning, SymPyDeprecationWarning, ignore_warnings) +from sympy.utilities.misc import debugf + +_LT_level = 0 + + +def DEBUG_WRAP(func): + def wrap(*args, **kwargs): + from sympy import SYMPY_DEBUG + global _LT_level + + if not SYMPY_DEBUG: + return func(*args, **kwargs) + + if _LT_level == 0: + print('\n' + '-'*78, file=sys.stderr) + print('-LT- %s%s%s' % (' '*_LT_level, func.__name__, args), + file=sys.stderr) + _LT_level += 1 + if ( + func.__name__ == '_laplace_transform_integration' or + func.__name__ == '_inverse_laplace_transform_integration'): + sympy.SYMPY_DEBUG = False + print('**** %sIntegrating ...' % (' '*_LT_level), file=sys.stderr) + result = func(*args, **kwargs) + sympy.SYMPY_DEBUG = True + else: + result = func(*args, **kwargs) + _LT_level -= 1 + print('-LT- %s---> %s' % (' '*_LT_level, result), file=sys.stderr) + if _LT_level == 0: + print('-'*78 + '\n', file=sys.stderr) + return result + return wrap + + +def _debug(text): + from sympy import SYMPY_DEBUG + global _LT_level + + if SYMPY_DEBUG: + print('-LT- %s%s' % (' '*_LT_level, text), file=sys.stderr) + + +def _simplifyconds(expr, s, a): + r""" + Naively simplify some conditions occurring in ``expr``, + given that `\operatorname{Re}(s) > a`. + + Examples + ======== + + >>> from sympy.integrals.laplace import _simplifyconds + >>> from sympy.abc import x + >>> from sympy import sympify as S + >>> _simplifyconds(abs(x**2) < 1, x, 1) + False + >>> _simplifyconds(abs(x**2) < 1, x, 2) + False + >>> _simplifyconds(abs(x**2) < 1, x, 0) + Abs(x**2) < 1 + >>> _simplifyconds(abs(1/x**2) < 1, x, 1) + True + >>> _simplifyconds(S(1) < abs(x), x, 1) + True + >>> _simplifyconds(S(1) < abs(1/x), x, 1) + False + + >>> from sympy import Ne + >>> _simplifyconds(Ne(1, x**3), x, 1) + True + >>> _simplifyconds(Ne(1, x**3), x, 2) + True + >>> _simplifyconds(Ne(1, x**3), x, 0) + Ne(1, x**3) + """ + + def power(ex): + if ex == s: + return 1 + if ex.is_Pow and ex.base == s: + return ex.exp + return None + + def bigger(ex1, ex2): + """ Return True only if |ex1| > |ex2|, False only if |ex1| < |ex2|. + Else return None. """ + if ex1.has(s) and ex2.has(s): + return None + if isinstance(ex1, Abs): + ex1 = ex1.args[0] + if isinstance(ex2, Abs): + ex2 = ex2.args[0] + if ex1.has(s): + return bigger(1/ex2, 1/ex1) + n = power(ex2) + if n is None: + return None + try: + if n > 0 and (Abs(ex1) <= Abs(a)**n) == S.true: + return False + if n < 0 and (Abs(ex1) >= Abs(a)**n) == S.true: + return True + except TypeError: + return None + + def replie(x, y): + """ simplify x < y """ + if (not (x.is_positive or isinstance(x, Abs)) + or not (y.is_positive or isinstance(y, Abs))): + return (x < y) + r = bigger(x, y) + if r is not None: + return not r + return (x < y) + + def replue(x, y): + b = bigger(x, y) + if b in (True, False): + return True + return Unequality(x, y) + + def repl(ex, *args): + if ex in (True, False): + return bool(ex) + return ex.replace(*args) + + from sympy.simplify.radsimp import collect_abs + expr = collect_abs(expr) + expr = repl(expr, Lt, replie) + expr = repl(expr, Gt, lambda x, y: replie(y, x)) + expr = repl(expr, Unequality, replue) + return S(expr) + + +@DEBUG_WRAP +def expand_dirac_delta(expr): + """ + Expand an expression involving DiractDelta to get it as a linear + combination of DiracDelta functions. + """ + return _lin_eq2dict(expr, expr.atoms(DiracDelta)) + + +@DEBUG_WRAP +def _laplace_transform_integration(f, t, s_, *, simplify): + """ The backend function for doing Laplace transforms by integration. + + This backend assumes that the frontend has already split sums + such that `f` is to an addition anymore. + """ + s = Dummy('s') + + if f.has(DiracDelta): + return None + + F = integrate(f*exp(-s*t), (t, S.Zero, S.Infinity)) + + if not F.has(Integral): + return _simplify(F.subs(s, s_), simplify), S.NegativeInfinity, S.true + + if not F.is_Piecewise: + return None + + F, cond = F.args[0] + if F.has(Integral): + return None + + def process_conds(conds): + """ Turn ``conds`` into a strip and auxiliary conditions. """ + from sympy.solvers.inequalities import _solve_inequality + a = S.NegativeInfinity + aux = S.true + conds = conjuncts(to_cnf(conds)) + p, q, w1, w2, w3, w4, w5 = symbols( + 'p q w1 w2 w3 w4 w5', cls=Wild, exclude=[s]) + patterns = ( + p*Abs(arg((s + w3)*q)) < w2, + p*Abs(arg((s + w3)*q)) <= w2, + Abs(periodic_argument((s + w3)**p*q, w1)) < w2, + Abs(periodic_argument((s + w3)**p*q, w1)) <= w2, + Abs(periodic_argument((polar_lift(s + w3))**p*q, w1)) < w2, + Abs(periodic_argument((polar_lift(s + w3))**p*q, w1)) <= w2) + for c in conds: + a_ = S.Infinity + aux_ = [] + for d in disjuncts(c): + if d.is_Relational and s in d.rhs.free_symbols: + d = d.reversed + if d.is_Relational and isinstance(d, (Ge, Gt)): + d = d.reversedsign + for pat in patterns: + m = d.match(pat) + if m: + break + if m and m[q].is_positive and m[w2]/m[p] == pi/2: + d = -re(s + m[w3]) < 0 + m = d.match(p - cos(w1*Abs(arg(s*w5))*w2)*Abs(s**w3)**w4 < 0) + if not m: + m = d.match( + cos(p - Abs(periodic_argument(s**w1*w5, q))*w2) * + Abs(s**w3)**w4 < 0) + if not m: + m = d.match( + p - cos( + Abs(periodic_argument(polar_lift(s)**w1*w5, q))*w2 + )*Abs(s**w3)**w4 < 0) + if m and all(m[wild].is_positive for wild in [ + w1, w2, w3, w4, w5]): + d = re(s) > m[p] + d_ = d.replace( + re, lambda x: x.expand().as_real_imag()[0]).subs(re(s), t) + if ( + not d.is_Relational or d.rel_op in ('==', '!=') + or d_.has(s) or not d_.has(t)): + aux_ += [d] + continue + soln = _solve_inequality(d_, t) + if not soln.is_Relational or soln.rel_op in ('==', '!='): + aux_ += [d] + continue + if soln.lts == t: + return None + else: + a_ = Min(soln.lts, a_) + if a_ is not S.Infinity: + a = Max(a_, a) + else: + aux = And(aux, Or(*aux_)) + return a, aux.canonical if aux.is_Relational else aux + + conds = [process_conds(c) for c in disjuncts(cond)] + conds2 = [x for x in conds if x[1] != + S.false and x[0] is not S.NegativeInfinity] + if not conds2: + conds2 = [x for x in conds if x[1] != S.false] + conds = list(ordered(conds2)) + + def cnt(expr): + if expr in (True, False): + return 0 + return expr.count_ops() + conds.sort(key=lambda x: (-x[0], cnt(x[1]))) + + if not conds: + return None + a, aux = conds[0] # XXX is [0] always the right one? + + def sbs(expr): + return expr.subs(s, s_) + if simplify: + F = _simplifyconds(F, s, a) + aux = _simplifyconds(aux, s, a) + return _simplify(F.subs(s, s_), simplify), sbs(a), _canonical(sbs(aux)) + + +@DEBUG_WRAP +def _laplace_deep_collect(f, t): + """ + This is an internal helper function that traverses through the epression + tree of `f(t)` and collects arguments. The purpose of it is that + anything like `f(w*t-1*t-c)` will be written as `f((w-1)*t-c)` such that + it can match `f(a*t+b)`. + """ + if not isinstance(f, Expr): + return f + if (p := f.as_poly(t)) is not None: + return p.as_expr() + func = f.func + args = [_laplace_deep_collect(arg, t) for arg in f.args] + return func(*args) + + +@cacheit +def _laplace_build_rules(): + """ + This is an internal helper function that returns the table of Laplace + transform rules in terms of the time variable `t` and the frequency + variable `s`. It is used by ``_laplace_apply_rules``. Each entry is a + tuple containing: + + (time domain pattern, + frequency-domain replacement, + condition for the rule to be applied, + convergence plane, + preparation function) + + The preparation function is a function with one argument that is applied + to the expression before matching. For most rules it should be + ``_laplace_deep_collect``. + """ + t = Dummy('t') + s = Dummy('s') + a = Wild('a', exclude=[t]) + b = Wild('b', exclude=[t]) + n = Wild('n', exclude=[t]) + tau = Wild('tau', exclude=[t]) + omega = Wild('omega', exclude=[t]) + def dco(f): return _laplace_deep_collect(f, t) + _debug('_laplace_build_rules is building rules') + + laplace_transform_rules = [ + (a, a/s, + S.true, S.Zero, dco), # 4.2.1 + (DiracDelta(a*t-b), exp(-s*b/a)/Abs(a), + Or(And(a > 0, b >= 0), And(a < 0, b <= 0)), + S.NegativeInfinity, dco), # Not in Bateman54 + (DiracDelta(a*t-b), S(0), + Or(And(a < 0, b >= 0), And(a > 0, b <= 0)), + S.NegativeInfinity, dco), # Not in Bateman54 + (Heaviside(a*t-b), exp(-s*b/a)/s, + And(a > 0, b > 0), S.Zero, dco), # 4.4.1 + (Heaviside(a*t-b), (1-exp(-s*b/a))/s, + And(a < 0, b < 0), S.Zero, dco), # 4.4.1 + (Heaviside(a*t-b), 1/s, + And(a > 0, b <= 0), S.Zero, dco), # 4.4.1 + (Heaviside(a*t-b), 0, + And(a < 0, b > 0), S.Zero, dco), # 4.4.1 + (t, 1/s**2, + S.true, S.Zero, dco), # 4.2.3 + (1/(a*t+b), -exp(-b/a*s)*Ei(-b/a*s)/a, + Abs(arg(b/a)) < pi, S.Zero, dco), # 4.2.6 + (1/sqrt(a*t+b), sqrt(a*pi/s)*exp(b/a*s)*erfc(sqrt(b/a*s))/a, + Abs(arg(b/a)) < pi, S.Zero, dco), # 4.2.18 + ((a*t+b)**(-S(3)/2), + 2*b**(-S(1)/2)-2*(pi*s/a)**(S(1)/2)*exp(b/a*s) * erfc(sqrt(b/a*s))/a, + Abs(arg(b/a)) < pi, S.Zero, dco), # 4.2.20 + (sqrt(t)/(t+b), sqrt(pi/s)-pi*sqrt(b)*exp(b*s)*erfc(sqrt(b*s)), + Abs(arg(b)) < pi, S.Zero, dco), # 4.2.22 + (1/(a*sqrt(t) + t**(3/2)), pi*a**(S(1)/2)*exp(a*s)*erfc(sqrt(a*s)), + S.true, S.Zero, dco), # Not in Bateman54 + (t**n, gamma(n+1)/s**(n+1), + n > -1, S.Zero, dco), # 4.3.1 + ((a*t+b)**n, uppergamma(n+1, b/a*s)*exp(-b/a*s)/s**(n+1)/a, + And(n > -1, Abs(arg(b/a)) < pi), S.Zero, dco), # 4.3.4 + (t**n/(t+a), a**n*gamma(n+1)*uppergamma(-n, a*s), + And(n > -1, Abs(arg(a)) < pi), S.Zero, dco), # 4.3.7 + (exp(a*t-tau), exp(-tau)/(s-a), + S.true, re(a), dco), # 4.5.1 + (t*exp(a*t-tau), exp(-tau)/(s-a)**2, + S.true, re(a), dco), # 4.5.2 + (t**n*exp(a*t), gamma(n+1)/(s-a)**(n+1), + re(n) > -1, re(a), dco), # 4.5.3 + (exp(-a*t**2), sqrt(pi/4/a)*exp(s**2/4/a)*erfc(s/sqrt(4*a)), + re(a) > 0, S.Zero, dco), # 4.5.21 + (t*exp(-a*t**2), + 1/(2*a)-2/sqrt(pi)/(4*a)**(S(3)/2)*s*erfc(s/sqrt(4*a)), + re(a) > 0, S.Zero, dco), # 4.5.22 + (exp(-a/t), 2*sqrt(a/s)*besselk(1, 2*sqrt(a*s)), + re(a) >= 0, S.Zero, dco), # 4.5.25 + (sqrt(t)*exp(-a/t), + S(1)/2*sqrt(pi/s**3)*(1+2*sqrt(a*s))*exp(-2*sqrt(a*s)), + re(a) >= 0, S.Zero, dco), # 4.5.26 + (exp(-a/t)/sqrt(t), sqrt(pi/s)*exp(-2*sqrt(a*s)), + re(a) >= 0, S.Zero, dco), # 4.5.27 + (exp(-a/t)/(t*sqrt(t)), sqrt(pi/a)*exp(-2*sqrt(a*s)), + re(a) > 0, S.Zero, dco), # 4.5.28 + (t**n*exp(-a/t), 2*(a/s)**((n+1)/2)*besselk(n+1, 2*sqrt(a*s)), + re(a) > 0, S.Zero, dco), # 4.5.29 + (exp(-2*sqrt(a*t)), + s**(-1)-sqrt(pi*a)*s**(-S(3)/2)*exp(a/s) * erfc(sqrt(a/s)), + Abs(arg(a)) < pi, S.Zero, dco), # 4.5.31 + (exp(-2*sqrt(a*t))/sqrt(t), (pi/s)**(S(1)/2)*exp(a/s)*erfc(sqrt(a/s)), + Abs(arg(a)) < pi, S.Zero, dco), # 4.5.33 + (exp(-a*exp(-t)), a**(-s)*lowergamma(s, a), + S.true, S.Zero, dco), # 4.5.36 + (exp(-a*exp(t)), a**s*uppergamma(-s, a), + re(a) > 0, S.Zero, dco), # 4.5.37 + (log(a*t), -log(exp(S.EulerGamma)*s/a)/s, + a > 0, S.Zero, dco), # 4.6.1 + (log(1+a*t), -exp(s/a)/s*Ei(-s/a), + Abs(arg(a)) < pi, S.Zero, dco), # 4.6.4 + (log(a*t+b), (log(b)-exp(s/b/a)/s*a*Ei(-s/b))/s*a, + And(a > 0, Abs(arg(b)) < pi), S.Zero, dco), # 4.6.5 + (log(t)/sqrt(t), -sqrt(pi/s)*log(4*s*exp(S.EulerGamma)), + S.true, S.Zero, dco), # 4.6.9 + (t**n*log(t), gamma(n+1)*s**(-n-1)*(digamma(n+1)-log(s)), + re(n) > -1, S.Zero, dco), # 4.6.11 + (log(a*t)**2, (log(exp(S.EulerGamma)*s/a)**2+pi**2/6)/s, + a > 0, S.Zero, dco), # 4.6.13 + (sin(omega*t), omega/(s**2+omega**2), + S.true, Abs(im(omega)), dco), # 4,7,1 + (Abs(sin(omega*t)), omega/(s**2+omega**2)*coth(pi*s/2/omega), + omega > 0, S.Zero, dco), # 4.7.2 + (sin(omega*t)/t, atan(omega/s), + S.true, Abs(im(omega)), dco), # 4.7.16 + (sin(omega*t)**2/t, log(1+4*omega**2/s**2)/4, + S.true, 2*Abs(im(omega)), dco), # 4.7.17 + (sin(omega*t)**2/t**2, + omega*atan(2*omega/s)-s*log(1+4*omega**2/s**2)/4, + S.true, 2*Abs(im(omega)), dco), # 4.7.20 + (sin(2*sqrt(a*t)), sqrt(pi*a)/s/sqrt(s)*exp(-a/s), + S.true, S.Zero, dco), # 4.7.32 + (sin(2*sqrt(a*t))/t, pi*erf(sqrt(a/s)), + S.true, S.Zero, dco), # 4.7.34 + (cos(omega*t), s/(s**2+omega**2), + S.true, Abs(im(omega)), dco), # 4.7.43 + (cos(omega*t)**2, (s**2+2*omega**2)/(s**2+4*omega**2)/s, + S.true, 2*Abs(im(omega)), dco), # 4.7.45 + (sqrt(t)*cos(2*sqrt(a*t)), sqrt(pi)/2*s**(-S(5)/2)*(s-2*a)*exp(-a/s), + S.true, S.Zero, dco), # 4.7.66 + (cos(2*sqrt(a*t))/sqrt(t), sqrt(pi/s)*exp(-a/s), + S.true, S.Zero, dco), # 4.7.67 + (sin(a*t)*sin(b*t), 2*a*b*s/(s**2+(a+b)**2)/(s**2+(a-b)**2), + S.true, Abs(im(a))+Abs(im(b)), dco), # 4.7.78 + (cos(a*t)*sin(b*t), b*(s**2-a**2+b**2)/(s**2+(a+b)**2)/(s**2+(a-b)**2), + S.true, Abs(im(a))+Abs(im(b)), dco), # 4.7.79 + (cos(a*t)*cos(b*t), s*(s**2+a**2+b**2)/(s**2+(a+b)**2)/(s**2+(a-b)**2), + S.true, Abs(im(a))+Abs(im(b)), dco), # 4.7.80 + (sinh(a*t), a/(s**2-a**2), + S.true, Abs(re(a)), dco), # 4.9.1 + (cosh(a*t), s/(s**2-a**2), + S.true, Abs(re(a)), dco), # 4.9.2 + (sinh(a*t)**2, 2*a**2/(s**3-4*a**2*s), + S.true, 2*Abs(re(a)), dco), # 4.9.3 + (cosh(a*t)**2, (s**2-2*a**2)/(s**3-4*a**2*s), + S.true, 2*Abs(re(a)), dco), # 4.9.4 + (sinh(a*t)/t, log((s+a)/(s-a))/2, + S.true, Abs(re(a)), dco), # 4.9.12 + (t**n*sinh(a*t), gamma(n+1)/2*((s-a)**(-n-1)-(s+a)**(-n-1)), + n > -2, Abs(a), dco), # 4.9.18 + (t**n*cosh(a*t), gamma(n+1)/2*((s-a)**(-n-1)+(s+a)**(-n-1)), + n > -1, Abs(a), dco), # 4.9.19 + (sinh(2*sqrt(a*t)), sqrt(pi*a)/s/sqrt(s)*exp(a/s), + S.true, S.Zero, dco), # 4.9.34 + (cosh(2*sqrt(a*t)), 1/s+sqrt(pi*a)/s/sqrt(s)*exp(a/s)*erf(sqrt(a/s)), + S.true, S.Zero, dco), # 4.9.35 + ( + sqrt(t)*sinh(2*sqrt(a*t)), + pi**(S(1)/2)*s**(-S(5)/2)*(s/2+a) * + exp(a/s)*erf(sqrt(a/s))-a**(S(1)/2)*s**(-2), + S.true, S.Zero, dco), # 4.9.36 + (sqrt(t)*cosh(2*sqrt(a*t)), pi**(S(1)/2)*s**(-S(5)/2)*(s/2+a)*exp(a/s), + S.true, S.Zero, dco), # 4.9.37 + (sinh(2*sqrt(a*t))/sqrt(t), + pi**(S(1)/2)*s**(-S(1)/2)*exp(a/s) * erf(sqrt(a/s)), + S.true, S.Zero, dco), # 4.9.38 + (cosh(2*sqrt(a*t))/sqrt(t), pi**(S(1)/2)*s**(-S(1)/2)*exp(a/s), + S.true, S.Zero, dco), # 4.9.39 + (sinh(sqrt(a*t))**2/sqrt(t), pi**(S(1)/2)/2*s**(-S(1)/2)*(exp(a/s)-1), + S.true, S.Zero, dco), # 4.9.40 + (cosh(sqrt(a*t))**2/sqrt(t), pi**(S(1)/2)/2*s**(-S(1)/2)*(exp(a/s)+1), + S.true, S.Zero, dco), # 4.9.41 + (erf(a*t), exp(s**2/(2*a)**2)*erfc(s/(2*a))/s, + 4*Abs(arg(a)) < pi, S.Zero, dco), # 4.12.2 + (erf(sqrt(a*t)), sqrt(a)/sqrt(s+a)/s, + S.true, Max(S.Zero, -re(a)), dco), # 4.12.4 + (exp(a*t)*erf(sqrt(a*t)), sqrt(a)/sqrt(s)/(s-a), + S.true, Max(S.Zero, re(a)), dco), # 4.12.5 + (erf(sqrt(a/t)/2), (1-exp(-sqrt(a*s)))/s, + re(a) > 0, S.Zero, dco), # 4.12.6 + (erfc(sqrt(a*t)), (sqrt(s+a)-sqrt(a))/sqrt(s+a)/s, + S.true, -re(a), dco), # 4.12.9 + (exp(a*t)*erfc(sqrt(a*t)), 1/(s+sqrt(a*s)), + S.true, S.Zero, dco), # 4.12.10 + (erfc(sqrt(a/t)/2), exp(-sqrt(a*s))/s, + re(a) > 0, S.Zero, dco), # 4.2.11 + (besselj(n, a*t), a**n/(sqrt(s**2+a**2)*(s+sqrt(s**2+a**2))**n), + re(n) > -1, Abs(im(a)), dco), # 4.14.1 + (t**b*besselj(n, a*t), + 2**n/sqrt(pi)*gamma(n+S.Half)*a**n*(s**2+a**2)**(-n-S.Half), + And(re(n) > -S.Half, Eq(b, n)), Abs(im(a)), dco), # 4.14.7 + (t**b*besselj(n, a*t), + 2**(n+1)/sqrt(pi)*gamma(n+S(3)/2)*a**n*s*(s**2+a**2)**(-n-S(3)/2), + And(re(n) > -1, Eq(b, n+1)), Abs(im(a)), dco), # 4.14.8 + (besselj(0, 2*sqrt(a*t)), exp(-a/s)/s, + S.true, S.Zero, dco), # 4.14.25 + (t**(b)*besselj(n, 2*sqrt(a*t)), a**(n/2)*s**(-n-1)*exp(-a/s), + And(re(n) > -1, Eq(b, n*S.Half)), S.Zero, dco), # 4.14.30 + (besselj(0, a*sqrt(t**2+b*t)), + exp(b*s-b*sqrt(s**2+a**2))/sqrt(s**2+a**2), + Abs(arg(b)) < pi, Abs(im(a)), dco), # 4.15.19 + (besseli(n, a*t), a**n/(sqrt(s**2-a**2)*(s+sqrt(s**2-a**2))**n), + re(n) > -1, Abs(re(a)), dco), # 4.16.1 + (t**b*besseli(n, a*t), + 2**n/sqrt(pi)*gamma(n+S.Half)*a**n*(s**2-a**2)**(-n-S.Half), + And(re(n) > -S.Half, Eq(b, n)), Abs(re(a)), dco), # 4.16.6 + (t**b*besseli(n, a*t), + 2**(n+1)/sqrt(pi)*gamma(n+S(3)/2)*a**n*s*(s**2-a**2)**(-n-S(3)/2), + And(re(n) > -1, Eq(b, n+1)), Abs(re(a)), dco), # 4.16.7 + (t**(b)*besseli(n, 2*sqrt(a*t)), a**(n/2)*s**(-n-1)*exp(a/s), + And(re(n) > -1, Eq(b, n*S.Half)), S.Zero, dco), # 4.16.18 + (bessely(0, a*t), -2/pi*asinh(s/a)/sqrt(s**2+a**2), + S.true, Abs(im(a)), dco), # 4.15.44 + (besselk(0, a*t), log((s + sqrt(s**2-a**2))/a)/(sqrt(s**2-a**2)), + S.true, -re(a), dco) # 4.16.23 + ] + return laplace_transform_rules, t, s + + +@DEBUG_WRAP +def _laplace_rule_timescale(f, t, s): + """ + This function applies the time-scaling rule of the Laplace transform in + a straight-forward way. For example, if it gets ``(f(a*t), t, s)``, it will + compute ``LaplaceTransform(f(t)/a, t, s/a)`` if ``a>0``. + """ + + a = Wild('a', exclude=[t]) + g = WildFunction('g', nargs=1) + ma1 = f.match(g) + if ma1: + arg = ma1[g].args[0].collect(t) + ma2 = arg.match(a*t) + if ma2 and ma2[a].is_positive and ma2[a] != 1: + _debug(' rule: time scaling (4.1.4)') + r, pr, cr = _laplace_transform( + 1/ma2[a]*ma1[g].func(t), t, s/ma2[a], simplify=False) + return (r, pr, cr) + return None + + +@DEBUG_WRAP +def _laplace_rule_heaviside(f, t, s): + """ + This function deals with time-shifted Heaviside step functions. If the time + shift is positive, it applies the time-shift rule of the Laplace transform. + For example, if it gets ``(Heaviside(t-a)*f(t), t, s)``, it will compute + ``exp(-a*s)*LaplaceTransform(f(t+a), t, s)``. + + If the time shift is negative, the Heaviside function is simply removed + as it means nothing to the Laplace transform. + + The function does not remove a factor ``Heaviside(t)``; this is done by + the simple rules. + """ + + a = Wild('a', exclude=[t]) + y = Wild('y') + g = Wild('g') + if ma1 := f.match(Heaviside(y) * g): + if ma2 := ma1[y].match(t - a): + if ma2[a].is_positive: + _debug(' rule: time shift (4.1.4)') + r, pr, cr = _laplace_transform( + ma1[g].subs(t, t + ma2[a]), t, s, simplify=False) + return (exp(-ma2[a] * s) * r, pr, cr) + if ma2[a].is_negative: + _debug( + ' rule: Heaviside factor; negative time shift (4.1.4)') + r, pr, cr = _laplace_transform(ma1[g], t, s, simplify=False) + return (r, pr, cr) + if ma2 := ma1[y].match(a - t): + if ma2[a].is_positive: + _debug(' rule: Heaviside window open') + r, pr, cr = _laplace_transform( + (1 - Heaviside(t - ma2[a])) * ma1[g], t, s, simplify=False) + return (r, pr, cr) + if ma2[a].is_negative: + _debug(' rule: Heaviside window closed') + return (0, 0, S.true) + return None + + +@DEBUG_WRAP +def _laplace_rule_exp(f, t, s): + """ + If this function finds a factor ``exp(a*t)``, it applies the + frequency-shift rule of the Laplace transform and adjusts the convergence + plane accordingly. For example, if it gets ``(exp(-a*t)*f(t), t, s)``, it + will compute ``LaplaceTransform(f(t), t, s+a)``. + """ + + a = Wild('a', exclude=[t]) + y = Wild('y') + z = Wild('z') + ma1 = f.match(exp(y)*z) + if ma1: + ma2 = ma1[y].collect(t).match(a*t) + if ma2: + _debug(' rule: multiply with exp (4.1.5)') + r, pr, cr = _laplace_transform(ma1[z], t, s-ma2[a], + simplify=False) + return (r, pr+re(ma2[a]), cr) + return None + + +@DEBUG_WRAP +def _laplace_rule_delta(f, t, s): + """ + If this function finds a factor ``DiracDelta(b*t-a)``, it applies the + masking property of the delta distribution. For example, if it gets + ``(DiracDelta(t-a)*f(t), t, s)``, it will return + ``(f(a)*exp(-a*s), -a, True)``. + """ + # This rule is not in Bateman54 + + a = Wild('a', exclude=[t]) + b = Wild('b', exclude=[t]) + + y = Wild('y') + z = Wild('z') + ma1 = f.match(DiracDelta(y)*z) + if ma1 and not ma1[z].has(DiracDelta): + ma2 = ma1[y].collect(t).match(b*t-a) + if ma2: + _debug(' rule: multiply with DiracDelta') + loc = ma2[a]/ma2[b] + if re(loc) >= 0 and im(loc) == 0: + fn = exp(-ma2[a]/ma2[b]*s)*ma1[z] + if fn.has(sin, cos): + # Then it may be possible that a sinc() is present in the + # term; let's try this: + fn = fn.rewrite(sinc).ratsimp() + n, d = [x.subs(t, ma2[a]/ma2[b]) for x in fn.as_numer_denom()] + if d != 0: + return (n/d/ma2[b], S.NegativeInfinity, S.true) + else: + return None + else: + return (0, S.NegativeInfinity, S.true) + if ma1[y].is_polynomial(t): + ro = roots(ma1[y], t) + if ro != {} and set(ro.values()) == {1}: + slope = diff(ma1[y], t) + r = Add( + *[exp(-x*s)*ma1[z].subs(t, s)/slope.subs(t, x) + for x in list(ro.keys()) if im(x) == 0 and re(x) >= 0]) + return (r, S.NegativeInfinity, S.true) + return None + + +@DEBUG_WRAP +def _laplace_trig_split(fn): + """ + Helper function for `_laplace_rule_trig`. This function returns two terms + `f` and `g`. `f` contains all product terms with sin, cos, sinh, cosh in + them; `g` contains everything else. + """ + trigs = [S.One] + other = [S.One] + for term in Mul.make_args(fn): + if term.has(sin, cos, sinh, cosh, exp): + trigs.append(term) + else: + other.append(term) + f = Mul(*trigs) + g = Mul(*other) + return f, g + + +@DEBUG_WRAP +def _laplace_trig_expsum(f, t): + """ + Helper function for `_laplace_rule_trig`. This function expects the `f` + from `_laplace_trig_split`. It returns two lists `xm` and `xn`. `xm` is + a list of dictionaries with keys `k` and `a` representing a function + `k*exp(a*t)`. `xn` is a list of all terms that cannot be brought into + that form, which may happen, e.g., when a trigonometric function has + another function in its argument. + """ + c1 = Wild('c1', exclude=[t]) + c0 = Wild('c0', exclude=[t]) + p = Wild('p', exclude=[t]) + xm = [] + xn = [] + + x1 = f.rewrite(exp).expand() + + for term in Add.make_args(x1): + if not term.has(t): + xm.append({'k': term, 'a': 0, re: 0, im: 0}) + continue + term = _laplace_deep_collect(term.powsimp(combine='exp'), t) + + if (r := term.match(p*exp(c1*t+c0))) is not None: + xm.append({ + 'k': r[p]*exp(r[c0]), 'a': r[c1], + re: re(r[c1]), im: im(r[c1])}) + else: + xn.append(term) + return xm, xn + + +@DEBUG_WRAP +def _laplace_trig_ltex(xm, t, s): + """ + Helper function for `_laplace_rule_trig`. This function takes the list of + exponentials `xm` from `_laplace_trig_expsum` and simplifies complex + conjugate and real symmetric poles. It returns the result as a sum and + the convergence plane. + """ + results = [] + planes = [] + + def _simpc(coeffs): + nc = coeffs.copy() + for k in range(len(nc)): + ri = nc[k].as_real_imag() + if ri[0].has(im): + nc[k] = nc[k].rewrite(cos) + else: + nc[k] = (ri[0] + I*ri[1]).rewrite(cos) + return nc + + def _quadpole(t1, k1, k2, k3, s): + a, k0, a_r, a_i = t1['a'], t1['k'], t1[re], t1[im] + nc = [ + k0 + k1 + k2 + k3, + a*(k0 + k1 - k2 - k3) - 2*I*a_i*k1 + 2*I*a_i*k2, + ( + a**2*(-k0 - k1 - k2 - k3) + + a*(4*I*a_i*k0 + 4*I*a_i*k3) + + 4*a_i**2*k0 + 4*a_i**2*k3), + ( + a**3*(-k0 - k1 + k2 + k3) + + a**2*(4*I*a_i*k0 + 2*I*a_i*k1 - 2*I*a_i*k2 - 4*I*a_i*k3) + + a*(4*a_i**2*k0 - 4*a_i**2*k3)) + ] + dc = [ + S.One, S.Zero, 2*a_i**2 - 2*a_r**2, + S.Zero, a_i**4 + 2*a_i**2*a_r**2 + a_r**4] + n = Add( + *[x*s**y for x, y in zip(_simpc(nc), range(len(nc))[::-1])]) + d = Add( + *[x*s**y for x, y in zip(dc, range(len(dc))[::-1])]) + return n/d + + def _ccpole(t1, k1, s): + a, k0, a_r, a_i = t1['a'], t1['k'], t1[re], t1[im] + nc = [k0 + k1, -a*k0 - a*k1 + 2*I*a_i*k0] + dc = [S.One, -2*a_r, a_i**2 + a_r**2] + n = Add( + *[x*s**y for x, y in zip(_simpc(nc), range(len(nc))[::-1])]) + d = Add( + *[x*s**y for x, y in zip(dc, range(len(dc))[::-1])]) + return n/d + + def _rspole(t1, k2, s): + a, k0, a_r, a_i = t1['a'], t1['k'], t1[re], t1[im] + nc = [k0 + k2, a*k0 - a*k2 - 2*I*a_i*k0] + dc = [S.One, -2*I*a_i, -a_i**2 - a_r**2] + n = Add( + *[x*s**y for x, y in zip(_simpc(nc), range(len(nc))[::-1])]) + d = Add( + *[x*s**y for x, y in zip(dc, range(len(dc))[::-1])]) + return n/d + + def _sypole(t1, k3, s): + a, k0 = t1['a'], t1['k'] + nc = [k0 + k3, a*(k0 - k3)] + dc = [S.One, S.Zero, -a**2] + n = Add( + *[x*s**y for x, y in zip(_simpc(nc), range(len(nc))[::-1])]) + d = Add( + *[x*s**y for x, y in zip(dc, range(len(dc))[::-1])]) + return n/d + + def _simplepole(t1, s): + a, k0 = t1['a'], t1['k'] + n = k0 + d = s - a + return n/d + + while len(xm) > 0: + t1 = xm.pop() + i_imagsym = None + i_realsym = None + i_pointsym = None + # The following code checks all remaining poles. If t1 is a pole at + # a+b*I, then we check for a-b*I, -a+b*I, and -a-b*I, and + # assign the respective indices to i_imagsym, i_realsym, i_pointsym. + # -a-b*I / i_pointsym only applies if both a and b are != 0. + for i in range(len(xm)): + real_eq = t1[re] == xm[i][re] + realsym = t1[re] == -xm[i][re] + imag_eq = t1[im] == xm[i][im] + imagsym = t1[im] == -xm[i][im] + if realsym and imagsym and t1[re] != 0 and t1[im] != 0: + i_pointsym = i + elif realsym and imag_eq and t1[re] != 0: + i_realsym = i + elif real_eq and imagsym and t1[im] != 0: + i_imagsym = i + + # The next part looks for four possible pole constellations: + # quad: a+b*I, a-b*I, -a+b*I, -a-b*I + # cc: a+b*I, a-b*I (a may be zero) + # quad: a+b*I, -a+b*I (b may be zero) + # point: a+b*I, -a-b*I (a!=0 and b!=0 is needed, but that has been + # asserted when finding i_pointsym above.) + # If none apply, then t1 is a simple pole. + if ( + i_imagsym is not None and i_realsym is not None + and i_pointsym is not None): + results.append( + _quadpole(t1, + xm[i_imagsym]['k'], xm[i_realsym]['k'], + xm[i_pointsym]['k'], s)) + planes.append(Abs(re(t1['a']))) + # The three additional poles have now been used; to pop them + # easily we have to do it from the back. + indices_to_pop = [i_imagsym, i_realsym, i_pointsym] + indices_to_pop.sort(reverse=True) + for i in indices_to_pop: + xm.pop(i) + elif i_imagsym is not None: + results.append(_ccpole(t1, xm[i_imagsym]['k'], s)) + planes.append(t1[re]) + xm.pop(i_imagsym) + elif i_realsym is not None: + results.append(_rspole(t1, xm[i_realsym]['k'], s)) + planes.append(Abs(t1[re])) + xm.pop(i_realsym) + elif i_pointsym is not None: + results.append(_sypole(t1, xm[i_pointsym]['k'], s)) + planes.append(Abs(t1[re])) + xm.pop(i_pointsym) + else: + results.append(_simplepole(t1, s)) + planes.append(t1[re]) + + return Add(*results), Max(*planes) + + +@DEBUG_WRAP +def _laplace_rule_trig(fn, t_, s): + """ + This rule covers trigonometric factors by splitting everything into a + sum of exponential functions and collecting complex conjugate poles and + real symmetric poles. + """ + t = Dummy('t', real=True) + + if not fn.has(sin, cos, sinh, cosh): + return None + + f, g = _laplace_trig_split(fn.subs(t_, t)) + xm, xn = _laplace_trig_expsum(f, t) + + if len(xn) > 0: + # TODO not implemented yet, but also not important + return None + + if not g.has(t): + r, p = _laplace_trig_ltex(xm, t, s) + return g*r, p, S.true + else: + # Just transform `g` and make frequency-shifted copies + planes = [] + results = [] + G, G_plane, G_cond = _laplace_transform(g, t, s, simplify=False) + for x1 in xm: + results.append(x1['k']*G.subs(s, s-x1['a'])) + planes.append(G_plane+re(x1['a'])) + return Add(*results).subs(t, t_), Max(*planes), G_cond + + +@DEBUG_WRAP +def _laplace_rule_diff(f, t, s): + """ + This function looks for derivatives in the time domain and replaces it + by factors of `s` and initial conditions in the frequency domain. For + example, if it gets ``(diff(f(t), t), t, s)``, it will compute + ``s*LaplaceTransform(f(t), t, s) - f(0)``. + """ + + a = Wild('a', exclude=[t]) + n = Wild('n', exclude=[t]) + g = WildFunction('g') + ma1 = f.match(a*Derivative(g, (t, n))) + if ma1 and ma1[n].is_integer: + m = [z.has(t) for z in ma1[g].args] + if sum(m) == 1: + _debug(' rule: time derivative (4.1.8)') + d = [] + for k in range(ma1[n]): + if k == 0: + y = ma1[g].subs(t, 0) + else: + y = Derivative(ma1[g], (t, k)).subs(t, 0) + d.append(s**(ma1[n]-k-1)*y) + r, pr, cr = _laplace_transform(ma1[g], t, s, simplify=False) + return (ma1[a]*(s**ma1[n]*r - Add(*d)), pr, cr) + return None + + +@DEBUG_WRAP +def _laplace_rule_sdiff(f, t, s): + """ + This function looks for multiplications with polynoimials in `t` as they + correspond to differentiation in the frequency domain. For example, if it + gets ``(t*f(t), t, s)``, it will compute + ``-Derivative(LaplaceTransform(f(t), t, s), s)``. + """ + + if f.is_Mul: + pfac = [1] + ofac = [1] + for fac in Mul.make_args(f): + if fac.is_polynomial(t): + pfac.append(fac) + else: + ofac.append(fac) + if len(pfac) > 1: + pex = prod(pfac) + pc = Poly(pex, t).all_coeffs() + N = len(pc) + if N > 1: + oex = prod(ofac) + r_, p_, c_ = _laplace_transform(oex, t, s, simplify=False) + deri = [r_] + d1 = False + try: + d1 = -diff(deri[-1], s) + except ValueError: + d1 = False + if r_.has(LaplaceTransform): + for k in range(N-1): + deri.append((-1)**(k+1)*Derivative(r_, s, k+1)) + elif d1: + deri.append(d1) + for k in range(N-2): + deri.append(-diff(deri[-1], s)) + if d1: + r = Add(*[pc[N-n-1]*deri[n] for n in range(N)]) + return (r, p_, c_) + # We still have to cover the possibility that there is a symbolic positive + # integer exponent. + n = Wild('n', exclude=[t]) + g = Wild('g') + if ma1 := f.match(t**n*g): + if ma1[n].is_integer and ma1[n].is_positive: + r_, p_, c_ = _laplace_transform(ma1[g], t, s, simplify=False) + return (-1)**ma1[n]*diff(r_, (s, ma1[n])), p_, c_ + return None + + +@DEBUG_WRAP +def _laplace_expand(f, t, s): + """ + This function tries to expand its argument with successively stronger + methods: first it will expand on the top level, then it will expand any + multiplications in depth, then it will try all avilable expansion methods, + and finally it will try to expand trigonometric functions. + + If it can expand, it will then compute the Laplace transform of the + expanded term. + """ + + r = expand(f, deep=False) + if r.is_Add: + return _laplace_transform(r, t, s, simplify=False) + r = expand_mul(f) + if r.is_Add: + return _laplace_transform(r, t, s, simplify=False) + r = expand(f) + if r.is_Add: + return _laplace_transform(r, t, s, simplify=False) + if r != f: + return _laplace_transform(r, t, s, simplify=False) + r = expand(expand_trig(f)) + if r.is_Add: + return _laplace_transform(r, t, s, simplify=False) + return None + + +@DEBUG_WRAP +def _laplace_apply_prog_rules(f, t, s): + """ + This function applies all program rules and returns the result if one + of them gives a result. + """ + + prog_rules = [_laplace_rule_heaviside, _laplace_rule_delta, + _laplace_rule_timescale, _laplace_rule_exp, + _laplace_rule_trig, + _laplace_rule_diff, _laplace_rule_sdiff] + + for p_rule in prog_rules: + if (L := p_rule(f, t, s)) is not None: + return L + return None + + +@DEBUG_WRAP +def _laplace_apply_simple_rules(f, t, s): + """ + This function applies all simple rules and returns the result if one + of them gives a result. + """ + simple_rules, t_, s_ = _laplace_build_rules() + prep_old = '' + prep_f = '' + for t_dom, s_dom, check, plane, prep in simple_rules: + if prep_old != prep: + prep_f = prep(f.subs({t: t_})) + prep_old = prep + ma = prep_f.match(t_dom) + if ma: + try: + c = check.xreplace(ma) + except TypeError: + # This may happen if the time function has imaginary + # numbers in it. Then we give up. + continue + if c == S.true: + return (s_dom.xreplace(ma).subs({s_: s}), + plane.xreplace(ma), S.true) + return None + + +@DEBUG_WRAP +def _piecewise_to_heaviside(f, t): + """ + This function converts a Piecewise expression to an expression written + with Heaviside. It is not exact, but valid in the context of the Laplace + transform. + """ + if not t.is_real: + r = Dummy('r', real=True) + return _piecewise_to_heaviside(f.xreplace({t: r}), r).xreplace({r: t}) + x = piecewise_exclusive(f) + r = [] + for fn, cond in x.args: + # Here we do not need to do many checks because piecewise_exclusive + # has a clearly predictable output. However, if any of the conditions + # is not relative to t, this function just returns the input argument. + if isinstance(cond, Relational) and t in cond.args: + if isinstance(cond, (Eq, Ne)): + # We do not cover this case; these would be single-point + # exceptions that do not play a role in Laplace practice, + # except if they contain Dirac impulses, and then we can + # expect users to not try to use Piecewise for writing it. + return f + else: + r.append(Heaviside(cond.gts - cond.lts)*fn) + elif isinstance(cond, Or) and len(cond.args) == 2: + # Or(t<2, t>4), Or(t>4, t<=2), ... in any order with any <= >= + for c2 in cond.args: + if c2.lhs == t: + r.append(Heaviside(c2.gts - c2.lts)*fn) + else: + return f + elif isinstance(cond, And) and len(cond.args) == 2: + # And(t>2, t<4), And(t>4, t<=2), ... in any order with any <= >= + c0, c1 = cond.args + if c0.lhs == t and c1.lhs == t: + if '>' in c0.rel_op: + c0, c1 = c1, c0 + r.append( + (Heaviside(c1.gts - c1.lts) - + Heaviside(c0.lts - c0.gts))*fn) + else: + return f + else: + return f + return Add(*r) + + +def laplace_correspondence(f, fdict, /): + """ + This helper function takes a function `f` that is the result of a + ``laplace_transform`` or an ``inverse_laplace_transform``. It replaces all + unevaluated ``LaplaceTransform(y(t), t, s)`` by `Y(s)` for any `s` and + all ``InverseLaplaceTransform(Y(s), s, t)`` by `y(t)` for any `t` if + ``fdict`` contains a correspondence ``{y: Y}``. + + Parameters + ========== + + f : sympy expression + Expression containing unevaluated ``LaplaceTransform`` or + ``LaplaceTransform`` objects. + fdict : dictionary + Dictionary containing one or more function correspondences, + e.g., ``{x: X, y: Y}`` meaning that ``X`` and ``Y`` are the + Laplace transforms of ``x`` and ``y``, respectively. + + Examples + ======== + + >>> from sympy import laplace_transform, diff, Function + >>> from sympy import laplace_correspondence, inverse_laplace_transform + >>> from sympy.abc import t, s + >>> y = Function("y") + >>> Y = Function("Y") + >>> z = Function("z") + >>> Z = Function("Z") + >>> f = laplace_transform(diff(y(t), t, 1) + z(t), t, s, noconds=True) + >>> laplace_correspondence(f, {y: Y, z: Z}) + s*Y(s) + Z(s) - y(0) + >>> f = inverse_laplace_transform(Y(s), s, t) + >>> laplace_correspondence(f, {y: Y}) + y(t) + """ + p = Wild('p') + s = Wild('s') + t = Wild('t') + a = Wild('a') + if ( + not isinstance(f, Expr) + or (not f.has(LaplaceTransform) + and not f.has(InverseLaplaceTransform))): + return f + for y, Y in fdict.items(): + if ( + (m := f.match(LaplaceTransform(y(a), t, s))) is not None + and m[a] == m[t]): + return Y(m[s]) + if ( + (m := f.match(InverseLaplaceTransform(Y(a), s, t, p))) + is not None + and m[a] == m[s]): + return y(m[t]) + func = f.func + args = [laplace_correspondence(arg, fdict) for arg in f.args] + return func(*args) + + +def laplace_initial_conds(f, t, fdict, /): + """ + This helper function takes a function `f` that is the result of a + ``laplace_transform``. It takes an fdict of the form ``{y: [1, 4, 2]}``, + where the values in the list are the initial value, the initial slope, the + initial second derivative, etc., of the function `y(t)`, and replaces all + unevaluated initial conditions. + + Parameters + ========== + + f : sympy expression + Expression containing initial conditions of unevaluated functions. + t : sympy expression + Variable for which the initial conditions are to be applied. + fdict : dictionary + Dictionary containing a list of initial conditions for every + function, e.g., ``{y: [0, 1, 2], x: [3, 4, 5]}``. The order + of derivatives is ascending, so `0`, `1`, `2` are `y(0)`, `y'(0)`, + and `y''(0)`, respectively. + + Examples + ======== + + >>> from sympy import laplace_transform, diff, Function + >>> from sympy import laplace_correspondence, laplace_initial_conds + >>> from sympy.abc import t, s + >>> y = Function("y") + >>> Y = Function("Y") + >>> f = laplace_transform(diff(y(t), t, 3), t, s, noconds=True) + >>> g = laplace_correspondence(f, {y: Y}) + >>> laplace_initial_conds(g, t, {y: [2, 4, 8, 16, 32]}) + s**3*Y(s) - 2*s**2 - 4*s - 8 + """ + for y, ic in fdict.items(): + for k in range(len(ic)): + if k == 0: + f = f.replace(y(0), ic[0]) + elif k == 1: + f = f.replace(Subs(Derivative(y(t), t), t, 0), ic[1]) + else: + f = f.replace(Subs(Derivative(y(t), (t, k)), t, 0), ic[k]) + return f + + +@DEBUG_WRAP +def _laplace_transform(fn, t_, s_, *, simplify): + """ + Front-end function of the Laplace transform. It tries to apply all known + rules recursively, and if everything else fails, it tries to integrate. + """ + + terms_t = Add.make_args(fn) + terms_s = [] + terms = [] + planes = [] + conditions = [] + + for ff in terms_t: + k, ft = ff.as_independent(t_, as_Add=False) + if ft.has(SingularityFunction): + _terms = Add.make_args(ft.rewrite(Heaviside)) + for _term in _terms: + k1, f1 = _term.as_independent(t_, as_Add=False) + terms.append((k*k1, f1)) + elif ft.func == Piecewise and not ft.has(DiracDelta(t_)): + _terms = Add.make_args(_piecewise_to_heaviside(ft, t_)) + for _term in _terms: + k1, f1 = _term.as_independent(t_, as_Add=False) + terms.append((k*k1, f1)) + else: + terms.append((k, ft)) + + for k, ft in terms: + if ft.has(SingularityFunction): + r = (LaplaceTransform(ft, t_, s_), S.NegativeInfinity, True) + else: + if ft.has(Heaviside(t_)) and not ft.has(DiracDelta(t_)): + # For t>=0, Heaviside(t)=1 can be used, except if there is also + # a DiracDelta(t) present, in which case removing Heaviside(t) + # is unnecessary because _laplace_rule_delta can deal with it. + ft = ft.subs(Heaviside(t_), 1) + if ( + (r := _laplace_apply_simple_rules(ft, t_, s_)) + is not None or + (r := _laplace_apply_prog_rules(ft, t_, s_)) + is not None or + (r := _laplace_expand(ft, t_, s_)) is not None): + pass + elif any(undef.has(t_) for undef in ft.atoms(AppliedUndef)): + # If there are undefined functions f(t) then integration is + # unlikely to do anything useful so we skip it and given an + # unevaluated LaplaceTransform. + r = (LaplaceTransform(ft, t_, s_), S.NegativeInfinity, True) + elif (r := _laplace_transform_integration( + ft, t_, s_, simplify=simplify)) is not None: + pass + else: + r = (LaplaceTransform(ft, t_, s_), S.NegativeInfinity, True) + (ri_, pi_, ci_) = r + terms_s.append(k*ri_) + planes.append(pi_) + conditions.append(ci_) + + result = Add(*terms_s) + if simplify: + result = result.simplify(doit=False) + plane = Max(*planes) + condition = And(*conditions) + + return result, plane, condition + + +class LaplaceTransform(IntegralTransform): + """ + Class representing unevaluated Laplace transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute Laplace transforms, see the :func:`laplace_transform` + docstring. + + If this is called with ``.doit()``, it returns the Laplace transform as an + expression. If it is called with ``.doit(noconds=False)``, it returns a + tuple containing the same expression, a convergence plane, and conditions. + """ + + _name = 'Laplace' + + def _compute_transform(self, f, t, s, **hints): + _simplify = hints.get('simplify', False) + LT = _laplace_transform_integration(f, t, s, simplify=_simplify) + return LT + + def _as_integral(self, f, t, s): + return Integral(f*exp(-s*t), (t, S.Zero, S.Infinity)) + + def doit(self, **hints): + """ + Try to evaluate the transform in closed form. + + Explanation + =========== + + Standard hints are the following: + - ``noconds``: if True, do not return convergence conditions. The + default setting is `True`. + - ``simplify``: if True, it simplifies the final result. The + default setting is `False`. + """ + _noconds = hints.get('noconds', True) + _simplify = hints.get('simplify', False) + + debugf('[LT doit] (%s, %s, %s)', (self.function, + self.function_variable, + self.transform_variable)) + + t_ = self.function_variable + s_ = self.transform_variable + fn = self.function + + r = _laplace_transform(fn, t_, s_, simplify=_simplify) + + if _noconds: + return r[0] + else: + return r + + +def laplace_transform(f, t, s, legacy_matrix=True, **hints): + r""" + Compute the Laplace Transform `F(s)` of `f(t)`, + + .. math :: F(s) = \int_{0^{-}}^\infty e^{-st} f(t) \mathrm{d}t. + + Explanation + =========== + + For all sensible functions, this converges absolutely in a + half-plane + + .. math :: a < \operatorname{Re}(s) + + This function returns ``(F, a, cond)`` where ``F`` is the Laplace + transform of ``f``, `a` is the half-plane of convergence, and `cond` are + auxiliary convergence conditions. + + The implementation is rule-based, and if you are interested in which + rules are applied, and whether integration is attempted, you can switch + debug information on by setting ``sympy.SYMPY_DEBUG=True``. The numbers + of the rules in the debug information (and the code) refer to Bateman's + Tables of Integral Transforms [1]. + + The lower bound is `0-`, meaning that this bound should be approached + from the lower side. This is only necessary if distributions are involved. + At present, it is only done if `f(t)` contains ``DiracDelta``, in which + case the Laplace transform is computed implicitly as + + .. math :: + F(s) = \lim_{\tau\to 0^{-}} \int_{\tau}^\infty e^{-st} + f(t) \mathrm{d}t + + by applying rules. + + If the Laplace transform cannot be fully computed in closed form, this + function returns expressions containing unevaluated + :class:`LaplaceTransform` objects. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. If + ``noconds=True``, only `F` will be returned (i.e. not ``cond``, and also + not the plane ``a``). + + .. deprecated:: 1.9 + Legacy behavior for matrices where ``laplace_transform`` with + ``noconds=False`` (the default) returns a Matrix whose elements are + tuples. The behavior of ``laplace_transform`` for matrices will change + in a future release of SymPy to return a tuple of the transformed + Matrix and the convergence conditions for the matrix as a whole. Use + ``legacy_matrix=False`` to enable the new behavior. + + Examples + ======== + + >>> from sympy import DiracDelta, exp, laplace_transform + >>> from sympy.abc import t, s, a + >>> laplace_transform(t**4, t, s) + (24/s**5, 0, True) + >>> laplace_transform(t**a, t, s) + (gamma(a + 1)/(s*s**a), 0, re(a) > -1) + >>> laplace_transform(DiracDelta(t)-a*exp(-a*t), t, s, simplify=True) + (s/(a + s), -re(a), True) + + There are also helper functions that make it easy to solve differential + equations by Laplace transform. For example, to solve + + .. math :: m x''(t) + d x'(t) + k x(t) = 0 + + with initial value `0` and initial derivative `v`: + + >>> from sympy import Function, laplace_correspondence, diff, solve + >>> from sympy import laplace_initial_conds, inverse_laplace_transform + >>> from sympy.abc import d, k, m, v + >>> x = Function('x') + >>> X = Function('X') + >>> f = m*diff(x(t), t, 2) + d*diff(x(t), t) + k*x(t) + >>> F = laplace_transform(f, t, s, noconds=True) + >>> F = laplace_correspondence(F, {x: X}) + >>> F = laplace_initial_conds(F, t, {x: [0, v]}) + >>> F + d*s*X(s) + k*X(s) + m*(s**2*X(s) - v) + >>> Xs = solve(F, X(s))[0] + >>> Xs + m*v/(d*s + k + m*s**2) + >>> inverse_laplace_transform(Xs, s, t) + 2*v*exp(-d*t/(2*m))*sin(t*sqrt((-d**2 + 4*k*m)/m**2)/2)*Heaviside(t)/sqrt((-d**2 + 4*k*m)/m**2) + + References + ========== + + .. [1] Erdelyi, A. (ed.), Tables of Integral Transforms, Volume 1, + Bateman Manuscript Prooject, McGraw-Hill (1954), available: + https://resolver.caltech.edu/CaltechAUTHORS:20140123-101456353 + + See Also + ======== + + inverse_laplace_transform, mellin_transform, fourier_transform + hankel_transform, inverse_hankel_transform + + """ + + _noconds = hints.get('noconds', False) + _simplify = hints.get('simplify', False) + + if isinstance(f, MatrixBase) and hasattr(f, 'applyfunc'): + + conds = not hints.get('noconds', False) + + if conds and legacy_matrix: + adt = 'deprecated-laplace-transform-matrix' + sympy_deprecation_warning( + """ +Calling laplace_transform() on a Matrix with noconds=False (the default) is +deprecated. Either noconds=True or use legacy_matrix=False to get the new +behavior. + """, + deprecated_since_version='1.9', + active_deprecations_target=adt, + ) + # Temporarily disable the deprecation warning for non-Expr objects + # in Matrix + with ignore_warnings(SymPyDeprecationWarning): + return f.applyfunc( + lambda fij: laplace_transform(fij, t, s, **hints)) + else: + elements_trans = [laplace_transform( + fij, t, s, **hints) for fij in f] + if conds: + elements, avals, conditions = zip(*elements_trans) + f_laplace = type(f)(*f.shape, elements) + return f_laplace, Max(*avals), And(*conditions) + else: + return type(f)(*f.shape, elements_trans) + + LT, p, c = LaplaceTransform(f, t, s).doit(noconds=False, + simplify=_simplify) + + if not _noconds: + return LT, p, c + else: + return LT + + +@DEBUG_WRAP +def _inverse_laplace_transform_integration(F, s, t_, plane, *, simplify): + """ The backend function for inverse Laplace transforms. """ + from sympy.integrals.meijerint import meijerint_inversion, _get_coeff_exp + from sympy.integrals.transforms import inverse_mellin_transform + + # There are two strategies we can try: + # 1) Use inverse mellin transform, related by a simple change of variables. + # 2) Use the inversion integral. + + t = Dummy('t', real=True) + + def pw_simp(*args): + """ Simplify a piecewise expression from hyperexpand. """ + if len(args) != 3: + return Piecewise(*args) + arg = args[2].args[0].argument + coeff, exponent = _get_coeff_exp(arg, t) + e1 = args[0].args[0] + e2 = args[1].args[0] + return ( + Heaviside(1/Abs(coeff) - t**exponent)*e1 + + Heaviside(t**exponent - 1/Abs(coeff))*e2) + + if F.is_rational_function(s): + F = F.apart(s) + + if F.is_Add: + f = Add( + *[_inverse_laplace_transform_integration(X, s, t, plane, simplify) + for X in F.args]) + return _simplify(f.subs(t, t_), simplify), True + + try: + f, cond = inverse_mellin_transform(F, s, exp(-t), (None, S.Infinity), + needeval=True, noconds=False) + except IntegralTransformError: + f = None + + if f is None: + f = meijerint_inversion(F, s, t) + if f is None: + return None + if f.is_Piecewise: + f, cond = f.args[0] + if f.has(Integral): + return None + else: + cond = S.true + f = f.replace(Piecewise, pw_simp) + + if f.is_Piecewise: + # many of the functions called below can't work with piecewise + # (b/c it has a bool in args) + return f.subs(t, t_), cond + + u = Dummy('u') + + def simp_heaviside(arg, H0=S.Half): + a = arg.subs(exp(-t), u) + if a.has(t): + return Heaviside(arg, H0) + from sympy.solvers.inequalities import _solve_inequality + rel = _solve_inequality(a > 0, u) + if rel.lts == u: + k = log(rel.gts) + return Heaviside(t + k, H0) + else: + k = log(rel.lts) + return Heaviside(-(t + k), H0) + + f = f.replace(Heaviside, simp_heaviside) + + def simp_exp(arg): + return expand_complex(exp(arg)) + + f = f.replace(exp, simp_exp) + + return _simplify(f.subs(t, t_), simplify), cond + + +@DEBUG_WRAP +def _complete_the_square_in_denom(f, s): + from sympy.simplify.radsimp import fraction + [n, d] = fraction(f) + if d.is_polynomial(s): + cf = d.as_poly(s).all_coeffs() + if len(cf) == 3: + a, b, c = cf + d = a*((s+b/(2*a))**2+c/a-(b/(2*a))**2) + return n/d + + +@cacheit +def _inverse_laplace_build_rules(): + """ + This is an internal helper function that returns the table of inverse + Laplace transform rules in terms of the time variable `t` and the + frequency variable `s`. It is used by `_inverse_laplace_apply_rules`. + """ + s = Dummy('s') + t = Dummy('t') + a = Wild('a', exclude=[s]) + b = Wild('b', exclude=[s]) + c = Wild('c', exclude=[s]) + + _debug('_inverse_laplace_build_rules is building rules') + + def _frac(f, s): + try: + return f.factor(s) + except PolynomialError: + return f + + def same(f): return f + # This list is sorted according to the prep function needed. + _ILT_rules = [ + (a/s, a, S.true, same, 1), + ( + b*(s+a)**(-c), t**(c-1)*exp(-a*t)/gamma(c), + S.true, same, 1), + (1/(s**2+a**2)**2, (sin(a*t) - a*t*cos(a*t))/(2*a**3), + S.true, same, 1), + # The next two rules must be there in that order. For the second + # one, the condition would be a != 0 or, respectively, to take the + # limit a -> 0 after the transform if a == 0. It is much simpler if + # the case a == 0 has its own rule. + (1/(s**b), t**(b - 1)/gamma(b), S.true, same, 1), + (1/(s*(s+a)**b), lowergamma(b, a*t)/(a**b*gamma(b)), + S.true, same, 1) + ] + return _ILT_rules, s, t + + +@DEBUG_WRAP +def _inverse_laplace_apply_simple_rules(f, s, t): + """ + Helper function for the class InverseLaplaceTransform. + """ + if f == 1: + _debug(' rule: 1 o---o DiracDelta()') + return DiracDelta(t), S.true + + _ILT_rules, s_, t_ = _inverse_laplace_build_rules() + _prep = '' + fsubs = f.subs({s: s_}) + + for s_dom, t_dom, check, prep, fac in _ILT_rules: + if _prep != (prep, fac): + _F = prep(fsubs*fac) + _prep = (prep, fac) + ma = _F.match(s_dom) + if ma: + c = check + if c is not S.true: + args = [x.xreplace(ma) for x in c[0]] + c = c[1](*args) + if c == S.true: + return Heaviside(t)*t_dom.xreplace(ma).subs({t_: t}), S.true + + return None + + +@DEBUG_WRAP +def _inverse_laplace_diff(f, s, t, plane): + """ + Helper function for the class InverseLaplaceTransform. + """ + a = Wild('a', exclude=[s]) + n = Wild('n', exclude=[s]) + g = Wild('g') + ma = f.match(a*Derivative(g, (s, n))) + if ma and ma[n].is_integer: + _debug(' rule: t**n*f(t) o---o (-1)**n*diff(F(s), s, n)') + r, c = _inverse_laplace_transform( + ma[g], s, t, plane, simplify=False, dorational=False) + return (-t)**ma[n]*r, c + return None + + +@DEBUG_WRAP +def _inverse_laplace_time_shift(F, s, t, plane): + """ + Helper function for the class InverseLaplaceTransform. + """ + a = Wild('a', exclude=[s]) + g = Wild('g') + + if not F.has(s): + return F*DiracDelta(t), S.true + if not F.has(exp): + return None + + ma1 = F.match(exp(a*s)) + if ma1: + if ma1[a].is_negative: + _debug(' rule: exp(-a*s) o---o DiracDelta(t-a)') + return DiracDelta(t+ma1[a]), S.true + else: + return InverseLaplaceTransform(F, s, t, plane), S.true + + ma1 = F.match(exp(a*s)*g) + if ma1: + if ma1[a].is_negative: + _debug(' rule: exp(-a*s)*F(s) o---o Heaviside(t-a)*f(t-a)') + return _inverse_laplace_transform( + ma1[g], s, t+ma1[a], plane, simplify=False, dorational=True) + else: + return InverseLaplaceTransform(F, s, t, plane), S.true + return None + + +@DEBUG_WRAP +def _inverse_laplace_freq_shift(F, s, t, plane): + """ + Helper function for the class InverseLaplaceTransform. + """ + if not F.has(s): + return F*DiracDelta(t), S.true + if len(args := F.args) == 1: + a = Wild('a', exclude=[s]) + if (ma := args[0].match(s-a)) and re(ma[a]).is_positive: + _debug(' rule: F(s-a) o---o exp(-a*t)*f(t)') + return ( + exp(-ma[a]*t) * + InverseLaplaceTransform(F.func(s), s, t, plane), S.true) + return None + + +@DEBUG_WRAP +def _inverse_laplace_time_diff(F, s, t, plane): + """ + Helper function for the class InverseLaplaceTransform. + """ + n = Wild('n', exclude=[s]) + g = Wild('g') + + ma1 = F.match(s**n*g) + if ma1 and ma1[n].is_integer and ma1[n].is_positive: + _debug(' rule: s**n*F(s) o---o diff(f(t), t, n)') + r, c = _inverse_laplace_transform( + ma1[g], s, t, plane, simplify=False, dorational=True) + r = r.replace(Heaviside(t), 1) + if r.has(InverseLaplaceTransform): + return diff(r, t, ma1[n]), c + else: + return Heaviside(t)*diff(r, t, ma1[n]), c + return None + + +@DEBUG_WRAP +def _inverse_laplace_irrational(fn, s, t, plane): + """ + Helper function for the class InverseLaplaceTransform. + """ + + a = Wild('a', exclude=[s]) + b = Wild('b', exclude=[s]) + m = Wild('m', exclude=[s]) + n = Wild('n', exclude=[s]) + + result = None + condition = S.true + + fa = fn.as_ordered_factors() + + ma = [x.match((a*s**m+b)**n) for x in fa] + + if None in ma: + return None + + constants = S.One + zeros = [] + poles = [] + rest = [] + + for term in ma: + if term[a] == 0: + constants = constants*term + elif term[n].is_positive: + zeros.append(term) + elif term[n].is_negative: + poles.append(term) + else: + rest.append(term) + + # The code below assumes that the poles are sorted in a specific way: + poles = sorted(poles, key=lambda x: (x[n], x[b] != 0, x[b])) + zeros = sorted(zeros, key=lambda x: (x[n], x[b] != 0, x[b])) + + if len(rest) != 0: + return None + + if len(poles) == 1 and len(zeros) == 0: + if poles[0][n] == -1 and poles[0][m] == S.Half: + # 1/(a0*sqrt(s)+b0) == 1/a0 * 1/(sqrt(s)+b0/a0) + a_ = poles[0][b]/poles[0][a] + k_ = 1/poles[0][a]*constants + if a_.is_positive: + result = ( + k_/sqrt(pi)/sqrt(t) - + k_*a_*exp(a_**2*t)*erfc(a_*sqrt(t))) + _debug(' rule 5.3.4') + elif poles[0][n] == -2 and poles[0][m] == S.Half: + # 1/(a0*sqrt(s)+b0)**2 == 1/a0**2 * 1/(sqrt(s)+b0/a0)**2 + a_sq = poles[0][b]/poles[0][a] + a_ = a_sq**2 + k_ = 1/poles[0][a]**2*constants + if a_sq.is_positive: + result = ( + k_*(1 - 2/sqrt(pi)*sqrt(a_)*sqrt(t) + + (1-2*a_*t)*exp(a_*t)*(erf(sqrt(a_)*sqrt(t))-1))) + _debug(' rule 5.3.10') + elif poles[0][n] == -3 and poles[0][m] == S.Half: + # 1/(a0*sqrt(s)+b0)**3 == 1/a0**3 * 1/(sqrt(s)+b0/a0)**3 + a_ = poles[0][b]/poles[0][a] + k_ = 1/poles[0][a]**3*constants + if a_.is_positive: + result = ( + k_*(2/sqrt(pi)*(a_**2*t+1)*sqrt(t) - + a_*t*exp(a_**2*t)*(2*a_**2*t+3)*erfc(a_*sqrt(t)))) + _debug(' rule 5.3.13') + elif poles[0][n] == -4 and poles[0][m] == S.Half: + # 1/(a0*sqrt(s)+b0)**4 == 1/a0**4 * 1/(sqrt(s)+b0/a0)**4 + a_ = poles[0][b]/poles[0][a] + k_ = 1/poles[0][a]**4*constants/3 + if a_.is_positive: + result = ( + k_*(t*(4*a_**4*t**2+12*a_**2*t+3)*exp(a_**2*t) * + erfc(a_*sqrt(t)) - + 2/sqrt(pi)*a_**3*t**(S(5)/2)*(2*a_**2*t+5))) + _debug(' rule 5.3.16') + elif poles[0][n] == -S.Half and poles[0][m] == 2: + # 1/sqrt(a0*s**2+b0) == 1/sqrt(a0) * 1/sqrt(s**2+b0/a0) + a_ = sqrt(poles[0][b]/poles[0][a]) + k_ = 1/sqrt(poles[0][a])*constants + result = (k_*(besselj(0, a_*t))) + _debug(' rule 5.3.35/44') + + elif len(poles) == 1 and len(zeros) == 1: + if ( + poles[0][n] == -3 and poles[0][m] == S.Half and + zeros[0][n] == S.Half and zeros[0][b] == 0): + # sqrt(az*s)/(ap*sqrt(s+bp)**3) + # == sqrt(az)/ap * sqrt(s)/(sqrt(s+bp)**3) + a_ = poles[0][b] + k_ = sqrt(zeros[0][a])/poles[0][a]*constants + result = ( + k_*(2*a_**4*t**2+5*a_**2*t+1)*exp(a_**2*t) * + erfc(a_*sqrt(t)) - 2/sqrt(pi)*a_*(a_**2*t+2)*sqrt(t)) + _debug(' rule 5.3.14') + if ( + poles[0][n] == -1 and poles[0][m] == 1 and + zeros[0][n] == S.Half and zeros[0][m] == 1): + # sqrt(az*s+bz)/(ap*s+bp) + # == sqrt(az)/ap * (sqrt(s+bz/az)/(s+bp/ap)) + a_ = zeros[0][b]/zeros[0][a] + b_ = poles[0][b]/poles[0][a] + k_ = sqrt(zeros[0][a])/poles[0][a]*constants + result = ( + k_*(exp(-a_*t)/sqrt(t)/sqrt(pi)+sqrt(a_-b_) * + exp(-b_*t)*erf(sqrt(a_-b_)*sqrt(t)))) + _debug(' rule 5.3.22') + + elif len(poles) == 2 and len(zeros) == 0: + if ( + poles[0][n] == -1 and poles[0][m] == 1 and + poles[1][n] == -S.Half and poles[1][m] == 1 and + poles[1][b] == 0): + # 1/((a0*s+b0)*sqrt(a1*s)) + # == 1/(a0*sqrt(a1)) * 1/((s+b0/a0)*sqrt(s)) + a_ = -poles[0][b]/poles[0][a] + k_ = 1/sqrt(poles[1][a])/poles[0][a]*constants + if a_.is_positive: + result = (k_/sqrt(a_)*exp(a_*t)*erf(sqrt(a_)*sqrt(t))) + _debug(' rule 5.3.1') + elif ( + poles[0][n] == -1 and poles[0][m] == 1 and poles[0][b] == 0 and + poles[1][n] == -1 and poles[1][m] == S.Half): + # 1/(a0*s*(a1*sqrt(s)+b1)) + # == 1/(a0*a1) * 1/(s*(sqrt(s)+b1/a1)) + a_ = poles[1][b]/poles[1][a] + k_ = 1/poles[0][a]/poles[1][a]/a_*constants + if a_.is_positive: + result = k_*(1-exp(a_**2*t)*erfc(a_*sqrt(t))) + _debug(' rule 5.3.5') + elif ( + poles[0][n] == -1 and poles[0][m] == S.Half and + poles[1][n] == -S.Half and poles[1][m] == 1 and + poles[1][b] == 0): + # 1/((a0*sqrt(s)+b0)*(sqrt(a1*s)) + # == 1/(a0*sqrt(a1)) * 1/((sqrt(s)+b0/a0)"sqrt(s)) + a_ = poles[0][b]/poles[0][a] + k_ = 1/(poles[0][a]*sqrt(poles[1][a]))*constants + if a_.is_positive: + result = k_*exp(a_**2*t)*erfc(a_*sqrt(t)) + _debug(' rule 5.3.7') + elif ( + poles[0][n] == -S(3)/2 and poles[0][m] == 1 and + poles[0][b] == 0 and poles[1][n] == -1 and + poles[1][m] == S.Half): + # 1/((a0**(3/2)*s**(3/2))*(a1*sqrt(s)+b1)) + # == 1/(a0**(3/2)*a1) 1/((s**(3/2))*(sqrt(s)+b1/a1)) + # Note that Bateman54 5.3 (8) is incorrect; there (sqrt(p)+a) + # should be (sqrt(p)+a)**(-1). + a_ = poles[1][b]/poles[1][a] + k_ = 1/(poles[0][a]**(S(3)/2)*poles[1][a])/a_**2*constants + if a_.is_positive: + result = ( + k_*(2/sqrt(pi)*a_*sqrt(t)+exp(a_**2*t)*erfc(a_*sqrt(t))-1)) + _debug(' rule 5.3.8') + elif ( + poles[0][n] == -2 and poles[0][m] == S.Half and + poles[1][n] == -1 and poles[1][m] == 1 and + poles[1][b] == 0): + # 1/((a0*sqrt(s)+b0)**2*a1*s) + # == 1/a0**2/a1 * 1/(sqrt(s)+b0/a0)**2/s + a_sq = poles[0][b]/poles[0][a] + a_ = a_sq**2 + k_ = 1/poles[0][a]**2/poles[1][a]*constants + if a_sq.is_positive: + result = ( + k_*(1/a_ + (2*t-1/a_)*exp(a_*t)*erfc(sqrt(a_)*sqrt(t)) - + 2/sqrt(pi)/sqrt(a_)*sqrt(t))) + _debug(' rule 5.3.11') + elif ( + poles[0][n] == -2 and poles[0][m] == S.Half and + poles[1][n] == -S.Half and poles[1][m] == 1 and + poles[1][b] == 0): + # 1/((a0*sqrt(s)+b0)**2*sqrt(a1*s)) + # == 1/a0**2/sqrt(a1) * 1/(sqrt(s)+b0/a0)**2/sqrt(s) + a_ = poles[0][b]/poles[0][a] + k_ = 1/poles[0][a]**2/sqrt(poles[1][a])*constants + if a_.is_positive: + result = ( + k_*(2/sqrt(pi)*sqrt(t) - + 2*a_*t*exp(a_**2*t)*erfc(a_*sqrt(t)))) + _debug(' rule 5.3.12') + elif ( + poles[0][n] == -3 and poles[0][m] == S.Half and + poles[1][n] == -S.Half and poles[1][m] == 1 and + poles[1][b] == 0): + # 1 / (sqrt(a1*s)*(a0*sqrt(s+b0)**3)) + # == 1/(sqrt(a1)*a0) * 1/(sqrt(s)*(sqrt(s+b0)**3)) + a_ = poles[0][b] + k_ = constants/sqrt(poles[1][a])/poles[0][a] + result = k_*( + (2*a_**2*t+1)*t*exp(a_**2*t)*erfc(a_*sqrt(t)) - + 2/sqrt(pi)*a_*t**(S(3)/2)) + _debug(' rule 5.3.15') + elif ( + poles[0][n] == -1 and poles[0][m] == 1 and + poles[1][n] == -S.Half and poles[1][m] == 1): + # 1 / ( (a0*s+b0)* sqrt(a1*s+b1) ) + # == 1/(sqrt(a1)*a0) * 1 / ( (s+b0/a0)* sqrt(s+b1/a1) ) + a_ = poles[0][b]/poles[0][a] + b_ = poles[1][b]/poles[1][a] + k_ = constants/sqrt(poles[1][a])/poles[0][a] + result = k_*( + 1/sqrt(b_-a_)*exp(-a_*t)*erf(sqrt(b_-a_)*sqrt(t))) + _debug(' rule 5.3.23') + + elif len(poles) == 2 and len(zeros) == 1: + if ( + poles[0][n] == -1 and poles[0][m] == 1 and + poles[1][n] == -1 and poles[1][m] == S.Half and + zeros[0][n] == S.Half and zeros[0][m] == 1 and + zeros[0][b] == 0): + # sqrt(za0*s)/((a0*s+b0)*(a1*sqrt(s)+b1)) + # == sqrt(za0)/(a0*a1) * s/((s+b0/a0)*(sqrt(s)+b1/a1)) + a_sq = poles[1][b]/poles[1][a] + a_ = a_sq**2 + b_ = -poles[0][b]/poles[0][a] + k_ = sqrt(zeros[0][a])/poles[0][a]/poles[1][a]/(a_-b_)*constants + if a_sq.is_positive and b_.is_positive: + result = k_*( + a_*exp(a_*t)*erfc(sqrt(a_)*sqrt(t)) + + sqrt(a_)*sqrt(b_)*exp(b_*t)*erfc(sqrt(b_)*sqrt(t)) - + b_*exp(b_*t)) + _debug(' rule 5.3.6') + elif ( + poles[0][n] == -1 and poles[0][m] == 1 and + poles[0][b] == 0 and poles[1][n] == -1 and + poles[1][m] == S.Half and zeros[0][n] == 1 and + zeros[0][m] == S.Half): + # (az*sqrt(s)+bz)/(a0*s*(a1*sqrt(s)+b1)) + # == az/a0/a1 * (sqrt(z)+bz/az)/(s*(sqrt(s)+b1/a1)) + a_num = zeros[0][b]/zeros[0][a] + a_ = poles[1][b]/poles[1][a] + if a_+a_num == 0: + k_ = zeros[0][a]/poles[0][a]/poles[1][a]*constants + result = k_*( + 2*exp(a_**2*t)*erfc(a_*sqrt(t))-1) + _debug(' rule 5.3.17') + elif ( + poles[1][n] == -1 and poles[1][m] == 1 and + poles[1][b] == 0 and poles[0][n] == -2 and + poles[0][m] == S.Half and zeros[0][n] == 2 and + zeros[0][m] == S.Half): + # (az*sqrt(s)+bz)**2/(a1*s*(a0*sqrt(s)+b0)**2) + # == az**2/a1/a0**2 * (sqrt(z)+bz/az)**2/(s*(sqrt(s)+b0/a0)**2) + a_num = zeros[0][b]/zeros[0][a] + a_ = poles[0][b]/poles[0][a] + if a_+a_num == 0: + k_ = zeros[0][a]**2/poles[1][a]/poles[0][a]**2*constants + result = k_*( + 1 + 8*a_**2*t*exp(a_**2*t)*erfc(a_*sqrt(t)) - + 8/sqrt(pi)*a_*sqrt(t)) + _debug(' rule 5.3.18') + elif ( + poles[1][n] == -1 and poles[1][m] == 1 and + poles[1][b] == 0 and poles[0][n] == -3 and + poles[0][m] == S.Half and zeros[0][n] == 3 and + zeros[0][m] == S.Half): + # (az*sqrt(s)+bz)**3/(a1*s*(a0*sqrt(s)+b0)**3) + # == az**3/a1/a0**3 * (sqrt(z)+bz/az)**3/(s*(sqrt(s)+b0/a0)**3) + a_num = zeros[0][b]/zeros[0][a] + a_ = poles[0][b]/poles[0][a] + if a_+a_num == 0: + k_ = zeros[0][a]**3/poles[1][a]/poles[0][a]**3*constants + result = k_*( + 2*(8*a_**4*t**2+8*a_**2*t+1)*exp(a_**2*t) * + erfc(a_*sqrt(t))-8/sqrt(pi)*a_*sqrt(t)*(2*a_**2*t+1)-1) + _debug(' rule 5.3.19') + + elif len(poles) == 3 and len(zeros) == 0: + if ( + poles[0][n] == -1 and poles[0][b] == 0 and poles[0][m] == 1 and + poles[1][n] == -1 and poles[1][m] == 1 and + poles[2][n] == -S.Half and poles[2][m] == 1): + # 1/((a0*s)*(a1*s+b1)*sqrt(a2*s)) + # == 1/(a0*a1*sqrt(a2)) * 1/((s)*(s+b1/a1)*sqrt(s)) + a_ = -poles[1][b]/poles[1][a] + k_ = 1/poles[0][a]/poles[1][a]/sqrt(poles[2][a])*constants + if a_.is_positive: + result = k_ * ( + a_**(-S(3)/2) * exp(a_*t) * erf(sqrt(a_)*sqrt(t)) - + 2/a_/sqrt(pi)*sqrt(t)) + _debug(' rule 5.3.2') + elif ( + poles[0][n] == -1 and poles[0][m] == 1 and + poles[1][n] == -1 and poles[1][m] == S.Half and + poles[2][n] == -S.Half and poles[2][m] == 1 and + poles[2][b] == 0): + # 1/((a0*s+b0)*(a1*sqrt(s)+b1)*(sqrt(a2)*sqrt(s))) + # == 1/(a0*a1*sqrt(a2)) * 1/((s+b0/a0)*(sqrt(s)+b1/a1)*sqrt(s)) + a_sq = poles[1][b]/poles[1][a] + a_ = a_sq**2 + b_ = -poles[0][b]/poles[0][a] + k_ = ( + 1/poles[0][a]/poles[1][a]/sqrt(poles[2][a]) / + (sqrt(b_)*(a_-b_))) + if a_sq.is_positive and b_.is_positive: + result = k_ * ( + sqrt(b_)*exp(a_*t)*erfc(sqrt(a_)*sqrt(t)) + + sqrt(a_)*exp(b_*t)*erf(sqrt(b_)*sqrt(t)) - + sqrt(b_)*exp(b_*t)) + _debug(' rule 5.3.9') + + if result is None: + return None + else: + return Heaviside(t)*result, condition + + +@DEBUG_WRAP +def _inverse_laplace_early_prog_rules(F, s, t, plane): + """ + Helper function for the class InverseLaplaceTransform. + """ + prog_rules = [_inverse_laplace_irrational] + + for p_rule in prog_rules: + if (r := p_rule(F, s, t, plane)) is not None: + return r + return None + + +@DEBUG_WRAP +def _inverse_laplace_apply_prog_rules(F, s, t, plane): + """ + Helper function for the class InverseLaplaceTransform. + """ + prog_rules = [_inverse_laplace_time_shift, _inverse_laplace_freq_shift, + _inverse_laplace_time_diff, _inverse_laplace_diff, + _inverse_laplace_irrational] + + for p_rule in prog_rules: + if (r := p_rule(F, s, t, plane)) is not None: + return r + return None + + +@DEBUG_WRAP +def _inverse_laplace_expand(fn, s, t, plane): + """ + Helper function for the class InverseLaplaceTransform. + """ + if fn.is_Add: + return None + r = expand(fn, deep=False) + if r.is_Add: + return _inverse_laplace_transform( + r, s, t, plane, simplify=False, dorational=True) + r = expand_mul(fn) + if r.is_Add: + return _inverse_laplace_transform( + r, s, t, plane, simplify=False, dorational=True) + r = expand(fn) + if r.is_Add: + return _inverse_laplace_transform( + r, s, t, plane, simplify=False, dorational=True) + if fn.is_rational_function(s): + r = fn.apart(s).doit() + if r.is_Add: + return _inverse_laplace_transform( + r, s, t, plane, simplify=False, dorational=True) + return None + + +@DEBUG_WRAP +def _inverse_laplace_rational(fn, s, t, plane, *, simplify): + """ + Helper function for the class InverseLaplaceTransform. + """ + x_ = symbols('x_') + f = fn.apart(s) + terms = Add.make_args(f) + terms_t = [] + conditions = [S.true] + for term in terms: + [n, d] = term.as_numer_denom() + dc = d.as_poly(s).all_coeffs() + dc_lead = dc[0] + dc = [x/dc_lead for x in dc] + nc = [x/dc_lead for x in n.as_poly(s).all_coeffs()] + if len(dc) == 1: + r = nc[0]*DiracDelta(t) + terms_t.append(r) + elif len(dc) == 2: + r = nc[0]*exp(-dc[1]*t) + terms_t.append(Heaviside(t)*r) + elif len(dc) == 3: + a = dc[1]/2 + b = (dc[2]-a**2).factor() + if len(nc) == 1: + nc = [S.Zero] + nc + l, m = tuple(nc) + if b == 0: + r = (m*t+l*(1-a*t))*exp(-a*t) + else: + hyp = False + if b.is_negative: + b = -b + hyp = True + b2 = list(roots(x_**2-b, x_).keys())[0] + bs = sqrt(b).simplify() + if hyp: + r = ( + l*exp(-a*t)*cosh(b2*t) + (m-a*l) / + bs*exp(-a*t)*sinh(bs*t)) + else: + r = l*exp(-a*t)*cos(b2*t) + (m-a*l)/bs*exp(-a*t)*sin(bs*t) + terms_t.append(Heaviside(t)*r) + else: + ft, cond = _inverse_laplace_transform( + term, s, t, plane, simplify=simplify, dorational=False) + terms_t.append(ft) + conditions.append(cond) + + result = Add(*terms_t) + if simplify: + result = result.simplify(doit=False) + return result, And(*conditions) + + +@DEBUG_WRAP +def _inverse_laplace_transform(fn, s_, t_, plane, *, simplify, dorational): + """ + Front-end function of the inverse Laplace transform. It tries to apply all + known rules recursively. If everything else fails, it tries to integrate. + """ + terms = Add.make_args(fn) + terms_t = [] + conditions = [] + + for term in terms: + if term.has(exp): + # Simplify expressions with exp() such that time-shifted + # expressions have negative exponents in the numerator instead of + # positive exponents in the numerator and denominator; this is a + # (necessary) trick. It will, for example, convert + # (s**2*exp(2*s) + 4*exp(s) - 4)*exp(-2*s)/(s*(s**2 + 1)) into + # (s**2 + 4*exp(-s) - 4*exp(-2*s))/(s*(s**2 + 1)) + term = term.subs(s_, -s_).together().subs(s_, -s_) + k, f = term.as_independent(s_, as_Add=False) + if ( + dorational and term.is_rational_function(s_) and + (r := _inverse_laplace_rational( + f, s_, t_, plane, simplify=simplify)) + is not None or + (r := _inverse_laplace_apply_simple_rules(f, s_, t_)) + is not None or + (r := _inverse_laplace_early_prog_rules(f, s_, t_, plane)) + is not None or + (r := _inverse_laplace_expand(f, s_, t_, plane)) + is not None or + (r := _inverse_laplace_apply_prog_rules(f, s_, t_, plane)) + is not None): + pass + elif any(undef.has(s_) for undef in f.atoms(AppliedUndef)): + # If there are undefined functions f(t) then integration is + # unlikely to do anything useful so we skip it and given an + # unevaluated LaplaceTransform. + r = (InverseLaplaceTransform(f, s_, t_, plane), S.true) + elif ( + r := _inverse_laplace_transform_integration( + f, s_, t_, plane, simplify=simplify)) is not None: + pass + else: + r = (InverseLaplaceTransform(f, s_, t_, plane), S.true) + (ri_, ci_) = r + terms_t.append(k*ri_) + conditions.append(ci_) + + result = Add(*terms_t) + if simplify: + result = result.simplify(doit=False) + condition = And(*conditions) + + return result, condition + + +class InverseLaplaceTransform(IntegralTransform): + """ + Class representing unevaluated inverse Laplace transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute inverse Laplace transforms, see the + :func:`inverse_laplace_transform` docstring. + """ + + _name = 'Inverse Laplace' + _none_sentinel = Dummy('None') + _c = Dummy('c') + + def __new__(cls, F, s, x, plane, **opts): + if plane is None: + plane = InverseLaplaceTransform._none_sentinel + return IntegralTransform.__new__(cls, F, s, x, plane, **opts) + + @property + def fundamental_plane(self): + plane = self.args[3] + if plane is InverseLaplaceTransform._none_sentinel: + plane = None + return plane + + def _compute_transform(self, F, s, t, **hints): + return _inverse_laplace_transform_integration( + F, s, t, self.fundamental_plane, **hints) + + def _as_integral(self, F, s, t): + c = self.__class__._c + return ( + Integral(exp(s*t)*F, (s, c - S.ImaginaryUnit*S.Infinity, + c + S.ImaginaryUnit*S.Infinity)) / + (2*S.Pi*S.ImaginaryUnit)) + + def doit(self, **hints): + """ + Try to evaluate the transform in closed form. + + Explanation + =========== + + Standard hints are the following: + - ``noconds``: if True, do not return convergence conditions. The + default setting is `True`. + - ``simplify``: if True, it simplifies the final result. The + default setting is `False`. + """ + _noconds = hints.get('noconds', True) + _simplify = hints.get('simplify', False) + + debugf('[ILT doit] (%s, %s, %s)', (self.function, + self.function_variable, + self.transform_variable)) + + s_ = self.function_variable + t_ = self.transform_variable + fn = self.function + plane = self.fundamental_plane + + r = _inverse_laplace_transform( + fn, s_, t_, plane, simplify=_simplify, dorational=True) + + if _noconds: + return r[0] + else: + return r + + +def inverse_laplace_transform(F, s, t, plane=None, **hints): + r""" + Compute the inverse Laplace transform of `F(s)`, defined as + + .. math :: + f(t) = \frac{1}{2\pi i} \int_{c-i\infty}^{c+i\infty} e^{st} + F(s) \mathrm{d}s, + + for `c` so large that `F(s)` has no singularites in the + half-plane `\operatorname{Re}(s) > c-\epsilon`. + + Explanation + =========== + + The plane can be specified by + argument ``plane``, but will be inferred if passed as None. + + Under certain regularity conditions, this recovers `f(t)` from its + Laplace Transform `F(s)`, for non-negative `t`, and vice + versa. + + If the integral cannot be computed in closed form, this function returns + an unevaluated :class:`InverseLaplaceTransform` object. + + Note that this function will always assume `t` to be real, + regardless of the SymPy assumption on `t`. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + + Examples + ======== + + >>> from sympy import inverse_laplace_transform, exp, Symbol + >>> from sympy.abc import s, t + >>> a = Symbol('a', positive=True) + >>> inverse_laplace_transform(exp(-a*s)/s, s, t) + Heaviside(-a + t) + + See Also + ======== + + laplace_transform + hankel_transform, inverse_hankel_transform + """ + _noconds = hints.get('noconds', True) + _simplify = hints.get('simplify', False) + + if isinstance(F, MatrixBase) and hasattr(F, 'applyfunc'): + return F.applyfunc( + lambda Fij: inverse_laplace_transform(Fij, s, t, plane, **hints)) + + r, c = InverseLaplaceTransform(F, s, t, plane).doit( + noconds=False, simplify=_simplify) + + if _noconds: + return r + else: + return r, c + + +def _fast_inverse_laplace(e, s, t): + """Fast inverse Laplace transform of rational function including RootSum""" + a, b, n = symbols('a, b, n', cls=Wild, exclude=[s]) + + def _ilt(e): + if not e.has(s): + return e + elif e.is_Add: + return _ilt_add(e) + elif e.is_Mul: + return _ilt_mul(e) + elif e.is_Pow: + return _ilt_pow(e) + elif isinstance(e, RootSum): + return _ilt_rootsum(e) + else: + raise NotImplementedError + + def _ilt_add(e): + return e.func(*map(_ilt, e.args)) + + def _ilt_mul(e): + coeff, expr = e.as_independent(s) + if expr.is_Mul: + raise NotImplementedError + return coeff * _ilt(expr) + + def _ilt_pow(e): + match = e.match((a*s + b)**n) + if match is not None: + nm, am, bm = match[n], match[a], match[b] + if nm.is_Integer and nm < 0: + return t**(-nm-1)*exp(-(bm/am)*t)/(am**-nm*gamma(-nm)) + if nm == 1: + return exp(-(bm/am)*t) / am + raise NotImplementedError + + def _ilt_rootsum(e): + expr = e.fun.expr + [variable] = e.fun.variables + return RootSum(e.poly, Lambda(variable, together(_ilt(expr)))) + + return _ilt(e) diff --git a/lib/python3.12/site-packages/sympy/integrals/manualintegrate.py b/lib/python3.12/site-packages/sympy/integrals/manualintegrate.py new file mode 100644 index 0000000000000000000000000000000000000000..2e0789349ca84da3ef1f08bf2603cb05868e4473 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/manualintegrate.py @@ -0,0 +1,2174 @@ +"""Integration method that emulates by-hand techniques. + +This module also provides functionality to get the steps used to evaluate a +particular integral, in the ``integral_steps`` function. This will return +nested ``Rule`` s representing the integration rules used. + +Each ``Rule`` class represents a (maybe parametrized) integration rule, e.g. +``SinRule`` for integrating ``sin(x)`` and ``ReciprocalSqrtQuadraticRule`` +for integrating ``1/sqrt(a+b*x+c*x**2)``. The ``eval`` method returns the +integration result. + +The ``manualintegrate`` function computes the integral by calling ``eval`` +on the rule returned by ``integral_steps``. + +The integrator can be extended with new heuristics and evaluation +techniques. To do so, extend the ``Rule`` class, implement ``eval`` method, +then write a function that accepts an ``IntegralInfo`` object and returns +either a ``Rule`` instance or ``None``. If the new technique requires a new +match, add the key and call to the antiderivative function to integral_steps. +To enable simple substitutions, add the match to find_substitutions. + +""" + +from __future__ import annotations +from typing import NamedTuple, Type, Callable, Sequence +from abc import ABC, abstractmethod +from dataclasses import dataclass +from collections import defaultdict +from collections.abc import Mapping + +from sympy.core.add import Add +from sympy.core.cache import cacheit +from sympy.core.containers import Dict +from sympy.core.expr import Expr +from sympy.core.function import Derivative +from sympy.core.logic import fuzzy_not +from sympy.core.mul import Mul +from sympy.core.numbers import Integer, Number, E +from sympy.core.power import Pow +from sympy.core.relational import Eq, Ne, Boolean +from sympy.core.singleton import S +from sympy.core.symbol import Dummy, Symbol, Wild +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.elementary.hyperbolic import (HyperbolicFunction, csch, + cosh, coth, sech, sinh, tanh, asinh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (TrigonometricFunction, + cos, sin, tan, cot, csc, sec, acos, asin, atan, acot, acsc, asec) +from sympy.functions.special.delta_functions import Heaviside, DiracDelta +from sympy.functions.special.error_functions import (erf, erfi, fresnelc, + fresnels, Ci, Chi, Si, Shi, Ei, li) +from sympy.functions.special.gamma_functions import uppergamma +from sympy.functions.special.elliptic_integrals import elliptic_e, elliptic_f +from sympy.functions.special.polynomials import (chebyshevt, chebyshevu, + legendre, hermite, laguerre, assoc_laguerre, gegenbauer, jacobi, + OrthogonalPolynomial) +from sympy.functions.special.zeta_functions import polylog +from .integrals import Integral +from sympy.logic.boolalg import And +from sympy.ntheory.factor_ import primefactors +from sympy.polys.polytools import degree, lcm_list, gcd_list, Poly +from sympy.simplify.radsimp import fraction +from sympy.simplify.simplify import simplify +from sympy.solvers.solvers import solve +from sympy.strategies.core import switch, do_one, null_safe, condition +from sympy.utilities.iterables import iterable +from sympy.utilities.misc import debug + + +@dataclass +class Rule(ABC): + integrand: Expr + variable: Symbol + + @abstractmethod + def eval(self) -> Expr: + pass + + @abstractmethod + def contains_dont_know(self) -> bool: + pass + + +@dataclass +class AtomicRule(Rule, ABC): + """A simple rule that does not depend on other rules""" + def contains_dont_know(self) -> bool: + return False + + +@dataclass +class ConstantRule(AtomicRule): + """integrate(a, x) -> a*x""" + def eval(self) -> Expr: + return self.integrand * self.variable + + +@dataclass +class ConstantTimesRule(Rule): + """integrate(a*f(x), x) -> a*integrate(f(x), x)""" + constant: Expr + other: Expr + substep: Rule + + def eval(self) -> Expr: + return self.constant * self.substep.eval() + + def contains_dont_know(self) -> bool: + return self.substep.contains_dont_know() + + +@dataclass +class PowerRule(AtomicRule): + """integrate(x**a, x)""" + base: Expr + exp: Expr + + def eval(self) -> Expr: + return Piecewise( + ((self.base**(self.exp + 1))/(self.exp + 1), Ne(self.exp, -1)), + (log(self.base), True), + ) + + +@dataclass +class NestedPowRule(AtomicRule): + """integrate((x**a)**b, x)""" + base: Expr + exp: Expr + + def eval(self) -> Expr: + m = self.base * self.integrand + return Piecewise((m / (self.exp + 1), Ne(self.exp, -1)), + (m * log(self.base), True)) + + +@dataclass +class AddRule(Rule): + """integrate(f(x) + g(x), x) -> integrate(f(x), x) + integrate(g(x), x)""" + substeps: list[Rule] + + def eval(self) -> Expr: + return Add(*(substep.eval() for substep in self.substeps)) + + def contains_dont_know(self) -> bool: + return any(substep.contains_dont_know() for substep in self.substeps) + + +@dataclass +class URule(Rule): + """integrate(f(g(x))*g'(x), x) -> integrate(f(u), u), u = g(x)""" + u_var: Symbol + u_func: Expr + substep: Rule + + def eval(self) -> Expr: + result = self.substep.eval() + if self.u_func.is_Pow: + base, exp_ = self.u_func.as_base_exp() + if exp_ == -1: + # avoid needless -log(1/x) from substitution + result = result.subs(log(self.u_var), -log(base)) + return result.subs(self.u_var, self.u_func) + + def contains_dont_know(self) -> bool: + return self.substep.contains_dont_know() + + +@dataclass +class PartsRule(Rule): + """integrate(u(x)*v'(x), x) -> u(x)*v(x) - integrate(u'(x)*v(x), x)""" + u: Symbol + dv: Expr + v_step: Rule + second_step: Rule | None # None when is a substep of CyclicPartsRule + + def eval(self) -> Expr: + assert self.second_step is not None + v = self.v_step.eval() + return self.u * v - self.second_step.eval() + + def contains_dont_know(self) -> bool: + return self.v_step.contains_dont_know() or ( + self.second_step is not None and self.second_step.contains_dont_know()) + + +@dataclass +class CyclicPartsRule(Rule): + """Apply PartsRule multiple times to integrate exp(x)*sin(x)""" + parts_rules: list[PartsRule] + coefficient: Expr + + def eval(self) -> Expr: + result = [] + sign = 1 + for rule in self.parts_rules: + result.append(sign * rule.u * rule.v_step.eval()) + sign *= -1 + return Add(*result) / (1 - self.coefficient) + + def contains_dont_know(self) -> bool: + return any(substep.contains_dont_know() for substep in self.parts_rules) + + +@dataclass +class TrigRule(AtomicRule, ABC): + pass + + +@dataclass +class SinRule(TrigRule): + """integrate(sin(x), x) -> -cos(x)""" + def eval(self) -> Expr: + return -cos(self.variable) + + +@dataclass +class CosRule(TrigRule): + """integrate(cos(x), x) -> sin(x)""" + def eval(self) -> Expr: + return sin(self.variable) + + +@dataclass +class SecTanRule(TrigRule): + """integrate(sec(x)*tan(x), x) -> sec(x)""" + def eval(self) -> Expr: + return sec(self.variable) + + +@dataclass +class CscCotRule(TrigRule): + """integrate(csc(x)*cot(x), x) -> -csc(x)""" + def eval(self) -> Expr: + return -csc(self.variable) + + +@dataclass +class Sec2Rule(TrigRule): + """integrate(sec(x)**2, x) -> tan(x)""" + def eval(self) -> Expr: + return tan(self.variable) + + +@dataclass +class Csc2Rule(TrigRule): + """integrate(csc(x)**2, x) -> -cot(x)""" + def eval(self) -> Expr: + return -cot(self.variable) + + +@dataclass +class HyperbolicRule(AtomicRule, ABC): + pass + + +@dataclass +class SinhRule(HyperbolicRule): + """integrate(sinh(x), x) -> cosh(x)""" + def eval(self) -> Expr: + return cosh(self.variable) + + +@dataclass +class CoshRule(HyperbolicRule): + """integrate(cosh(x), x) -> sinh(x)""" + def eval(self): + return sinh(self.variable) + + +@dataclass +class ExpRule(AtomicRule): + """integrate(a**x, x) -> a**x/ln(a)""" + base: Expr + exp: Expr + + def eval(self) -> Expr: + return self.integrand / log(self.base) + + +@dataclass +class ReciprocalRule(AtomicRule): + """integrate(1/x, x) -> ln(x)""" + base: Expr + + def eval(self) -> Expr: + return log(self.base) + + +@dataclass +class ArcsinRule(AtomicRule): + """integrate(1/sqrt(1-x**2), x) -> asin(x)""" + def eval(self) -> Expr: + return asin(self.variable) + + +@dataclass +class ArcsinhRule(AtomicRule): + """integrate(1/sqrt(1+x**2), x) -> asin(x)""" + def eval(self) -> Expr: + return asinh(self.variable) + + +@dataclass +class ReciprocalSqrtQuadraticRule(AtomicRule): + """integrate(1/sqrt(a+b*x+c*x**2), x) -> log(2*sqrt(c)*sqrt(a+b*x+c*x**2)+b+2*c*x)/sqrt(c)""" + a: Expr + b: Expr + c: Expr + + def eval(self) -> Expr: + a, b, c, x = self.a, self.b, self.c, self.variable + return log(2*sqrt(c)*sqrt(a+b*x+c*x**2)+b+2*c*x)/sqrt(c) + + +@dataclass +class SqrtQuadraticDenomRule(AtomicRule): + """integrate(poly(x)/sqrt(a+b*x+c*x**2), x)""" + a: Expr + b: Expr + c: Expr + coeffs: list[Expr] + + def eval(self) -> Expr: + a, b, c, coeffs, x = self.a, self.b, self.c, self.coeffs.copy(), self.variable + # Integrate poly/sqrt(a+b*x+c*x**2) using recursion. + # coeffs are coefficients of the polynomial. + # Let I_n = x**n/sqrt(a+b*x+c*x**2), then + # I_n = A * x**(n-1)*sqrt(a+b*x+c*x**2) - B * I_{n-1} - C * I_{n-2} + # where A = 1/(n*c), B = (2*n-1)*b/(2*n*c), C = (n-1)*a/(n*c) + # See https://github.com/sympy/sympy/pull/23608 for proof. + result_coeffs = [] + coeffs = coeffs.copy() + for i in range(len(coeffs)-2): + n = len(coeffs)-1-i + coeff = coeffs[i]/(c*n) + result_coeffs.append(coeff) + coeffs[i+1] -= (2*n-1)*b/2*coeff + coeffs[i+2] -= (n-1)*a*coeff + d, e = coeffs[-1], coeffs[-2] + s = sqrt(a+b*x+c*x**2) + constant = d-b*e/(2*c) + if constant == 0: + I0 = 0 + else: + step = inverse_trig_rule(IntegralInfo(1/s, x), degenerate=False) + I0 = constant*step.eval() + return Add(*(result_coeffs[i]*x**(len(coeffs)-2-i) + for i in range(len(result_coeffs))), e/c)*s + I0 + + +@dataclass +class SqrtQuadraticRule(AtomicRule): + """integrate(sqrt(a+b*x+c*x**2), x)""" + a: Expr + b: Expr + c: Expr + + def eval(self) -> Expr: + step = sqrt_quadratic_rule(IntegralInfo(self.integrand, self.variable), degenerate=False) + return step.eval() + + +@dataclass +class AlternativeRule(Rule): + """Multiple ways to do integration.""" + alternatives: list[Rule] + + def eval(self) -> Expr: + return self.alternatives[0].eval() + + def contains_dont_know(self) -> bool: + return any(substep.contains_dont_know() for substep in self.alternatives) + + +@dataclass +class DontKnowRule(Rule): + """Leave the integral as is.""" + def eval(self) -> Expr: + return Integral(self.integrand, self.variable) + + def contains_dont_know(self) -> bool: + return True + + +@dataclass +class DerivativeRule(AtomicRule): + """integrate(f'(x), x) -> f(x)""" + def eval(self) -> Expr: + assert isinstance(self.integrand, Derivative) + variable_count = list(self.integrand.variable_count) + for i, (var, count) in enumerate(variable_count): + if var == self.variable: + variable_count[i] = (var, count - 1) + break + return Derivative(self.integrand.expr, *variable_count) + + +@dataclass +class RewriteRule(Rule): + """Rewrite integrand to another form that is easier to handle.""" + rewritten: Expr + substep: Rule + + def eval(self) -> Expr: + return self.substep.eval() + + def contains_dont_know(self) -> bool: + return self.substep.contains_dont_know() + + +@dataclass +class CompleteSquareRule(RewriteRule): + """Rewrite a+b*x+c*x**2 to a-b**2/(4*c) + c*(x+b/(2*c))**2""" + pass + + +@dataclass +class PiecewiseRule(Rule): + subfunctions: Sequence[tuple[Rule, bool | Boolean]] + + def eval(self) -> Expr: + return Piecewise(*[(substep.eval(), cond) + for substep, cond in self.subfunctions]) + + def contains_dont_know(self) -> bool: + return any(substep.contains_dont_know() for substep, _ in self.subfunctions) + + +@dataclass +class HeavisideRule(Rule): + harg: Expr + ibnd: Expr + substep: Rule + + def eval(self) -> Expr: + # If we are integrating over x and the integrand has the form + # Heaviside(m*x+b)*g(x) == Heaviside(harg)*g(symbol) + # then there needs to be continuity at -b/m == ibnd, + # so we subtract the appropriate term. + result = self.substep.eval() + return Heaviside(self.harg) * (result - result.subs(self.variable, self.ibnd)) + + def contains_dont_know(self) -> bool: + return self.substep.contains_dont_know() + + +@dataclass +class DiracDeltaRule(AtomicRule): + n: Expr + a: Expr + b: Expr + + def eval(self) -> Expr: + n, a, b, x = self.n, self.a, self.b, self.variable + if n == 0: + return Heaviside(a+b*x)/b + return DiracDelta(a+b*x, n-1)/b + + +@dataclass +class TrigSubstitutionRule(Rule): + theta: Expr + func: Expr + rewritten: Expr + substep: Rule + restriction: bool | Boolean + + def eval(self) -> Expr: + theta, func, x = self.theta, self.func, self.variable + func = func.subs(sec(theta), 1/cos(theta)) + func = func.subs(csc(theta), 1/sin(theta)) + func = func.subs(cot(theta), 1/tan(theta)) + + trig_function = list(func.find(TrigonometricFunction)) + assert len(trig_function) == 1 + trig_function = trig_function[0] + relation = solve(x - func, trig_function) + assert len(relation) == 1 + numer, denom = fraction(relation[0]) + + if isinstance(trig_function, sin): + opposite = numer + hypotenuse = denom + adjacent = sqrt(denom**2 - numer**2) + inverse = asin(relation[0]) + elif isinstance(trig_function, cos): + adjacent = numer + hypotenuse = denom + opposite = sqrt(denom**2 - numer**2) + inverse = acos(relation[0]) + else: # tan + opposite = numer + adjacent = denom + hypotenuse = sqrt(denom**2 + numer**2) + inverse = atan(relation[0]) + + substitution = [ + (sin(theta), opposite/hypotenuse), + (cos(theta), adjacent/hypotenuse), + (tan(theta), opposite/adjacent), + (theta, inverse) + ] + return Piecewise( + (self.substep.eval().subs(substitution).trigsimp(), self.restriction) + ) + + def contains_dont_know(self) -> bool: + return self.substep.contains_dont_know() + + +@dataclass +class ArctanRule(AtomicRule): + """integrate(a/(b*x**2+c), x) -> a/b / sqrt(c/b) * atan(x/sqrt(c/b))""" + a: Expr + b: Expr + c: Expr + + def eval(self) -> Expr: + a, b, c, x = self.a, self.b, self.c, self.variable + return a/b / sqrt(c/b) * atan(x/sqrt(c/b)) + + +@dataclass +class OrthogonalPolyRule(AtomicRule, ABC): + n: Expr + + +@dataclass +class JacobiRule(OrthogonalPolyRule): + a: Expr + b: Expr + + def eval(self) -> Expr: + n, a, b, x = self.n, self.a, self.b, self.variable + return Piecewise( + (2*jacobi(n + 1, a - 1, b - 1, x)/(n + a + b), Ne(n + a + b, 0)), + (x, Eq(n, 0)), + ((a + b + 2)*x**2/4 + (a - b)*x/2, Eq(n, 1))) + + +@dataclass +class GegenbauerRule(OrthogonalPolyRule): + a: Expr + + def eval(self) -> Expr: + n, a, x = self.n, self.a, self.variable + return Piecewise( + (gegenbauer(n + 1, a - 1, x)/(2*(a - 1)), Ne(a, 1)), + (chebyshevt(n + 1, x)/(n + 1), Ne(n, -1)), + (S.Zero, True)) + + +@dataclass +class ChebyshevTRule(OrthogonalPolyRule): + def eval(self) -> Expr: + n, x = self.n, self.variable + return Piecewise( + ((chebyshevt(n + 1, x)/(n + 1) - + chebyshevt(n - 1, x)/(n - 1))/2, Ne(Abs(n), 1)), + (x**2/2, True)) + + +@dataclass +class ChebyshevURule(OrthogonalPolyRule): + def eval(self) -> Expr: + n, x = self.n, self.variable + return Piecewise( + (chebyshevt(n + 1, x)/(n + 1), Ne(n, -1)), + (S.Zero, True)) + + +@dataclass +class LegendreRule(OrthogonalPolyRule): + def eval(self) -> Expr: + n, x = self.n, self.variable + return(legendre(n + 1, x) - legendre(n - 1, x))/(2*n + 1) + + +@dataclass +class HermiteRule(OrthogonalPolyRule): + def eval(self) -> Expr: + n, x = self.n, self.variable + return hermite(n + 1, x)/(2*(n + 1)) + + +@dataclass +class LaguerreRule(OrthogonalPolyRule): + def eval(self) -> Expr: + n, x = self.n, self.variable + return laguerre(n, x) - laguerre(n + 1, x) + + +@dataclass +class AssocLaguerreRule(OrthogonalPolyRule): + a: Expr + + def eval(self) -> Expr: + return -assoc_laguerre(self.n + 1, self.a - 1, self.variable) + + +@dataclass +class IRule(AtomicRule, ABC): + a: Expr + b: Expr + + +@dataclass +class CiRule(IRule): + def eval(self) -> Expr: + a, b, x = self.a, self.b, self.variable + return cos(b)*Ci(a*x) - sin(b)*Si(a*x) + + +@dataclass +class ChiRule(IRule): + def eval(self) -> Expr: + a, b, x = self.a, self.b, self.variable + return cosh(b)*Chi(a*x) + sinh(b)*Shi(a*x) + + +@dataclass +class EiRule(IRule): + def eval(self) -> Expr: + a, b, x = self.a, self.b, self.variable + return exp(b)*Ei(a*x) + + +@dataclass +class SiRule(IRule): + def eval(self) -> Expr: + a, b, x = self.a, self.b, self.variable + return sin(b)*Ci(a*x) + cos(b)*Si(a*x) + + +@dataclass +class ShiRule(IRule): + def eval(self) -> Expr: + a, b, x = self.a, self.b, self.variable + return sinh(b)*Chi(a*x) + cosh(b)*Shi(a*x) + + +@dataclass +class LiRule(IRule): + def eval(self) -> Expr: + a, b, x = self.a, self.b, self.variable + return li(a*x + b)/a + + +@dataclass +class ErfRule(AtomicRule): + a: Expr + b: Expr + c: Expr + + def eval(self) -> Expr: + a, b, c, x = self.a, self.b, self.c, self.variable + if a.is_extended_real: + return Piecewise( + (sqrt(S.Pi)/sqrt(-a)/2 * exp(c - b**2/(4*a)) * + erf((-2*a*x - b)/(2*sqrt(-a))), a < 0), + (sqrt(S.Pi)/sqrt(a)/2 * exp(c - b**2/(4*a)) * + erfi((2*a*x + b)/(2*sqrt(a))), True)) + return sqrt(S.Pi)/sqrt(a)/2 * exp(c - b**2/(4*a)) * \ + erfi((2*a*x + b)/(2*sqrt(a))) + + +@dataclass +class FresnelCRule(AtomicRule): + a: Expr + b: Expr + c: Expr + + def eval(self) -> Expr: + a, b, c, x = self.a, self.b, self.c, self.variable + return sqrt(S.Pi)/sqrt(2*a) * ( + cos(b**2/(4*a) - c)*fresnelc((2*a*x + b)/sqrt(2*a*S.Pi)) + + sin(b**2/(4*a) - c)*fresnels((2*a*x + b)/sqrt(2*a*S.Pi))) + + +@dataclass +class FresnelSRule(AtomicRule): + a: Expr + b: Expr + c: Expr + + def eval(self) -> Expr: + a, b, c, x = self.a, self.b, self.c, self.variable + return sqrt(S.Pi)/sqrt(2*a) * ( + cos(b**2/(4*a) - c)*fresnels((2*a*x + b)/sqrt(2*a*S.Pi)) - + sin(b**2/(4*a) - c)*fresnelc((2*a*x + b)/sqrt(2*a*S.Pi))) + + +@dataclass +class PolylogRule(AtomicRule): + a: Expr + b: Expr + + def eval(self) -> Expr: + return polylog(self.b + 1, self.a * self.variable) + + +@dataclass +class UpperGammaRule(AtomicRule): + a: Expr + e: Expr + + def eval(self) -> Expr: + a, e, x = self.a, self.e, self.variable + return x**e * (-a*x)**(-e) * uppergamma(e + 1, -a*x)/a + + +@dataclass +class EllipticFRule(AtomicRule): + a: Expr + d: Expr + + def eval(self) -> Expr: + return elliptic_f(self.variable, self.d/self.a)/sqrt(self.a) + + +@dataclass +class EllipticERule(AtomicRule): + a: Expr + d: Expr + + def eval(self) -> Expr: + return elliptic_e(self.variable, self.d/self.a)*sqrt(self.a) + + +class IntegralInfo(NamedTuple): + integrand: Expr + symbol: Symbol + + +def manual_diff(f, symbol): + """Derivative of f in form expected by find_substitutions + + SymPy's derivatives for some trig functions (like cot) are not in a form + that works well with finding substitutions; this replaces the + derivatives for those particular forms with something that works better. + + """ + if f.args: + arg = f.args[0] + if isinstance(f, tan): + return arg.diff(symbol) * sec(arg)**2 + elif isinstance(f, cot): + return -arg.diff(symbol) * csc(arg)**2 + elif isinstance(f, sec): + return arg.diff(symbol) * sec(arg) * tan(arg) + elif isinstance(f, csc): + return -arg.diff(symbol) * csc(arg) * cot(arg) + elif isinstance(f, Add): + return sum(manual_diff(arg, symbol) for arg in f.args) + elif isinstance(f, Mul): + if len(f.args) == 2 and isinstance(f.args[0], Number): + return f.args[0] * manual_diff(f.args[1], symbol) + return f.diff(symbol) + +def manual_subs(expr, *args): + """ + A wrapper for `expr.subs(*args)` with additional logic for substitution + of invertible functions. + """ + if len(args) == 1: + sequence = args[0] + if isinstance(sequence, (Dict, Mapping)): + sequence = sequence.items() + elif not iterable(sequence): + raise ValueError("Expected an iterable of (old, new) pairs") + elif len(args) == 2: + sequence = [args] + else: + raise ValueError("subs accepts either 1 or 2 arguments") + + new_subs = [] + for old, new in sequence: + if isinstance(old, log): + # If log(x) = y, then exp(a*log(x)) = exp(a*y) + # that is, x**a = exp(a*y). Replace nontrivial powers of x + # before subs turns them into `exp(y)**a`, but + # do not replace x itself yet, to avoid `log(exp(y))`. + x0 = old.args[0] + expr = expr.replace(lambda x: x.is_Pow and x.base == x0, + lambda x: exp(x.exp*new)) + new_subs.append((x0, exp(new))) + + return expr.subs(list(sequence) + new_subs) + +# Method based on that on SIN, described in "Symbolic Integration: The +# Stormy Decade" + +inverse_trig_functions = (atan, asin, acos, acot, acsc, asec) + + +def find_substitutions(integrand, symbol, u_var): + results = [] + + def test_subterm(u, u_diff): + if u_diff == 0: + return False + substituted = integrand / u_diff + debug("substituted: {}, u: {}, u_var: {}".format(substituted, u, u_var)) + substituted = manual_subs(substituted, u, u_var).cancel() + + if substituted.has_free(symbol): + return False + # avoid increasing the degree of a rational function + if integrand.is_rational_function(symbol) and substituted.is_rational_function(u_var): + deg_before = max(degree(t, symbol) for t in integrand.as_numer_denom()) + deg_after = max(degree(t, u_var) for t in substituted.as_numer_denom()) + if deg_after > deg_before: + return False + return substituted.as_independent(u_var, as_Add=False) + + def exp_subterms(term: Expr): + linear_coeffs = [] + terms = [] + n = Wild('n', properties=[lambda n: n.is_Integer]) + for exp_ in term.find(exp): + arg = exp_.args[0] + if symbol not in arg.free_symbols: + continue + match = arg.match(n*symbol) + if match: + linear_coeffs.append(match[n]) + else: + terms.append(exp_) + if linear_coeffs: + terms.append(exp(gcd_list(linear_coeffs)*symbol)) + return terms + + def possible_subterms(term): + if isinstance(term, (TrigonometricFunction, HyperbolicFunction, + *inverse_trig_functions, + exp, log, Heaviside)): + return [term.args[0]] + elif isinstance(term, (chebyshevt, chebyshevu, + legendre, hermite, laguerre)): + return [term.args[1]] + elif isinstance(term, (gegenbauer, assoc_laguerre)): + return [term.args[2]] + elif isinstance(term, jacobi): + return [term.args[3]] + elif isinstance(term, Mul): + r = [] + for u in term.args: + r.append(u) + r.extend(possible_subterms(u)) + return r + elif isinstance(term, Pow): + r = [arg for arg in term.args if arg.has(symbol)] + if term.exp.is_Integer: + r.extend([term.base**d for d in primefactors(term.exp) + if 1 < d < abs(term.args[1])]) + if term.base.is_Add: + r.extend([t for t in possible_subterms(term.base) + if t.is_Pow]) + return r + elif isinstance(term, Add): + r = [] + for arg in term.args: + r.append(arg) + r.extend(possible_subterms(arg)) + return r + return [] + + for u in list(dict.fromkeys(possible_subterms(integrand) + exp_subterms(integrand))): + if u == symbol: + continue + u_diff = manual_diff(u, symbol) + new_integrand = test_subterm(u, u_diff) + if new_integrand is not False: + constant, new_integrand = new_integrand + if new_integrand == integrand.subs(symbol, u_var): + continue + substitution = (u, constant, new_integrand) + if substitution not in results: + results.append(substitution) + + return results + +def rewriter(condition, rewrite): + """Strategy that rewrites an integrand.""" + def _rewriter(integral): + integrand, symbol = integral + debug("Integral: {} is rewritten with {} on symbol: {}".format(integrand, rewrite, symbol)) + if condition(*integral): + rewritten = rewrite(*integral) + if rewritten != integrand: + substep = integral_steps(rewritten, symbol) + if not isinstance(substep, DontKnowRule) and substep: + return RewriteRule(integrand, symbol, rewritten, substep) + return _rewriter + +def proxy_rewriter(condition, rewrite): + """Strategy that rewrites an integrand based on some other criteria.""" + def _proxy_rewriter(criteria): + criteria, integral = criteria + integrand, symbol = integral + debug("Integral: {} is rewritten with {} on symbol: {} and criteria: {}".format(integrand, rewrite, symbol, criteria)) + args = criteria + list(integral) + if condition(*args): + rewritten = rewrite(*args) + if rewritten != integrand: + return RewriteRule(integrand, symbol, rewritten, integral_steps(rewritten, symbol)) + return _proxy_rewriter + +def multiplexer(conditions): + """Apply the rule that matches the condition, else None""" + def multiplexer_rl(expr): + for key, rule in conditions.items(): + if key(expr): + return rule(expr) + return multiplexer_rl + +def alternatives(*rules): + """Strategy that makes an AlternativeRule out of multiple possible results.""" + def _alternatives(integral): + alts = [] + count = 0 + debug("List of Alternative Rules") + for rule in rules: + count = count + 1 + debug("Rule {}: {}".format(count, rule)) + + result = rule(integral) + if (result and not isinstance(result, DontKnowRule) and + result != integral and result not in alts): + alts.append(result) + if len(alts) == 1: + return alts[0] + elif alts: + doable = [rule for rule in alts if not rule.contains_dont_know()] + if doable: + return AlternativeRule(*integral, doable) + else: + return AlternativeRule(*integral, alts) + return _alternatives + +def constant_rule(integral): + return ConstantRule(*integral) + +def power_rule(integral): + integrand, symbol = integral + base, expt = integrand.as_base_exp() + + if symbol not in expt.free_symbols and isinstance(base, Symbol): + if simplify(expt + 1) == 0: + return ReciprocalRule(integrand, symbol, base) + return PowerRule(integrand, symbol, base, expt) + elif symbol not in base.free_symbols and isinstance(expt, Symbol): + rule = ExpRule(integrand, symbol, base, expt) + + if fuzzy_not(log(base).is_zero): + return rule + elif log(base).is_zero: + return ConstantRule(1, symbol) + + return PiecewiseRule(integrand, symbol, [ + (rule, Ne(log(base), 0)), + (ConstantRule(1, symbol), True) + ]) + +def exp_rule(integral): + integrand, symbol = integral + if isinstance(integrand.args[0], Symbol): + return ExpRule(integrand, symbol, E, integrand.args[0]) + + +def orthogonal_poly_rule(integral): + orthogonal_poly_classes = { + jacobi: JacobiRule, + gegenbauer: GegenbauerRule, + chebyshevt: ChebyshevTRule, + chebyshevu: ChebyshevURule, + legendre: LegendreRule, + hermite: HermiteRule, + laguerre: LaguerreRule, + assoc_laguerre: AssocLaguerreRule + } + orthogonal_poly_var_index = { + jacobi: 3, + gegenbauer: 2, + assoc_laguerre: 2 + } + integrand, symbol = integral + for klass in orthogonal_poly_classes: + if isinstance(integrand, klass): + var_index = orthogonal_poly_var_index.get(klass, 1) + if (integrand.args[var_index] is symbol and not + any(v.has(symbol) for v in integrand.args[:var_index])): + return orthogonal_poly_classes[klass](integrand, symbol, *integrand.args[:var_index]) + + +_special_function_patterns: list[tuple[Type, Expr, Callable | None, tuple]] = [] +_wilds = [] +_symbol = Dummy('x') + + +def special_function_rule(integral): + integrand, symbol = integral + if not _special_function_patterns: + a = Wild('a', exclude=[_symbol], properties=[lambda x: not x.is_zero]) + b = Wild('b', exclude=[_symbol]) + c = Wild('c', exclude=[_symbol]) + d = Wild('d', exclude=[_symbol], properties=[lambda x: not x.is_zero]) + e = Wild('e', exclude=[_symbol], properties=[ + lambda x: not (x.is_nonnegative and x.is_integer)]) + _wilds.extend((a, b, c, d, e)) + # patterns consist of a SymPy class, a wildcard expr, an optional + # condition coded as a lambda (when Wild properties are not enough), + # followed by an applicable rule + linear_pattern = a*_symbol + b + quadratic_pattern = a*_symbol**2 + b*_symbol + c + _special_function_patterns.extend(( + (Mul, exp(linear_pattern, evaluate=False)/_symbol, None, EiRule), + (Mul, cos(linear_pattern, evaluate=False)/_symbol, None, CiRule), + (Mul, cosh(linear_pattern, evaluate=False)/_symbol, None, ChiRule), + (Mul, sin(linear_pattern, evaluate=False)/_symbol, None, SiRule), + (Mul, sinh(linear_pattern, evaluate=False)/_symbol, None, ShiRule), + (Pow, 1/log(linear_pattern, evaluate=False), None, LiRule), + (exp, exp(quadratic_pattern, evaluate=False), None, ErfRule), + (sin, sin(quadratic_pattern, evaluate=False), None, FresnelSRule), + (cos, cos(quadratic_pattern, evaluate=False), None, FresnelCRule), + (Mul, _symbol**e*exp(a*_symbol, evaluate=False), None, UpperGammaRule), + (Mul, polylog(b, a*_symbol, evaluate=False)/_symbol, None, PolylogRule), + (Pow, 1/sqrt(a - d*sin(_symbol, evaluate=False)**2), + lambda a, d: a != d, EllipticFRule), + (Pow, sqrt(a - d*sin(_symbol, evaluate=False)**2), + lambda a, d: a != d, EllipticERule), + )) + _integrand = integrand.subs(symbol, _symbol) + for type_, pattern, constraint, rule in _special_function_patterns: + if isinstance(_integrand, type_): + match = _integrand.match(pattern) + if match: + wild_vals = tuple(match.get(w) for w in _wilds + if match.get(w) is not None) + if constraint is None or constraint(*wild_vals): + return rule(integrand, symbol, *wild_vals) + + +def _add_degenerate_step(generic_cond, generic_step: Rule, degenerate_step: Rule | None) -> Rule: + if degenerate_step is None: + return generic_step + if isinstance(generic_step, PiecewiseRule): + subfunctions = [(substep, (cond & generic_cond).simplify()) + for substep, cond in generic_step.subfunctions] + else: + subfunctions = [(generic_step, generic_cond)] + if isinstance(degenerate_step, PiecewiseRule): + subfunctions += degenerate_step.subfunctions + else: + subfunctions.append((degenerate_step, S.true)) + return PiecewiseRule(generic_step.integrand, generic_step.variable, subfunctions) + + +def nested_pow_rule(integral: IntegralInfo): + # nested (c*(a+b*x)**d)**e + integrand, x = integral + + a_ = Wild('a', exclude=[x]) + b_ = Wild('b', exclude=[x, 0]) + pattern = a_+b_*x + generic_cond = S.true + + class NoMatch(Exception): + pass + + def _get_base_exp(expr: Expr) -> tuple[Expr, Expr]: + if not expr.has_free(x): + return S.One, S.Zero + if expr.is_Mul: + _, terms = expr.as_coeff_mul() + if not terms: + return S.One, S.Zero + results = [_get_base_exp(term) for term in terms] + bases = {b for b, _ in results} + bases.discard(S.One) + if len(bases) == 1: + return bases.pop(), Add(*(e for _, e in results)) + raise NoMatch + if expr.is_Pow: + b, e = expr.base, expr.exp # type: ignore + if e.has_free(x): + raise NoMatch + base_, sub_exp = _get_base_exp(b) + return base_, sub_exp * e + match = expr.match(pattern) + if match: + a, b = match[a_], match[b_] + base_ = x + a/b + nonlocal generic_cond + generic_cond = Ne(b, 0) + return base_, S.One + raise NoMatch + + try: + base, exp_ = _get_base_exp(integrand) + except NoMatch: + return + if generic_cond is S.true: + degenerate_step = None + else: + # equivalent with subs(b, 0) but no need to find b + degenerate_step = ConstantRule(integrand.subs(x, 0), x) + generic_step = NestedPowRule(integrand, x, base, exp_) + return _add_degenerate_step(generic_cond, generic_step, degenerate_step) + + +def inverse_trig_rule(integral: IntegralInfo, degenerate=True): + """ + Set degenerate=False on recursive call where coefficient of quadratic term + is assumed non-zero. + """ + integrand, symbol = integral + base, exp = integrand.as_base_exp() + a = Wild('a', exclude=[symbol]) + b = Wild('b', exclude=[symbol]) + c = Wild('c', exclude=[symbol, 0]) + match = base.match(a + b*symbol + c*symbol**2) + + if not match: + return + + def make_inverse_trig(RuleClass, a, sign_a, c, sign_c, h) -> Rule: + u_var = Dummy("u") + rewritten = 1/sqrt(sign_a*a + sign_c*c*(symbol-h)**2) # a>0, c>0 + quadratic_base = sqrt(c/a)*(symbol-h) + constant = 1/sqrt(c) + u_func = None + if quadratic_base is not symbol: + u_func = quadratic_base + quadratic_base = u_var + standard_form = 1/sqrt(sign_a + sign_c*quadratic_base**2) + substep = RuleClass(standard_form, quadratic_base) + if constant != 1: + substep = ConstantTimesRule(constant*standard_form, symbol, constant, standard_form, substep) + if u_func is not None: + substep = URule(rewritten, symbol, u_var, u_func, substep) + if h != 0: + substep = CompleteSquareRule(integrand, symbol, rewritten, substep) + return substep + + a, b, c = [match.get(i, S.Zero) for i in (a, b, c)] + generic_cond = Ne(c, 0) + if not degenerate or generic_cond is S.true: + degenerate_step = None + elif b.is_zero: + degenerate_step = ConstantRule(a ** exp, symbol) + else: + degenerate_step = sqrt_linear_rule(IntegralInfo((a + b * symbol) ** exp, symbol)) + + if simplify(2*exp + 1) == 0: + h, k = -b/(2*c), a - b**2/(4*c) # rewrite base to k + c*(symbol-h)**2 + non_square_cond = Ne(k, 0) + square_step = None + if non_square_cond is not S.true: + square_step = NestedPowRule(1/sqrt(c*(symbol-h)**2), symbol, symbol-h, S.NegativeOne) + if non_square_cond is S.false: + return square_step + generic_step = ReciprocalSqrtQuadraticRule(integrand, symbol, a, b, c) + step = _add_degenerate_step(non_square_cond, generic_step, square_step) + if k.is_real and c.is_real: + # list of ((rule, base_exp, a, sign_a, b, sign_b), condition) + rules = [] + for args, cond in ( # don't apply ArccoshRule to x**2-1 + ((ArcsinRule, k, 1, -c, -1, h), And(k > 0, c < 0)), # 1-x**2 + ((ArcsinhRule, k, 1, c, 1, h), And(k > 0, c > 0)), # 1+x**2 + ): + if cond is S.true: + return make_inverse_trig(*args) + if cond is not S.false: + rules.append((make_inverse_trig(*args), cond)) + if rules: + if not k.is_positive: # conditions are not thorough, need fall back rule + rules.append((generic_step, S.true)) + step = PiecewiseRule(integrand, symbol, rules) + else: + step = generic_step + return _add_degenerate_step(generic_cond, step, degenerate_step) + if exp == S.Half: + step = SqrtQuadraticRule(integrand, symbol, a, b, c) + return _add_degenerate_step(generic_cond, step, degenerate_step) + + +def add_rule(integral): + integrand, symbol = integral + results = [integral_steps(g, symbol) + for g in integrand.as_ordered_terms()] + return None if None in results else AddRule(integrand, symbol, results) + + +def mul_rule(integral: IntegralInfo): + integrand, symbol = integral + + # Constant times function case + coeff, f = integrand.as_independent(symbol) + if coeff != 1: + next_step = integral_steps(f, symbol) + if next_step is not None: + return ConstantTimesRule(integrand, symbol, coeff, f, next_step) + + +def _parts_rule(integrand, symbol) -> tuple[Expr, Expr, Expr, Expr, Rule] | None: + # LIATE rule: + # log, inverse trig, algebraic, trigonometric, exponential + def pull_out_algebraic(integrand): + integrand = integrand.cancel().together() + # iterating over Piecewise args would not work here + algebraic = ([] if isinstance(integrand, Piecewise) or not integrand.is_Mul + else [arg for arg in integrand.args if arg.is_algebraic_expr(symbol)]) + if algebraic: + u = Mul(*algebraic) + dv = (integrand / u).cancel() + return u, dv + + def pull_out_u(*functions) -> Callable[[Expr], tuple[Expr, Expr] | None]: + def pull_out_u_rl(integrand: Expr) -> tuple[Expr, Expr] | None: + if any(integrand.has(f) for f in functions): + args = [arg for arg in integrand.args + if any(isinstance(arg, cls) for cls in functions)] + if args: + u = Mul(*args) + dv = integrand / u + return u, dv + return None + + return pull_out_u_rl + + liate_rules = [pull_out_u(log), pull_out_u(*inverse_trig_functions), + pull_out_algebraic, pull_out_u(sin, cos), + pull_out_u(exp)] + + + dummy = Dummy("temporary") + # we can integrate log(x) and atan(x) by setting dv = 1 + if isinstance(integrand, (log, *inverse_trig_functions)): + integrand = dummy * integrand + + for index, rule in enumerate(liate_rules): + result = rule(integrand) + + if result: + u, dv = result + + # Don't pick u to be a constant if possible + if symbol not in u.free_symbols and not u.has(dummy): + return None + + u = u.subs(dummy, 1) + dv = dv.subs(dummy, 1) + + # Don't pick a non-polynomial algebraic to be differentiated + if rule == pull_out_algebraic and not u.is_polynomial(symbol): + return None + # Don't trade one logarithm for another + if isinstance(u, log): + rec_dv = 1/dv + if (rec_dv.is_polynomial(symbol) and + degree(rec_dv, symbol) == 1): + return None + + # Can integrate a polynomial times OrthogonalPolynomial + if rule == pull_out_algebraic: + if dv.is_Derivative or dv.has(TrigonometricFunction) or \ + isinstance(dv, OrthogonalPolynomial): + v_step = integral_steps(dv, symbol) + if v_step.contains_dont_know(): + return None + else: + du = u.diff(symbol) + v = v_step.eval() + return u, dv, v, du, v_step + + # make sure dv is amenable to integration + accept = False + if index < 2: # log and inverse trig are usually worth trying + accept = True + elif (rule == pull_out_algebraic and dv.args and + all(isinstance(a, (sin, cos, exp)) + for a in dv.args)): + accept = True + else: + for lrule in liate_rules[index + 1:]: + r = lrule(integrand) + if r and r[0].subs(dummy, 1).equals(dv): + accept = True + break + + if accept: + du = u.diff(symbol) + v_step = integral_steps(simplify(dv), symbol) + if not v_step.contains_dont_know(): + v = v_step.eval() + return u, dv, v, du, v_step + return None + + +def parts_rule(integral): + integrand, symbol = integral + constant, integrand = integrand.as_coeff_Mul() + + result = _parts_rule(integrand, symbol) + + steps = [] + if result: + u, dv, v, du, v_step = result + debug("u : {}, dv : {}, v : {}, du : {}, v_step: {}".format(u, dv, v, du, v_step)) + steps.append(result) + + if isinstance(v, Integral): + return + + # Set a limit on the number of times u can be used + if isinstance(u, (sin, cos, exp, sinh, cosh)): + cachekey = u.xreplace({symbol: _cache_dummy}) + if _parts_u_cache[cachekey] > 2: + return + _parts_u_cache[cachekey] += 1 + + # Try cyclic integration by parts a few times + for _ in range(4): + debug("Cyclic integration {} with v: {}, du: {}, integrand: {}".format(_, v, du, integrand)) + coefficient = ((v * du) / integrand).cancel() + if coefficient == 1: + break + if symbol not in coefficient.free_symbols: + rule = CyclicPartsRule(integrand, symbol, + [PartsRule(None, None, u, dv, v_step, None) + for (u, dv, v, du, v_step) in steps], + (-1) ** len(steps) * coefficient) + if (constant != 1) and rule: + rule = ConstantTimesRule(constant * integrand, symbol, constant, integrand, rule) + return rule + + # _parts_rule is sensitive to constants, factor it out + next_constant, next_integrand = (v * du).as_coeff_Mul() + result = _parts_rule(next_integrand, symbol) + + if result: + u, dv, v, du, v_step = result + u *= next_constant + du *= next_constant + steps.append((u, dv, v, du, v_step)) + else: + break + + def make_second_step(steps, integrand): + if steps: + u, dv, v, du, v_step = steps[0] + return PartsRule(integrand, symbol, u, dv, v_step, make_second_step(steps[1:], v * du)) + return integral_steps(integrand, symbol) + + if steps: + u, dv, v, du, v_step = steps[0] + rule = PartsRule(integrand, symbol, u, dv, v_step, make_second_step(steps[1:], v * du)) + if (constant != 1) and rule: + rule = ConstantTimesRule(constant * integrand, symbol, constant, integrand, rule) + return rule + + +def trig_rule(integral): + integrand, symbol = integral + if integrand == sin(symbol): + return SinRule(integrand, symbol) + if integrand == cos(symbol): + return CosRule(integrand, symbol) + if integrand == sec(symbol)**2: + return Sec2Rule(integrand, symbol) + if integrand == csc(symbol)**2: + return Csc2Rule(integrand, symbol) + + if isinstance(integrand, tan): + rewritten = sin(*integrand.args) / cos(*integrand.args) + elif isinstance(integrand, cot): + rewritten = cos(*integrand.args) / sin(*integrand.args) + elif isinstance(integrand, sec): + arg = integrand.args[0] + rewritten = ((sec(arg)**2 + tan(arg) * sec(arg)) / + (sec(arg) + tan(arg))) + elif isinstance(integrand, csc): + arg = integrand.args[0] + rewritten = ((csc(arg)**2 + cot(arg) * csc(arg)) / + (csc(arg) + cot(arg))) + else: + return + + return RewriteRule(integrand, symbol, rewritten, integral_steps(rewritten, symbol)) + +def trig_product_rule(integral: IntegralInfo): + integrand, symbol = integral + if integrand == sec(symbol) * tan(symbol): + return SecTanRule(integrand, symbol) + if integrand == csc(symbol) * cot(symbol): + return CscCotRule(integrand, symbol) + + +def quadratic_denom_rule(integral): + integrand, symbol = integral + a = Wild('a', exclude=[symbol]) + b = Wild('b', exclude=[symbol]) + c = Wild('c', exclude=[symbol]) + + match = integrand.match(a / (b * symbol ** 2 + c)) + + if match: + a, b, c = match[a], match[b], match[c] + general_rule = ArctanRule(integrand, symbol, a, b, c) + if b.is_extended_real and c.is_extended_real: + positive_cond = c/b > 0 + if positive_cond is S.true: + return general_rule + coeff = a/(2*sqrt(-c)*sqrt(b)) + constant = sqrt(-c/b) + r1 = 1/(symbol-constant) + r2 = 1/(symbol+constant) + log_steps = [ReciprocalRule(r1, symbol, symbol-constant), + ConstantTimesRule(-r2, symbol, -1, r2, ReciprocalRule(r2, symbol, symbol+constant))] + rewritten = sub = r1 - r2 + negative_step = AddRule(sub, symbol, log_steps) + if coeff != 1: + rewritten = Mul(coeff, sub, evaluate=False) + negative_step = ConstantTimesRule(rewritten, symbol, coeff, sub, negative_step) + negative_step = RewriteRule(integrand, symbol, rewritten, negative_step) + if positive_cond is S.false: + return negative_step + return PiecewiseRule(integrand, symbol, [(general_rule, positive_cond), (negative_step, S.true)]) + + power = PowerRule(integrand, symbol, symbol, -2) + if b != 1: + power = ConstantTimesRule(integrand, symbol, 1/b, symbol**-2, power) + + return PiecewiseRule(integrand, symbol, [(general_rule, Ne(c, 0)), (power, True)]) + + d = Wild('d', exclude=[symbol]) + match2 = integrand.match(a / (b * symbol ** 2 + c * symbol + d)) + if match2: + b, c = match2[b], match2[c] + if b.is_zero: + return + u = Dummy('u') + u_func = symbol + c/(2*b) + integrand2 = integrand.subs(symbol, u - c / (2*b)) + next_step = integral_steps(integrand2, u) + if next_step: + return URule(integrand2, symbol, u, u_func, next_step) + else: + return + e = Wild('e', exclude=[symbol]) + match3 = integrand.match((a* symbol + b) / (c * symbol ** 2 + d * symbol + e)) + if match3: + a, b, c, d, e = match3[a], match3[b], match3[c], match3[d], match3[e] + if c.is_zero: + return + denominator = c * symbol**2 + d * symbol + e + const = a/(2*c) + numer1 = (2*c*symbol+d) + numer2 = - const*d + b + u = Dummy('u') + step1 = URule(integrand, symbol, + u, denominator, integral_steps(u**(-1), u)) + if const != 1: + step1 = ConstantTimesRule(const*numer1/denominator, symbol, + const, numer1/denominator, step1) + if numer2.is_zero: + return step1 + step2 = integral_steps(numer2/denominator, symbol) + substeps = AddRule(integrand, symbol, [step1, step2]) + rewriten = const*numer1/denominator+numer2/denominator + return RewriteRule(integrand, symbol, rewriten, substeps) + + return + + +def sqrt_linear_rule(integral: IntegralInfo): + """ + Substitute common (a+b*x)**(1/n) + """ + integrand, x = integral + a = Wild('a', exclude=[x]) + b = Wild('b', exclude=[x, 0]) + a0 = b0 = 0 + bases, qs, bs = [], [], [] + for pow_ in integrand.find(Pow): # collect all (a+b*x)**(p/q) + base, exp_ = pow_.base, pow_.exp + if exp_.is_Integer or x not in base.free_symbols: # skip 1/x and sqrt(2) + continue + if not exp_.is_Rational: # exclude x**pi + return + match = base.match(a+b*x) + if not match: # skip non-linear + continue # for sqrt(x+sqrt(x)), although base is non-linear, we can still substitute sqrt(x) + a1, b1 = match[a], match[b] + if a0*b1 != a1*b0 or not (b0/b1).is_nonnegative: # cannot transform sqrt(x) to sqrt(x+1) or sqrt(-x) + return + if b0 == 0 or (b0/b1 > 1) is S.true: # choose the latter of sqrt(2*x) and sqrt(x) as representative + a0, b0 = a1, b1 + bases.append(base) + bs.append(b1) + qs.append(exp_.q) + if b0 == 0: # no such pattern found + return + q0: Integer = lcm_list(qs) + u_x = (a0 + b0*x)**(1/q0) + u = Dummy("u") + substituted = integrand.subs({base**(S.One/q): (b/b0)**(S.One/q)*u**(q0/q) + for base, b, q in zip(bases, bs, qs)}).subs(x, (u**q0-a0)/b0) + substep = integral_steps(substituted*u**(q0-1)*q0/b0, u) + if not substep.contains_dont_know(): + step: Rule = URule(integrand, x, u, u_x, substep) + generic_cond = Ne(b0, 0) + if generic_cond is not S.true: # possible degenerate case + simplified = integrand.subs(dict.fromkeys(bs, 0)) + degenerate_step = integral_steps(simplified, x) + step = PiecewiseRule(integrand, x, [(step, generic_cond), (degenerate_step, S.true)]) + return step + + +def sqrt_quadratic_rule(integral: IntegralInfo, degenerate=True): + integrand, x = integral + a = Wild('a', exclude=[x]) + b = Wild('b', exclude=[x]) + c = Wild('c', exclude=[x, 0]) + f = Wild('f') + n = Wild('n', properties=[lambda n: n.is_Integer and n.is_odd]) + match = integrand.match(f*sqrt(a+b*x+c*x**2)**n) + if not match: + return + a, b, c, f, n = match[a], match[b], match[c], match[f], match[n] + f_poly = f.as_poly(x) + if f_poly is None: + return + + generic_cond = Ne(c, 0) + if not degenerate or generic_cond is S.true: + degenerate_step = None + elif b.is_zero: + degenerate_step = integral_steps(f*sqrt(a)**n, x) + else: + degenerate_step = sqrt_linear_rule(IntegralInfo(f*sqrt(a+b*x)**n, x)) + + def sqrt_quadratic_denom_rule(numer_poly: Poly, integrand: Expr): + denom = sqrt(a+b*x+c*x**2) + deg = numer_poly.degree() + if deg <= 1: + # integrand == (d+e*x)/sqrt(a+b*x+c*x**2) + e, d = numer_poly.all_coeffs() if deg == 1 else (S.Zero, numer_poly.as_expr()) + # rewrite numerator to A*(2*c*x+b) + B + A = e/(2*c) + B = d-A*b + pre_substitute = (2*c*x+b)/denom + constant_step: Rule | None = None + linear_step: Rule | None = None + if A != 0: + u = Dummy("u") + pow_rule = PowerRule(1/sqrt(u), u, u, -S.Half) + linear_step = URule(pre_substitute, x, u, a+b*x+c*x**2, pow_rule) + if A != 1: + linear_step = ConstantTimesRule(A*pre_substitute, x, A, pre_substitute, linear_step) + if B != 0: + constant_step = inverse_trig_rule(IntegralInfo(1/denom, x), degenerate=False) + if B != 1: + constant_step = ConstantTimesRule(B/denom, x, B, 1/denom, constant_step) # type: ignore + if linear_step and constant_step: + add = Add(A*pre_substitute, B/denom, evaluate=False) + step: Rule | None = RewriteRule(integrand, x, add, AddRule(add, x, [linear_step, constant_step])) + else: + step = linear_step or constant_step + else: + coeffs = numer_poly.all_coeffs() + step = SqrtQuadraticDenomRule(integrand, x, a, b, c, coeffs) + return step + + if n > 0: # rewrite poly * sqrt(s)**(2*k-1) to poly*s**k / sqrt(s) + numer_poly = f_poly * (a+b*x+c*x**2)**((n+1)/2) + rewritten = numer_poly.as_expr()/sqrt(a+b*x+c*x**2) + substep = sqrt_quadratic_denom_rule(numer_poly, rewritten) + generic_step = RewriteRule(integrand, x, rewritten, substep) + elif n == -1: + generic_step = sqrt_quadratic_denom_rule(f_poly, integrand) + else: + return # todo: handle n < -1 case + return _add_degenerate_step(generic_cond, generic_step, degenerate_step) + + +def hyperbolic_rule(integral: tuple[Expr, Symbol]): + integrand, symbol = integral + if isinstance(integrand, HyperbolicFunction) and integrand.args[0] == symbol: + if integrand.func == sinh: + return SinhRule(integrand, symbol) + if integrand.func == cosh: + return CoshRule(integrand, symbol) + u = Dummy('u') + if integrand.func == tanh: + rewritten = sinh(symbol)/cosh(symbol) + return RewriteRule(integrand, symbol, rewritten, + URule(rewritten, symbol, u, cosh(symbol), ReciprocalRule(1/u, u, u))) + if integrand.func == coth: + rewritten = cosh(symbol)/sinh(symbol) + return RewriteRule(integrand, symbol, rewritten, + URule(rewritten, symbol, u, sinh(symbol), ReciprocalRule(1/u, u, u))) + else: + rewritten = integrand.rewrite(tanh) + if integrand.func == sech: + return RewriteRule(integrand, symbol, rewritten, + URule(rewritten, symbol, u, tanh(symbol/2), + ArctanRule(2/(u**2 + 1), u, S(2), S.One, S.One))) + if integrand.func == csch: + return RewriteRule(integrand, symbol, rewritten, + URule(rewritten, symbol, u, tanh(symbol/2), + ReciprocalRule(1/u, u, u))) + +@cacheit +def make_wilds(symbol): + a = Wild('a', exclude=[symbol]) + b = Wild('b', exclude=[symbol]) + m = Wild('m', exclude=[symbol], properties=[lambda n: isinstance(n, Integer)]) + n = Wild('n', exclude=[symbol], properties=[lambda n: isinstance(n, Integer)]) + + return a, b, m, n + +@cacheit +def sincos_pattern(symbol): + a, b, m, n = make_wilds(symbol) + pattern = sin(a*symbol)**m * cos(b*symbol)**n + + return pattern, a, b, m, n + +@cacheit +def tansec_pattern(symbol): + a, b, m, n = make_wilds(symbol) + pattern = tan(a*symbol)**m * sec(b*symbol)**n + + return pattern, a, b, m, n + +@cacheit +def cotcsc_pattern(symbol): + a, b, m, n = make_wilds(symbol) + pattern = cot(a*symbol)**m * csc(b*symbol)**n + + return pattern, a, b, m, n + +@cacheit +def heaviside_pattern(symbol): + m = Wild('m', exclude=[symbol]) + b = Wild('b', exclude=[symbol]) + g = Wild('g') + pattern = Heaviside(m*symbol + b) * g + + return pattern, m, b, g + +def uncurry(func): + def uncurry_rl(args): + return func(*args) + return uncurry_rl + +def trig_rewriter(rewrite): + def trig_rewriter_rl(args): + a, b, m, n, integrand, symbol = args + rewritten = rewrite(a, b, m, n, integrand, symbol) + if rewritten != integrand: + return RewriteRule(integrand, symbol, rewritten, integral_steps(rewritten, symbol)) + return trig_rewriter_rl + +sincos_botheven_condition = uncurry( + lambda a, b, m, n, i, s: m.is_even and n.is_even and + m.is_nonnegative and n.is_nonnegative) + +sincos_botheven = trig_rewriter( + lambda a, b, m, n, i, symbol: ( (((1 - cos(2*a*symbol)) / 2) ** (m / 2)) * + (((1 + cos(2*b*symbol)) / 2) ** (n / 2)) )) + +sincos_sinodd_condition = uncurry(lambda a, b, m, n, i, s: m.is_odd and m >= 3) + +sincos_sinodd = trig_rewriter( + lambda a, b, m, n, i, symbol: ( (1 - cos(a*symbol)**2)**((m - 1) / 2) * + sin(a*symbol) * + cos(b*symbol) ** n)) + +sincos_cosodd_condition = uncurry(lambda a, b, m, n, i, s: n.is_odd and n >= 3) + +sincos_cosodd = trig_rewriter( + lambda a, b, m, n, i, symbol: ( (1 - sin(b*symbol)**2)**((n - 1) / 2) * + cos(b*symbol) * + sin(a*symbol) ** m)) + +tansec_seceven_condition = uncurry(lambda a, b, m, n, i, s: n.is_even and n >= 4) +tansec_seceven = trig_rewriter( + lambda a, b, m, n, i, symbol: ( (1 + tan(b*symbol)**2) ** (n/2 - 1) * + sec(b*symbol)**2 * + tan(a*symbol) ** m )) + +tansec_tanodd_condition = uncurry(lambda a, b, m, n, i, s: m.is_odd) +tansec_tanodd = trig_rewriter( + lambda a, b, m, n, i, symbol: ( (sec(a*symbol)**2 - 1) ** ((m - 1) / 2) * + tan(a*symbol) * + sec(b*symbol) ** n )) + +tan_tansquared_condition = uncurry(lambda a, b, m, n, i, s: m == 2 and n == 0) +tan_tansquared = trig_rewriter( + lambda a, b, m, n, i, symbol: ( sec(a*symbol)**2 - 1)) + +cotcsc_csceven_condition = uncurry(lambda a, b, m, n, i, s: n.is_even and n >= 4) +cotcsc_csceven = trig_rewriter( + lambda a, b, m, n, i, symbol: ( (1 + cot(b*symbol)**2) ** (n/2 - 1) * + csc(b*symbol)**2 * + cot(a*symbol) ** m )) + +cotcsc_cotodd_condition = uncurry(lambda a, b, m, n, i, s: m.is_odd) +cotcsc_cotodd = trig_rewriter( + lambda a, b, m, n, i, symbol: ( (csc(a*symbol)**2 - 1) ** ((m - 1) / 2) * + cot(a*symbol) * + csc(b*symbol) ** n )) + +def trig_sincos_rule(integral): + integrand, symbol = integral + + if any(integrand.has(f) for f in (sin, cos)): + pattern, a, b, m, n = sincos_pattern(symbol) + match = integrand.match(pattern) + if not match: + return + + return multiplexer({ + sincos_botheven_condition: sincos_botheven, + sincos_sinodd_condition: sincos_sinodd, + sincos_cosodd_condition: sincos_cosodd + })(tuple( + [match.get(i, S.Zero) for i in (a, b, m, n)] + + [integrand, symbol])) + +def trig_tansec_rule(integral): + integrand, symbol = integral + + integrand = integrand.subs({ + 1 / cos(symbol): sec(symbol) + }) + + if any(integrand.has(f) for f in (tan, sec)): + pattern, a, b, m, n = tansec_pattern(symbol) + match = integrand.match(pattern) + if not match: + return + + return multiplexer({ + tansec_tanodd_condition: tansec_tanodd, + tansec_seceven_condition: tansec_seceven, + tan_tansquared_condition: tan_tansquared + })(tuple( + [match.get(i, S.Zero) for i in (a, b, m, n)] + + [integrand, symbol])) + +def trig_cotcsc_rule(integral): + integrand, symbol = integral + integrand = integrand.subs({ + 1 / sin(symbol): csc(symbol), + 1 / tan(symbol): cot(symbol), + cos(symbol) / tan(symbol): cot(symbol) + }) + + if any(integrand.has(f) for f in (cot, csc)): + pattern, a, b, m, n = cotcsc_pattern(symbol) + match = integrand.match(pattern) + if not match: + return + + return multiplexer({ + cotcsc_cotodd_condition: cotcsc_cotodd, + cotcsc_csceven_condition: cotcsc_csceven + })(tuple( + [match.get(i, S.Zero) for i in (a, b, m, n)] + + [integrand, symbol])) + +def trig_sindouble_rule(integral): + integrand, symbol = integral + a = Wild('a', exclude=[sin(2*symbol)]) + match = integrand.match(sin(2*symbol)*a) + if match: + sin_double = 2*sin(symbol)*cos(symbol)/sin(2*symbol) + return integral_steps(integrand * sin_double, symbol) + +def trig_powers_products_rule(integral): + return do_one(null_safe(trig_sincos_rule), + null_safe(trig_tansec_rule), + null_safe(trig_cotcsc_rule), + null_safe(trig_sindouble_rule))(integral) + +def trig_substitution_rule(integral): + integrand, symbol = integral + A = Wild('a', exclude=[0, symbol]) + B = Wild('b', exclude=[0, symbol]) + theta = Dummy("theta") + target_pattern = A + B*symbol**2 + + matches = integrand.find(target_pattern) + for expr in matches: + match = expr.match(target_pattern) + a = match.get(A, S.Zero) + b = match.get(B, S.Zero) + + a_positive = ((a.is_number and a > 0) or a.is_positive) + b_positive = ((b.is_number and b > 0) or b.is_positive) + a_negative = ((a.is_number and a < 0) or a.is_negative) + b_negative = ((b.is_number and b < 0) or b.is_negative) + x_func = None + if a_positive and b_positive: + # a**2 + b*x**2. Assume sec(theta) > 0, -pi/2 < theta < pi/2 + x_func = (sqrt(a)/sqrt(b)) * tan(theta) + # Do not restrict the domain: tan(theta) takes on any real + # value on the interval -pi/2 < theta < pi/2 so x takes on + # any value + restriction = True + elif a_positive and b_negative: + # a**2 - b*x**2. Assume cos(theta) > 0, -pi/2 < theta < pi/2 + constant = sqrt(a)/sqrt(-b) + x_func = constant * sin(theta) + restriction = And(symbol > -constant, symbol < constant) + elif a_negative and b_positive: + # b*x**2 - a**2. Assume sin(theta) > 0, 0 < theta < pi + constant = sqrt(-a)/sqrt(b) + x_func = constant * sec(theta) + restriction = And(symbol > -constant, symbol < constant) + if x_func: + # Manually simplify sqrt(trig(theta)**2) to trig(theta) + # Valid due to assumed domain restriction + substitutions = {} + for f in [sin, cos, tan, + sec, csc, cot]: + substitutions[sqrt(f(theta)**2)] = f(theta) + substitutions[sqrt(f(theta)**(-2))] = 1/f(theta) + + replaced = integrand.subs(symbol, x_func).trigsimp() + replaced = manual_subs(replaced, substitutions) + if not replaced.has(symbol): + replaced *= manual_diff(x_func, theta) + replaced = replaced.trigsimp() + secants = replaced.find(1/cos(theta)) + if secants: + replaced = replaced.xreplace({ + 1/cos(theta): sec(theta) + }) + + substep = integral_steps(replaced, theta) + if not substep.contains_dont_know(): + return TrigSubstitutionRule(integrand, symbol, + theta, x_func, replaced, substep, restriction) + +def heaviside_rule(integral): + integrand, symbol = integral + pattern, m, b, g = heaviside_pattern(symbol) + match = integrand.match(pattern) + if match and 0 != match[g]: + # f = Heaviside(m*x + b)*g + substep = integral_steps(match[g], symbol) + m, b = match[m], match[b] + return HeavisideRule(integrand, symbol, m*symbol + b, -b/m, substep) + + +def dirac_delta_rule(integral: IntegralInfo): + integrand, x = integral + if len(integrand.args) == 1: + n = S.Zero + else: + n = integrand.args[1] + if not n.is_Integer or n < 0: + return + a, b = Wild('a', exclude=[x]), Wild('b', exclude=[x, 0]) + match = integrand.args[0].match(a+b*x) + if not match: + return + a, b = match[a], match[b] + generic_cond = Ne(b, 0) + if generic_cond is S.true: + degenerate_step = None + else: + degenerate_step = ConstantRule(DiracDelta(a, n), x) + generic_step = DiracDeltaRule(integrand, x, n, a, b) + return _add_degenerate_step(generic_cond, generic_step, degenerate_step) + + +def substitution_rule(integral): + integrand, symbol = integral + + u_var = Dummy("u") + substitutions = find_substitutions(integrand, symbol, u_var) + count = 0 + if substitutions: + debug("List of Substitution Rules") + ways = [] + for u_func, c, substituted in substitutions: + subrule = integral_steps(substituted, u_var) + count = count + 1 + debug("Rule {}: {}".format(count, subrule)) + + if subrule.contains_dont_know(): + continue + + if simplify(c - 1) != 0: + _, denom = c.as_numer_denom() + if subrule: + subrule = ConstantTimesRule(c * substituted, u_var, c, substituted, subrule) + + if denom.free_symbols: + piecewise = [] + could_be_zero = [] + + if isinstance(denom, Mul): + could_be_zero = denom.args + else: + could_be_zero.append(denom) + + for expr in could_be_zero: + if not fuzzy_not(expr.is_zero): + substep = integral_steps(manual_subs(integrand, expr, 0), symbol) + + if substep: + piecewise.append(( + substep, + Eq(expr, 0) + )) + piecewise.append((subrule, True)) + subrule = PiecewiseRule(substituted, symbol, piecewise) + + ways.append(URule(integrand, symbol, u_var, u_func, subrule)) + + if len(ways) > 1: + return AlternativeRule(integrand, symbol, ways) + elif ways: + return ways[0] + + +partial_fractions_rule = rewriter( + lambda integrand, symbol: integrand.is_rational_function(), + lambda integrand, symbol: integrand.apart(symbol)) + +cancel_rule = rewriter( + # lambda integrand, symbol: integrand.is_algebraic_expr(), + # lambda integrand, symbol: isinstance(integrand, Mul), + lambda integrand, symbol: True, + lambda integrand, symbol: integrand.cancel()) + +distribute_expand_rule = rewriter( + lambda integrand, symbol: ( + isinstance(integrand, (Pow, Mul)) or all(arg.is_Pow or arg.is_polynomial(symbol) for arg in integrand.args)), + lambda integrand, symbol: integrand.expand()) + +trig_expand_rule = rewriter( + # If there are trig functions with different arguments, expand them + lambda integrand, symbol: ( + len({a.args[0] for a in integrand.atoms(TrigonometricFunction)}) > 1), + lambda integrand, symbol: integrand.expand(trig=True)) + +def derivative_rule(integral): + integrand = integral[0] + diff_variables = integrand.variables + undifferentiated_function = integrand.expr + integrand_variables = undifferentiated_function.free_symbols + + if integral.symbol in integrand_variables: + if integral.symbol in diff_variables: + return DerivativeRule(*integral) + else: + return DontKnowRule(integrand, integral.symbol) + else: + return ConstantRule(*integral) + +def rewrites_rule(integral): + integrand, symbol = integral + + if integrand.match(1/cos(symbol)): + rewritten = integrand.subs(1/cos(symbol), sec(symbol)) + return RewriteRule(integrand, symbol, rewritten, integral_steps(rewritten, symbol)) + +def fallback_rule(integral): + return DontKnowRule(*integral) + +# Cache is used to break cyclic integrals. +# Need to use the same dummy variable in cached expressions for them to match. +# Also record "u" of integration by parts, to avoid infinite repetition. +_integral_cache: dict[Expr, Expr | None] = {} +_parts_u_cache: dict[Expr, int] = defaultdict(int) +_cache_dummy = Dummy("z") + +def integral_steps(integrand, symbol, **options): + """Returns the steps needed to compute an integral. + + Explanation + =========== + + This function attempts to mirror what a student would do by hand as + closely as possible. + + SymPy Gamma uses this to provide a step-by-step explanation of an + integral. The code it uses to format the results of this function can be + found at + https://github.com/sympy/sympy_gamma/blob/master/app/logic/intsteps.py. + + Examples + ======== + + >>> from sympy import exp, sin + >>> from sympy.integrals.manualintegrate import integral_steps + >>> from sympy.abc import x + >>> print(repr(integral_steps(exp(x) / (1 + exp(2 * x)), x))) \ + # doctest: +NORMALIZE_WHITESPACE + URule(integrand=exp(x)/(exp(2*x) + 1), variable=x, u_var=_u, u_func=exp(x), + substep=ArctanRule(integrand=1/(_u**2 + 1), variable=_u, a=1, b=1, c=1)) + >>> print(repr(integral_steps(sin(x), x))) \ + # doctest: +NORMALIZE_WHITESPACE + SinRule(integrand=sin(x), variable=x) + >>> print(repr(integral_steps((x**2 + 3)**2, x))) \ + # doctest: +NORMALIZE_WHITESPACE + RewriteRule(integrand=(x**2 + 3)**2, variable=x, rewritten=x**4 + 6*x**2 + 9, + substep=AddRule(integrand=x**4 + 6*x**2 + 9, variable=x, + substeps=[PowerRule(integrand=x**4, variable=x, base=x, exp=4), + ConstantTimesRule(integrand=6*x**2, variable=x, constant=6, other=x**2, + substep=PowerRule(integrand=x**2, variable=x, base=x, exp=2)), + ConstantRule(integrand=9, variable=x)])) + + Returns + ======= + + rule : Rule + The first step; most rules have substeps that must also be + considered. These substeps can be evaluated using ``manualintegrate`` + to obtain a result. + + """ + cachekey = integrand.xreplace({symbol: _cache_dummy}) + if cachekey in _integral_cache: + if _integral_cache[cachekey] is None: + # Stop this attempt, because it leads around in a loop + return DontKnowRule(integrand, symbol) + else: + # TODO: This is for future development, as currently + # _integral_cache gets no values other than None + return (_integral_cache[cachekey].xreplace(_cache_dummy, symbol), + symbol) + else: + _integral_cache[cachekey] = None + + integral = IntegralInfo(integrand, symbol) + + def key(integral): + integrand = integral.integrand + + if symbol not in integrand.free_symbols: + return Number + for cls in (Symbol, TrigonometricFunction, OrthogonalPolynomial): + if isinstance(integrand, cls): + return cls + return type(integrand) + + def integral_is_subclass(*klasses): + def _integral_is_subclass(integral): + k = key(integral) + return k and issubclass(k, klasses) + return _integral_is_subclass + + result = do_one( + null_safe(special_function_rule), + null_safe(switch(key, { + Pow: do_one(null_safe(power_rule), null_safe(inverse_trig_rule), + null_safe(sqrt_linear_rule), + null_safe(quadratic_denom_rule)), + Symbol: power_rule, + exp: exp_rule, + Add: add_rule, + Mul: do_one(null_safe(mul_rule), null_safe(trig_product_rule), + null_safe(heaviside_rule), null_safe(quadratic_denom_rule), + null_safe(sqrt_linear_rule), + null_safe(sqrt_quadratic_rule)), + Derivative: derivative_rule, + TrigonometricFunction: trig_rule, + Heaviside: heaviside_rule, + DiracDelta: dirac_delta_rule, + OrthogonalPolynomial: orthogonal_poly_rule, + Number: constant_rule + })), + do_one( + null_safe(trig_rule), + null_safe(hyperbolic_rule), + null_safe(alternatives( + rewrites_rule, + substitution_rule, + condition( + integral_is_subclass(Mul, Pow), + partial_fractions_rule), + condition( + integral_is_subclass(Mul, Pow), + cancel_rule), + condition( + integral_is_subclass(Mul, log, + *inverse_trig_functions), + parts_rule), + condition( + integral_is_subclass(Mul, Pow), + distribute_expand_rule), + trig_powers_products_rule, + trig_expand_rule + )), + null_safe(condition(integral_is_subclass(Mul, Pow), nested_pow_rule)), + null_safe(trig_substitution_rule) + ), + fallback_rule)(integral) + del _integral_cache[cachekey] + return result + + +def manualintegrate(f, var): + """manualintegrate(f, var) + + Explanation + =========== + + Compute indefinite integral of a single variable using an algorithm that + resembles what a student would do by hand. + + Unlike :func:`~.integrate`, var can only be a single symbol. + + Examples + ======== + + >>> from sympy import sin, cos, tan, exp, log, integrate + >>> from sympy.integrals.manualintegrate import manualintegrate + >>> from sympy.abc import x + >>> manualintegrate(1 / x, x) + log(x) + >>> integrate(1/x) + log(x) + >>> manualintegrate(log(x), x) + x*log(x) - x + >>> integrate(log(x)) + x*log(x) - x + >>> manualintegrate(exp(x) / (1 + exp(2 * x)), x) + atan(exp(x)) + >>> integrate(exp(x) / (1 + exp(2 * x))) + RootSum(4*_z**2 + 1, Lambda(_i, _i*log(2*_i + exp(x)))) + >>> manualintegrate(cos(x)**4 * sin(x), x) + -cos(x)**5/5 + >>> integrate(cos(x)**4 * sin(x), x) + -cos(x)**5/5 + >>> manualintegrate(cos(x)**4 * sin(x)**3, x) + cos(x)**7/7 - cos(x)**5/5 + >>> integrate(cos(x)**4 * sin(x)**3, x) + cos(x)**7/7 - cos(x)**5/5 + >>> manualintegrate(tan(x), x) + -log(cos(x)) + >>> integrate(tan(x), x) + -log(cos(x)) + + See Also + ======== + + sympy.integrals.integrals.integrate + sympy.integrals.integrals.Integral.doit + sympy.integrals.integrals.Integral + """ + result = integral_steps(f, var).eval() + # Clear the cache of u-parts + _parts_u_cache.clear() + # If we got Piecewise with two parts, put generic first + if isinstance(result, Piecewise) and len(result.args) == 2: + cond = result.args[0][1] + if isinstance(cond, Eq) and result.args[1][1] == True: + result = result.func( + (result.args[1][0], Ne(*cond.args)), + (result.args[0][0], True)) + return result diff --git a/lib/python3.12/site-packages/sympy/integrals/meijerint.py b/lib/python3.12/site-packages/sympy/integrals/meijerint.py new file mode 100644 index 0000000000000000000000000000000000000000..5b518fdf2a16b5de63e311bf706fd5a4678e6cee --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/meijerint.py @@ -0,0 +1,2191 @@ +""" +Integrate functions by rewriting them as Meijer G-functions. + +There are three user-visible functions that can be used by other parts of the +sympy library to solve various integration problems: + +- meijerint_indefinite +- meijerint_definite +- meijerint_inversion + +They can be used to compute, respectively, indefinite integrals, definite +integrals over intervals of the real line, and inverse laplace-type integrals +(from c-I*oo to c+I*oo). See the respective docstrings for details. + +The main references for this are: + +[L] Luke, Y. L. (1969), The Special Functions and Their Approximations, + Volume 1 + +[R] Kelly B. Roach. Meijer G Function Representations. + In: Proceedings of the 1997 International Symposium on Symbolic and + Algebraic Computation, pages 205-211, New York, 1997. ACM. + +[P] A. P. Prudnikov, Yu. A. Brychkov and O. I. Marichev (1990). + Integrals and Series: More Special Functions, Vol. 3,. + Gordon and Breach Science Publisher +""" + +from __future__ import annotations +import itertools + +from sympy import SYMPY_DEBUG +from sympy.core import S, Expr +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.cache import cacheit +from sympy.core.containers import Tuple +from sympy.core.exprtools import factor_terms +from sympy.core.function import (expand, expand_mul, expand_power_base, + expand_trig, Function) +from sympy.core.mul import Mul +from sympy.core.intfunc import ilcm +from sympy.core.numbers import Rational, pi +from sympy.core.relational import Eq, Ne, _canonical_coeff +from sympy.core.sorting import default_sort_key, ordered +from sympy.core.symbol import Dummy, symbols, Wild, Symbol +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.complexes import (re, im, arg, Abs, sign, + unpolarify, polarify, polar_lift, principal_branch, unbranched_argument, + periodic_argument) +from sympy.functions.elementary.exponential import exp, exp_polar, log +from sympy.functions.elementary.integers import ceiling +from sympy.functions.elementary.hyperbolic import (cosh, sinh, + _rewrite_hyperbolics_as_exp, HyperbolicFunction) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise, piecewise_fold +from sympy.functions.elementary.trigonometric import (cos, sin, sinc, + TrigonometricFunction) +from sympy.functions.special.bessel import besselj, bessely, besseli, besselk +from sympy.functions.special.delta_functions import DiracDelta, Heaviside +from sympy.functions.special.elliptic_integrals import elliptic_k, elliptic_e +from sympy.functions.special.error_functions import (erf, erfc, erfi, Ei, + expint, Si, Ci, Shi, Chi, fresnels, fresnelc) +from sympy.functions.special.gamma_functions import gamma +from sympy.functions.special.hyper import hyper, meijerg +from sympy.functions.special.singularity_functions import SingularityFunction +from .integrals import Integral +from sympy.logic.boolalg import And, Or, BooleanAtom, Not, BooleanFunction +from sympy.polys import cancel, factor +from sympy.utilities.iterables import multiset_partitions +from sympy.utilities.misc import debug as _debug +from sympy.utilities.misc import debugf as _debugf + +# keep this at top for easy reference +z = Dummy('z') + + +def _has(res, *f): + # return True if res has f; in the case of Piecewise + # only return True if *all* pieces have f + res = piecewise_fold(res) + if getattr(res, 'is_Piecewise', False): + return all(_has(i, *f) for i in res.args) + return res.has(*f) + + +def _create_lookup_table(table): + """ Add formulae for the function -> meijerg lookup table. """ + def wild(n): + return Wild(n, exclude=[z]) + p, q, a, b, c = list(map(wild, 'pqabc')) + n = Wild('n', properties=[lambda x: x.is_Integer and x > 0]) + t = p*z**q + + def add(formula, an, ap, bm, bq, arg=t, fac=S.One, cond=True, hint=True): + table.setdefault(_mytype(formula, z), []).append((formula, + [(fac, meijerg(an, ap, bm, bq, arg))], cond, hint)) + + def addi(formula, inst, cond, hint=True): + table.setdefault( + _mytype(formula, z), []).append((formula, inst, cond, hint)) + + def constant(a): + return [(a, meijerg([1], [], [], [0], z)), + (a, meijerg([], [1], [0], [], z))] + table[()] = [(a, constant(a), True, True)] + + # [P], Section 8. + class IsNonPositiveInteger(Function): + + @classmethod + def eval(cls, arg): + arg = unpolarify(arg) + if arg.is_Integer is True: + return arg <= 0 + + # Section 8.4.2 + # TODO this needs more polar_lift (c/f entry for exp) + add(Heaviside(t - b)*(t - b)**(a - 1), [a], [], [], [0], t/b, + gamma(a)*b**(a - 1), And(b > 0)) + add(Heaviside(b - t)*(b - t)**(a - 1), [], [a], [0], [], t/b, + gamma(a)*b**(a - 1), And(b > 0)) + add(Heaviside(z - (b/p)**(1/q))*(t - b)**(a - 1), [a], [], [], [0], t/b, + gamma(a)*b**(a - 1), And(b > 0)) + add(Heaviside((b/p)**(1/q) - z)*(b - t)**(a - 1), [], [a], [0], [], t/b, + gamma(a)*b**(a - 1), And(b > 0)) + add((b + t)**(-a), [1 - a], [], [0], [], t/b, b**(-a)/gamma(a), + hint=Not(IsNonPositiveInteger(a))) + add(Abs(b - t)**(-a), [1 - a], [(1 - a)/2], [0], [(1 - a)/2], t/b, + 2*sin(pi*a/2)*gamma(1 - a)*Abs(b)**(-a), re(a) < 1) + add((t**a - b**a)/(t - b), [0, a], [], [0, a], [], t/b, + b**(a - 1)*sin(a*pi)/pi) + + # 12 + def A1(r, sign, nu): + return pi**Rational(-1, 2)*(-sign*nu/2)**(1 - 2*r) + + def tmpadd(r, sgn): + # XXX the a**2 is bad for matching + add((sqrt(a**2 + t) + sgn*a)**b/(a**2 + t)**r, + [(1 + b)/2, 1 - 2*r + b/2], [], + [(b - sgn*b)/2], [(b + sgn*b)/2], t/a**2, + a**(b - 2*r)*A1(r, sgn, b)) + tmpadd(0, 1) + tmpadd(0, -1) + tmpadd(S.Half, 1) + tmpadd(S.Half, -1) + + # 13 + def tmpadd(r, sgn): + add((sqrt(a + p*z**q) + sgn*sqrt(p)*z**(q/2))**b/(a + p*z**q)**r, + [1 - r + sgn*b/2], [1 - r - sgn*b/2], [0, S.Half], [], + p*z**q/a, a**(b/2 - r)*A1(r, sgn, b)) + tmpadd(0, 1) + tmpadd(0, -1) + tmpadd(S.Half, 1) + tmpadd(S.Half, -1) + # (those after look obscure) + + # Section 8.4.3 + add(exp(polar_lift(-1)*t), [], [], [0], []) + + # TODO can do sin^n, sinh^n by expansion ... where? + # 8.4.4 (hyperbolic functions) + add(sinh(t), [], [1], [S.Half], [1, 0], t**2/4, pi**Rational(3, 2)) + add(cosh(t), [], [S.Half], [0], [S.Half, S.Half], t**2/4, pi**Rational(3, 2)) + + # Section 8.4.5 + # TODO can do t + a. but can also do by expansion... (XXX not really) + add(sin(t), [], [], [S.Half], [0], t**2/4, sqrt(pi)) + add(cos(t), [], [], [0], [S.Half], t**2/4, sqrt(pi)) + + # Section 8.4.6 (sinc function) + add(sinc(t), [], [], [0], [Rational(-1, 2)], t**2/4, sqrt(pi)/2) + + # Section 8.5.5 + def make_log1(subs): + N = subs[n] + return [(S.NegativeOne**N*factorial(N), + meijerg([], [1]*(N + 1), [0]*(N + 1), [], t))] + + def make_log2(subs): + N = subs[n] + return [(factorial(N), + meijerg([1]*(N + 1), [], [], [0]*(N + 1), t))] + # TODO these only hold for positive p, and can be made more general + # but who uses log(x)*Heaviside(a-x) anyway ... + # TODO also it would be nice to derive them recursively ... + addi(log(t)**n*Heaviside(1 - t), make_log1, True) + addi(log(t)**n*Heaviside(t - 1), make_log2, True) + + def make_log3(subs): + return make_log1(subs) + make_log2(subs) + addi(log(t)**n, make_log3, True) + addi(log(t + a), + constant(log(a)) + [(S.One, meijerg([1, 1], [], [1], [0], t/a))], + True) + addi(log(Abs(t - a)), constant(log(Abs(a))) + + [(pi, meijerg([1, 1], [S.Half], [1], [0, S.Half], t/a))], + True) + # TODO log(x)/(x+a) and log(x)/(x-1) can also be done. should they + # be derivable? + # TODO further formulae in this section seem obscure + + # Sections 8.4.9-10 + # TODO + + # Section 8.4.11 + addi(Ei(t), + constant(-S.ImaginaryUnit*pi) + [(S.NegativeOne, meijerg([], [1], [0, 0], [], + t*polar_lift(-1)))], + True) + + # Section 8.4.12 + add(Si(t), [1], [], [S.Half], [0, 0], t**2/4, sqrt(pi)/2) + add(Ci(t), [], [1], [0, 0], [S.Half], t**2/4, -sqrt(pi)/2) + + # Section 8.4.13 + add(Shi(t), [S.Half], [], [0], [Rational(-1, 2), Rational(-1, 2)], polar_lift(-1)*t**2/4, + t*sqrt(pi)/4) + add(Chi(t), [], [S.Half, 1], [0, 0], [S.Half, S.Half], t**2/4, - + pi**S('3/2')/2) + + # generalized exponential integral + add(expint(a, t), [], [a], [a - 1, 0], [], t) + + # Section 8.4.14 + add(erf(t), [1], [], [S.Half], [0], t**2, 1/sqrt(pi)) + # TODO exp(-x)*erf(I*x) does not work + add(erfc(t), [], [1], [0, S.Half], [], t**2, 1/sqrt(pi)) + # This formula for erfi(z) yields a wrong(?) minus sign + #add(erfi(t), [1], [], [S.Half], [0], -t**2, I/sqrt(pi)) + add(erfi(t), [S.Half], [], [0], [Rational(-1, 2)], -t**2, t/sqrt(pi)) + + # Fresnel Integrals + add(fresnels(t), [1], [], [Rational(3, 4)], [0, Rational(1, 4)], pi**2*t**4/16, S.Half) + add(fresnelc(t), [1], [], [Rational(1, 4)], [0, Rational(3, 4)], pi**2*t**4/16, S.Half) + + ##### bessel-type functions ##### + # Section 8.4.19 + add(besselj(a, t), [], [], [a/2], [-a/2], t**2/4) + + # all of the following are derivable + #add(sin(t)*besselj(a, t), [Rational(1, 4), Rational(3, 4)], [], [(1+a)/2], + # [-a/2, a/2, (1-a)/2], t**2, 1/sqrt(2)) + #add(cos(t)*besselj(a, t), [Rational(1, 4), Rational(3, 4)], [], [a/2], + # [-a/2, (1+a)/2, (1-a)/2], t**2, 1/sqrt(2)) + #add(besselj(a, t)**2, [S.Half], [], [a], [-a, 0], t**2, 1/sqrt(pi)) + #add(besselj(a, t)*besselj(b, t), [0, S.Half], [], [(a + b)/2], + # [-(a+b)/2, (a - b)/2, (b - a)/2], t**2, 1/sqrt(pi)) + + # Section 8.4.20 + add(bessely(a, t), [], [-(a + 1)/2], [a/2, -a/2], [-(a + 1)/2], t**2/4) + + # TODO all of the following should be derivable + #add(sin(t)*bessely(a, t), [Rational(1, 4), Rational(3, 4)], [(1 - a - 1)/2], + # [(1 + a)/2, (1 - a)/2], [(1 - a - 1)/2, (1 - 1 - a)/2, (1 - 1 + a)/2], + # t**2, 1/sqrt(2)) + #add(cos(t)*bessely(a, t), [Rational(1, 4), Rational(3, 4)], [(0 - a - 1)/2], + # [(0 + a)/2, (0 - a)/2], [(0 - a - 1)/2, (1 - 0 - a)/2, (1 - 0 + a)/2], + # t**2, 1/sqrt(2)) + #add(besselj(a, t)*bessely(b, t), [0, S.Half], [(a - b - 1)/2], + # [(a + b)/2, (a - b)/2], [(a - b - 1)/2, -(a + b)/2, (b - a)/2], + # t**2, 1/sqrt(pi)) + #addi(bessely(a, t)**2, + # [(2/sqrt(pi), meijerg([], [S.Half, S.Half - a], [0, a, -a], + # [S.Half - a], t**2)), + # (1/sqrt(pi), meijerg([S.Half], [], [a], [-a, 0], t**2))], + # True) + #addi(bessely(a, t)*bessely(b, t), + # [(2/sqrt(pi), meijerg([], [0, S.Half, (1 - a - b)/2], + # [(a + b)/2, (a - b)/2, (b - a)/2, -(a + b)/2], + # [(1 - a - b)/2], t**2)), + # (1/sqrt(pi), meijerg([0, S.Half], [], [(a + b)/2], + # [-(a + b)/2, (a - b)/2, (b - a)/2], t**2))], + # True) + + # Section 8.4.21 ? + # Section 8.4.22 + add(besseli(a, t), [], [(1 + a)/2], [a/2], [-a/2, (1 + a)/2], t**2/4, pi) + # TODO many more formulas. should all be derivable + + # Section 8.4.23 + add(besselk(a, t), [], [], [a/2, -a/2], [], t**2/4, S.Half) + # TODO many more formulas. should all be derivable + + # Complete elliptic integrals K(z) and E(z) + add(elliptic_k(t), [S.Half, S.Half], [], [0], [0], -t, S.Half) + add(elliptic_e(t), [S.Half, 3*S.Half], [], [0], [0], -t, Rational(-1, 2)/2) + + +#################################################################### +# First some helper functions. +#################################################################### + +from sympy.utilities.timeutils import timethis +timeit = timethis('meijerg') + + +def _mytype(f: Basic, x: Symbol) -> tuple[type[Basic], ...]: + """ Create a hashable entity describing the type of f. """ + def key(x: type[Basic]) -> tuple[int, int, str]: + return x.class_key() + + if x not in f.free_symbols: + return () + elif f.is_Function: + return type(f), + return tuple(sorted((t for a in f.args for t in _mytype(a, x)), key=key)) + + +class _CoeffExpValueError(ValueError): + """ + Exception raised by _get_coeff_exp, for internal use only. + """ + pass + + +def _get_coeff_exp(expr, x): + """ + When expr is known to be of the form c*x**b, with c and/or b possibly 1, + return c, b. + + Examples + ======== + + >>> from sympy.abc import x, a, b + >>> from sympy.integrals.meijerint import _get_coeff_exp + >>> _get_coeff_exp(a*x**b, x) + (a, b) + >>> _get_coeff_exp(x, x) + (1, 1) + >>> _get_coeff_exp(2*x, x) + (2, 1) + >>> _get_coeff_exp(x**3, x) + (1, 3) + """ + from sympy.simplify import powsimp + (c, m) = expand_power_base(powsimp(expr)).as_coeff_mul(x) + if not m: + return c, S.Zero + [m] = m + if m.is_Pow: + if m.base != x: + raise _CoeffExpValueError('expr not of form a*x**b') + return c, m.exp + elif m == x: + return c, S.One + else: + raise _CoeffExpValueError('expr not of form a*x**b: %s' % expr) + + +def _exponents(expr, x): + """ + Find the exponents of ``x`` (not including zero) in ``expr``. + + Examples + ======== + + >>> from sympy.integrals.meijerint import _exponents + >>> from sympy.abc import x, y + >>> from sympy import sin + >>> _exponents(x, x) + {1} + >>> _exponents(x**2, x) + {2} + >>> _exponents(x**2 + x, x) + {1, 2} + >>> _exponents(x**3*sin(x + x**y) + 1/x, x) + {-1, 1, 3, y} + """ + def _exponents_(expr, x, res): + if expr == x: + res.update([1]) + return + if expr.is_Pow and expr.base == x: + res.update([expr.exp]) + return + for argument in expr.args: + _exponents_(argument, x, res) + res = set() + _exponents_(expr, x, res) + return res + + +def _functions(expr, x): + """ Find the types of functions in expr, to estimate the complexity. """ + return {e.func for e in expr.atoms(Function) if x in e.free_symbols} + + +def _find_splitting_points(expr, x): + """ + Find numbers a such that a linear substitution x -> x + a would + (hopefully) simplify expr. + + Examples + ======== + + >>> from sympy.integrals.meijerint import _find_splitting_points as fsp + >>> from sympy import sin + >>> from sympy.abc import x + >>> fsp(x, x) + {0} + >>> fsp((x-1)**3, x) + {1} + >>> fsp(sin(x+3)*x, x) + {-3, 0} + """ + p, q = [Wild(n, exclude=[x]) for n in 'pq'] + + def compute_innermost(expr, res): + if not isinstance(expr, Expr): + return + m = expr.match(p*x + q) + if m and m[p] != 0: + res.add(-m[q]/m[p]) + return + if expr.is_Atom: + return + for argument in expr.args: + compute_innermost(argument, res) + innermost = set() + compute_innermost(expr, innermost) + return innermost + + +def _split_mul(f, x): + """ + Split expression ``f`` into fac, po, g, where fac is a constant factor, + po = x**s for some s independent of s, and g is "the rest". + + Examples + ======== + + >>> from sympy.integrals.meijerint import _split_mul + >>> from sympy import sin + >>> from sympy.abc import s, x + >>> _split_mul((3*x)**s*sin(x**2)*x, x) + (3**s, x*x**s, sin(x**2)) + """ + fac = S.One + po = S.One + g = S.One + f = expand_power_base(f) + + args = Mul.make_args(f) + for a in args: + if a == x: + po *= x + elif x not in a.free_symbols: + fac *= a + else: + if a.is_Pow and x not in a.exp.free_symbols: + c, t = a.base.as_coeff_mul(x) + if t != (x,): + c, t = expand_mul(a.base).as_coeff_mul(x) + if t == (x,): + po *= x**a.exp + fac *= unpolarify(polarify(c**a.exp, subs=False)) + continue + g *= a + + return fac, po, g + + +def _mul_args(f): + """ + Return a list ``L`` such that ``Mul(*L) == f``. + + If ``f`` is not a ``Mul`` or ``Pow``, ``L=[f]``. + If ``f=g**n`` for an integer ``n``, ``L=[g]*n``. + If ``f`` is a ``Mul``, ``L`` comes from applying ``_mul_args`` to all factors of ``f``. + """ + args = Mul.make_args(f) + gs = [] + for g in args: + if g.is_Pow and g.exp.is_Integer: + n = g.exp + base = g.base + if n < 0: + n = -n + base = 1/base + gs += [base]*n + else: + gs.append(g) + return gs + + +def _mul_as_two_parts(f): + """ + Find all the ways to split ``f`` into a product of two terms. + Return None on failure. + + Explanation + =========== + + Although the order is canonical from multiset_partitions, this is + not necessarily the best order to process the terms. For example, + if the case of len(gs) == 2 is removed and multiset is allowed to + sort the terms, some tests fail. + + Examples + ======== + + >>> from sympy.integrals.meijerint import _mul_as_two_parts + >>> from sympy import sin, exp, ordered + >>> from sympy.abc import x + >>> list(ordered(_mul_as_two_parts(x*sin(x)*exp(x)))) + [(x, exp(x)*sin(x)), (x*exp(x), sin(x)), (x*sin(x), exp(x))] + """ + + gs = _mul_args(f) + if len(gs) < 2: + return None + if len(gs) == 2: + return [tuple(gs)] + return [(Mul(*x), Mul(*y)) for (x, y) in multiset_partitions(gs, 2)] + + +def _inflate_g(g, n): + """ Return C, h such that h is a G function of argument z**n and + g = C*h. """ + # TODO should this be a method of meijerg? + # See: [L, page 150, equation (5)] + def inflate(params, n): + """ (a1, .., ak) -> (a1/n, (a1+1)/n, ..., (ak + n-1)/n) """ + return [(a + i)/n for a, i in itertools.product(params, range(n))] + v = S(len(g.ap) - len(g.bq)) + C = n**(1 + g.nu + v/2) + C /= (2*pi)**((n - 1)*g.delta) + return C, meijerg(inflate(g.an, n), inflate(g.aother, n), + inflate(g.bm, n), inflate(g.bother, n), + g.argument**n * n**(n*v)) + + +def _flip_g(g): + """ Turn the G function into one of inverse argument + (i.e. G(1/x) -> G'(x)) """ + # See [L], section 5.2 + def tr(l): + return [1 - a for a in l] + return meijerg(tr(g.bm), tr(g.bother), tr(g.an), tr(g.aother), 1/g.argument) + + +def _inflate_fox_h(g, a): + r""" + Let d denote the integrand in the definition of the G function ``g``. + Consider the function H which is defined in the same way, but with + integrand d/Gamma(a*s) (contour conventions as usual). + + If ``a`` is rational, the function H can be written as C*G, for a constant C + and a G-function G. + + This function returns C, G. + """ + if a < 0: + return _inflate_fox_h(_flip_g(g), -a) + p = S(a.p) + q = S(a.q) + # We use the substitution s->qs, i.e. inflate g by q. We are left with an + # extra factor of Gamma(p*s), for which we use Gauss' multiplication + # theorem. + D, g = _inflate_g(g, q) + z = g.argument + D /= (2*pi)**((1 - p)/2)*p**Rational(-1, 2) + z /= p**p + bs = [(n + 1)/p for n in range(p)] + return D, meijerg(g.an, g.aother, g.bm, list(g.bother) + bs, z) + +_dummies: dict[tuple[str, str], Dummy] = {} + + +def _dummy(name, token, expr, **kwargs): + """ + Return a dummy. This will return the same dummy if the same token+name is + requested more than once, and it is not already in expr. + This is for being cache-friendly. + """ + d = _dummy_(name, token, **kwargs) + if d in expr.free_symbols: + return Dummy(name, **kwargs) + return d + + +def _dummy_(name, token, **kwargs): + """ + Return a dummy associated to name and token. Same effect as declaring + it globally. + """ + global _dummies + if not (name, token) in _dummies: + _dummies[(name, token)] = Dummy(name, **kwargs) + return _dummies[(name, token)] + + +def _is_analytic(f, x): + """ Check if f(x), when expressed using G functions on the positive reals, + will in fact agree with the G functions almost everywhere """ + return not any(x in expr.free_symbols for expr in f.atoms(Heaviside, Abs)) + + +def _condsimp(cond, first=True): + """ + Do naive simplifications on ``cond``. + + Explanation + =========== + + Note that this routine is completely ad-hoc, simplification rules being + added as need arises rather than following any logical pattern. + + Examples + ======== + + >>> from sympy.integrals.meijerint import _condsimp as simp + >>> from sympy import Or, Eq + >>> from sympy.abc import x, y + >>> simp(Or(x < y, Eq(x, y))) + x <= y + """ + if first: + cond = cond.replace(lambda _: _.is_Relational, _canonical_coeff) + first = False + if not isinstance(cond, BooleanFunction): + return cond + p, q, r = symbols('p q r', cls=Wild) + # transforms tests use 0, 4, 5 and 11-14 + # meijer tests use 0, 2, 11, 14 + # joint_rv uses 6, 7 + rules = [ + (Or(p < q, Eq(p, q)), p <= q), # 0 + # The next two obviously are instances of a general pattern, but it is + # easier to spell out the few cases we care about. + (And(Abs(arg(p)) <= pi, Abs(arg(p) - 2*pi) <= pi), + Eq(arg(p) - pi, 0)), # 1 + (And(Abs(2*arg(p) + pi) <= pi, Abs(2*arg(p) - pi) <= pi), + Eq(arg(p), 0)), # 2 + (And(Abs(2*arg(p) + pi) < pi, Abs(2*arg(p) - pi) <= pi), + S.false), # 3 + (And(Abs(arg(p) - pi/2) <= pi/2, Abs(arg(p) + pi/2) <= pi/2), + Eq(arg(p), 0)), # 4 + (And(Abs(arg(p) - pi/2) <= pi/2, Abs(arg(p) + pi/2) < pi/2), + S.false), # 5 + (And(Abs(arg(p**2/2 + 1)) < pi, Ne(Abs(arg(p**2/2 + 1)), pi)), + S.true), # 6 + (Or(Abs(arg(p**2/2 + 1)) < pi, Ne(1/(p**2/2 + 1), 0)), + S.true), # 7 + (And(Abs(unbranched_argument(p)) <= pi, + Abs(unbranched_argument(exp_polar(-2*pi*S.ImaginaryUnit)*p)) <= pi), + Eq(unbranched_argument(exp_polar(-S.ImaginaryUnit*pi)*p), 0)), # 8 + (And(Abs(unbranched_argument(p)) <= pi/2, + Abs(unbranched_argument(exp_polar(-pi*S.ImaginaryUnit)*p)) <= pi/2), + Eq(unbranched_argument(exp_polar(-S.ImaginaryUnit*pi/2)*p), 0)), # 9 + (Or(p <= q, And(p < q, r)), p <= q), # 10 + (Ne(p**2, 1) & (p**2 > 1), p**2 > 1), # 11 + (Ne(1/p, 1) & (cos(Abs(arg(p)))*Abs(p) > 1), Abs(p) > 1), # 12 + (Ne(p, 2) & (cos(Abs(arg(p)))*Abs(p) > 2), Abs(p) > 2), # 13 + ((Abs(arg(p)) < pi/2) & (cos(Abs(arg(p)))*sqrt(Abs(p**2)) > 1), p**2 > 1), # 14 + ] + cond = cond.func(*[_condsimp(_, first) for _ in cond.args]) + change = True + while change: + change = False + for irule, (fro, to) in enumerate(rules): + if fro.func != cond.func: + continue + for n, arg1 in enumerate(cond.args): + if r in fro.args[0].free_symbols: + m = arg1.match(fro.args[1]) + num = 1 + else: + num = 0 + m = arg1.match(fro.args[0]) + if not m: + continue + otherargs = [x.subs(m) for x in fro.args[:num] + fro.args[num + 1:]] + otherlist = [n] + for arg2 in otherargs: + for k, arg3 in enumerate(cond.args): + if k in otherlist: + continue + if arg2 == arg3: + otherlist += [k] + break + if isinstance(arg3, And) and arg2.args[1] == r and \ + isinstance(arg2, And) and arg2.args[0] in arg3.args: + otherlist += [k] + break + if isinstance(arg3, And) and arg2.args[0] == r and \ + isinstance(arg2, And) and arg2.args[1] in arg3.args: + otherlist += [k] + break + if len(otherlist) != len(otherargs) + 1: + continue + newargs = [arg_ for (k, arg_) in enumerate(cond.args) + if k not in otherlist] + [to.subs(m)] + if SYMPY_DEBUG: + if irule not in (0, 2, 4, 5, 6, 7, 11, 12, 13, 14): + print('used new rule:', irule) + cond = cond.func(*newargs) + change = True + break + + # final tweak + def rel_touchup(rel): + if rel.rel_op != '==' or rel.rhs != 0: + return rel + + # handle Eq(*, 0) + LHS = rel.lhs + m = LHS.match(arg(p)**q) + if not m: + m = LHS.match(unbranched_argument(polar_lift(p)**q)) + if not m: + if isinstance(LHS, periodic_argument) and not LHS.args[0].is_polar \ + and LHS.args[1] is S.Infinity: + return (LHS.args[0] > 0) + return rel + return (m[p] > 0) + cond = cond.replace(lambda _: _.is_Relational, rel_touchup) + if SYMPY_DEBUG: + print('_condsimp: ', cond) + return cond + +def _eval_cond(cond): + """ Re-evaluate the conditions. """ + if isinstance(cond, bool): + return cond + return _condsimp(cond.doit()) + +#################################################################### +# Now the "backbone" functions to do actual integration. +#################################################################### + + +def _my_principal_branch(expr, period, full_pb=False): + """ Bring expr nearer to its principal branch by removing superfluous + factors. + This function does *not* guarantee to yield the principal branch, + to avoid introducing opaque principal_branch() objects, + unless full_pb=True. """ + res = principal_branch(expr, period) + if not full_pb: + res = res.replace(principal_branch, lambda x, y: x) + return res + + +def _rewrite_saxena_1(fac, po, g, x): + """ + Rewrite the integral fac*po*g dx, from zero to infinity, as + integral fac*G, where G has argument a*x. Note po=x**s. + Return fac, G. + """ + _, s = _get_coeff_exp(po, x) + a, b = _get_coeff_exp(g.argument, x) + period = g.get_period() + a = _my_principal_branch(a, period) + + # We substitute t = x**b. + C = fac/(Abs(b)*a**((s + 1)/b - 1)) + # Absorb a factor of (at)**((1 + s)/b - 1). + + def tr(l): + return [a + (1 + s)/b - 1 for a in l] + return C, meijerg(tr(g.an), tr(g.aother), tr(g.bm), tr(g.bother), + a*x) + + +def _check_antecedents_1(g, x, helper=False): + r""" + Return a condition under which the mellin transform of g exists. + Any power of x has already been absorbed into the G function, + so this is just $\int_0^\infty g\, dx$. + + See [L, section 5.6.1]. (Note that s=1.) + + If ``helper`` is True, only check if the MT exists at infinity, i.e. if + $\int_1^\infty g\, dx$ exists. + """ + # NOTE if you update these conditions, please update the documentation as well + delta = g.delta + eta, _ = _get_coeff_exp(g.argument, x) + m, n, p, q = S([len(g.bm), len(g.an), len(g.ap), len(g.bq)]) + + if p > q: + def tr(l): + return [1 - x for x in l] + return _check_antecedents_1(meijerg(tr(g.bm), tr(g.bother), + tr(g.an), tr(g.aother), x/eta), + x) + + tmp = [-re(b) < 1 for b in g.bm] + [1 < 1 - re(a) for a in g.an] + cond_3 = And(*tmp) + + tmp += [-re(b) < 1 for b in g.bother] + tmp += [1 < 1 - re(a) for a in g.aother] + cond_3_star = And(*tmp) + + cond_4 = (-re(g.nu) + (q + 1 - p)/2 > q - p) + + def debug(*msg): + _debug(*msg) + + def debugf(string, arg): + _debugf(string, arg) + + debug('Checking antecedents for 1 function:') + debugf(' delta=%s, eta=%s, m=%s, n=%s, p=%s, q=%s', + (delta, eta, m, n, p, q)) + debugf(' ap = %s, %s', (list(g.an), list(g.aother))) + debugf(' bq = %s, %s', (list(g.bm), list(g.bother))) + debugf(' cond_3=%s, cond_3*=%s, cond_4=%s', (cond_3, cond_3_star, cond_4)) + + conds = [] + + # case 1 + case1 = [] + tmp1 = [1 <= n, p < q, 1 <= m] + tmp2 = [1 <= p, 1 <= m, Eq(q, p + 1), Not(And(Eq(n, 0), Eq(m, p + 1)))] + tmp3 = [1 <= p, Eq(q, p)] + for k in range(ceiling(delta/2) + 1): + tmp3 += [Ne(Abs(unbranched_argument(eta)), (delta - 2*k)*pi)] + tmp = [delta > 0, Abs(unbranched_argument(eta)) < delta*pi] + extra = [Ne(eta, 0), cond_3] + if helper: + extra = [] + for t in [tmp1, tmp2, tmp3]: + case1 += [And(*(t + tmp + extra))] + conds += case1 + debug(' case 1:', case1) + + # case 2 + extra = [cond_3] + if helper: + extra = [] + case2 = [And(Eq(n, 0), p + 1 <= m, m <= q, + Abs(unbranched_argument(eta)) < delta*pi, *extra)] + conds += case2 + debug(' case 2:', case2) + + # case 3 + extra = [cond_3, cond_4] + if helper: + extra = [] + case3 = [And(p < q, 1 <= m, delta > 0, Eq(Abs(unbranched_argument(eta)), delta*pi), + *extra)] + case3 += [And(p <= q - 2, Eq(delta, 0), Eq(Abs(unbranched_argument(eta)), 0), *extra)] + conds += case3 + debug(' case 3:', case3) + + # TODO altered cases 4-7 + + # extra case from wofram functions site: + # (reproduced verbatim from Prudnikov, section 2.24.2) + # https://functions.wolfram.com/HypergeometricFunctions/MeijerG/21/02/01/ + case_extra = [] + case_extra += [Eq(p, q), Eq(delta, 0), Eq(unbranched_argument(eta), 0), Ne(eta, 0)] + if not helper: + case_extra += [cond_3] + s = [] + for a, b in zip(g.ap, g.bq): + s += [b - a] + case_extra += [re(Add(*s)) < 0] + case_extra = And(*case_extra) + conds += [case_extra] + debug(' extra case:', [case_extra]) + + case_extra_2 = [And(delta > 0, Abs(unbranched_argument(eta)) < delta*pi)] + if not helper: + case_extra_2 += [cond_3] + case_extra_2 = And(*case_extra_2) + conds += [case_extra_2] + debug(' second extra case:', [case_extra_2]) + + # TODO This leaves only one case from the three listed by Prudnikov. + # Investigate if these indeed cover everything; if so, remove the rest. + + return Or(*conds) + + +def _int0oo_1(g, x): + r""" + Evaluate $\int_0^\infty g\, dx$ using G functions, + assuming the necessary conditions are fulfilled. + + Examples + ======== + + >>> from sympy.abc import a, b, c, d, x, y + >>> from sympy import meijerg + >>> from sympy.integrals.meijerint import _int0oo_1 + >>> _int0oo_1(meijerg([a], [b], [c], [d], x*y), x) + gamma(-a)*gamma(c + 1)/(y*gamma(-d)*gamma(b + 1)) + """ + from sympy.simplify import gammasimp + # See [L, section 5.6.1]. Note that s=1. + eta, _ = _get_coeff_exp(g.argument, x) + res = 1/eta + # XXX TODO we should reduce order first + for b in g.bm: + res *= gamma(b + 1) + for a in g.an: + res *= gamma(1 - a - 1) + for b in g.bother: + res /= gamma(1 - b - 1) + for a in g.aother: + res /= gamma(a + 1) + return gammasimp(unpolarify(res)) + + +def _rewrite_saxena(fac, po, g1, g2, x, full_pb=False): + """ + Rewrite the integral ``fac*po*g1*g2`` from 0 to oo in terms of G + functions with argument ``c*x``. + + Explanation + =========== + + Return C, f1, f2 such that integral C f1 f2 from 0 to infinity equals + integral fac ``po``, ``g1``, ``g2`` from 0 to infinity. + + Examples + ======== + + >>> from sympy.integrals.meijerint import _rewrite_saxena + >>> from sympy.abc import s, t, m + >>> from sympy import meijerg + >>> g1 = meijerg([], [], [0], [], s*t) + >>> g2 = meijerg([], [], [m/2], [-m/2], t**2/4) + >>> r = _rewrite_saxena(1, t**0, g1, g2, t) + >>> r[0] + s/(4*sqrt(pi)) + >>> r[1] + meijerg(((), ()), ((-1/2, 0), ()), s**2*t/4) + >>> r[2] + meijerg(((), ()), ((m/2,), (-m/2,)), t/4) + """ + def pb(g): + a, b = _get_coeff_exp(g.argument, x) + per = g.get_period() + return meijerg(g.an, g.aother, g.bm, g.bother, + _my_principal_branch(a, per, full_pb)*x**b) + + _, s = _get_coeff_exp(po, x) + _, b1 = _get_coeff_exp(g1.argument, x) + _, b2 = _get_coeff_exp(g2.argument, x) + if (b1 < 0) == True: + b1 = -b1 + g1 = _flip_g(g1) + if (b2 < 0) == True: + b2 = -b2 + g2 = _flip_g(g2) + if not b1.is_Rational or not b2.is_Rational: + return + m1, n1 = b1.p, b1.q + m2, n2 = b2.p, b2.q + tau = ilcm(m1*n2, m2*n1) + r1 = tau//(m1*n2) + r2 = tau//(m2*n1) + + C1, g1 = _inflate_g(g1, r1) + C2, g2 = _inflate_g(g2, r2) + g1 = pb(g1) + g2 = pb(g2) + + fac *= C1*C2 + a1, b = _get_coeff_exp(g1.argument, x) + a2, _ = _get_coeff_exp(g2.argument, x) + + # arbitrarily tack on the x**s part to g1 + # TODO should we try both? + exp = (s + 1)/b - 1 + fac = fac/(Abs(b) * a1**exp) + + def tr(l): + return [a + exp for a in l] + g1 = meijerg(tr(g1.an), tr(g1.aother), tr(g1.bm), tr(g1.bother), a1*x) + g2 = meijerg(g2.an, g2.aother, g2.bm, g2.bother, a2*x) + + from sympy.simplify import powdenest + return powdenest(fac, polar=True), g1, g2 + + +def _check_antecedents(g1, g2, x): + """ Return a condition under which the integral theorem applies. """ + # Yes, this is madness. + # XXX TODO this is a testing *nightmare* + # NOTE if you update these conditions, please update the documentation as well + + # The following conditions are found in + # [P], Section 2.24.1 + # + # They are also reproduced (verbatim!) at + # https://functions.wolfram.com/HypergeometricFunctions/MeijerG/21/02/03/ + # + # Note: k=l=r=alpha=1 + sigma, _ = _get_coeff_exp(g1.argument, x) + omega, _ = _get_coeff_exp(g2.argument, x) + s, t, u, v = S([len(g1.bm), len(g1.an), len(g1.ap), len(g1.bq)]) + m, n, p, q = S([len(g2.bm), len(g2.an), len(g2.ap), len(g2.bq)]) + bstar = s + t - (u + v)/2 + cstar = m + n - (p + q)/2 + rho = g1.nu + (u - v)/2 + 1 + mu = g2.nu + (p - q)/2 + 1 + phi = q - p - (v - u) + eta = 1 - (v - u) - mu - rho + psi = (pi*(q - m - n) + Abs(unbranched_argument(omega)))/(q - p) + theta = (pi*(v - s - t) + Abs(unbranched_argument(sigma)))/(v - u) + + _debug('Checking antecedents:') + _debugf(' sigma=%s, s=%s, t=%s, u=%s, v=%s, b*=%s, rho=%s', + (sigma, s, t, u, v, bstar, rho)) + _debugf(' omega=%s, m=%s, n=%s, p=%s, q=%s, c*=%s, mu=%s,', + (omega, m, n, p, q, cstar, mu)) + _debugf(' phi=%s, eta=%s, psi=%s, theta=%s', (phi, eta, psi, theta)) + + def _c1(): + for g in [g1, g2]: + for i, j in itertools.product(g.an, g.bm): + diff = i - j + if diff.is_integer and diff.is_positive: + return False + return True + c1 = _c1() + c2 = And(*[re(1 + i + j) > 0 for i in g1.bm for j in g2.bm]) + c3 = And(*[re(1 + i + j) < 1 + 1 for i in g1.an for j in g2.an]) + c4 = And(*[(p - q)*re(1 + i - 1) - re(mu) > Rational(-3, 2) for i in g1.an]) + c5 = And(*[(p - q)*re(1 + i) - re(mu) > Rational(-3, 2) for i in g1.bm]) + c6 = And(*[(u - v)*re(1 + i - 1) - re(rho) > Rational(-3, 2) for i in g2.an]) + c7 = And(*[(u - v)*re(1 + i) - re(rho) > Rational(-3, 2) for i in g2.bm]) + c8 = (Abs(phi) + 2*re((rho - 1)*(q - p) + (v - u)*(q - p) + (mu - + 1)*(v - u)) > 0) + c9 = (Abs(phi) - 2*re((rho - 1)*(q - p) + (v - u)*(q - p) + (mu - + 1)*(v - u)) > 0) + c10 = (Abs(unbranched_argument(sigma)) < bstar*pi) + c11 = Eq(Abs(unbranched_argument(sigma)), bstar*pi) + c12 = (Abs(unbranched_argument(omega)) < cstar*pi) + c13 = Eq(Abs(unbranched_argument(omega)), cstar*pi) + + # The following condition is *not* implemented as stated on the wolfram + # function site. In the book of Prudnikov there is an additional part + # (the And involving re()). However, I only have this book in russian, and + # I don't read any russian. The following condition is what other people + # have told me it means. + # Worryingly, it is different from the condition implemented in REDUCE. + # The REDUCE implementation: + # https://reduce-algebra.svn.sourceforge.net/svnroot/reduce-algebra/trunk/packages/defint/definta.red + # (search for tst14) + # The Wolfram alpha version: + # https://functions.wolfram.com/HypergeometricFunctions/MeijerG/21/02/03/03/0014/ + z0 = exp(-(bstar + cstar)*pi*S.ImaginaryUnit) + zos = unpolarify(z0*omega/sigma) + zso = unpolarify(z0*sigma/omega) + if zos == 1/zso: + c14 = And(Eq(phi, 0), bstar + cstar <= 1, + Or(Ne(zos, 1), re(mu + rho + v - u) < 1, + re(mu + rho + q - p) < 1)) + else: + def _cond(z): + '''Returns True if abs(arg(1-z)) < pi, avoiding arg(0). + + Explanation + =========== + + If ``z`` is 1 then arg is NaN. This raises a + TypeError on `NaN < pi`. Previously this gave `False` so + this behavior has been hardcoded here but someone should + check if this NaN is more serious! This NaN is triggered by + test_meijerint() in test_meijerint.py: + `meijerint_definite(exp(x), x, 0, I)` + ''' + return z != 1 and Abs(arg(1 - z)) < pi + + c14 = And(Eq(phi, 0), bstar - 1 + cstar <= 0, + Or(And(Ne(zos, 1), _cond(zos)), + And(re(mu + rho + v - u) < 1, Eq(zos, 1)))) + + c14_alt = And(Eq(phi, 0), cstar - 1 + bstar <= 0, + Or(And(Ne(zso, 1), _cond(zso)), + And(re(mu + rho + q - p) < 1, Eq(zso, 1)))) + + # Since r=k=l=1, in our case there is c14_alt which is the same as calling + # us with (g1, g2) = (g2, g1). The conditions below enumerate all cases + # (i.e. we don't have to try arguments reversed by hand), and indeed try + # all symmetric cases. (i.e. whenever there is a condition involving c14, + # there is also a dual condition which is exactly what we would get when g1, + # g2 were interchanged, *but c14 was unaltered*). + # Hence the following seems correct: + c14 = Or(c14, c14_alt) + + ''' + When `c15` is NaN (e.g. from `psi` being NaN as happens during + 'test_issue_4992' and/or `theta` is NaN as in 'test_issue_6253', + both in `test_integrals.py`) the comparison to 0 formerly gave False + whereas now an error is raised. To keep the old behavior, the value + of NaN is replaced with False but perhaps a closer look at this condition + should be made: XXX how should conditions leading to c15=NaN be handled? + ''' + try: + lambda_c = (q - p)*Abs(omega)**(1/(q - p))*cos(psi) \ + + (v - u)*Abs(sigma)**(1/(v - u))*cos(theta) + # the TypeError might be raised here, e.g. if lambda_c is NaN + if _eval_cond(lambda_c > 0) != False: + c15 = (lambda_c > 0) + else: + def lambda_s0(c1, c2): + return c1*(q - p)*Abs(omega)**(1/(q - p))*sin(psi) \ + + c2*(v - u)*Abs(sigma)**(1/(v - u))*sin(theta) + lambda_s = Piecewise( + ((lambda_s0(+1, +1)*lambda_s0(-1, -1)), + And(Eq(unbranched_argument(sigma), 0), Eq(unbranched_argument(omega), 0))), + (lambda_s0(sign(unbranched_argument(omega)), +1)*lambda_s0(sign(unbranched_argument(omega)), -1), + And(Eq(unbranched_argument(sigma), 0), Ne(unbranched_argument(omega), 0))), + (lambda_s0(+1, sign(unbranched_argument(sigma)))*lambda_s0(-1, sign(unbranched_argument(sigma))), + And(Ne(unbranched_argument(sigma), 0), Eq(unbranched_argument(omega), 0))), + (lambda_s0(sign(unbranched_argument(omega)), sign(unbranched_argument(sigma))), True)) + tmp = [lambda_c > 0, + And(Eq(lambda_c, 0), Ne(lambda_s, 0), re(eta) > -1), + And(Eq(lambda_c, 0), Eq(lambda_s, 0), re(eta) > 0)] + c15 = Or(*tmp) + except TypeError: + c15 = False + for cond, i in [(c1, 1), (c2, 2), (c3, 3), (c4, 4), (c5, 5), (c6, 6), + (c7, 7), (c8, 8), (c9, 9), (c10, 10), (c11, 11), + (c12, 12), (c13, 13), (c14, 14), (c15, 15)]: + _debugf(' c%s: %s', (i, cond)) + + # We will return Or(*conds) + conds = [] + + def pr(count): + _debugf(' case %s: %s', (count, conds[-1])) + conds += [And(m*n*s*t != 0, bstar.is_positive is True, cstar.is_positive is True, c1, c2, c3, c10, + c12)] # 1 + pr(1) + conds += [And(Eq(u, v), Eq(bstar, 0), cstar.is_positive is True, sigma.is_positive is True, re(rho) < 1, + c1, c2, c3, c12)] # 2 + pr(2) + conds += [And(Eq(p, q), Eq(cstar, 0), bstar.is_positive is True, omega.is_positive is True, re(mu) < 1, + c1, c2, c3, c10)] # 3 + pr(3) + conds += [And(Eq(p, q), Eq(u, v), Eq(bstar, 0), Eq(cstar, 0), + sigma.is_positive is True, omega.is_positive is True, re(mu) < 1, re(rho) < 1, + Ne(sigma, omega), c1, c2, c3)] # 4 + pr(4) + conds += [And(Eq(p, q), Eq(u, v), Eq(bstar, 0), Eq(cstar, 0), + sigma.is_positive is True, omega.is_positive is True, re(mu + rho) < 1, + Ne(omega, sigma), c1, c2, c3)] # 5 + pr(5) + conds += [And(p > q, s.is_positive is True, bstar.is_positive is True, cstar >= 0, + c1, c2, c3, c5, c10, c13)] # 6 + pr(6) + conds += [And(p < q, t.is_positive is True, bstar.is_positive is True, cstar >= 0, + c1, c2, c3, c4, c10, c13)] # 7 + pr(7) + conds += [And(u > v, m.is_positive is True, cstar.is_positive is True, bstar >= 0, + c1, c2, c3, c7, c11, c12)] # 8 + pr(8) + conds += [And(u < v, n.is_positive is True, cstar.is_positive is True, bstar >= 0, + c1, c2, c3, c6, c11, c12)] # 9 + pr(9) + conds += [And(p > q, Eq(u, v), Eq(bstar, 0), cstar >= 0, sigma.is_positive is True, + re(rho) < 1, c1, c2, c3, c5, c13)] # 10 + pr(10) + conds += [And(p < q, Eq(u, v), Eq(bstar, 0), cstar >= 0, sigma.is_positive is True, + re(rho) < 1, c1, c2, c3, c4, c13)] # 11 + pr(11) + conds += [And(Eq(p, q), u > v, bstar >= 0, Eq(cstar, 0), omega.is_positive is True, + re(mu) < 1, c1, c2, c3, c7, c11)] # 12 + pr(12) + conds += [And(Eq(p, q), u < v, bstar >= 0, Eq(cstar, 0), omega.is_positive is True, + re(mu) < 1, c1, c2, c3, c6, c11)] # 13 + pr(13) + conds += [And(p < q, u > v, bstar >= 0, cstar >= 0, + c1, c2, c3, c4, c7, c11, c13)] # 14 + pr(14) + conds += [And(p > q, u < v, bstar >= 0, cstar >= 0, + c1, c2, c3, c5, c6, c11, c13)] # 15 + pr(15) + conds += [And(p > q, u > v, bstar >= 0, cstar >= 0, + c1, c2, c3, c5, c7, c8, c11, c13, c14)] # 16 + pr(16) + conds += [And(p < q, u < v, bstar >= 0, cstar >= 0, + c1, c2, c3, c4, c6, c9, c11, c13, c14)] # 17 + pr(17) + conds += [And(Eq(t, 0), s.is_positive is True, bstar.is_positive is True, phi.is_positive is True, c1, c2, c10)] # 18 + pr(18) + conds += [And(Eq(s, 0), t.is_positive is True, bstar.is_positive is True, phi.is_negative is True, c1, c3, c10)] # 19 + pr(19) + conds += [And(Eq(n, 0), m.is_positive is True, cstar.is_positive is True, phi.is_negative is True, c1, c2, c12)] # 20 + pr(20) + conds += [And(Eq(m, 0), n.is_positive is True, cstar.is_positive is True, phi.is_positive is True, c1, c3, c12)] # 21 + pr(21) + conds += [And(Eq(s*t, 0), bstar.is_positive is True, cstar.is_positive is True, + c1, c2, c3, c10, c12)] # 22 + pr(22) + conds += [And(Eq(m*n, 0), bstar.is_positive is True, cstar.is_positive is True, + c1, c2, c3, c10, c12)] # 23 + pr(23) + + # The following case is from [Luke1969]. As far as I can tell, it is *not* + # covered by Prudnikov's. + # Let G1 and G2 be the two G-functions. Suppose the integral exists from + # 0 to a > 0 (this is easy the easy part), that G1 is exponential decay at + # infinity, and that the mellin transform of G2 exists. + # Then the integral exists. + mt1_exists = _check_antecedents_1(g1, x, helper=True) + mt2_exists = _check_antecedents_1(g2, x, helper=True) + conds += [And(mt2_exists, Eq(t, 0), u < s, bstar.is_positive is True, c10, c1, c2, c3)] + pr('E1') + conds += [And(mt2_exists, Eq(s, 0), v < t, bstar.is_positive is True, c10, c1, c2, c3)] + pr('E2') + conds += [And(mt1_exists, Eq(n, 0), p < m, cstar.is_positive is True, c12, c1, c2, c3)] + pr('E3') + conds += [And(mt1_exists, Eq(m, 0), q < n, cstar.is_positive is True, c12, c1, c2, c3)] + pr('E4') + + # Let's short-circuit if this worked ... + # the rest is corner-cases and terrible to read. + r = Or(*conds) + if _eval_cond(r) != False: + return r + + conds += [And(m + n > p, Eq(t, 0), Eq(phi, 0), s.is_positive is True, bstar.is_positive is True, cstar.is_negative is True, + Abs(unbranched_argument(omega)) < (m + n - p + 1)*pi, + c1, c2, c10, c14, c15)] # 24 + pr(24) + conds += [And(m + n > q, Eq(s, 0), Eq(phi, 0), t.is_positive is True, bstar.is_positive is True, cstar.is_negative is True, + Abs(unbranched_argument(omega)) < (m + n - q + 1)*pi, + c1, c3, c10, c14, c15)] # 25 + pr(25) + conds += [And(Eq(p, q - 1), Eq(t, 0), Eq(phi, 0), s.is_positive is True, bstar.is_positive is True, + cstar >= 0, cstar*pi < Abs(unbranched_argument(omega)), + c1, c2, c10, c14, c15)] # 26 + pr(26) + conds += [And(Eq(p, q + 1), Eq(s, 0), Eq(phi, 0), t.is_positive is True, bstar.is_positive is True, + cstar >= 0, cstar*pi < Abs(unbranched_argument(omega)), + c1, c3, c10, c14, c15)] # 27 + pr(27) + conds += [And(p < q - 1, Eq(t, 0), Eq(phi, 0), s.is_positive is True, bstar.is_positive is True, + cstar >= 0, cstar*pi < Abs(unbranched_argument(omega)), + Abs(unbranched_argument(omega)) < (m + n - p + 1)*pi, + c1, c2, c10, c14, c15)] # 28 + pr(28) + conds += [And( + p > q + 1, Eq(s, 0), Eq(phi, 0), t.is_positive is True, bstar.is_positive is True, cstar >= 0, + cstar*pi < Abs(unbranched_argument(omega)), + Abs(unbranched_argument(omega)) < (m + n - q + 1)*pi, + c1, c3, c10, c14, c15)] # 29 + pr(29) + conds += [And(Eq(n, 0), Eq(phi, 0), s + t > 0, m.is_positive is True, cstar.is_positive is True, bstar.is_negative is True, + Abs(unbranched_argument(sigma)) < (s + t - u + 1)*pi, + c1, c2, c12, c14, c15)] # 30 + pr(30) + conds += [And(Eq(m, 0), Eq(phi, 0), s + t > v, n.is_positive is True, cstar.is_positive is True, bstar.is_negative is True, + Abs(unbranched_argument(sigma)) < (s + t - v + 1)*pi, + c1, c3, c12, c14, c15)] # 31 + pr(31) + conds += [And(Eq(n, 0), Eq(phi, 0), Eq(u, v - 1), m.is_positive is True, cstar.is_positive is True, + bstar >= 0, bstar*pi < Abs(unbranched_argument(sigma)), + Abs(unbranched_argument(sigma)) < (bstar + 1)*pi, + c1, c2, c12, c14, c15)] # 32 + pr(32) + conds += [And(Eq(m, 0), Eq(phi, 0), Eq(u, v + 1), n.is_positive is True, cstar.is_positive is True, + bstar >= 0, bstar*pi < Abs(unbranched_argument(sigma)), + Abs(unbranched_argument(sigma)) < (bstar + 1)*pi, + c1, c3, c12, c14, c15)] # 33 + pr(33) + conds += [And( + Eq(n, 0), Eq(phi, 0), u < v - 1, m.is_positive is True, cstar.is_positive is True, bstar >= 0, + bstar*pi < Abs(unbranched_argument(sigma)), + Abs(unbranched_argument(sigma)) < (s + t - u + 1)*pi, + c1, c2, c12, c14, c15)] # 34 + pr(34) + conds += [And( + Eq(m, 0), Eq(phi, 0), u > v + 1, n.is_positive is True, cstar.is_positive is True, bstar >= 0, + bstar*pi < Abs(unbranched_argument(sigma)), + Abs(unbranched_argument(sigma)) < (s + t - v + 1)*pi, + c1, c3, c12, c14, c15)] # 35 + pr(35) + + return Or(*conds) + + # NOTE An alternative, but as far as I can tell weaker, set of conditions + # can be found in [L, section 5.6.2]. + + +def _int0oo(g1, g2, x): + """ + Express integral from zero to infinity g1*g2 using a G function, + assuming the necessary conditions are fulfilled. + + Examples + ======== + + >>> from sympy.integrals.meijerint import _int0oo + >>> from sympy.abc import s, t, m + >>> from sympy import meijerg, S + >>> g1 = meijerg([], [], [-S(1)/2, 0], [], s**2*t/4) + >>> g2 = meijerg([], [], [m/2], [-m/2], t/4) + >>> _int0oo(g1, g2, t) + 4*meijerg(((0, 1/2), ()), ((m/2,), (-m/2,)), s**(-2))/s**2 + """ + # See: [L, section 5.6.2, equation (1)] + eta, _ = _get_coeff_exp(g1.argument, x) + omega, _ = _get_coeff_exp(g2.argument, x) + + def neg(l): + return [-x for x in l] + a1 = neg(g1.bm) + list(g2.an) + a2 = list(g2.aother) + neg(g1.bother) + b1 = neg(g1.an) + list(g2.bm) + b2 = list(g2.bother) + neg(g1.aother) + return meijerg(a1, a2, b1, b2, omega/eta)/eta + + +def _rewrite_inversion(fac, po, g, x): + """ Absorb ``po`` == x**s into g. """ + _, s = _get_coeff_exp(po, x) + a, b = _get_coeff_exp(g.argument, x) + + def tr(l): + return [t + s/b for t in l] + from sympy.simplify import powdenest + return (powdenest(fac/a**(s/b), polar=True), + meijerg(tr(g.an), tr(g.aother), tr(g.bm), tr(g.bother), g.argument)) + + +def _check_antecedents_inversion(g, x): + """ Check antecedents for the laplace inversion integral. """ + _debug('Checking antecedents for inversion:') + z = g.argument + _, e = _get_coeff_exp(z, x) + if e < 0: + _debug(' Flipping G.') + # We want to assume that argument gets large as |x| -> oo + return _check_antecedents_inversion(_flip_g(g), x) + + def statement_half(a, b, c, z, plus): + coeff, exponent = _get_coeff_exp(z, x) + a *= exponent + b *= coeff**c + c *= exponent + conds = [] + wp = b*exp(S.ImaginaryUnit*re(c)*pi/2) + wm = b*exp(-S.ImaginaryUnit*re(c)*pi/2) + if plus: + w = wp + else: + w = wm + conds += [And(Or(Eq(b, 0), re(c) <= 0), re(a) <= -1)] + conds += [And(Ne(b, 0), Eq(im(c), 0), re(c) > 0, re(w) < 0)] + conds += [And(Ne(b, 0), Eq(im(c), 0), re(c) > 0, re(w) <= 0, + re(a) <= -1)] + return Or(*conds) + + def statement(a, b, c, z): + """ Provide a convergence statement for z**a * exp(b*z**c), + c/f sphinx docs. """ + return And(statement_half(a, b, c, z, True), + statement_half(a, b, c, z, False)) + + # Notations from [L], section 5.7-10 + m, n, p, q = S([len(g.bm), len(g.an), len(g.ap), len(g.bq)]) + tau = m + n - p + nu = q - m - n + rho = (tau - nu)/2 + sigma = q - p + if sigma == 1: + epsilon = S.Half + elif sigma > 1: + epsilon = 1 + else: + epsilon = S.NaN + theta = ((1 - sigma)/2 + Add(*g.bq) - Add(*g.ap))/sigma + delta = g.delta + _debugf(' m=%s, n=%s, p=%s, q=%s, tau=%s, nu=%s, rho=%s, sigma=%s', + (m, n, p, q, tau, nu, rho, sigma)) + _debugf(' epsilon=%s, theta=%s, delta=%s', (epsilon, theta, delta)) + + # First check if the computation is valid. + if not (g.delta >= e/2 or (p >= 1 and p >= q)): + _debug(' Computation not valid for these parameters.') + return False + + # Now check if the inversion integral exists. + + # Test "condition A" + for a, b in itertools.product(g.an, g.bm): + if (a - b).is_integer and a > b: + _debug(' Not a valid G function.') + return False + + # There are two cases. If p >= q, we can directly use a slater expansion + # like [L], 5.2 (11). Note in particular that the asymptotics of such an + # expansion even hold when some of the parameters differ by integers, i.e. + # the formula itself would not be valid! (b/c G functions are cts. in their + # parameters) + # When p < q, we need to use the theorems of [L], 5.10. + + if p >= q: + _debug(' Using asymptotic Slater expansion.') + return And(*[statement(a - 1, 0, 0, z) for a in g.an]) + + def E(z): + return And(*[statement(a - 1, 0, 0, z) for a in g.an]) + + def H(z): + return statement(theta, -sigma, 1/sigma, z) + + def Hp(z): + return statement_half(theta, -sigma, 1/sigma, z, True) + + def Hm(z): + return statement_half(theta, -sigma, 1/sigma, z, False) + + # [L], section 5.10 + conds = [] + # Theorem 1 -- p < q from test above + conds += [And(1 <= n, 1 <= m, rho*pi - delta >= pi/2, delta > 0, + E(z*exp(S.ImaginaryUnit*pi*(nu + 1))))] + # Theorem 2, statements (2) and (3) + conds += [And(p + 1 <= m, m + 1 <= q, delta > 0, delta < pi/2, n == 0, + (m - p + 1)*pi - delta >= pi/2, + Hp(z*exp(S.ImaginaryUnit*pi*(q - m))), + Hm(z*exp(-S.ImaginaryUnit*pi*(q - m))))] + # Theorem 2, statement (5) -- p < q from test above + conds += [And(m == q, n == 0, delta > 0, + (sigma + epsilon)*pi - delta >= pi/2, H(z))] + # Theorem 3, statements (6) and (7) + conds += [And(Or(And(p <= q - 2, 1 <= tau, tau <= sigma/2), + And(p + 1 <= m + n, m + n <= (p + q)/2)), + delta > 0, delta < pi/2, (tau + 1)*pi - delta >= pi/2, + Hp(z*exp(S.ImaginaryUnit*pi*nu)), + Hm(z*exp(-S.ImaginaryUnit*pi*nu)))] + # Theorem 4, statements (10) and (11) -- p < q from test above + conds += [And(1 <= m, rho > 0, delta > 0, delta + rho*pi < pi/2, + (tau + epsilon)*pi - delta >= pi/2, + Hp(z*exp(S.ImaginaryUnit*pi*nu)), + Hm(z*exp(-S.ImaginaryUnit*pi*nu)))] + # Trivial case + conds += [m == 0] + + # TODO + # Theorem 5 is quite general + # Theorem 6 contains special cases for q=p+1 + + return Or(*conds) + + +def _int_inversion(g, x, t): + """ + Compute the laplace inversion integral, assuming the formula applies. + """ + b, a = _get_coeff_exp(g.argument, x) + C, g = _inflate_fox_h(meijerg(g.an, g.aother, g.bm, g.bother, b/t**a), -a) + return C/t*g + + +#################################################################### +# Finally, the real meat. +#################################################################### + +_lookup_table = None + + +@cacheit +@timeit +def _rewrite_single(f, x, recursive=True): + """ + Try to rewrite f as a sum of single G functions of the form + C*x**s*G(a*x**b), where b is a rational number and C is independent of x. + We guarantee that result.argument.as_coeff_mul(x) returns (a, (x**b,)) + or (a, ()). + Returns a list of tuples (C, s, G) and a condition cond. + Returns None on failure. + """ + from .transforms import (mellin_transform, inverse_mellin_transform, + IntegralTransformError, MellinTransformStripError) + + global _lookup_table + if not _lookup_table: + _lookup_table = {} + _create_lookup_table(_lookup_table) + + if isinstance(f, meijerg): + coeff, m = factor(f.argument, x).as_coeff_mul(x) + if len(m) > 1: + return None + m = m[0] + if m.is_Pow: + if m.base != x or not m.exp.is_Rational: + return None + elif m != x: + return None + return [(1, 0, meijerg(f.an, f.aother, f.bm, f.bother, coeff*m))], True + + f_ = f + f = f.subs(x, z) + t = _mytype(f, z) + if t in _lookup_table: + l = _lookup_table[t] + for formula, terms, cond, hint in l: + subs = f.match(formula, old=True) + if subs: + subs_ = {} + for fro, to in subs.items(): + subs_[fro] = unpolarify(polarify(to, lift=True), + exponents_only=True) + subs = subs_ + if not isinstance(hint, bool): + hint = hint.subs(subs) + if hint == False: + continue + if not isinstance(cond, (bool, BooleanAtom)): + cond = unpolarify(cond.subs(subs)) + if _eval_cond(cond) == False: + continue + if not isinstance(terms, list): + terms = terms(subs) + res = [] + for fac, g in terms: + r1 = _get_coeff_exp(unpolarify(fac.subs(subs).subs(z, x), + exponents_only=True), x) + try: + g = g.subs(subs).subs(z, x) + except ValueError: + continue + # NOTE these substitutions can in principle introduce oo, + # zoo and other absurdities. It shouldn't matter, + # but better be safe. + if Tuple(*(r1 + (g,))).has(S.Infinity, S.ComplexInfinity, S.NegativeInfinity): + continue + g = meijerg(g.an, g.aother, g.bm, g.bother, + unpolarify(g.argument, exponents_only=True)) + res.append(r1 + (g,)) + if res: + return res, cond + + # try recursive mellin transform + if not recursive: + return None + _debug('Trying recursive Mellin transform method.') + + def my_imt(F, s, x, strip): + """ Calling simplify() all the time is slow and not helpful, since + most of the time it only factors things in a way that has to be + un-done anyway. But sometimes it can remove apparent poles. """ + # XXX should this be in inverse_mellin_transform? + try: + return inverse_mellin_transform(F, s, x, strip, + as_meijerg=True, needeval=True) + except MellinTransformStripError: + from sympy.simplify import simplify + return inverse_mellin_transform( + simplify(cancel(expand(F))), s, x, strip, + as_meijerg=True, needeval=True) + f = f_ + s = _dummy('s', 'rewrite-single', f) + # to avoid infinite recursion, we have to force the two g functions case + + def my_integrator(f, x): + r = _meijerint_definite_4(f, x, only_double=True) + if r is not None: + from sympy.simplify import hyperexpand + res, cond = r + res = _my_unpolarify(hyperexpand(res, rewrite='nonrepsmall')) + return Piecewise((res, cond), + (Integral(f, (x, S.Zero, S.Infinity)), True)) + return Integral(f, (x, S.Zero, S.Infinity)) + try: + F, strip, _ = mellin_transform(f, x, s, integrator=my_integrator, + simplify=False, needeval=True) + g = my_imt(F, s, x, strip) + except IntegralTransformError: + g = None + if g is None: + # We try to find an expression by analytic continuation. + # (also if the dummy is already in the expression, there is no point in + # putting in another one) + a = _dummy_('a', 'rewrite-single') + if a not in f.free_symbols and _is_analytic(f, x): + try: + F, strip, _ = mellin_transform(f.subs(x, a*x), x, s, + integrator=my_integrator, + needeval=True, simplify=False) + g = my_imt(F, s, x, strip).subs(a, 1) + except IntegralTransformError: + g = None + if g is None or g.has(S.Infinity, S.NaN, S.ComplexInfinity): + _debug('Recursive Mellin transform failed.') + return None + args = Add.make_args(g) + res = [] + for f in args: + c, m = f.as_coeff_mul(x) + if len(m) > 1: + raise NotImplementedError('Unexpected form...') + g = m[0] + a, b = _get_coeff_exp(g.argument, x) + res += [(c, 0, meijerg(g.an, g.aother, g.bm, g.bother, + unpolarify(polarify( + a, lift=True), exponents_only=True) + *x**b))] + _debug('Recursive Mellin transform worked:', g) + return res, True + + +def _rewrite1(f, x, recursive=True): + """ + Try to rewrite ``f`` using a (sum of) single G functions with argument a*x**b. + Return fac, po, g such that f = fac*po*g, fac is independent of ``x``. + and po = x**s. + Here g is a result from _rewrite_single. + Return None on failure. + """ + fac, po, g = _split_mul(f, x) + g = _rewrite_single(g, x, recursive) + if g: + return fac, po, g[0], g[1] + + +def _rewrite2(f, x): + """ + Try to rewrite ``f`` as a product of two G functions of arguments a*x**b. + Return fac, po, g1, g2 such that f = fac*po*g1*g2, where fac is + independent of x and po is x**s. + Here g1 and g2 are results of _rewrite_single. + Returns None on failure. + """ + fac, po, g = _split_mul(f, x) + if any(_rewrite_single(expr, x, False) is None for expr in _mul_args(g)): + return None + l = _mul_as_two_parts(g) + if not l: + return None + l = list(ordered(l, [ + lambda p: max(len(_exponents(p[0], x)), len(_exponents(p[1], x))), + lambda p: max(len(_functions(p[0], x)), len(_functions(p[1], x))), + lambda p: max(len(_find_splitting_points(p[0], x)), + len(_find_splitting_points(p[1], x)))])) + + for recursive, (fac1, fac2) in itertools.product((False, True), l): + g1 = _rewrite_single(fac1, x, recursive) + g2 = _rewrite_single(fac2, x, recursive) + if g1 and g2: + cond = And(g1[1], g2[1]) + if cond != False: + return fac, po, g1[0], g2[0], cond + + +def meijerint_indefinite(f, x): + """ + Compute an indefinite integral of ``f`` by rewriting it as a G function. + + Examples + ======== + + >>> from sympy.integrals.meijerint import meijerint_indefinite + >>> from sympy import sin + >>> from sympy.abc import x + >>> meijerint_indefinite(sin(x), x) + -cos(x) + """ + f = sympify(f) + results = [] + for a in sorted(_find_splitting_points(f, x) | {S.Zero}, key=default_sort_key): + res = _meijerint_indefinite_1(f.subs(x, x + a), x) + if not res: + continue + res = res.subs(x, x - a) + if _has(res, hyper, meijerg): + results.append(res) + else: + return res + if f.has(HyperbolicFunction): + _debug('Try rewriting hyperbolics in terms of exp.') + rv = meijerint_indefinite( + _rewrite_hyperbolics_as_exp(f), x) + if rv: + if not isinstance(rv, list): + from sympy.simplify.radsimp import collect + return collect(factor_terms(rv), rv.atoms(exp)) + results.extend(rv) + if results: + return next(ordered(results)) + + +def _meijerint_indefinite_1(f, x): + """ Helper that does not attempt any substitution. """ + _debug('Trying to compute the indefinite integral of', f, 'wrt', x) + from sympy.simplify import hyperexpand, powdenest + + gs = _rewrite1(f, x) + if gs is None: + # Note: the code that calls us will do expand() and try again + return None + + fac, po, gl, cond = gs + _debug(' could rewrite:', gs) + res = S.Zero + for C, s, g in gl: + a, b = _get_coeff_exp(g.argument, x) + _, c = _get_coeff_exp(po, x) + c += s + + # we do a substitution t=a*x**b, get integrand fac*t**rho*g + fac_ = fac * C * x**(1 + c) / b + rho = (c + 1)/b + + # we now use t**rho*G(params, t) = G(params + rho, t) + # [L, page 150, equation (4)] + # and integral G(params, t) dt = G(1, params+1, 0, t) + # (or a similar expression with 1 and 0 exchanged ... pick the one + # which yields a well-defined function) + # [R, section 5] + # (Note that this dummy will immediately go away again, so we + # can safely pass S.One for ``expr``.) + t = _dummy('t', 'meijerint-indefinite', S.One) + + def tr(p): + return [a + rho for a in p] + if any(b.is_integer and (b <= 0) == True for b in tr(g.bm)): + r = -meijerg( + list(g.an), list(g.aother) + [1-rho], list(g.bm) + [-rho], list(g.bother), t) + else: + r = meijerg( + list(g.an) + [1-rho], list(g.aother), list(g.bm), list(g.bother) + [-rho], t) + # The antiderivative is most often expected to be defined + # in the neighborhood of x = 0. + if b.is_extended_nonnegative and not f.subs(x, 0).has(S.NaN, S.ComplexInfinity): + place = 0 # Assume we can expand at zero + else: + place = None + r = hyperexpand(r.subs(t, a*x**b), place=place) + + # now substitute back + # Note: we really do want the powers of x to combine. + res += powdenest(fac_*r, polar=True) + + def _clean(res): + """This multiplies out superfluous powers of x we created, and chops off + constants: + + >> _clean(x*(exp(x)/x - 1/x) + 3) + exp(x) + + cancel is used before mul_expand since it is possible for an + expression to have an additive constant that does not become isolated + with simple expansion. Such a situation was identified in issue 6369: + + Examples + ======== + + >>> from sympy import sqrt, cancel + >>> from sympy.abc import x + >>> a = sqrt(2*x + 1) + >>> bad = (3*x*a**5 + 2*x - a**5 + 1)/a**2 + >>> bad.expand().as_independent(x)[0] + 0 + >>> cancel(bad).expand().as_independent(x)[0] + 1 + """ + res = expand_mul(cancel(res), deep=False) + return Add._from_args(res.as_coeff_add(x)[1]) + + res = piecewise_fold(res, evaluate=None) + if res.is_Piecewise: + newargs = [] + for e, c in res.args: + e = _my_unpolarify(_clean(e)) + newargs += [(e, c)] + res = Piecewise(*newargs, evaluate=False) + else: + res = _my_unpolarify(_clean(res)) + return Piecewise((res, _my_unpolarify(cond)), (Integral(f, x), True)) + + +@timeit +def meijerint_definite(f, x, a, b): + """ + Integrate ``f`` over the interval [``a``, ``b``], by rewriting it as a product + of two G functions, or as a single G function. + + Return res, cond, where cond are convergence conditions. + + Examples + ======== + + >>> from sympy.integrals.meijerint import meijerint_definite + >>> from sympy import exp, oo + >>> from sympy.abc import x + >>> meijerint_definite(exp(-x**2), x, -oo, oo) + (sqrt(pi), True) + + This function is implemented as a succession of functions + meijerint_definite, _meijerint_definite_2, _meijerint_definite_3, + _meijerint_definite_4. Each function in the list calls the next one + (presumably) several times. This means that calling meijerint_definite + can be very costly. + """ + # This consists of three steps: + # 1) Change the integration limits to 0, oo + # 2) Rewrite in terms of G functions + # 3) Evaluate the integral + # + # There are usually several ways of doing this, and we want to try all. + # This function does (1), calls _meijerint_definite_2 for step (2). + _debugf('Integrating %s wrt %s from %s to %s.', (f, x, a, b)) + f = sympify(f) + if f.has(DiracDelta): + _debug('Integrand has DiracDelta terms - giving up.') + return None + + if f.has(SingularityFunction): + _debug('Integrand has Singularity Function terms - giving up.') + return None + + f_, x_, a_, b_ = f, x, a, b + + # Let's use a dummy in case any of the boundaries has x. + d = Dummy('x') + f = f.subs(x, d) + x = d + + if a == b: + return (S.Zero, True) + + results = [] + if a is S.NegativeInfinity and b is not S.Infinity: + return meijerint_definite(f.subs(x, -x), x, -b, -a) + + elif a is S.NegativeInfinity: + # Integrating -oo to oo. We need to find a place to split the integral. + _debug(' Integrating -oo to +oo.') + innermost = _find_splitting_points(f, x) + _debug(' Sensible splitting points:', innermost) + for c in sorted(innermost, key=default_sort_key, reverse=True) + [S.Zero]: + _debug(' Trying to split at', c) + if not c.is_extended_real: + _debug(' Non-real splitting point.') + continue + res1 = _meijerint_definite_2(f.subs(x, x + c), x) + if res1 is None: + _debug(' But could not compute first integral.') + continue + res2 = _meijerint_definite_2(f.subs(x, c - x), x) + if res2 is None: + _debug(' But could not compute second integral.') + continue + res1, cond1 = res1 + res2, cond2 = res2 + cond = _condsimp(And(cond1, cond2)) + if cond == False: + _debug(' But combined condition is always false.') + continue + res = res1 + res2 + return res, cond + + elif a is S.Infinity: + res = meijerint_definite(f, x, b, S.Infinity) + return -res[0], res[1] + + elif (a, b) == (S.Zero, S.Infinity): + # This is a common case - try it directly first. + res = _meijerint_definite_2(f, x) + if res: + if _has(res[0], meijerg): + results.append(res) + else: + return res + + else: + if b is S.Infinity: + for split in _find_splitting_points(f, x): + if (a - split >= 0) == True: + _debugf('Trying x -> x + %s', split) + res = _meijerint_definite_2(f.subs(x, x + split) + *Heaviside(x + split - a), x) + if res: + if _has(res[0], meijerg): + results.append(res) + else: + return res + + f = f.subs(x, x + a) + b = b - a + a = 0 + if b is not S.Infinity: + phi = exp(S.ImaginaryUnit*arg(b)) + b = Abs(b) + f = f.subs(x, phi*x) + f *= Heaviside(b - x)*phi + b = S.Infinity + + _debug('Changed limits to', a, b) + _debug('Changed function to', f) + res = _meijerint_definite_2(f, x) + if res: + if _has(res[0], meijerg): + results.append(res) + else: + return res + if f_.has(HyperbolicFunction): + _debug('Try rewriting hyperbolics in terms of exp.') + rv = meijerint_definite( + _rewrite_hyperbolics_as_exp(f_), x_, a_, b_) + if rv: + if not isinstance(rv, list): + from sympy.simplify.radsimp import collect + rv = (collect(factor_terms(rv[0]), rv[0].atoms(exp)),) + rv[1:] + return rv + results.extend(rv) + if results: + return next(ordered(results)) + + +def _guess_expansion(f, x): + """ Try to guess sensible rewritings for integrand f(x). """ + res = [(f, 'original integrand')] + + orig = res[-1][0] + saw = {orig} + expanded = expand_mul(orig) + if expanded not in saw: + res += [(expanded, 'expand_mul')] + saw.add(expanded) + + expanded = expand(orig) + if expanded not in saw: + res += [(expanded, 'expand')] + saw.add(expanded) + + if orig.has(TrigonometricFunction, HyperbolicFunction): + expanded = expand_mul(expand_trig(orig)) + if expanded not in saw: + res += [(expanded, 'expand_trig, expand_mul')] + saw.add(expanded) + + if orig.has(cos, sin): + from sympy.simplify.fu import sincos_to_sum + reduced = sincos_to_sum(orig) + if reduced not in saw: + res += [(reduced, 'trig power reduction')] + saw.add(reduced) + + return res + + +def _meijerint_definite_2(f, x): + """ + Try to integrate f dx from zero to infinity. + + The body of this function computes various 'simplifications' + f1, f2, ... of f (e.g. by calling expand_mul(), trigexpand() + - see _guess_expansion) and calls _meijerint_definite_3 with each of + these in succession. + If _meijerint_definite_3 succeeds with any of the simplified functions, + returns this result. + """ + # This function does preparation for (2), calls + # _meijerint_definite_3 for (2) and (3) combined. + + # use a positive dummy - we integrate from 0 to oo + # XXX if a nonnegative symbol is used there will be test failures + dummy = _dummy('x', 'meijerint-definite2', f, positive=True) + f = f.subs(x, dummy) + x = dummy + + if f == 0: + return S.Zero, True + + for g, explanation in _guess_expansion(f, x): + _debug('Trying', explanation) + res = _meijerint_definite_3(g, x) + if res: + return res + + +def _meijerint_definite_3(f, x): + """ + Try to integrate f dx from zero to infinity. + + This function calls _meijerint_definite_4 to try to compute the + integral. If this fails, it tries using linearity. + """ + res = _meijerint_definite_4(f, x) + if res and res[1] != False: + return res + if f.is_Add: + _debug('Expanding and evaluating all terms.') + ress = [_meijerint_definite_4(g, x) for g in f.args] + if all(r is not None for r in ress): + conds = [] + res = S.Zero + for r, c in ress: + res += r + conds += [c] + c = And(*conds) + if c != False: + return res, c + + +def _my_unpolarify(f): + return _eval_cond(unpolarify(f)) + + +@timeit +def _meijerint_definite_4(f, x, only_double=False): + """ + Try to integrate f dx from zero to infinity. + + Explanation + =========== + + This function tries to apply the integration theorems found in literature, + i.e. it tries to rewrite f as either one or a product of two G-functions. + + The parameter ``only_double`` is used internally in the recursive algorithm + to disable trying to rewrite f as a single G-function. + """ + from sympy.simplify import hyperexpand + # This function does (2) and (3) + _debug('Integrating', f) + # Try single G function. + if not only_double: + gs = _rewrite1(f, x, recursive=False) + if gs is not None: + fac, po, g, cond = gs + _debug('Could rewrite as single G function:', fac, po, g) + res = S.Zero + for C, s, f in g: + if C == 0: + continue + C, f = _rewrite_saxena_1(fac*C, po*x**s, f, x) + res += C*_int0oo_1(f, x) + cond = And(cond, _check_antecedents_1(f, x)) + if cond == False: + break + cond = _my_unpolarify(cond) + if cond == False: + _debug('But cond is always False.') + else: + _debug('Result before branch substitutions is:', res) + return _my_unpolarify(hyperexpand(res)), cond + + # Try two G functions. + gs = _rewrite2(f, x) + if gs is not None: + for full_pb in [False, True]: + fac, po, g1, g2, cond = gs + _debug('Could rewrite as two G functions:', fac, po, g1, g2) + res = S.Zero + for C1, s1, f1 in g1: + for C2, s2, f2 in g2: + r = _rewrite_saxena(fac*C1*C2, po*x**(s1 + s2), + f1, f2, x, full_pb) + if r is None: + _debug('Non-rational exponents.') + return + C, f1_, f2_ = r + _debug('Saxena subst for yielded:', C, f1_, f2_) + cond = And(cond, _check_antecedents(f1_, f2_, x)) + if cond == False: + break + res += C*_int0oo(f1_, f2_, x) + else: + continue + break + cond = _my_unpolarify(cond) + if cond == False: + _debugf('But cond is always False (full_pb=%s).', full_pb) + else: + _debugf('Result before branch substitutions is: %s', (res, )) + if only_double: + return res, cond + return _my_unpolarify(hyperexpand(res)), cond + + +def meijerint_inversion(f, x, t): + r""" + Compute the inverse laplace transform + $\int_{c+i\infty}^{c-i\infty} f(x) e^{tx}\, dx$, + for real c larger than the real part of all singularities of ``f``. + + Note that ``t`` is always assumed real and positive. + + Return None if the integral does not exist or could not be evaluated. + + Examples + ======== + + >>> from sympy.abc import x, t + >>> from sympy.integrals.meijerint import meijerint_inversion + >>> meijerint_inversion(1/x, x, t) + Heaviside(t) + """ + f_ = f + t_ = t + t = Dummy('t', polar=True) # We don't want sqrt(t**2) = abs(t) etc + f = f.subs(t_, t) + _debug('Laplace-inverting', f) + if not _is_analytic(f, x): + _debug('But expression is not analytic.') + return None + # Exponentials correspond to shifts; we filter them out and then + # shift the result later. If we are given an Add this will not + # work, but the calling code will take care of that. + shift = S.Zero + + if f.is_Mul: + args = list(f.args) + elif isinstance(f, exp): + args = [f] + else: + args = None + + if args: + newargs = [] + exponentials = [] + while args: + arg = args.pop() + if isinstance(arg, exp): + arg2 = expand(arg) + if arg2.is_Mul: + args += arg2.args + continue + try: + a, b = _get_coeff_exp(arg.args[0], x) + except _CoeffExpValueError: + b = 0 + if b == 1: + exponentials.append(a) + else: + newargs.append(arg) + elif arg.is_Pow: + arg2 = expand(arg) + if arg2.is_Mul: + args += arg2.args + continue + if x not in arg.base.free_symbols: + try: + a, b = _get_coeff_exp(arg.exp, x) + except _CoeffExpValueError: + b = 0 + if b == 1: + exponentials.append(a*log(arg.base)) + newargs.append(arg) + else: + newargs.append(arg) + shift = Add(*exponentials) + f = Mul(*newargs) + + if x not in f.free_symbols: + _debug('Expression consists of constant and exp shift:', f, shift) + cond = Eq(im(shift), 0) + if cond == False: + _debug('but shift is nonreal, cannot be a Laplace transform') + return None + res = f*DiracDelta(t + shift) + _debug('Result is a delta function, possibly conditional:', res, cond) + # cond is True or Eq + return Piecewise((res.subs(t, t_), cond)) + + gs = _rewrite1(f, x) + if gs is not None: + fac, po, g, cond = gs + _debug('Could rewrite as single G function:', fac, po, g) + res = S.Zero + for C, s, f in g: + C, f = _rewrite_inversion(fac*C, po*x**s, f, x) + res += C*_int_inversion(f, x, t) + cond = And(cond, _check_antecedents_inversion(f, x)) + if cond == False: + break + cond = _my_unpolarify(cond) + if cond == False: + _debug('But cond is always False.') + else: + _debug('Result before branch substitution:', res) + from sympy.simplify import hyperexpand + res = _my_unpolarify(hyperexpand(res)) + if not res.has(Heaviside): + res *= Heaviside(t) + res = res.subs(t, t + shift) + if not isinstance(cond, bool): + cond = cond.subs(t, t + shift) + from .transforms import InverseLaplaceTransform + return Piecewise((res.subs(t, t_), cond), + (InverseLaplaceTransform(f_.subs(t, t_), x, t_, None), True)) diff --git a/lib/python3.12/site-packages/sympy/integrals/meijerint_doc.py b/lib/python3.12/site-packages/sympy/integrals/meijerint_doc.py new file mode 100644 index 0000000000000000000000000000000000000000..712a52e183006528b8c50a56207d44742e28f0c0 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/meijerint_doc.py @@ -0,0 +1,37 @@ +""" This module cooks up a docstring when imported. Its only purpose is to + be displayed in the sphinx documentation. """ + +from __future__ import annotations +from typing import Any + +from sympy.integrals.meijerint import _create_lookup_table +from sympy.core.add import Add +from sympy.core.basic import Basic +from sympy.core.relational import Eq +from sympy.core.symbol import Symbol +from sympy.printing.latex import latex + +t: dict[tuple[type[Basic], ...], list[Any]] = {} +_create_lookup_table(t) + + +doc = "" +for about, category in t.items(): + if about == (): + doc += 'Elementary functions:\n\n' + else: + doc += 'Functions involving ' + ', '.join('`%s`' % latex( + list(category[0][0].atoms(func))[0]) for func in about) + ':\n\n' + for formula, gs, cond, hint in category: + if not isinstance(gs, list): + g = Symbol('\\text{generated}') + else: + g = Add(*[fac*f for (fac, f) in gs]) + obj = Eq(formula, g) + if cond is True: + cond = "" + else: + cond = ',\\text{ if } %s' % latex(cond) + doc += ".. math::\n %s%s\n\n" % (latex(obj), cond) + +__doc__ = doc diff --git a/lib/python3.12/site-packages/sympy/integrals/prde.py b/lib/python3.12/site-packages/sympy/integrals/prde.py new file mode 100644 index 0000000000000000000000000000000000000000..4488cbfc4000a06d47886d026f6807e68d64c8cb --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/prde.py @@ -0,0 +1,1333 @@ +""" +Algorithms for solving Parametric Risch Differential Equations. + +The methods used for solving Parametric Risch Differential Equations parallel +those for solving Risch Differential Equations. See the outline in the +docstring of rde.py for more information. + +The Parametric Risch Differential Equation problem is, given f, g1, ..., gm in +K(t), to determine if there exist y in K(t) and c1, ..., cm in Const(K) such +that Dy + f*y == Sum(ci*gi, (i, 1, m)), and to find such y and ci if they exist. + +For the algorithms here G is a list of tuples of factions of the terms on the +right hand side of the equation (i.e., gi in k(t)), and Q is a list of terms on +the right hand side of the equation (i.e., qi in k[t]). See the docstring of +each function for more information. +""" +import itertools +from functools import reduce + +from sympy.core.intfunc import ilcm +from sympy.core import Dummy, Add, Mul, Pow, S +from sympy.integrals.rde import (order_at, order_at_oo, weak_normalizer, + bound_degree) +from sympy.integrals.risch import (gcdex_diophantine, frac_in, derivation, + residue_reduce, splitfactor, residue_reduce_derivation, DecrementLevel, + recognize_log_derivative) +from sympy.polys import Poly, lcm, cancel, sqf_list +from sympy.polys.polymatrix import PolyMatrix as Matrix +from sympy.solvers import solve + +zeros = Matrix.zeros +eye = Matrix.eye + + +def prde_normal_denom(fa, fd, G, DE): + """ + Parametric Risch Differential Equation - Normal part of the denominator. + + Explanation + =========== + + Given a derivation D on k[t] and f, g1, ..., gm in k(t) with f weakly + normalized with respect to t, return the tuple (a, b, G, h) such that + a, h in k[t], b in k, G = [g1, ..., gm] in k(t)^m, and for any solution + c1, ..., cm in Const(k) and y in k(t) of Dy + f*y == Sum(ci*gi, (i, 1, m)), + q == y*h in k satisfies a*Dq + b*q == Sum(ci*Gi, (i, 1, m)). + """ + dn, ds = splitfactor(fd, DE) + Gas, Gds = list(zip(*G)) + gd = reduce(lambda i, j: i.lcm(j), Gds, Poly(1, DE.t)) + en, es = splitfactor(gd, DE) + + p = dn.gcd(en) + h = en.gcd(en.diff(DE.t)).quo(p.gcd(p.diff(DE.t))) + + a = dn*h + c = a*h + + ba = a*fa - dn*derivation(h, DE)*fd + ba, bd = ba.cancel(fd, include=True) + + G = [(c*A).cancel(D, include=True) for A, D in G] + + return (a, (ba, bd), G, h) + +def real_imag(ba, bd, gen): + """ + Helper function, to get the real and imaginary part of a rational function + evaluated at sqrt(-1) without actually evaluating it at sqrt(-1). + + Explanation + =========== + + Separates the even and odd power terms by checking the degree of terms wrt + mod 4. Returns a tuple (ba[0], ba[1], bd) where ba[0] is real part + of the numerator ba[1] is the imaginary part and bd is the denominator + of the rational function. + """ + bd = bd.as_poly(gen).as_dict() + ba = ba.as_poly(gen).as_dict() + denom_real = [value if key[0] % 4 == 0 else -value if key[0] % 4 == 2 else 0 for key, value in bd.items()] + denom_imag = [value if key[0] % 4 == 1 else -value if key[0] % 4 == 3 else 0 for key, value in bd.items()] + bd_real = sum(r for r in denom_real) + bd_imag = sum(r for r in denom_imag) + num_real = [value if key[0] % 4 == 0 else -value if key[0] % 4 == 2 else 0 for key, value in ba.items()] + num_imag = [value if key[0] % 4 == 1 else -value if key[0] % 4 == 3 else 0 for key, value in ba.items()] + ba_real = sum(r for r in num_real) + ba_imag = sum(r for r in num_imag) + ba = ((ba_real*bd_real + ba_imag*bd_imag).as_poly(gen), (ba_imag*bd_real - ba_real*bd_imag).as_poly(gen)) + bd = (bd_real*bd_real + bd_imag*bd_imag).as_poly(gen) + return (ba[0], ba[1], bd) + + +def prde_special_denom(a, ba, bd, G, DE, case='auto'): + """ + Parametric Risch Differential Equation - Special part of the denominator. + + Explanation + =========== + + Case is one of {'exp', 'tan', 'primitive'} for the hyperexponential, + hypertangent, and primitive cases, respectively. For the hyperexponential + (resp. hypertangent) case, given a derivation D on k[t] and a in k[t], + b in k, and g1, ..., gm in k(t) with Dt/t in k (resp. Dt/(t**2 + 1) in + k, sqrt(-1) not in k), a != 0, and gcd(a, t) == 1 (resp. + gcd(a, t**2 + 1) == 1), return the tuple (A, B, GG, h) such that A, B, h in + k[t], GG = [gg1, ..., ggm] in k(t)^m, and for any solution c1, ..., cm in + Const(k) and q in k of a*Dq + b*q == Sum(ci*gi, (i, 1, m)), r == q*h in + k[t] satisfies A*Dr + B*r == Sum(ci*ggi, (i, 1, m)). + + For case == 'primitive', k == k[t], so it returns (a, b, G, 1) in this + case. + """ + # TODO: Merge this with the very similar special_denom() in rde.py + if case == 'auto': + case = DE.case + + if case == 'exp': + p = Poly(DE.t, DE.t) + elif case == 'tan': + p = Poly(DE.t**2 + 1, DE.t) + elif case in ('primitive', 'base'): + B = ba.quo(bd) + return (a, B, G, Poly(1, DE.t)) + else: + raise ValueError("case must be one of {'exp', 'tan', 'primitive', " + "'base'}, not %s." % case) + + nb = order_at(ba, p, DE.t) - order_at(bd, p, DE.t) + nc = min(order_at(Ga, p, DE.t) - order_at(Gd, p, DE.t) for Ga, Gd in G) + n = min(0, nc - min(0, nb)) + if not nb: + # Possible cancellation. + if case == 'exp': + dcoeff = DE.d.quo(Poly(DE.t, DE.t)) + with DecrementLevel(DE): # We are guaranteed to not have problems, + # because case != 'base'. + alphaa, alphad = frac_in(-ba.eval(0)/bd.eval(0)/a.eval(0), DE.t) + etaa, etad = frac_in(dcoeff, DE.t) + A = parametric_log_deriv(alphaa, alphad, etaa, etad, DE) + if A is not None: + Q, m, z = A + if Q == 1: + n = min(n, m) + + elif case == 'tan': + dcoeff = DE.d.quo(Poly(DE.t**2 + 1, DE.t)) + with DecrementLevel(DE): # We are guaranteed to not have problems, + # because case != 'base'. + betaa, alphaa, alphad = real_imag(ba, bd*a, DE.t) + betad = alphad + etaa, etad = frac_in(dcoeff, DE.t) + if recognize_log_derivative(Poly(2, DE.t)*betaa, betad, DE): + A = parametric_log_deriv(alphaa, alphad, etaa, etad, DE) + B = parametric_log_deriv(betaa, betad, etaa, etad, DE) + if A is not None and B is not None: + Q, s, z = A + # TODO: Add test + if Q == 1: + n = min(n, s/2) + + N = max(0, -nb) + pN = p**N + pn = p**-n # This is 1/h + + A = a*pN + B = ba*pN.quo(bd) + Poly(n, DE.t)*a*derivation(p, DE).quo(p)*pN + G = [(Ga*pN*pn).cancel(Gd, include=True) for Ga, Gd in G] + h = pn + + # (a*p**N, (b + n*a*Dp/p)*p**N, g1*p**(N - n), ..., gm*p**(N - n), p**-n) + return (A, B, G, h) + + +def prde_linear_constraints(a, b, G, DE): + """ + Parametric Risch Differential Equation - Generate linear constraints on the constants. + + Explanation + =========== + + Given a derivation D on k[t], a, b, in k[t] with gcd(a, b) == 1, and + G = [g1, ..., gm] in k(t)^m, return Q = [q1, ..., qm] in k[t]^m and a + matrix M with entries in k(t) such that for any solution c1, ..., cm in + Const(k) and p in k[t] of a*Dp + b*p == Sum(ci*gi, (i, 1, m)), + (c1, ..., cm) is a solution of Mx == 0, and p and the ci satisfy + a*Dp + b*p == Sum(ci*qi, (i, 1, m)). + + Because M has entries in k(t), and because Matrix does not play well with + Poly, M will be a Matrix of Basic expressions. + """ + m = len(G) + + Gns, Gds = list(zip(*G)) + d = reduce(lambda i, j: i.lcm(j), Gds) + d = Poly(d, field=True) + Q = [(ga*(d).quo(gd)).div(d) for ga, gd in G] + + if not all(ri.is_zero for _, ri in Q): + N = max(ri.degree(DE.t) for _, ri in Q) + M = Matrix(N + 1, m, lambda i, j: Q[j][1].nth(i), DE.t) + else: + M = Matrix(0, m, [], DE.t) # No constraints, return the empty matrix. + + qs, _ = list(zip(*Q)) + return (qs, M) + +def poly_linear_constraints(p, d): + """ + Given p = [p1, ..., pm] in k[t]^m and d in k[t], return + q = [q1, ..., qm] in k[t]^m and a matrix M with entries in k such + that Sum(ci*pi, (i, 1, m)), for c1, ..., cm in k, is divisible + by d if and only if (c1, ..., cm) is a solution of Mx = 0, in + which case the quotient is Sum(ci*qi, (i, 1, m)). + """ + m = len(p) + q, r = zip(*[pi.div(d) for pi in p]) + + if not all(ri.is_zero for ri in r): + n = max(ri.degree() for ri in r) + M = Matrix(n + 1, m, lambda i, j: r[j].nth(i), d.gens) + else: + M = Matrix(0, m, [], d.gens) # No constraints. + + return q, M + +def constant_system(A, u, DE): + """ + Generate a system for the constant solutions. + + Explanation + =========== + + Given a differential field (K, D) with constant field C = Const(K), a Matrix + A, and a vector (Matrix) u with coefficients in K, returns the tuple + (B, v, s), where B is a Matrix with coefficients in C and v is a vector + (Matrix) such that either v has coefficients in C, in which case s is True + and the solutions in C of Ax == u are exactly all the solutions of Bx == v, + or v has a non-constant coefficient, in which case s is False Ax == u has no + constant solution. + + This algorithm is used both in solving parametric problems and in + determining if an element a of K is a derivative of an element of K or the + logarithmic derivative of a K-radical using the structure theorem approach. + + Because Poly does not play well with Matrix yet, this algorithm assumes that + all matrix entries are Basic expressions. + """ + if not A: + return A, u + Au = A.row_join(u) + Au, _ = Au.rref() + # Warning: This will NOT return correct results if cancel() cannot reduce + # an identically zero expression to 0. The danger is that we might + # incorrectly prove that an integral is nonelementary (such as + # risch_integrate(exp((sin(x)**2 + cos(x)**2 - 1)*x**2), x). + # But this is a limitation in computer algebra in general, and implicit + # in the correctness of the Risch Algorithm is the computability of the + # constant field (actually, this same correctness problem exists in any + # algorithm that uses rref()). + # + # We therefore limit ourselves to constant fields that are computable + # via the cancel() function, in order to prevent a speed bottleneck from + # calling some more complex simplification function (rational function + # coefficients will fall into this class). Furthermore, (I believe) this + # problem will only crop up if the integral explicitly contains an + # expression in the constant field that is identically zero, but cannot + # be reduced to such by cancel(). Therefore, a careful user can avoid this + # problem entirely by being careful with the sorts of expressions that + # appear in his integrand in the variables other than the integration + # variable (the structure theorems should be able to completely decide these + # problems in the integration variable). + + A, u = Au[:, :-1], Au[:, -1] + + D = lambda x: derivation(x, DE, basic=True) + + for j, i in itertools.product(range(A.cols), range(A.rows)): + if A[i, j].expr.has(*DE.T): + # This assumes that const(F(t0, ..., tn) == const(K) == F + Ri = A[i, :] + # Rm+1; m = A.rows + DAij = D(A[i, j]) + Rm1 = Ri.applyfunc(lambda x: D(x) / DAij) + um1 = D(u[i]) / DAij + + Aj = A[:, j] + A = A - Aj * Rm1 + u = u - Aj * um1 + + A = A.col_join(Rm1) + u = u.col_join(Matrix([um1], u.gens)) + + return (A, u) + + +def prde_spde(a, b, Q, n, DE): + """ + Special Polynomial Differential Equation algorithm: Parametric Version. + + Explanation + =========== + + Given a derivation D on k[t], an integer n, and a, b, q1, ..., qm in k[t] + with deg(a) > 0 and gcd(a, b) == 1, return (A, B, Q, R, n1), with + Qq = [q1, ..., qm] and R = [r1, ..., rm], such that for any solution + c1, ..., cm in Const(k) and q in k[t] of degree at most n of + a*Dq + b*q == Sum(ci*gi, (i, 1, m)), p = (q - Sum(ci*ri, (i, 1, m)))/a has + degree at most n1 and satisfies A*Dp + B*p == Sum(ci*qi, (i, 1, m)) + """ + R, Z = list(zip(*[gcdex_diophantine(b, a, qi) for qi in Q])) + + A = a + B = b + derivation(a, DE) + Qq = [zi - derivation(ri, DE) for ri, zi in zip(R, Z)] + R = list(R) + n1 = n - a.degree(DE.t) + + return (A, B, Qq, R, n1) + + +def prde_no_cancel_b_large(b, Q, n, DE): + """ + Parametric Poly Risch Differential Equation - No cancellation: deg(b) large enough. + + Explanation + =========== + + Given a derivation D on k[t], n in ZZ, and b, q1, ..., qm in k[t] with + b != 0 and either D == d/dt or deg(b) > max(0, deg(D) - 1), returns + h1, ..., hr in k[t] and a matrix A with coefficients in Const(k) such that + if c1, ..., cm in Const(k) and q in k[t] satisfy deg(q) <= n and + Dq + b*q == Sum(ci*qi, (i, 1, m)), then q = Sum(dj*hj, (j, 1, r)), where + d1, ..., dr in Const(k) and A*Matrix([[c1, ..., cm, d1, ..., dr]]).T == 0. + """ + db = b.degree(DE.t) + m = len(Q) + H = [Poly(0, DE.t)]*m + + for N, i in itertools.product(range(n, -1, -1), range(m)): # [n, ..., 0] + si = Q[i].nth(N + db)/b.LC() + sitn = Poly(si*DE.t**N, DE.t) + H[i] = H[i] + sitn + Q[i] = Q[i] - derivation(sitn, DE) - b*sitn + + if all(qi.is_zero for qi in Q): + dc = -1 + else: + dc = max(qi.degree(DE.t) for qi in Q) + M = Matrix(dc + 1, m, lambda i, j: Q[j].nth(i), DE.t) + A, u = constant_system(M, zeros(dc + 1, 1, DE.t), DE) + c = eye(m, DE.t) + A = A.row_join(zeros(A.rows, m, DE.t)).col_join(c.row_join(-c)) + + return (H, A) + + +def prde_no_cancel_b_small(b, Q, n, DE): + """ + Parametric Poly Risch Differential Equation - No cancellation: deg(b) small enough. + + Explanation + =========== + + Given a derivation D on k[t], n in ZZ, and b, q1, ..., qm in k[t] with + deg(b) < deg(D) - 1 and either D == d/dt or deg(D) >= 2, returns + h1, ..., hr in k[t] and a matrix A with coefficients in Const(k) such that + if c1, ..., cm in Const(k) and q in k[t] satisfy deg(q) <= n and + Dq + b*q == Sum(ci*qi, (i, 1, m)) then q = Sum(dj*hj, (j, 1, r)) where + d1, ..., dr in Const(k) and A*Matrix([[c1, ..., cm, d1, ..., dr]]).T == 0. + """ + m = len(Q) + H = [Poly(0, DE.t)]*m + + for N, i in itertools.product(range(n, 0, -1), range(m)): # [n, ..., 1] + si = Q[i].nth(N + DE.d.degree(DE.t) - 1)/(N*DE.d.LC()) + sitn = Poly(si*DE.t**N, DE.t) + H[i] = H[i] + sitn + Q[i] = Q[i] - derivation(sitn, DE) - b*sitn + + if b.degree(DE.t) > 0: + for i in range(m): + si = Poly(Q[i].nth(b.degree(DE.t))/b.LC(), DE.t) + H[i] = H[i] + si + Q[i] = Q[i] - derivation(si, DE) - b*si + if all(qi.is_zero for qi in Q): + dc = -1 + else: + dc = max(qi.degree(DE.t) for qi in Q) + M = Matrix(dc + 1, m, lambda i, j: Q[j].nth(i), DE.t) + A, u = constant_system(M, zeros(dc + 1, 1, DE.t), DE) + c = eye(m, DE.t) + A = A.row_join(zeros(A.rows, m, DE.t)).col_join(c.row_join(-c)) + return (H, A) + + # else: b is in k, deg(qi) < deg(Dt) + + t = DE.t + if DE.case != 'base': + with DecrementLevel(DE): + t0 = DE.t # k = k0(t0) + ba, bd = frac_in(b, t0, field=True) + Q0 = [frac_in(qi.TC(), t0, field=True) for qi in Q] + f, B = param_rischDE(ba, bd, Q0, DE) + + # f = [f1, ..., fr] in k^r and B is a matrix with + # m + r columns and entries in Const(k) = Const(k0) + # such that Dy0 + b*y0 = Sum(ci*qi, (i, 1, m)) has + # a solution y0 in k with c1, ..., cm in Const(k) + # if and only y0 = Sum(dj*fj, (j, 1, r)) where + # d1, ..., dr ar in Const(k) and + # B*Matrix([c1, ..., cm, d1, ..., dr]) == 0. + + # Transform fractions (fa, fd) in f into constant + # polynomials fa/fd in k[t]. + # (Is there a better way?) + f = [Poly(fa.as_expr()/fd.as_expr(), t, field=True) + for fa, fd in f] + B = Matrix.from_Matrix(B.to_Matrix(), t) + else: + # Base case. Dy == 0 for all y in k and b == 0. + # Dy + b*y = Sum(ci*qi) is solvable if and only if + # Sum(ci*qi) == 0 in which case the solutions are + # y = d1*f1 for f1 = 1 and any d1 in Const(k) = k. + + f = [Poly(1, t, field=True)] # r = 1 + B = Matrix([[qi.TC() for qi in Q] + [S.Zero]], DE.t) + # The condition for solvability is + # B*Matrix([c1, ..., cm, d1]) == 0 + # There are no constraints on d1. + + # Coefficients of t^j (j > 0) in Sum(ci*qi) must be zero. + d = max(qi.degree(DE.t) for qi in Q) + if d > 0: + M = Matrix(d, m, lambda i, j: Q[j].nth(i + 1), DE.t) + A, _ = constant_system(M, zeros(d, 1, DE.t), DE) + else: + # No constraints on the hj. + A = Matrix(0, m, [], DE.t) + + # Solutions of the original equation are + # y = Sum(dj*fj, (j, 1, r) + Sum(ei*hi, (i, 1, m)), + # where ei == ci (i = 1, ..., m), when + # A*Matrix([c1, ..., cm]) == 0 and + # B*Matrix([c1, ..., cm, d1, ..., dr]) == 0 + + # Build combined constraint matrix with m + r + m columns. + + r = len(f) + I = eye(m, DE.t) + A = A.row_join(zeros(A.rows, r + m, DE.t)) + B = B.row_join(zeros(B.rows, m, DE.t)) + C = I.row_join(zeros(m, r, DE.t)).row_join(-I) + + return f + H, A.col_join(B).col_join(C) + + +def prde_cancel_liouvillian(b, Q, n, DE): + """ + Pg, 237. + """ + H = [] + + # Why use DecrementLevel? Below line answers that: + # Assuming that we can solve such problems over 'k' (not k[t]) + if DE.case == 'primitive': + with DecrementLevel(DE): + ba, bd = frac_in(b, DE.t, field=True) + + for i in range(n, -1, -1): + if DE.case == 'exp': # this re-checking can be avoided + with DecrementLevel(DE): + ba, bd = frac_in(b + (i*(derivation(DE.t, DE)/DE.t)).as_poly(b.gens), + DE.t, field=True) + with DecrementLevel(DE): + Qy = [frac_in(q.nth(i), DE.t, field=True) for q in Q] + fi, Ai = param_rischDE(ba, bd, Qy, DE) + fi = [Poly(fa.as_expr()/fd.as_expr(), DE.t, field=True) + for fa, fd in fi] + Ai = Ai.set_gens(DE.t) + + ri = len(fi) + + if i == n: + M = Ai + else: + M = Ai.col_join(M.row_join(zeros(M.rows, ri, DE.t))) + + Fi, hi = [None]*ri, [None]*ri + + # from eq. on top of p.238 (unnumbered) + for j in range(ri): + hji = fi[j] * (DE.t**i).as_poly(fi[j].gens) + hi[j] = hji + # building up Sum(djn*(D(fjn*t^n) - b*fjnt^n)) + Fi[j] = -(derivation(hji, DE) - b*hji) + + H += hi + # in the next loop instead of Q it has + # to be Q + Fi taking its place + Q = Q + Fi + + return (H, M) + + +def param_poly_rischDE(a, b, q, n, DE): + """Polynomial solutions of a parametric Risch differential equation. + + Explanation + =========== + + Given a derivation D in k[t], a, b in k[t] relatively prime, and q + = [q1, ..., qm] in k[t]^m, return h = [h1, ..., hr] in k[t]^r and + a matrix A with m + r columns and entries in Const(k) such that + a*Dp + b*p = Sum(ci*qi, (i, 1, m)) has a solution p of degree <= n + in k[t] with c1, ..., cm in Const(k) if and only if p = Sum(dj*hj, + (j, 1, r)) where d1, ..., dr are in Const(k) and (c1, ..., cm, + d1, ..., dr) is a solution of Ax == 0. + """ + m = len(q) + if n < 0: + # Only the trivial zero solution is possible. + # Find relations between the qi. + if all(qi.is_zero for qi in q): + return [], zeros(1, m, DE.t) # No constraints. + + N = max(qi.degree(DE.t) for qi in q) + M = Matrix(N + 1, m, lambda i, j: q[j].nth(i), DE.t) + A, _ = constant_system(M, zeros(M.rows, 1, DE.t), DE) + + return [], A + + if a.is_ground: + # Normalization: a = 1. + a = a.LC() + b, q = b.quo_ground(a), [qi.quo_ground(a) for qi in q] + + if not b.is_zero and (DE.case == 'base' or + b.degree() > max(0, DE.d.degree() - 1)): + return prde_no_cancel_b_large(b, q, n, DE) + + elif ((b.is_zero or b.degree() < DE.d.degree() - 1) + and (DE.case == 'base' or DE.d.degree() >= 2)): + return prde_no_cancel_b_small(b, q, n, DE) + + elif (DE.d.degree() >= 2 and + b.degree() == DE.d.degree() - 1 and + n > -b.as_poly().LC()/DE.d.as_poly().LC()): + raise NotImplementedError("prde_no_cancel_b_equal() is " + "not yet implemented.") + + else: + # Liouvillian cases + if DE.case in ('primitive', 'exp'): + return prde_cancel_liouvillian(b, q, n, DE) + else: + raise NotImplementedError("non-linear and hypertangent " + "cases have not yet been implemented") + + # else: deg(a) > 0 + + # Iterate SPDE as long as possible cumulating coefficient + # and terms for the recovery of original solutions. + alpha, beta = a.one, [a.zero]*m + while n >= 0: # and a, b relatively prime + a, b, q, r, n = prde_spde(a, b, q, n, DE) + beta = [betai + alpha*ri for betai, ri in zip(beta, r)] + alpha *= a + # Solutions p of a*Dp + b*p = Sum(ci*qi) correspond to + # solutions alpha*p + Sum(ci*betai) of the initial equation. + d = a.gcd(b) + if not d.is_ground: + break + + # a*Dp + b*p = Sum(ci*qi) may have a polynomial solution + # only if the sum is divisible by d. + + qq, M = poly_linear_constraints(q, d) + # qq = [qq1, ..., qqm] where qqi = qi.quo(d). + # M is a matrix with m columns an entries in k. + # Sum(fi*qi, (i, 1, m)), where f1, ..., fm are elements of k, is + # divisible by d if and only if M*Matrix([f1, ..., fm]) == 0, + # in which case the quotient is Sum(fi*qqi). + + A, _ = constant_system(M, zeros(M.rows, 1, DE.t), DE) + # A is a matrix with m columns and entries in Const(k). + # Sum(ci*qqi) is Sum(ci*qi).quo(d), and the remainder is zero + # for c1, ..., cm in Const(k) if and only if + # A*Matrix([c1, ...,cm]) == 0. + + V = A.nullspace() + # V = [v1, ..., vu] where each vj is a column matrix with + # entries aj1, ..., ajm in Const(k). + # Sum(aji*qi) is divisible by d with exact quotient Sum(aji*qqi). + # Sum(ci*qi) is divisible by d if and only if ci = Sum(dj*aji) + # (i = 1, ..., m) for some d1, ..., du in Const(k). + # In that case, solutions of + # a*Dp + b*p = Sum(ci*qi) = Sum(dj*Sum(aji*qi)) + # are the same as those of + # (a/d)*Dp + (b/d)*p = Sum(dj*rj) + # where rj = Sum(aji*qqi). + + if not V: # No non-trivial solution. + return [], eye(m, DE.t) # Could return A, but this has + # the minimum number of rows. + + Mqq = Matrix([qq]) # A single row. + r = [(Mqq*vj)[0] for vj in V] # [r1, ..., ru] + + # Solutions of (a/d)*Dp + (b/d)*p = Sum(dj*rj) correspond to + # solutions alpha*p + Sum(Sum(dj*aji)*betai) of the initial + # equation. These are equal to alpha*p + Sum(dj*fj) where + # fj = Sum(aji*betai). + Mbeta = Matrix([beta]) + f = [(Mbeta*vj)[0] for vj in V] # [f1, ..., fu] + + # + # Solve the reduced equation recursively. + # + g, B = param_poly_rischDE(a.quo(d), b.quo(d), r, n, DE) + + # g = [g1, ..., gv] in k[t]^v and and B is a matrix with u + v + # columns and entries in Const(k) such that + # (a/d)*Dp + (b/d)*p = Sum(dj*rj) has a solution p of degree <= n + # in k[t] if and only if p = Sum(ek*gk) where e1, ..., ev are in + # Const(k) and B*Matrix([d1, ..., du, e1, ..., ev]) == 0. + # The solutions of the original equation are then + # Sum(dj*fj, (j, 1, u)) + alpha*Sum(ek*gk, (k, 1, v)). + + # Collect solution components. + h = f + [alpha*gk for gk in g] + + # Build combined relation matrix. + A = -eye(m, DE.t) + for vj in V: + A = A.row_join(vj) + A = A.row_join(zeros(m, len(g), DE.t)) + A = A.col_join(zeros(B.rows, m, DE.t).row_join(B)) + + return h, A + + +def param_rischDE(fa, fd, G, DE): + """ + Solve a Parametric Risch Differential Equation: Dy + f*y == Sum(ci*Gi, (i, 1, m)). + + Explanation + =========== + + Given a derivation D in k(t), f in k(t), and G + = [G1, ..., Gm] in k(t)^m, return h = [h1, ..., hr] in k(t)^r and + a matrix A with m + r columns and entries in Const(k) such that + Dy + f*y = Sum(ci*Gi, (i, 1, m)) has a solution y + in k(t) with c1, ..., cm in Const(k) if and only if y = Sum(dj*hj, + (j, 1, r)) where d1, ..., dr are in Const(k) and (c1, ..., cm, + d1, ..., dr) is a solution of Ax == 0. + + Elements of k(t) are tuples (a, d) with a and d in k[t]. + """ + m = len(G) + q, (fa, fd) = weak_normalizer(fa, fd, DE) + # Solutions of the weakly normalized equation Dz + f*z = q*Sum(ci*Gi) + # correspond to solutions y = z/q of the original equation. + gamma = q + G = [(q*ga).cancel(gd, include=True) for ga, gd in G] + + a, (ba, bd), G, hn = prde_normal_denom(fa, fd, G, DE) + # Solutions q in k of a*Dq + b*q = Sum(ci*Gi) correspond + # to solutions z = q/hn of the weakly normalized equation. + gamma *= hn + + A, B, G, hs = prde_special_denom(a, ba, bd, G, DE) + # Solutions p in k[t] of A*Dp + B*p = Sum(ci*Gi) correspond + # to solutions q = p/hs of the previous equation. + gamma *= hs + + g = A.gcd(B) + a, b, g = A.quo(g), B.quo(g), [gia.cancel(gid*g, include=True) for + gia, gid in G] + + # a*Dp + b*p = Sum(ci*gi) may have a polynomial solution + # only if the sum is in k[t]. + + q, M = prde_linear_constraints(a, b, g, DE) + + # q = [q1, ..., qm] where qi in k[t] is the polynomial component + # of the partial fraction expansion of gi. + # M is a matrix with m columns and entries in k. + # Sum(fi*gi, (i, 1, m)), where f1, ..., fm are elements of k, + # is a polynomial if and only if M*Matrix([f1, ..., fm]) == 0, + # in which case the sum is equal to Sum(fi*qi). + + M, _ = constant_system(M, zeros(M.rows, 1, DE.t), DE) + # M is a matrix with m columns and entries in Const(k). + # Sum(ci*gi) is in k[t] for c1, ..., cm in Const(k) + # if and only if M*Matrix([c1, ..., cm]) == 0, + # in which case the sum is Sum(ci*qi). + + ## Reduce number of constants at this point + + V = M.nullspace() + # V = [v1, ..., vu] where each vj is a column matrix with + # entries aj1, ..., ajm in Const(k). + # Sum(aji*gi) is in k[t] and equal to Sum(aji*qi) (j = 1, ..., u). + # Sum(ci*gi) is in k[t] if and only is ci = Sum(dj*aji) + # (i = 1, ..., m) for some d1, ..., du in Const(k). + # In that case, + # Sum(ci*gi) = Sum(ci*qi) = Sum(dj*Sum(aji*qi)) = Sum(dj*rj) + # where rj = Sum(aji*qi) (j = 1, ..., u) in k[t]. + + if not V: # No non-trivial solution + return [], eye(m, DE.t) + + Mq = Matrix([q]) # A single row. + r = [(Mq*vj)[0] for vj in V] # [r1, ..., ru] + + # Solutions of a*Dp + b*p = Sum(dj*rj) correspond to solutions + # y = p/gamma of the initial equation with ci = Sum(dj*aji). + + try: + # We try n=5. At least for prde_spde, it will always + # terminate no matter what n is. + n = bound_degree(a, b, r, DE, parametric=True) + except NotImplementedError: + # A temporary bound is set. Eventually, it will be removed. + # the currently added test case takes large time + # even with n=5, and much longer with large n's. + n = 5 + + h, B = param_poly_rischDE(a, b, r, n, DE) + + # h = [h1, ..., hv] in k[t]^v and and B is a matrix with u + v + # columns and entries in Const(k) such that + # a*Dp + b*p = Sum(dj*rj) has a solution p of degree <= n + # in k[t] if and only if p = Sum(ek*hk) where e1, ..., ev are in + # Const(k) and B*Matrix([d1, ..., du, e1, ..., ev]) == 0. + # The solutions of the original equation for ci = Sum(dj*aji) + # (i = 1, ..., m) are then y = Sum(ek*hk, (k, 1, v))/gamma. + + ## Build combined relation matrix with m + u + v columns. + + A = -eye(m, DE.t) + for vj in V: + A = A.row_join(vj) + A = A.row_join(zeros(m, len(h), DE.t)) + A = A.col_join(zeros(B.rows, m, DE.t).row_join(B)) + + ## Eliminate d1, ..., du. + + W = A.nullspace() + + # W = [w1, ..., wt] where each wl is a column matrix with + # entries blk (k = 1, ..., m + u + v) in Const(k). + # The vectors (bl1, ..., blm) generate the space of those + # constant families (c1, ..., cm) for which a solution of + # the equation Dy + f*y == Sum(ci*Gi) exists. They generate + # the space and form a basis except possibly when Dy + f*y == 0 + # is solvable in k(t}. The corresponding solutions are + # y = Sum(blk'*hk, (k, 1, v))/gamma, where k' = k + m + u. + + v = len(h) + shape = (len(W), m+v) + elements = [wl[:m] + wl[-v:] for wl in W] # excise dj's. + items = [e for row in elements for e in row] + + # Need to set the shape in case W is empty + M = Matrix(*shape, items, DE.t) + N = M.nullspace() + + # N = [n1, ..., ns] where the ni in Const(k)^(m + v) are column + # vectors generating the space of linear relations between + # c1, ..., cm, e1, ..., ev. + + C = Matrix([ni[:] for ni in N], DE.t) # rows n1, ..., ns. + + return [hk.cancel(gamma, include=True) for hk in h], C + + +def limited_integrate_reduce(fa, fd, G, DE): + """ + Simpler version of step 1 & 2 for the limited integration problem. + + Explanation + =========== + + Given a derivation D on k(t) and f, g1, ..., gn in k(t), return + (a, b, h, N, g, V) such that a, b, h in k[t], N is a non-negative integer, + g in k(t), V == [v1, ..., vm] in k(t)^m, and for any solution v in k(t), + c1, ..., cm in C of f == Dv + Sum(ci*wi, (i, 1, m)), p = v*h is in k, and + p and the ci satisfy a*Dp + b*p == g + Sum(ci*vi, (i, 1, m)). Furthermore, + if S1irr == Sirr, then p is in k[t], and if t is nonlinear or Liouvillian + over k, then deg(p) <= N. + + So that the special part is always computed, this function calls the more + general prde_special_denom() automatically if it cannot determine that + S1irr == Sirr. Furthermore, it will automatically call bound_degree() when + t is linear and non-Liouvillian, which for the transcendental case, implies + that Dt == a*t + b with for some a, b in k*. + """ + dn, ds = splitfactor(fd, DE) + E = [splitfactor(gd, DE) for _, gd in G] + En, Es = list(zip(*E)) + c = reduce(lambda i, j: i.lcm(j), (dn,) + En) # lcm(dn, en1, ..., enm) + hn = c.gcd(c.diff(DE.t)) + a = hn + b = -derivation(hn, DE) + N = 0 + + # These are the cases where we know that S1irr = Sirr, but there could be + # others, and this algorithm will need to be extended to handle them. + if DE.case in ('base', 'primitive', 'exp', 'tan'): + hs = reduce(lambda i, j: i.lcm(j), (ds,) + Es) # lcm(ds, es1, ..., esm) + a = hn*hs + b -= (hn*derivation(hs, DE)).quo(hs) + mu = min(order_at_oo(fa, fd, DE.t), min(order_at_oo(ga, gd, DE.t) for + ga, gd in G)) + # So far, all the above are also nonlinear or Liouvillian, but if this + # changes, then this will need to be updated to call bound_degree() + # as per the docstring of this function (DE.case == 'other_linear'). + N = hn.degree(DE.t) + hs.degree(DE.t) + max(0, 1 - DE.d.degree(DE.t) - mu) + else: + # TODO: implement this + raise NotImplementedError + + V = [(-a*hn*ga).cancel(gd, include=True) for ga, gd in G] + return (a, b, a, N, (a*hn*fa).cancel(fd, include=True), V) + + +def limited_integrate(fa, fd, G, DE): + """ + Solves the limited integration problem: f = Dv + Sum(ci*wi, (i, 1, n)) + """ + fa, fd = fa*Poly(1/fd.LC(), DE.t), fd.monic() + # interpreting limited integration problem as a + # parametric Risch DE problem + Fa = Poly(0, DE.t) + Fd = Poly(1, DE.t) + G = [(fa, fd)] + G + h, A = param_rischDE(Fa, Fd, G, DE) + V = A.nullspace() + V = [v for v in V if v[0] != 0] + if not V: + return None + else: + # we can take any vector from V, we take V[0] + c0 = V[0][0] + # v = [-1, c1, ..., cm, d1, ..., dr] + v = V[0]/(-c0) + r = len(h) + m = len(v) - r - 1 + C = list(v[1: m + 1]) + y = -sum(v[m + 1 + i]*h[i][0].as_expr()/h[i][1].as_expr() \ + for i in range(r)) + y_num, y_den = y.as_numer_denom() + Ya, Yd = Poly(y_num, DE.t), Poly(y_den, DE.t) + Y = Ya*Poly(1/Yd.LC(), DE.t), Yd.monic() + return Y, C + + +def parametric_log_deriv_heu(fa, fd, wa, wd, DE, c1=None): + """ + Parametric logarithmic derivative heuristic. + + Explanation + =========== + + Given a derivation D on k[t], f in k(t), and a hyperexponential monomial + theta over k(t), raises either NotImplementedError, in which case the + heuristic failed, or returns None, in which case it has proven that no + solution exists, or returns a solution (n, m, v) of the equation + n*f == Dv/v + m*Dtheta/theta, with v in k(t)* and n, m in ZZ with n != 0. + + If this heuristic fails, the structure theorem approach will need to be + used. + + The argument w == Dtheta/theta + """ + # TODO: finish writing this and write tests + c1 = c1 or Dummy('c1') + + p, a = fa.div(fd) + q, b = wa.div(wd) + + B = max(0, derivation(DE.t, DE).degree(DE.t) - 1) + C = max(p.degree(DE.t), q.degree(DE.t)) + + if q.degree(DE.t) > B: + eqs = [p.nth(i) - c1*q.nth(i) for i in range(B + 1, C + 1)] + s = solve(eqs, c1) + if not s or not s[c1].is_Rational: + # deg(q) > B, no solution for c. + return None + + M, N = s[c1].as_numer_denom() + M_poly = M.as_poly(q.gens) + N_poly = N.as_poly(q.gens) + + nfmwa = N_poly*fa*wd - M_poly*wa*fd + nfmwd = fd*wd + Qv = is_log_deriv_k_t_radical_in_field(nfmwa, nfmwd, DE, 'auto') + if Qv is None: + # (N*f - M*w) is not the logarithmic derivative of a k(t)-radical. + return None + + Q, v = Qv + + if Q.is_zero or v.is_zero: + return None + + return (Q*N, Q*M, v) + + if p.degree(DE.t) > B: + return None + + c = lcm(fd.as_poly(DE.t).LC(), wd.as_poly(DE.t).LC()) + l = fd.monic().lcm(wd.monic())*Poly(c, DE.t) + ln, ls = splitfactor(l, DE) + z = ls*ln.gcd(ln.diff(DE.t)) + + if not z.has(DE.t): + # TODO: We treat this as 'no solution', until the structure + # theorem version of parametric_log_deriv is implemented. + return None + + u1, r1 = (fa*l.quo(fd)).div(z) # (l*f).div(z) + u2, r2 = (wa*l.quo(wd)).div(z) # (l*w).div(z) + + eqs = [r1.nth(i) - c1*r2.nth(i) for i in range(z.degree(DE.t))] + s = solve(eqs, c1) + if not s or not s[c1].is_Rational: + # deg(q) <= B, no solution for c. + return None + + M, N = s[c1].as_numer_denom() + + nfmwa = N.as_poly(DE.t)*fa*wd - M.as_poly(DE.t)*wa*fd + nfmwd = fd*wd + Qv = is_log_deriv_k_t_radical_in_field(nfmwa, nfmwd, DE) + if Qv is None: + # (N*f - M*w) is not the logarithmic derivative of a k(t)-radical. + return None + + Q, v = Qv + + if Q.is_zero or v.is_zero: + return None + + return (Q*N, Q*M, v) + + +def parametric_log_deriv(fa, fd, wa, wd, DE): + # TODO: Write the full algorithm using the structure theorems. +# try: + A = parametric_log_deriv_heu(fa, fd, wa, wd, DE) +# except NotImplementedError: + # Heuristic failed, we have to use the full method. + # TODO: This could be implemented more efficiently. + # It isn't too worrisome, because the heuristic handles most difficult + # cases. + return A + + +def is_deriv_k(fa, fd, DE): + r""" + Checks if Df/f is the derivative of an element of k(t). + + Explanation + =========== + + a in k(t) is the derivative of an element of k(t) if there exists b in k(t) + such that a = Db. Either returns (ans, u), such that Df/f == Du, or None, + which means that Df/f is not the derivative of an element of k(t). ans is + a list of tuples such that Add(*[i*j for i, j in ans]) == u. This is useful + for seeing exactly which elements of k(t) produce u. + + This function uses the structure theorem approach, which says that for any + f in K, Df/f is the derivative of a element of K if and only if there are ri + in QQ such that:: + + --- --- Dt + \ r * Dt + \ r * i Df + / i i / i --- = --. + --- --- t f + i in L i in E i + K/C(x) K/C(x) + + + Where C = Const(K), L_K/C(x) = { i in {1, ..., n} such that t_i is + transcendental over C(x)(t_1, ..., t_i-1) and Dt_i = Da_i/a_i, for some a_i + in C(x)(t_1, ..., t_i-1)* } (i.e., the set of all indices of logarithmic + monomials of K over C(x)), and E_K/C(x) = { i in {1, ..., n} such that t_i + is transcendental over C(x)(t_1, ..., t_i-1) and Dt_i/t_i = Da_i, for some + a_i in C(x)(t_1, ..., t_i-1) } (i.e., the set of all indices of + hyperexponential monomials of K over C(x)). If K is an elementary extension + over C(x), then the cardinality of L_K/C(x) U E_K/C(x) is exactly the + transcendence degree of K over C(x). Furthermore, because Const_D(K) == + Const_D(C(x)) == C, deg(Dt_i) == 1 when t_i is in E_K/C(x) and + deg(Dt_i) == 0 when t_i is in L_K/C(x), implying in particular that E_K/C(x) + and L_K/C(x) are disjoint. + + The sets L_K/C(x) and E_K/C(x) must, by their nature, be computed + recursively using this same function. Therefore, it is required to pass + them as indices to D (or T). E_args are the arguments of the + hyperexponentials indexed by E_K (i.e., if i is in E_K, then T[i] == + exp(E_args[i])). This is needed to compute the final answer u such that + Df/f == Du. + + log(f) will be the same as u up to a additive constant. This is because + they will both behave the same as monomials. For example, both log(x) and + log(2*x) == log(x) + log(2) satisfy Dt == 1/x, because log(2) is constant. + Therefore, the term const is returned. const is such that + log(const) + f == u. This is calculated by dividing the arguments of one + logarithm from the other. Therefore, it is necessary to pass the arguments + of the logarithmic terms in L_args. + + To handle the case where we are given Df/f, not f, use is_deriv_k_in_field(). + + See also + ======== + is_log_deriv_k_t_radical_in_field, is_log_deriv_k_t_radical + + """ + # Compute Df/f + dfa, dfd = (fd*derivation(fa, DE) - fa*derivation(fd, DE)), fd*fa + dfa, dfd = dfa.cancel(dfd, include=True) + + # Our assumption here is that each monomial is recursively transcendental + if len(DE.exts) != len(DE.D): + if [i for i in DE.cases if i == 'tan'] or \ + ({i for i in DE.cases if i == 'primitive'} - + set(DE.indices('log'))): + raise NotImplementedError("Real version of the structure " + "theorems with hypertangent support is not yet implemented.") + + # TODO: What should really be done in this case? + raise NotImplementedError("Nonelementary extensions not supported " + "in the structure theorems.") + + E_part = [DE.D[i].quo(Poly(DE.T[i], DE.T[i])).as_expr() for i in DE.indices('exp')] + L_part = [DE.D[i].as_expr() for i in DE.indices('log')] + + # The expression dfa/dfd might not be polynomial in any of its symbols so we + # use a Dummy as the generator for PolyMatrix. + dum = Dummy() + lhs = Matrix([E_part + L_part], dum) + rhs = Matrix([dfa.as_expr()/dfd.as_expr()], dum) + + A, u = constant_system(lhs, rhs, DE) + + u = u.to_Matrix() # Poly to Expr + + if not A or not all(derivation(i, DE, basic=True).is_zero for i in u): + # If the elements of u are not all constant + # Note: See comment in constant_system + + # Also note: derivation(basic=True) calls cancel() + return None + else: + if not all(i.is_Rational for i in u): + raise NotImplementedError("Cannot work with non-rational " + "coefficients in this case.") + else: + terms = ([DE.extargs[i] for i in DE.indices('exp')] + + [DE.T[i] for i in DE.indices('log')]) + ans = list(zip(terms, u)) + result = Add(*[Mul(i, j) for i, j in ans]) + argterms = ([DE.T[i] for i in DE.indices('exp')] + + [DE.extargs[i] for i in DE.indices('log')]) + l = [] + ld = [] + for i, j in zip(argterms, u): + # We need to get around things like sqrt(x**2) != x + # and also sqrt(x**2 + 2*x + 1) != x + 1 + # Issue 10798: i need not be a polynomial + i, d = i.as_numer_denom() + icoeff, iterms = sqf_list(i) + l.append(Mul(*([Pow(icoeff, j)] + [Pow(b, e*j) for b, e in iterms]))) + dcoeff, dterms = sqf_list(d) + ld.append(Mul(*([Pow(dcoeff, j)] + [Pow(b, e*j) for b, e in dterms]))) + const = cancel(fa.as_expr()/fd.as_expr()/Mul(*l)*Mul(*ld)) + + return (ans, result, const) + + +def is_log_deriv_k_t_radical(fa, fd, DE, Df=True): + r""" + Checks if Df is the logarithmic derivative of a k(t)-radical. + + Explanation + =========== + + b in k(t) can be written as the logarithmic derivative of a k(t) radical if + there exist n in ZZ and u in k(t) with n, u != 0 such that n*b == Du/u. + Either returns (ans, u, n, const) or None, which means that Df cannot be + written as the logarithmic derivative of a k(t)-radical. ans is a list of + tuples such that Mul(*[i**j for i, j in ans]) == u. This is useful for + seeing exactly what elements of k(t) produce u. + + This function uses the structure theorem approach, which says that for any + f in K, Df is the logarithmic derivative of a K-radical if and only if there + are ri in QQ such that:: + + --- --- Dt + \ r * Dt + \ r * i + / i i / i --- = Df. + --- --- t + i in L i in E i + K/C(x) K/C(x) + + + Where C = Const(K), L_K/C(x) = { i in {1, ..., n} such that t_i is + transcendental over C(x)(t_1, ..., t_i-1) and Dt_i = Da_i/a_i, for some a_i + in C(x)(t_1, ..., t_i-1)* } (i.e., the set of all indices of logarithmic + monomials of K over C(x)), and E_K/C(x) = { i in {1, ..., n} such that t_i + is transcendental over C(x)(t_1, ..., t_i-1) and Dt_i/t_i = Da_i, for some + a_i in C(x)(t_1, ..., t_i-1) } (i.e., the set of all indices of + hyperexponential monomials of K over C(x)). If K is an elementary extension + over C(x), then the cardinality of L_K/C(x) U E_K/C(x) is exactly the + transcendence degree of K over C(x). Furthermore, because Const_D(K) == + Const_D(C(x)) == C, deg(Dt_i) == 1 when t_i is in E_K/C(x) and + deg(Dt_i) == 0 when t_i is in L_K/C(x), implying in particular that E_K/C(x) + and L_K/C(x) are disjoint. + + The sets L_K/C(x) and E_K/C(x) must, by their nature, be computed + recursively using this same function. Therefore, it is required to pass + them as indices to D (or T). L_args are the arguments of the logarithms + indexed by L_K (i.e., if i is in L_K, then T[i] == log(L_args[i])). This is + needed to compute the final answer u such that n*f == Du/u. + + exp(f) will be the same as u up to a multiplicative constant. This is + because they will both behave the same as monomials. For example, both + exp(x) and exp(x + 1) == E*exp(x) satisfy Dt == t. Therefore, the term const + is returned. const is such that exp(const)*f == u. This is calculated by + subtracting the arguments of one exponential from the other. Therefore, it + is necessary to pass the arguments of the exponential terms in E_args. + + To handle the case where we are given Df, not f, use + is_log_deriv_k_t_radical_in_field(). + + See also + ======== + + is_log_deriv_k_t_radical_in_field, is_deriv_k + + """ + if Df: + dfa, dfd = (fd*derivation(fa, DE) - fa*derivation(fd, DE)).cancel(fd**2, + include=True) + else: + dfa, dfd = fa, fd + + # Our assumption here is that each monomial is recursively transcendental + if len(DE.exts) != len(DE.D): + if [i for i in DE.cases if i == 'tan'] or \ + ({i for i in DE.cases if i == 'primitive'} - + set(DE.indices('log'))): + raise NotImplementedError("Real version of the structure " + "theorems with hypertangent support is not yet implemented.") + + # TODO: What should really be done in this case? + raise NotImplementedError("Nonelementary extensions not supported " + "in the structure theorems.") + + E_part = [DE.D[i].quo(Poly(DE.T[i], DE.T[i])).as_expr() for i in DE.indices('exp')] + L_part = [DE.D[i].as_expr() for i in DE.indices('log')] + + # The expression dfa/dfd might not be polynomial in any of its symbols so we + # use a Dummy as the generator for PolyMatrix. + dum = Dummy() + lhs = Matrix([E_part + L_part], dum) + rhs = Matrix([dfa.as_expr()/dfd.as_expr()], dum) + + A, u = constant_system(lhs, rhs, DE) + + u = u.to_Matrix() # Poly to Expr + + if not A or not all(derivation(i, DE, basic=True).is_zero for i in u): + # If the elements of u are not all constant + # Note: See comment in constant_system + + # Also note: derivation(basic=True) calls cancel() + return None + else: + if not all(i.is_Rational for i in u): + # TODO: But maybe we can tell if they're not rational, like + # log(2)/log(3). Also, there should be an option to continue + # anyway, even if the result might potentially be wrong. + raise NotImplementedError("Cannot work with non-rational " + "coefficients in this case.") + else: + n = S.One*reduce(ilcm, [i.as_numer_denom()[1] for i in u]) + u *= n + terms = ([DE.T[i] for i in DE.indices('exp')] + + [DE.extargs[i] for i in DE.indices('log')]) + ans = list(zip(terms, u)) + result = Mul(*[Pow(i, j) for i, j in ans]) + + # exp(f) will be the same as result up to a multiplicative + # constant. We now find the log of that constant. + argterms = ([DE.extargs[i] for i in DE.indices('exp')] + + [DE.T[i] for i in DE.indices('log')]) + const = cancel(fa.as_expr()/fd.as_expr() - + Add(*[Mul(i, j/n) for i, j in zip(argterms, u)])) + + return (ans, result, n, const) + + +def is_log_deriv_k_t_radical_in_field(fa, fd, DE, case='auto', z=None): + """ + Checks if f can be written as the logarithmic derivative of a k(t)-radical. + + Explanation + =========== + + It differs from is_log_deriv_k_t_radical(fa, fd, DE, Df=False) + for any given fa, fd, DE in that it finds the solution in the + given field not in some (possibly unspecified extension) and + "in_field" with the function name is used to indicate that. + + f in k(t) can be written as the logarithmic derivative of a k(t) radical if + there exist n in ZZ and u in k(t) with n, u != 0 such that n*f == Du/u. + Either returns (n, u) or None, which means that f cannot be written as the + logarithmic derivative of a k(t)-radical. + + case is one of {'primitive', 'exp', 'tan', 'auto'} for the primitive, + hyperexponential, and hypertangent cases, respectively. If case is 'auto', + it will attempt to determine the type of the derivation automatically. + + See also + ======== + is_log_deriv_k_t_radical, is_deriv_k + + """ + fa, fd = fa.cancel(fd, include=True) + + # f must be simple + n, s = splitfactor(fd, DE) + if not s.is_one: + pass + + z = z or Dummy('z') + H, b = residue_reduce(fa, fd, DE, z=z) + if not b: + # I will have to verify, but I believe that the answer should be + # None in this case. This should never happen for the + # functions given when solving the parametric logarithmic + # derivative problem when integration elementary functions (see + # Bronstein's book, page 255), so most likely this indicates a bug. + return None + + roots = [(i, i.real_roots()) for i, _ in H] + if not all(len(j) == i.degree() and all(k.is_Rational for k in j) for + i, j in roots): + # If f is the logarithmic derivative of a k(t)-radical, then all the + # roots of the resultant must be rational numbers. + return None + + # [(a, i), ...], where i*log(a) is a term in the log-part of the integral + # of f + respolys, residues = list(zip(*roots)) or [[], []] + # Note: this might be empty, but everything below should work find in that + # case (it should be the same as if it were [[1, 1]]) + residueterms = [(H[j][1].subs(z, i), i) for j in range(len(H)) for + i in residues[j]] + + # TODO: finish writing this and write tests + + p = cancel(fa.as_expr()/fd.as_expr() - residue_reduce_derivation(H, DE, z)) + + p = p.as_poly(DE.t) + if p is None: + # f - Dg will be in k[t] if f is the logarithmic derivative of a k(t)-radical + return None + + if p.degree(DE.t) >= max(1, DE.d.degree(DE.t)): + return None + + if case == 'auto': + case = DE.case + + if case == 'exp': + wa, wd = derivation(DE.t, DE).cancel(Poly(DE.t, DE.t), include=True) + with DecrementLevel(DE): + pa, pd = frac_in(p, DE.t, cancel=True) + wa, wd = frac_in((wa, wd), DE.t) + A = parametric_log_deriv(pa, pd, wa, wd, DE) + if A is None: + return None + n, e, u = A + u *= DE.t**e + + elif case == 'primitive': + with DecrementLevel(DE): + pa, pd = frac_in(p, DE.t) + A = is_log_deriv_k_t_radical_in_field(pa, pd, DE, case='auto') + if A is None: + return None + n, u = A + + elif case == 'base': + # TODO: we can use more efficient residue reduction from ratint() + if not fd.is_sqf or fa.degree() >= fd.degree(): + # f is the logarithmic derivative in the base case if and only if + # f = fa/fd, fd is square-free, deg(fa) < deg(fd), and + # gcd(fa, fd) == 1. The last condition is handled by cancel() above. + return None + # Note: if residueterms = [], returns (1, 1) + # f had better be 0 in that case. + n = S.One*reduce(ilcm, [i.as_numer_denom()[1] for _, i in residueterms], 1) + u = Mul(*[Pow(i, j*n) for i, j in residueterms]) + return (n, u) + + elif case == 'tan': + raise NotImplementedError("The hypertangent case is " + "not yet implemented for is_log_deriv_k_t_radical_in_field()") + + elif case in ('other_linear', 'other_nonlinear'): + # XXX: If these are supported by the structure theorems, change to NotImplementedError. + raise ValueError("The %s case is not supported in this function." % case) + + else: + raise ValueError("case must be one of {'primitive', 'exp', 'tan', " + "'base', 'auto'}, not %s" % case) + + common_denom = S.One*reduce(ilcm, [i.as_numer_denom()[1] for i in [j for _, j in + residueterms]] + [n], 1) + residueterms = [(i, j*common_denom) for i, j in residueterms] + m = common_denom//n + if common_denom != n*m: # Verify exact division + raise ValueError("Inexact division") + u = cancel(u**m*Mul(*[Pow(i, j) for i, j in residueterms])) + + return (common_denom, u) diff --git a/lib/python3.12/site-packages/sympy/integrals/quadrature.py b/lib/python3.12/site-packages/sympy/integrals/quadrature.py new file mode 100644 index 0000000000000000000000000000000000000000..b518bd427dc9980d6a941d2e1ef4d139c5f0f5f9 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/quadrature.py @@ -0,0 +1,617 @@ +from sympy.core import S, Dummy, pi +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.trigonometric import sin, cos +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.special.gamma_functions import gamma +from sympy.polys.orthopolys import (legendre_poly, laguerre_poly, + hermite_poly, jacobi_poly) +from sympy.polys.rootoftools import RootOf + + +def gauss_legendre(n, n_digits): + r""" + Computes the Gauss-Legendre quadrature [1]_ points and weights. + + Explanation + =========== + + The Gauss-Legendre quadrature approximates the integral: + + .. math:: + \int_{-1}^1 f(x)\,dx \approx \sum_{i=1}^n w_i f(x_i) + + The nodes `x_i` of an order `n` quadrature rule are the roots of `P_n` + and the weights `w_i` are given by: + + .. math:: + w_i = \frac{2}{\left(1-x_i^2\right) \left(P'_n(x_i)\right)^2} + + Parameters + ========== + + n : + The order of quadrature. + n_digits : + Number of significant digits of the points and weights to return. + + Returns + ======= + + (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. + The points `x_i` and weights `w_i` are returned as ``(x, w)`` + tuple of lists. + + Examples + ======== + + >>> from sympy.integrals.quadrature import gauss_legendre + >>> x, w = gauss_legendre(3, 5) + >>> x + [-0.7746, 0, 0.7746] + >>> w + [0.55556, 0.88889, 0.55556] + >>> x, w = gauss_legendre(4, 5) + >>> x + [-0.86114, -0.33998, 0.33998, 0.86114] + >>> w + [0.34785, 0.65215, 0.65215, 0.34785] + + See Also + ======== + + gauss_laguerre, gauss_gen_laguerre, gauss_hermite, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi, gauss_lobatto + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gaussian_quadrature + .. [2] https://people.sc.fsu.edu/~jburkardt/cpp_src/legendre_rule/legendre_rule.html + """ + x = Dummy("x") + p = legendre_poly(n, x, polys=True) + pd = p.diff(x) + xi = [] + w = [] + for r in p.real_roots(): + if isinstance(r, RootOf): + r = r.eval_rational(S.One/10**(n_digits+2)) + xi.append(r.n(n_digits)) + w.append((2/((1-r**2) * pd.subs(x, r)**2)).n(n_digits)) + return xi, w + + +def gauss_laguerre(n, n_digits): + r""" + Computes the Gauss-Laguerre quadrature [1]_ points and weights. + + Explanation + =========== + + The Gauss-Laguerre quadrature approximates the integral: + + .. math:: + \int_0^{\infty} e^{-x} f(x)\,dx \approx \sum_{i=1}^n w_i f(x_i) + + + The nodes `x_i` of an order `n` quadrature rule are the roots of `L_n` + and the weights `w_i` are given by: + + .. math:: + w_i = \frac{x_i}{(n+1)^2 \left(L_{n+1}(x_i)\right)^2} + + Parameters + ========== + + n : + The order of quadrature. + n_digits : + Number of significant digits of the points and weights to return. + + Returns + ======= + + (x, w) : The ``x`` and ``w`` are lists of points and weights as Floats. + The points `x_i` and weights `w_i` are returned as ``(x, w)`` + tuple of lists. + + Examples + ======== + + >>> from sympy.integrals.quadrature import gauss_laguerre + >>> x, w = gauss_laguerre(3, 5) + >>> x + [0.41577, 2.2943, 6.2899] + >>> w + [0.71109, 0.27852, 0.010389] + >>> x, w = gauss_laguerre(6, 5) + >>> x + [0.22285, 1.1889, 2.9927, 5.7751, 9.8375, 15.983] + >>> w + [0.45896, 0.417, 0.11337, 0.010399, 0.00026102, 8.9855e-7] + + See Also + ======== + + gauss_legendre, gauss_gen_laguerre, gauss_hermite, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi, gauss_lobatto + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gauss%E2%80%93Laguerre_quadrature + .. [2] https://people.sc.fsu.edu/~jburkardt/cpp_src/laguerre_rule/laguerre_rule.html + """ + x = Dummy("x") + p = laguerre_poly(n, x, polys=True) + p1 = laguerre_poly(n+1, x, polys=True) + xi = [] + w = [] + for r in p.real_roots(): + if isinstance(r, RootOf): + r = r.eval_rational(S.One/10**(n_digits+2)) + xi.append(r.n(n_digits)) + w.append((r/((n+1)**2 * p1.subs(x, r)**2)).n(n_digits)) + return xi, w + + +def gauss_hermite(n, n_digits): + r""" + Computes the Gauss-Hermite quadrature [1]_ points and weights. + + Explanation + =========== + + The Gauss-Hermite quadrature approximates the integral: + + .. math:: + \int_{-\infty}^{\infty} e^{-x^2} f(x)\,dx \approx + \sum_{i=1}^n w_i f(x_i) + + The nodes `x_i` of an order `n` quadrature rule are the roots of `H_n` + and the weights `w_i` are given by: + + .. math:: + w_i = \frac{2^{n-1} n! \sqrt{\pi}}{n^2 \left(H_{n-1}(x_i)\right)^2} + + Parameters + ========== + + n : + The order of quadrature. + n_digits : + Number of significant digits of the points and weights to return. + + Returns + ======= + + (x, w) : The ``x`` and ``w`` are lists of points and weights as Floats. + The points `x_i` and weights `w_i` are returned as ``(x, w)`` + tuple of lists. + + Examples + ======== + + >>> from sympy.integrals.quadrature import gauss_hermite + >>> x, w = gauss_hermite(3, 5) + >>> x + [-1.2247, 0, 1.2247] + >>> w + [0.29541, 1.1816, 0.29541] + + >>> x, w = gauss_hermite(6, 5) + >>> x + [-2.3506, -1.3358, -0.43608, 0.43608, 1.3358, 2.3506] + >>> w + [0.00453, 0.15707, 0.72463, 0.72463, 0.15707, 0.00453] + + See Also + ======== + + gauss_legendre, gauss_laguerre, gauss_gen_laguerre, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi, gauss_lobatto + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gauss-Hermite_Quadrature + .. [2] https://people.sc.fsu.edu/~jburkardt/cpp_src/hermite_rule/hermite_rule.html + .. [3] https://people.sc.fsu.edu/~jburkardt/cpp_src/gen_hermite_rule/gen_hermite_rule.html + """ + x = Dummy("x") + p = hermite_poly(n, x, polys=True) + p1 = hermite_poly(n-1, x, polys=True) + xi = [] + w = [] + for r in p.real_roots(): + if isinstance(r, RootOf): + r = r.eval_rational(S.One/10**(n_digits+2)) + xi.append(r.n(n_digits)) + w.append(((2**(n-1) * factorial(n) * sqrt(pi)) / + (n**2 * p1.subs(x, r)**2)).n(n_digits)) + return xi, w + + +def gauss_gen_laguerre(n, alpha, n_digits): + r""" + Computes the generalized Gauss-Laguerre quadrature [1]_ points and weights. + + Explanation + =========== + + The generalized Gauss-Laguerre quadrature approximates the integral: + + .. math:: + \int_{0}^\infty x^{\alpha} e^{-x} f(x)\,dx \approx + \sum_{i=1}^n w_i f(x_i) + + The nodes `x_i` of an order `n` quadrature rule are the roots of + `L^{\alpha}_n` and the weights `w_i` are given by: + + .. math:: + w_i = \frac{\Gamma(\alpha+n)} + {n \Gamma(n) L^{\alpha}_{n-1}(x_i) L^{\alpha+1}_{n-1}(x_i)} + + Parameters + ========== + + n : + The order of quadrature. + + alpha : + The exponent of the singularity, `\alpha > -1`. + + n_digits : + Number of significant digits of the points and weights to return. + + Returns + ======= + + (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. + The points `x_i` and weights `w_i` are returned as ``(x, w)`` + tuple of lists. + + Examples + ======== + + >>> from sympy import S + >>> from sympy.integrals.quadrature import gauss_gen_laguerre + >>> x, w = gauss_gen_laguerre(3, -S.Half, 5) + >>> x + [0.19016, 1.7845, 5.5253] + >>> w + [1.4493, 0.31413, 0.00906] + + >>> x, w = gauss_gen_laguerre(4, 3*S.Half, 5) + >>> x + [0.97851, 2.9904, 6.3193, 11.712] + >>> w + [0.53087, 0.67721, 0.11895, 0.0023152] + + See Also + ======== + + gauss_legendre, gauss_laguerre, gauss_hermite, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi, gauss_lobatto + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gauss%E2%80%93Laguerre_quadrature + .. [2] https://people.sc.fsu.edu/~jburkardt/cpp_src/gen_laguerre_rule/gen_laguerre_rule.html + """ + x = Dummy("x") + p = laguerre_poly(n, x, alpha=alpha, polys=True) + p1 = laguerre_poly(n-1, x, alpha=alpha, polys=True) + p2 = laguerre_poly(n-1, x, alpha=alpha+1, polys=True) + xi = [] + w = [] + for r in p.real_roots(): + if isinstance(r, RootOf): + r = r.eval_rational(S.One/10**(n_digits+2)) + xi.append(r.n(n_digits)) + w.append((gamma(alpha+n) / + (n*gamma(n)*p1.subs(x, r)*p2.subs(x, r))).n(n_digits)) + return xi, w + + +def gauss_chebyshev_t(n, n_digits): + r""" + Computes the Gauss-Chebyshev quadrature [1]_ points and weights of + the first kind. + + Explanation + =========== + + The Gauss-Chebyshev quadrature of the first kind approximates the integral: + + .. math:: + \int_{-1}^{1} \frac{1}{\sqrt{1-x^2}} f(x)\,dx \approx + \sum_{i=1}^n w_i f(x_i) + + The nodes `x_i` of an order `n` quadrature rule are the roots of `T_n` + and the weights `w_i` are given by: + + .. math:: + w_i = \frac{\pi}{n} + + Parameters + ========== + + n : + The order of quadrature. + + n_digits : + Number of significant digits of the points and weights to return. + + Returns + ======= + + (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. + The points `x_i` and weights `w_i` are returned as ``(x, w)`` + tuple of lists. + + Examples + ======== + + >>> from sympy.integrals.quadrature import gauss_chebyshev_t + >>> x, w = gauss_chebyshev_t(3, 5) + >>> x + [0.86602, 0, -0.86602] + >>> w + [1.0472, 1.0472, 1.0472] + + >>> x, w = gauss_chebyshev_t(6, 5) + >>> x + [0.96593, 0.70711, 0.25882, -0.25882, -0.70711, -0.96593] + >>> w + [0.5236, 0.5236, 0.5236, 0.5236, 0.5236, 0.5236] + + See Also + ======== + + gauss_legendre, gauss_laguerre, gauss_hermite, gauss_gen_laguerre, gauss_chebyshev_u, gauss_jacobi, gauss_lobatto + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Chebyshev%E2%80%93Gauss_quadrature + .. [2] https://people.sc.fsu.edu/~jburkardt/cpp_src/chebyshev1_rule/chebyshev1_rule.html + """ + xi = [] + w = [] + for i in range(1, n+1): + xi.append((cos((2*i-S.One)/(2*n)*S.Pi)).n(n_digits)) + w.append((S.Pi/n).n(n_digits)) + return xi, w + + +def gauss_chebyshev_u(n, n_digits): + r""" + Computes the Gauss-Chebyshev quadrature [1]_ points and weights of + the second kind. + + Explanation + =========== + + The Gauss-Chebyshev quadrature of the second kind approximates the + integral: + + .. math:: + \int_{-1}^{1} \sqrt{1-x^2} f(x)\,dx \approx \sum_{i=1}^n w_i f(x_i) + + The nodes `x_i` of an order `n` quadrature rule are the roots of `U_n` + and the weights `w_i` are given by: + + .. math:: + w_i = \frac{\pi}{n+1} \sin^2 \left(\frac{i}{n+1}\pi\right) + + Parameters + ========== + + n : the order of quadrature + + n_digits : number of significant digits of the points and weights to return + + Returns + ======= + + (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. + The points `x_i` and weights `w_i` are returned as ``(x, w)`` + tuple of lists. + + Examples + ======== + + >>> from sympy.integrals.quadrature import gauss_chebyshev_u + >>> x, w = gauss_chebyshev_u(3, 5) + >>> x + [0.70711, 0, -0.70711] + >>> w + [0.3927, 0.7854, 0.3927] + + >>> x, w = gauss_chebyshev_u(6, 5) + >>> x + [0.90097, 0.62349, 0.22252, -0.22252, -0.62349, -0.90097] + >>> w + [0.084489, 0.27433, 0.42658, 0.42658, 0.27433, 0.084489] + + See Also + ======== + + gauss_legendre, gauss_laguerre, gauss_hermite, gauss_gen_laguerre, gauss_chebyshev_t, gauss_jacobi, gauss_lobatto + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Chebyshev%E2%80%93Gauss_quadrature + .. [2] https://people.sc.fsu.edu/~jburkardt/cpp_src/chebyshev2_rule/chebyshev2_rule.html + """ + xi = [] + w = [] + for i in range(1, n+1): + xi.append((cos(i/(n+S.One)*S.Pi)).n(n_digits)) + w.append((S.Pi/(n+S.One)*sin(i*S.Pi/(n+S.One))**2).n(n_digits)) + return xi, w + + +def gauss_jacobi(n, alpha, beta, n_digits): + r""" + Computes the Gauss-Jacobi quadrature [1]_ points and weights. + + Explanation + =========== + + The Gauss-Jacobi quadrature of the first kind approximates the integral: + + .. math:: + \int_{-1}^1 (1-x)^\alpha (1+x)^\beta f(x)\,dx \approx + \sum_{i=1}^n w_i f(x_i) + + The nodes `x_i` of an order `n` quadrature rule are the roots of + `P^{(\alpha,\beta)}_n` and the weights `w_i` are given by: + + .. math:: + w_i = -\frac{2n+\alpha+\beta+2}{n+\alpha+\beta+1} + \frac{\Gamma(n+\alpha+1)\Gamma(n+\beta+1)} + {\Gamma(n+\alpha+\beta+1)(n+1)!} + \frac{2^{\alpha+\beta}}{P'_n(x_i) + P^{(\alpha,\beta)}_{n+1}(x_i)} + + Parameters + ========== + + n : the order of quadrature + + alpha : the first parameter of the Jacobi Polynomial, `\alpha > -1` + + beta : the second parameter of the Jacobi Polynomial, `\beta > -1` + + n_digits : number of significant digits of the points and weights to return + + Returns + ======= + + (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. + The points `x_i` and weights `w_i` are returned as ``(x, w)`` + tuple of lists. + + Examples + ======== + + >>> from sympy import S + >>> from sympy.integrals.quadrature import gauss_jacobi + >>> x, w = gauss_jacobi(3, S.Half, -S.Half, 5) + >>> x + [-0.90097, -0.22252, 0.62349] + >>> w + [1.7063, 1.0973, 0.33795] + + >>> x, w = gauss_jacobi(6, 1, 1, 5) + >>> x + [-0.87174, -0.5917, -0.2093, 0.2093, 0.5917, 0.87174] + >>> w + [0.050584, 0.22169, 0.39439, 0.39439, 0.22169, 0.050584] + + See Also + ======== + + gauss_legendre, gauss_laguerre, gauss_hermite, gauss_gen_laguerre, + gauss_chebyshev_t, gauss_chebyshev_u, gauss_lobatto + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gauss%E2%80%93Jacobi_quadrature + .. [2] https://people.sc.fsu.edu/~jburkardt/cpp_src/jacobi_rule/jacobi_rule.html + .. [3] https://people.sc.fsu.edu/~jburkardt/cpp_src/gegenbauer_rule/gegenbauer_rule.html + """ + x = Dummy("x") + p = jacobi_poly(n, alpha, beta, x, polys=True) + pd = p.diff(x) + pn = jacobi_poly(n+1, alpha, beta, x, polys=True) + xi = [] + w = [] + for r in p.real_roots(): + if isinstance(r, RootOf): + r = r.eval_rational(S.One/10**(n_digits+2)) + xi.append(r.n(n_digits)) + w.append(( + - (2*n+alpha+beta+2) / (n+alpha+beta+S.One) * + (gamma(n+alpha+1)*gamma(n+beta+1)) / + (gamma(n+alpha+beta+S.One)*gamma(n+2)) * + 2**(alpha+beta) / (pd.subs(x, r) * pn.subs(x, r))).n(n_digits)) + return xi, w + + +def gauss_lobatto(n, n_digits): + r""" + Computes the Gauss-Lobatto quadrature [1]_ points and weights. + + Explanation + =========== + + The Gauss-Lobatto quadrature approximates the integral: + + .. math:: + \int_{-1}^1 f(x)\,dx \approx \sum_{i=1}^n w_i f(x_i) + + The nodes `x_i` of an order `n` quadrature rule are the roots of `P'_(n-1)` + and the weights `w_i` are given by: + + .. math:: + &w_i = \frac{2}{n(n-1) \left[P_{n-1}(x_i)\right]^2},\quad x\neq\pm 1\\ + &w_i = \frac{2}{n(n-1)},\quad x=\pm 1 + + Parameters + ========== + + n : the order of quadrature + + n_digits : number of significant digits of the points and weights to return + + Returns + ======= + + (x, w) : the ``x`` and ``w`` are lists of points and weights as Floats. + The points `x_i` and weights `w_i` are returned as ``(x, w)`` + tuple of lists. + + Examples + ======== + + >>> from sympy.integrals.quadrature import gauss_lobatto + >>> x, w = gauss_lobatto(3, 5) + >>> x + [-1, 0, 1] + >>> w + [0.33333, 1.3333, 0.33333] + >>> x, w = gauss_lobatto(4, 5) + >>> x + [-1, -0.44721, 0.44721, 1] + >>> w + [0.16667, 0.83333, 0.83333, 0.16667] + + See Also + ======== + + gauss_legendre,gauss_laguerre, gauss_gen_laguerre, gauss_hermite, gauss_chebyshev_t, gauss_chebyshev_u, gauss_jacobi + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Gaussian_quadrature#Gauss.E2.80.93Lobatto_rules + .. [2] https://web.archive.org/web/20200118141346/http://people.math.sfu.ca/~cbm/aands/page_888.htm + """ + x = Dummy("x") + p = legendre_poly(n-1, x, polys=True) + pd = p.diff(x) + xi = [] + w = [] + for r in pd.real_roots(): + if isinstance(r, RootOf): + r = r.eval_rational(S.One/10**(n_digits+2)) + xi.append(r.n(n_digits)) + w.append((2/(n*(n-1) * p.subs(x, r)**2)).n(n_digits)) + + xi.insert(0, -1) + xi.append(1) + w.insert(0, (S(2)/(n*(n-1))).n(n_digits)) + w.append((S(2)/(n*(n-1))).n(n_digits)) + return xi, w diff --git a/lib/python3.12/site-packages/sympy/integrals/rationaltools.py b/lib/python3.12/site-packages/sympy/integrals/rationaltools.py new file mode 100644 index 0000000000000000000000000000000000000000..cc6423c1bb40d69067f1b9364b06dba63fef0fd8 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/rationaltools.py @@ -0,0 +1,429 @@ +"""This module implements tools for integrating rational functions. """ + +from sympy.core.function import Lambda +from sympy.core.numbers import I +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, symbols) +from sympy.functions.elementary.exponential import log +from sympy.functions.elementary.trigonometric import atan +from sympy.polys.polyroots import roots +from sympy.polys.polytools import cancel +from sympy.polys.rootoftools import RootSum +from sympy.polys import Poly, resultant, ZZ + + +def ratint(f, x, **flags): + """ + Performs indefinite integration of rational functions. + + Explanation + =========== + + Given a field :math:`K` and a rational function :math:`f = p/q`, + where :math:`p` and :math:`q` are polynomials in :math:`K[x]`, + returns a function :math:`g` such that :math:`f = g'`. + + Examples + ======== + + >>> from sympy.integrals.rationaltools import ratint + >>> from sympy.abc import x + + >>> ratint(36/(x**5 - 2*x**4 - 2*x**3 + 4*x**2 + x - 2), x) + (12*x + 6)/(x**2 - 1) + 4*log(x - 2) - 4*log(x + 1) + + References + ========== + + .. [1] M. Bronstein, Symbolic Integration I: Transcendental + Functions, Second Edition, Springer-Verlag, 2005, pp. 35-70 + + See Also + ======== + + sympy.integrals.integrals.Integral.doit + sympy.integrals.rationaltools.ratint_logpart + sympy.integrals.rationaltools.ratint_ratpart + + """ + if isinstance(f, tuple): + p, q = f + else: + p, q = f.as_numer_denom() + + p, q = Poly(p, x, composite=False, field=True), Poly(q, x, composite=False, field=True) + + coeff, p, q = p.cancel(q) + poly, p = p.div(q) + + result = poly.integrate(x).as_expr() + + if p.is_zero: + return coeff*result + + g, h = ratint_ratpart(p, q, x) + + P, Q = h.as_numer_denom() + + P = Poly(P, x) + Q = Poly(Q, x) + + q, r = P.div(Q) + + result += g + q.integrate(x).as_expr() + + if not r.is_zero: + symbol = flags.get('symbol', 't') + + if not isinstance(symbol, Symbol): + t = Dummy(symbol) + else: + t = symbol.as_dummy() + + L = ratint_logpart(r, Q, x, t) + + real = flags.get('real') + + if real is None: + if isinstance(f, tuple): + p, q = f + atoms = p.atoms() | q.atoms() + else: + atoms = f.atoms() + + for elt in atoms - {x}: + if not elt.is_extended_real: + real = False + break + else: + real = True + + eps = S.Zero + + if not real: + for h, q in L: + _, h = h.primitive() + eps += RootSum( + q, Lambda(t, t*log(h.as_expr())), quadratic=True) + else: + for h, q in L: + _, h = h.primitive() + R = log_to_real(h, q, x, t) + + if R is not None: + eps += R + else: + eps += RootSum( + q, Lambda(t, t*log(h.as_expr())), quadratic=True) + + result += eps + + return coeff*result + + +def ratint_ratpart(f, g, x): + """ + Horowitz-Ostrogradsky algorithm. + + Explanation + =========== + + Given a field K and polynomials f and g in K[x], such that f and g + are coprime and deg(f) < deg(g), returns fractions A and B in K(x), + such that f/g = A' + B and B has square-free denominator. + + Examples + ======== + + >>> from sympy.integrals.rationaltools import ratint_ratpart + >>> from sympy.abc import x, y + >>> from sympy import Poly + >>> ratint_ratpart(Poly(1, x, domain='ZZ'), + ... Poly(x + 1, x, domain='ZZ'), x) + (0, 1/(x + 1)) + >>> ratint_ratpart(Poly(1, x, domain='EX'), + ... Poly(x**2 + y**2, x, domain='EX'), x) + (0, 1/(x**2 + y**2)) + >>> ratint_ratpart(Poly(36, x, domain='ZZ'), + ... Poly(x**5 - 2*x**4 - 2*x**3 + 4*x**2 + x - 2, x, domain='ZZ'), x) + ((12*x + 6)/(x**2 - 1), 12/(x**2 - x - 2)) + + See Also + ======== + + ratint, ratint_logpart + """ + from sympy.solvers.solvers import solve + + f = Poly(f, x) + g = Poly(g, x) + + u, v, _ = g.cofactors(g.diff()) + + n = u.degree() + m = v.degree() + + A_coeffs = [ Dummy('a' + str(n - i)) for i in range(0, n) ] + B_coeffs = [ Dummy('b' + str(m - i)) for i in range(0, m) ] + + C_coeffs = A_coeffs + B_coeffs + + A = Poly(A_coeffs, x, domain=ZZ[C_coeffs]) + B = Poly(B_coeffs, x, domain=ZZ[C_coeffs]) + + H = f - A.diff()*v + A*(u.diff()*v).quo(u) - B*u + + result = solve(H.coeffs(), C_coeffs) + + A = A.as_expr().subs(result) + B = B.as_expr().subs(result) + + rat_part = cancel(A/u.as_expr(), x) + log_part = cancel(B/v.as_expr(), x) + + return rat_part, log_part + + +def ratint_logpart(f, g, x, t=None): + r""" + Lazard-Rioboo-Trager algorithm. + + Explanation + =========== + + Given a field K and polynomials f and g in K[x], such that f and g + are coprime, deg(f) < deg(g) and g is square-free, returns a list + of tuples (s_i, q_i) of polynomials, for i = 1..n, such that s_i + in K[t, x] and q_i in K[t], and:: + + ___ ___ + d f d \ ` \ ` + -- - = -- ) ) a log(s_i(a, x)) + dx g dx /__, /__, + i=1..n a | q_i(a) = 0 + + Examples + ======== + + >>> from sympy.integrals.rationaltools import ratint_logpart + >>> from sympy.abc import x + >>> from sympy import Poly + >>> ratint_logpart(Poly(1, x, domain='ZZ'), + ... Poly(x**2 + x + 1, x, domain='ZZ'), x) + [(Poly(x + 3*_t/2 + 1/2, x, domain='QQ[_t]'), + ...Poly(3*_t**2 + 1, _t, domain='ZZ'))] + >>> ratint_logpart(Poly(12, x, domain='ZZ'), + ... Poly(x**2 - x - 2, x, domain='ZZ'), x) + [(Poly(x - 3*_t/8 - 1/2, x, domain='QQ[_t]'), + ...Poly(-_t**2 + 16, _t, domain='ZZ'))] + + See Also + ======== + + ratint, ratint_ratpart + """ + f, g = Poly(f, x), Poly(g, x) + + t = t or Dummy('t') + a, b = g, f - g.diff()*Poly(t, x) + + res, R = resultant(a, b, includePRS=True) + res = Poly(res, t, composite=False) + + assert res, "BUG: resultant(%s, %s) cannot be zero" % (a, b) + + R_map, H = {}, [] + + for r in R: + R_map[r.degree()] = r + + def _include_sign(c, sqf): + if c.is_extended_real and (c < 0) == True: + h, k = sqf[0] + c_poly = c.as_poly(h.gens) + sqf[0] = h*c_poly, k + + C, res_sqf = res.sqf_list() + _include_sign(C, res_sqf) + + for q, i in res_sqf: + _, q = q.primitive() + + if g.degree() == i: + H.append((g, q)) + else: + h = R_map[i] + h_lc = Poly(h.LC(), t, field=True) + + c, h_lc_sqf = h_lc.sqf_list(all=True) + _include_sign(c, h_lc_sqf) + + for a, j in h_lc_sqf: + h = h.quo(Poly(a.gcd(q)**j, x)) + + inv, coeffs = h_lc.invert(q), [S.One] + + for coeff in h.coeffs()[1:]: + coeff = coeff.as_poly(inv.gens) + T = (inv*coeff).rem(q) + coeffs.append(T.as_expr()) + + h = Poly(dict(list(zip(h.monoms(), coeffs))), x) + + H.append((h, q)) + + return H + + +def log_to_atan(f, g): + """ + Convert complex logarithms to real arctangents. + + Explanation + =========== + + Given a real field K and polynomials f and g in K[x], with g != 0, + returns a sum h of arctangents of polynomials in K[x], such that: + + dh d f + I g + -- = -- I log( ------- ) + dx dx f - I g + + Examples + ======== + + >>> from sympy.integrals.rationaltools import log_to_atan + >>> from sympy.abc import x + >>> from sympy import Poly, sqrt, S + >>> log_to_atan(Poly(x, x, domain='ZZ'), Poly(1, x, domain='ZZ')) + 2*atan(x) + >>> log_to_atan(Poly(x + S(1)/2, x, domain='QQ'), + ... Poly(sqrt(3)/2, x, domain='EX')) + 2*atan(2*sqrt(3)*x/3 + sqrt(3)/3) + + See Also + ======== + + log_to_real + """ + if f.degree() < g.degree(): + f, g = -g, f + + f = f.to_field() + g = g.to_field() + + p, q = f.div(g) + + if q.is_zero: + return 2*atan(p.as_expr()) + else: + s, t, h = g.gcdex(-f) + u = (f*s + g*t).quo(h) + A = 2*atan(u.as_expr()) + + return A + log_to_atan(s, t) + + +def log_to_real(h, q, x, t): + r""" + Convert complex logarithms to real functions. + + Explanation + =========== + + Given real field K and polynomials h in K[t,x] and q in K[t], + returns real function f such that: + ___ + df d \ ` + -- = -- ) a log(h(a, x)) + dx dx /__, + a | q(a) = 0 + + Examples + ======== + + >>> from sympy.integrals.rationaltools import log_to_real + >>> from sympy.abc import x, y + >>> from sympy import Poly, S + >>> log_to_real(Poly(x + 3*y/2 + S(1)/2, x, domain='QQ[y]'), + ... Poly(3*y**2 + 1, y, domain='ZZ'), x, y) + 2*sqrt(3)*atan(2*sqrt(3)*x/3 + sqrt(3)/3)/3 + >>> log_to_real(Poly(x**2 - 1, x, domain='ZZ'), + ... Poly(-2*y + 1, y, domain='ZZ'), x, y) + log(x**2 - 1)/2 + + See Also + ======== + + log_to_atan + """ + from sympy.simplify.radsimp import collect + u, v = symbols('u,v', cls=Dummy) + + H = h.as_expr().xreplace({t: u + I*v}).expand() + Q = q.as_expr().xreplace({t: u + I*v}).expand() + + H_map = collect(H, I, evaluate=False) + Q_map = collect(Q, I, evaluate=False) + + a, b = H_map.get(S.One, S.Zero), H_map.get(I, S.Zero) + c, d = Q_map.get(S.One, S.Zero), Q_map.get(I, S.Zero) + + R = Poly(resultant(c, d, v), u) + + R_u = roots(R, filter='R') + + if len(R_u) != R.count_roots(): + return None + + result = S.Zero + + for r_u in R_u.keys(): + C = Poly(c.xreplace({u: r_u}), v) + if not C: + # t was split into real and imaginary parts + # and denom Q(u, v) = c + I*d. We just found + # that c(r_u) is 0 so the roots are in d + C = Poly(d.xreplace({u: r_u}), v) + # we were going to reject roots from C that + # did not set d to zero, but since we are now + # using C = d and c is already 0, there is + # nothing to check + d = S.Zero + + R_v = roots(C, filter='R') + + if len(R_v) != C.count_roots(): + return None + + R_v_paired = [] # take one from each pair of conjugate roots + for r_v in R_v: + if r_v not in R_v_paired and -r_v not in R_v_paired: + if r_v.is_negative or r_v.could_extract_minus_sign(): + R_v_paired.append(-r_v) + elif not r_v.is_zero: + R_v_paired.append(r_v) + + for r_v in R_v_paired: + + D = d.xreplace({u: r_u, v: r_v}) + + if D.evalf(chop=True) != 0: + continue + + A = Poly(a.xreplace({u: r_u, v: r_v}), x) + B = Poly(b.xreplace({u: r_u, v: r_v}), x) + + AB = (A**2 + B**2).as_expr() + + result += r_u*log(AB) + r_v*log_to_atan(A, B) + + R_q = roots(q, filter='R') + + if len(R_q) != q.count_roots(): + return None + + for r in R_q.keys(): + result += r*log(h.as_expr().subs(t, r)) + + return result diff --git a/lib/python3.12/site-packages/sympy/integrals/rde.py b/lib/python3.12/site-packages/sympy/integrals/rde.py new file mode 100644 index 0000000000000000000000000000000000000000..142d37595de6d275205d1717a6e0fde7cc9d16b8 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/rde.py @@ -0,0 +1,800 @@ +""" +Algorithms for solving the Risch differential equation. + +Given a differential field K of characteristic 0 that is a simple +monomial extension of a base field k and f, g in K, the Risch +Differential Equation problem is to decide if there exist y in K such +that Dy + f*y == g and to find one if there are some. If t is a +monomial over k and the coefficients of f and g are in k(t), then y is +in k(t), and the outline of the algorithm here is given as: + +1. Compute the normal part n of the denominator of y. The problem is +then reduced to finding y' in k, where y == y'/n. +2. Compute the special part s of the denominator of y. The problem is +then reduced to finding y'' in k[t], where y == y''/(n*s) +3. Bound the degree of y''. +4. Reduce the equation Dy + f*y == g to a similar equation with f, g in +k[t]. +5. Find the solutions in k[t] of bounded degree of the reduced equation. + +See Chapter 6 of "Symbolic Integration I: Transcendental Functions" by +Manuel Bronstein. See also the docstring of risch.py. +""" + +from operator import mul +from functools import reduce + +from sympy.core import oo +from sympy.core.symbol import Dummy + +from sympy.polys import Poly, gcd, ZZ, cancel + +from sympy.functions.elementary.complexes import (im, re) +from sympy.functions.elementary.miscellaneous import sqrt + +from sympy.integrals.risch import (gcdex_diophantine, frac_in, derivation, + splitfactor, NonElementaryIntegralException, DecrementLevel, recognize_log_derivative) + +# TODO: Add messages to NonElementaryIntegralException errors + + +def order_at(a, p, t): + """ + Computes the order of a at p, with respect to t. + + Explanation + =========== + + For a, p in k[t], the order of a at p is defined as nu_p(a) = max({n + in Z+ such that p**n|a}), where a != 0. If a == 0, nu_p(a) = +oo. + + To compute the order at a rational function, a/b, use the fact that + nu_p(a/b) == nu_p(a) - nu_p(b). + """ + if a.is_zero: + return oo + if p == Poly(t, t): + return a.as_poly(t).ET()[0][0] + + # Uses binary search for calculating the power. power_list collects the tuples + # (p^k,k) where each k is some power of 2. After deciding the largest k + # such that k is power of 2 and p^k|a the loop iteratively calculates + # the actual power. + power_list = [] + p1 = p + r = a.rem(p1) + tracks_power = 1 + while r.is_zero: + power_list.append((p1,tracks_power)) + p1 = p1*p1 + tracks_power *= 2 + r = a.rem(p1) + n = 0 + product = Poly(1, t) + while len(power_list) != 0: + final = power_list.pop() + productf = product*final[0] + r = a.rem(productf) + if r.is_zero: + n += final[1] + product = productf + return n + + +def order_at_oo(a, d, t): + """ + Computes the order of a/d at oo (infinity), with respect to t. + + For f in k(t), the order or f at oo is defined as deg(d) - deg(a), where + f == a/d. + """ + if a.is_zero: + return oo + return d.degree(t) - a.degree(t) + + +def weak_normalizer(a, d, DE, z=None): + """ + Weak normalization. + + Explanation + =========== + + Given a derivation D on k[t] and f == a/d in k(t), return q in k[t] + such that f - Dq/q is weakly normalized with respect to t. + + f in k(t) is said to be "weakly normalized" with respect to t if + residue_p(f) is not a positive integer for any normal irreducible p + in k[t] such that f is in R_p (Definition 6.1.1). If f has an + elementary integral, this is equivalent to no logarithm of + integral(f) whose argument depends on t has a positive integer + coefficient, where the arguments of the logarithms not in k(t) are + in k[t]. + + Returns (q, f - Dq/q) + """ + z = z or Dummy('z') + dn, ds = splitfactor(d, DE) + + # Compute d1, where dn == d1*d2**2*...*dn**n is a square-free + # factorization of d. + g = gcd(dn, dn.diff(DE.t)) + d_sqf_part = dn.quo(g) + d1 = d_sqf_part.quo(gcd(d_sqf_part, g)) + + a1, b = gcdex_diophantine(d.quo(d1).as_poly(DE.t), d1.as_poly(DE.t), + a.as_poly(DE.t)) + r = (a - Poly(z, DE.t)*derivation(d1, DE)).as_poly(DE.t).resultant( + d1.as_poly(DE.t)) + r = Poly(r, z) + + if not r.expr.has(z): + return (Poly(1, DE.t), (a, d)) + + N = [i for i in r.real_roots() if i in ZZ and i > 0] + + q = reduce(mul, [gcd(a - Poly(n, DE.t)*derivation(d1, DE), d1) for n in N], + Poly(1, DE.t)) + + dq = derivation(q, DE) + sn = q*a - d*dq + sd = q*d + sn, sd = sn.cancel(sd, include=True) + + return (q, (sn, sd)) + + +def normal_denom(fa, fd, ga, gd, DE): + """ + Normal part of the denominator. + + Explanation + =========== + + Given a derivation D on k[t] and f, g in k(t) with f weakly + normalized with respect to t, either raise NonElementaryIntegralException, + in which case the equation Dy + f*y == g has no solution in k(t), or the + quadruplet (a, b, c, h) such that a, h in k[t], b, c in k, and for any + solution y in k(t) of Dy + f*y == g, q = y*h in k satisfies + a*Dq + b*q == c. + + This constitutes step 1 in the outline given in the rde.py docstring. + """ + dn, ds = splitfactor(fd, DE) + en, es = splitfactor(gd, DE) + + p = dn.gcd(en) + h = en.gcd(en.diff(DE.t)).quo(p.gcd(p.diff(DE.t))) + + a = dn*h + c = a*h + if c.div(en)[1]: + # en does not divide dn*h**2 + raise NonElementaryIntegralException + ca = c*ga + ca, cd = ca.cancel(gd, include=True) + + ba = a*fa - dn*derivation(h, DE)*fd + ba, bd = ba.cancel(fd, include=True) + + # (dn*h, dn*h*f - dn*Dh, dn*h**2*g, h) + return (a, (ba, bd), (ca, cd), h) + + +def special_denom(a, ba, bd, ca, cd, DE, case='auto'): + """ + Special part of the denominator. + + Explanation + =========== + + case is one of {'exp', 'tan', 'primitive'} for the hyperexponential, + hypertangent, and primitive cases, respectively. For the + hyperexponential (resp. hypertangent) case, given a derivation D on + k[t] and a in k[t], b, c, in k with Dt/t in k (resp. Dt/(t**2 + 1) in + k, sqrt(-1) not in k), a != 0, and gcd(a, t) == 1 (resp. + gcd(a, t**2 + 1) == 1), return the quadruplet (A, B, C, 1/h) such that + A, B, C, h in k[t] and for any solution q in k of a*Dq + b*q == c, + r = qh in k[t] satisfies A*Dr + B*r == C. + + For ``case == 'primitive'``, k == k[t], so it returns (a, b, c, 1) in + this case. + + This constitutes step 2 of the outline given in the rde.py docstring. + """ + # TODO: finish writing this and write tests + + if case == 'auto': + case = DE.case + + if case == 'exp': + p = Poly(DE.t, DE.t) + elif case == 'tan': + p = Poly(DE.t**2 + 1, DE.t) + elif case in ('primitive', 'base'): + B = ba.to_field().quo(bd) + C = ca.to_field().quo(cd) + return (a, B, C, Poly(1, DE.t)) + else: + raise ValueError("case must be one of {'exp', 'tan', 'primitive', " + "'base'}, not %s." % case) + + nb = order_at(ba, p, DE.t) - order_at(bd, p, DE.t) + nc = order_at(ca, p, DE.t) - order_at(cd, p, DE.t) + + n = min(0, nc - min(0, nb)) + if not nb: + # Possible cancellation. + from .prde import parametric_log_deriv + if case == 'exp': + dcoeff = DE.d.quo(Poly(DE.t, DE.t)) + with DecrementLevel(DE): # We are guaranteed to not have problems, + # because case != 'base'. + alphaa, alphad = frac_in(-ba.eval(0)/bd.eval(0)/a.eval(0), DE.t) + etaa, etad = frac_in(dcoeff, DE.t) + A = parametric_log_deriv(alphaa, alphad, etaa, etad, DE) + if A is not None: + Q, m, z = A + if Q == 1: + n = min(n, m) + + elif case == 'tan': + dcoeff = DE.d.quo(Poly(DE.t**2+1, DE.t)) + with DecrementLevel(DE): # We are guaranteed to not have problems, + # because case != 'base'. + alphaa, alphad = frac_in(im(-ba.eval(sqrt(-1))/bd.eval(sqrt(-1))/a.eval(sqrt(-1))), DE.t) + betaa, betad = frac_in(re(-ba.eval(sqrt(-1))/bd.eval(sqrt(-1))/a.eval(sqrt(-1))), DE.t) + etaa, etad = frac_in(dcoeff, DE.t) + + if recognize_log_derivative(Poly(2, DE.t)*betaa, betad, DE): + A = parametric_log_deriv(alphaa*Poly(sqrt(-1), DE.t)*betad+alphad*betaa, alphad*betad, etaa, etad, DE) + if A is not None: + Q, m, z = A + if Q == 1: + n = min(n, m) + N = max(0, -nb, n - nc) + pN = p**N + pn = p**-n + + A = a*pN + B = ba*pN.quo(bd) + Poly(n, DE.t)*a*derivation(p, DE).quo(p)*pN + C = (ca*pN*pn).quo(cd) + h = pn + + # (a*p**N, (b + n*a*Dp/p)*p**N, c*p**(N - n), p**-n) + return (A, B, C, h) + + +def bound_degree(a, b, cQ, DE, case='auto', parametric=False): + """ + Bound on polynomial solutions. + + Explanation + =========== + + Given a derivation D on k[t] and ``a``, ``b``, ``c`` in k[t] with ``a != 0``, return + n in ZZ such that deg(q) <= n for any solution q in k[t] of + a*Dq + b*q == c, when parametric=False, or deg(q) <= n for any solution + c1, ..., cm in Const(k) and q in k[t] of a*Dq + b*q == Sum(ci*gi, (i, 1, m)) + when parametric=True. + + For ``parametric=False``, ``cQ`` is ``c``, a ``Poly``; for ``parametric=True``, ``cQ`` is Q == + [q1, ..., qm], a list of Polys. + + This constitutes step 3 of the outline given in the rde.py docstring. + """ + # TODO: finish writing this and write tests + + if case == 'auto': + case = DE.case + + da = a.degree(DE.t) + db = b.degree(DE.t) + + # The parametric and regular cases are identical, except for this part + if parametric: + dc = max(i.degree(DE.t) for i in cQ) + else: + dc = cQ.degree(DE.t) + + alpha = cancel(-b.as_poly(DE.t).LC().as_expr()/ + a.as_poly(DE.t).LC().as_expr()) + + if case == 'base': + n = max(0, dc - max(db, da - 1)) + if db == da - 1 and alpha.is_Integer: + n = max(0, alpha, dc - db) + + elif case == 'primitive': + if db > da: + n = max(0, dc - db) + else: + n = max(0, dc - da + 1) + + etaa, etad = frac_in(DE.d, DE.T[DE.level - 1]) + + t1 = DE.t + with DecrementLevel(DE): + alphaa, alphad = frac_in(alpha, DE.t) + if db == da - 1: + from .prde import limited_integrate + # if alpha == m*Dt + Dz for z in k and m in ZZ: + try: + (za, zd), m = limited_integrate(alphaa, alphad, [(etaa, etad)], + DE) + except NonElementaryIntegralException: + pass + else: + if len(m) != 1: + raise ValueError("Length of m should be 1") + n = max(n, m[0]) + + elif db == da: + # if alpha == Dz/z for z in k*: + # beta = -lc(a*Dz + b*z)/(z*lc(a)) + # if beta == m*Dt + Dw for w in k and m in ZZ: + # n = max(n, m) + from .prde import is_log_deriv_k_t_radical_in_field + A = is_log_deriv_k_t_radical_in_field(alphaa, alphad, DE) + if A is not None: + aa, z = A + if aa == 1: + beta = -(a*derivation(z, DE).as_poly(t1) + + b*z.as_poly(t1)).LC()/(z.as_expr()*a.LC()) + betaa, betad = frac_in(beta, DE.t) + from .prde import limited_integrate + try: + (za, zd), m = limited_integrate(betaa, betad, + [(etaa, etad)], DE) + except NonElementaryIntegralException: + pass + else: + if len(m) != 1: + raise ValueError("Length of m should be 1") + n = max(n, m[0].as_expr()) + + elif case == 'exp': + from .prde import parametric_log_deriv + + n = max(0, dc - max(db, da)) + if da == db: + etaa, etad = frac_in(DE.d.quo(Poly(DE.t, DE.t)), DE.T[DE.level - 1]) + with DecrementLevel(DE): + alphaa, alphad = frac_in(alpha, DE.t) + A = parametric_log_deriv(alphaa, alphad, etaa, etad, DE) + if A is not None: + # if alpha == m*Dt/t + Dz/z for z in k* and m in ZZ: + # n = max(n, m) + a, m, z = A + if a == 1: + n = max(n, m) + + elif case in ('tan', 'other_nonlinear'): + delta = DE.d.degree(DE.t) + lam = DE.d.LC() + alpha = cancel(alpha/lam) + n = max(0, dc - max(da + delta - 1, db)) + if db == da + delta - 1 and alpha.is_Integer: + n = max(0, alpha, dc - db) + + else: + raise ValueError("case must be one of {'exp', 'tan', 'primitive', " + "'other_nonlinear', 'base'}, not %s." % case) + + return n + + +def spde(a, b, c, n, DE): + """ + Rothstein's Special Polynomial Differential Equation algorithm. + + Explanation + =========== + + Given a derivation D on k[t], an integer n and ``a``,``b``,``c`` in k[t] with + ``a != 0``, either raise NonElementaryIntegralException, in which case the + equation a*Dq + b*q == c has no solution of degree at most ``n`` in + k[t], or return the tuple (B, C, m, alpha, beta) such that B, C, + alpha, beta in k[t], m in ZZ, and any solution q in k[t] of degree + at most n of a*Dq + b*q == c must be of the form + q == alpha*h + beta, where h in k[t], deg(h) <= m, and Dh + B*h == C. + + This constitutes step 4 of the outline given in the rde.py docstring. + """ + zero = Poly(0, DE.t) + + alpha = Poly(1, DE.t) + beta = Poly(0, DE.t) + + while True: + if c.is_zero: + return (zero, zero, 0, zero, beta) # -1 is more to the point + if (n < 0) is True: + raise NonElementaryIntegralException + + g = a.gcd(b) + if not c.rem(g).is_zero: # g does not divide c + raise NonElementaryIntegralException + + a, b, c = a.quo(g), b.quo(g), c.quo(g) + + if a.degree(DE.t) == 0: + b = b.to_field().quo(a) + c = c.to_field().quo(a) + return (b, c, n, alpha, beta) + + r, z = gcdex_diophantine(b, a, c) + b += derivation(a, DE) + c = z - derivation(r, DE) + n -= a.degree(DE.t) + + beta += alpha * r + alpha *= a + +def no_cancel_b_large(b, c, n, DE): + """ + Poly Risch Differential Equation - No cancellation: deg(b) large enough. + + Explanation + =========== + + Given a derivation D on k[t], ``n`` either an integer or +oo, and ``b``,``c`` + in k[t] with ``b != 0`` and either D == d/dt or + deg(b) > max(0, deg(D) - 1), either raise NonElementaryIntegralException, in + which case the equation ``Dq + b*q == c`` has no solution of degree at + most n in k[t], or a solution q in k[t] of this equation with + ``deg(q) < n``. + """ + q = Poly(0, DE.t) + + while not c.is_zero: + m = c.degree(DE.t) - b.degree(DE.t) + if not 0 <= m <= n: # n < 0 or m < 0 or m > n + raise NonElementaryIntegralException + + p = Poly(c.as_poly(DE.t).LC()/b.as_poly(DE.t).LC()*DE.t**m, DE.t, + expand=False) + q = q + p + n = m - 1 + c = c - derivation(p, DE) - b*p + + return q + + +def no_cancel_b_small(b, c, n, DE): + """ + Poly Risch Differential Equation - No cancellation: deg(b) small enough. + + Explanation + =========== + + Given a derivation D on k[t], ``n`` either an integer or +oo, and ``b``,``c`` + in k[t] with deg(b) < deg(D) - 1 and either D == d/dt or + deg(D) >= 2, either raise NonElementaryIntegralException, in which case the + equation Dq + b*q == c has no solution of degree at most n in k[t], + or a solution q in k[t] of this equation with deg(q) <= n, or the + tuple (h, b0, c0) such that h in k[t], b0, c0, in k, and for any + solution q in k[t] of degree at most n of Dq + bq == c, y == q - h + is a solution in k of Dy + b0*y == c0. + """ + q = Poly(0, DE.t) + + while not c.is_zero: + if n == 0: + m = 0 + else: + m = c.degree(DE.t) - DE.d.degree(DE.t) + 1 + + if not 0 <= m <= n: # n < 0 or m < 0 or m > n + raise NonElementaryIntegralException + + if m > 0: + p = Poly(c.as_poly(DE.t).LC()/(m*DE.d.as_poly(DE.t).LC())*DE.t**m, + DE.t, expand=False) + else: + if b.degree(DE.t) != c.degree(DE.t): + raise NonElementaryIntegralException + if b.degree(DE.t) == 0: + return (q, b.as_poly(DE.T[DE.level - 1]), + c.as_poly(DE.T[DE.level - 1])) + p = Poly(c.as_poly(DE.t).LC()/b.as_poly(DE.t).LC(), DE.t, + expand=False) + + q = q + p + n = m - 1 + c = c - derivation(p, DE) - b*p + + return q + + +# TODO: better name for this function +def no_cancel_equal(b, c, n, DE): + """ + Poly Risch Differential Equation - No cancellation: deg(b) == deg(D) - 1 + + Explanation + =========== + + Given a derivation D on k[t] with deg(D) >= 2, n either an integer + or +oo, and b, c in k[t] with deg(b) == deg(D) - 1, either raise + NonElementaryIntegralException, in which case the equation Dq + b*q == c has + no solution of degree at most n in k[t], or a solution q in k[t] of + this equation with deg(q) <= n, or the tuple (h, m, C) such that h + in k[t], m in ZZ, and C in k[t], and for any solution q in k[t] of + degree at most n of Dq + b*q == c, y == q - h is a solution in k[t] + of degree at most m of Dy + b*y == C. + """ + q = Poly(0, DE.t) + lc = cancel(-b.as_poly(DE.t).LC()/DE.d.as_poly(DE.t).LC()) + if lc.is_Integer and lc.is_positive: + M = lc + else: + M = -1 + + while not c.is_zero: + m = max(M, c.degree(DE.t) - DE.d.degree(DE.t) + 1) + + if not 0 <= m <= n: # n < 0 or m < 0 or m > n + raise NonElementaryIntegralException + + u = cancel(m*DE.d.as_poly(DE.t).LC() + b.as_poly(DE.t).LC()) + if u.is_zero: + return (q, m, c) + if m > 0: + p = Poly(c.as_poly(DE.t).LC()/u*DE.t**m, DE.t, expand=False) + else: + if c.degree(DE.t) != DE.d.degree(DE.t) - 1: + raise NonElementaryIntegralException + else: + p = c.as_poly(DE.t).LC()/b.as_poly(DE.t).LC() + + q = q + p + n = m - 1 + c = c - derivation(p, DE) - b*p + + return q + + +def cancel_primitive(b, c, n, DE): + """ + Poly Risch Differential Equation - Cancellation: Primitive case. + + Explanation + =========== + + Given a derivation D on k[t], n either an integer or +oo, ``b`` in k, and + ``c`` in k[t] with Dt in k and ``b != 0``, either raise + NonElementaryIntegralException, in which case the equation Dq + b*q == c + has no solution of degree at most n in k[t], or a solution q in k[t] of + this equation with deg(q) <= n. + """ + # Delayed imports + from .prde import is_log_deriv_k_t_radical_in_field + with DecrementLevel(DE): + ba, bd = frac_in(b, DE.t) + A = is_log_deriv_k_t_radical_in_field(ba, bd, DE) + if A is not None: + n, z = A + if n == 1: # b == Dz/z + raise NotImplementedError("is_deriv_in_field() is required to " + " solve this problem.") + # if z*c == Dp for p in k[t] and deg(p) <= n: + # return p/z + # else: + # raise NonElementaryIntegralException + + if c.is_zero: + return c # return 0 + + if n < c.degree(DE.t): + raise NonElementaryIntegralException + + q = Poly(0, DE.t) + while not c.is_zero: + m = c.degree(DE.t) + if n < m: + raise NonElementaryIntegralException + with DecrementLevel(DE): + a2a, a2d = frac_in(c.LC(), DE.t) + sa, sd = rischDE(ba, bd, a2a, a2d, DE) + stm = Poly(sa.as_expr()/sd.as_expr()*DE.t**m, DE.t, expand=False) + q += stm + n = m - 1 + c -= b*stm + derivation(stm, DE) + + return q + + +def cancel_exp(b, c, n, DE): + """ + Poly Risch Differential Equation - Cancellation: Hyperexponential case. + + Explanation + =========== + + Given a derivation D on k[t], n either an integer or +oo, ``b`` in k, and + ``c`` in k[t] with Dt/t in k and ``b != 0``, either raise + NonElementaryIntegralException, in which case the equation Dq + b*q == c + has no solution of degree at most n in k[t], or a solution q in k[t] of + this equation with deg(q) <= n. + """ + from .prde import parametric_log_deriv + eta = DE.d.quo(Poly(DE.t, DE.t)).as_expr() + + with DecrementLevel(DE): + etaa, etad = frac_in(eta, DE.t) + ba, bd = frac_in(b, DE.t) + A = parametric_log_deriv(ba, bd, etaa, etad, DE) + if A is not None: + a, m, z = A + if a == 1: + raise NotImplementedError("is_deriv_in_field() is required to " + "solve this problem.") + # if c*z*t**m == Dp for p in k and q = p/(z*t**m) in k[t] and + # deg(q) <= n: + # return q + # else: + # raise NonElementaryIntegralException + + if c.is_zero: + return c # return 0 + + if n < c.degree(DE.t): + raise NonElementaryIntegralException + + q = Poly(0, DE.t) + while not c.is_zero: + m = c.degree(DE.t) + if n < m: + raise NonElementaryIntegralException + # a1 = b + m*Dt/t + a1 = b.as_expr() + with DecrementLevel(DE): + # TODO: Write a dummy function that does this idiom + a1a, a1d = frac_in(a1, DE.t) + a1a = a1a*etad + etaa*a1d*Poly(m, DE.t) + a1d = a1d*etad + + a2a, a2d = frac_in(c.LC(), DE.t) + + sa, sd = rischDE(a1a, a1d, a2a, a2d, DE) + stm = Poly(sa.as_expr()/sd.as_expr()*DE.t**m, DE.t, expand=False) + q += stm + n = m - 1 + c -= b*stm + derivation(stm, DE) # deg(c) becomes smaller + return q + + +def solve_poly_rde(b, cQ, n, DE, parametric=False): + """ + Solve a Polynomial Risch Differential Equation with degree bound ``n``. + + This constitutes step 4 of the outline given in the rde.py docstring. + + For parametric=False, cQ is c, a Poly; for parametric=True, cQ is Q == + [q1, ..., qm], a list of Polys. + """ + # No cancellation + if not b.is_zero and (DE.case == 'base' or + b.degree(DE.t) > max(0, DE.d.degree(DE.t) - 1)): + + if parametric: + # Delayed imports + from .prde import prde_no_cancel_b_large + return prde_no_cancel_b_large(b, cQ, n, DE) + return no_cancel_b_large(b, cQ, n, DE) + + elif (b.is_zero or b.degree(DE.t) < DE.d.degree(DE.t) - 1) and \ + (DE.case == 'base' or DE.d.degree(DE.t) >= 2): + + if parametric: + from .prde import prde_no_cancel_b_small + return prde_no_cancel_b_small(b, cQ, n, DE) + + R = no_cancel_b_small(b, cQ, n, DE) + + if isinstance(R, Poly): + return R + else: + # XXX: Might k be a field? (pg. 209) + h, b0, c0 = R + with DecrementLevel(DE): + b0, c0 = b0.as_poly(DE.t), c0.as_poly(DE.t) + if b0 is None: # See above comment + raise ValueError("b0 should be a non-Null value") + if c0 is None: + raise ValueError("c0 should be a non-Null value") + y = solve_poly_rde(b0, c0, n, DE).as_poly(DE.t) + return h + y + + elif DE.d.degree(DE.t) >= 2 and b.degree(DE.t) == DE.d.degree(DE.t) - 1 and \ + n > -b.as_poly(DE.t).LC()/DE.d.as_poly(DE.t).LC(): + + # TODO: Is this check necessary, and if so, what should it do if it fails? + # b comes from the first element returned from spde() + if not b.as_poly(DE.t).LC().is_number: + raise TypeError("Result should be a number") + + if parametric: + raise NotImplementedError("prde_no_cancel_b_equal() is not yet " + "implemented.") + + R = no_cancel_equal(b, cQ, n, DE) + + if isinstance(R, Poly): + return R + else: + h, m, C = R + # XXX: Or should it be rischDE()? + y = solve_poly_rde(b, C, m, DE) + return h + y + + else: + # Cancellation + if b.is_zero: + raise NotImplementedError("Remaining cases for Poly (P)RDE are " + "not yet implemented (is_deriv_in_field() required).") + else: + if DE.case == 'exp': + if parametric: + raise NotImplementedError("Parametric RDE cancellation " + "hyperexponential case is not yet implemented.") + return cancel_exp(b, cQ, n, DE) + + elif DE.case == 'primitive': + if parametric: + raise NotImplementedError("Parametric RDE cancellation " + "primitive case is not yet implemented.") + return cancel_primitive(b, cQ, n, DE) + + else: + raise NotImplementedError("Other Poly (P)RDE cancellation " + "cases are not yet implemented (%s)." % DE.case) + + if parametric: + raise NotImplementedError("Remaining cases for Poly PRDE not yet " + "implemented.") + raise NotImplementedError("Remaining cases for Poly RDE not yet " + "implemented.") + + +def rischDE(fa, fd, ga, gd, DE): + """ + Solve a Risch Differential Equation: Dy + f*y == g. + + Explanation + =========== + + See the outline in the docstring of rde.py for more information + about the procedure used. Either raise NonElementaryIntegralException, in + which case there is no solution y in the given differential field, + or return y in k(t) satisfying Dy + f*y == g, or raise + NotImplementedError, in which case, the algorithms necessary to + solve the given Risch Differential Equation have not yet been + implemented. + """ + _, (fa, fd) = weak_normalizer(fa, fd, DE) + a, (ba, bd), (ca, cd), hn = normal_denom(fa, fd, ga, gd, DE) + A, B, C, hs = special_denom(a, ba, bd, ca, cd, DE) + try: + # Until this is fully implemented, use oo. Note that this will almost + # certainly cause non-termination in spde() (unless A == 1), and + # *might* lead to non-termination in the next step for a nonelementary + # integral (I don't know for certain yet). Fortunately, spde() is + # currently written recursively, so this will just give + # RuntimeError: maximum recursion depth exceeded. + n = bound_degree(A, B, C, DE) + except NotImplementedError: + # Useful for debugging: + # import warnings + # warnings.warn("rischDE: Proceeding with n = oo; may cause " + # "non-termination.") + n = oo + + B, C, m, alpha, beta = spde(A, B, C, n, DE) + if C.is_zero: + y = C + else: + y = solve_poly_rde(B, C, m, DE) + + return (alpha*y + beta, hn*hs) diff --git a/lib/python3.12/site-packages/sympy/integrals/risch.py b/lib/python3.12/site-packages/sympy/integrals/risch.py new file mode 100644 index 0000000000000000000000000000000000000000..cff9fc4863b29333af727d905fba0e6854013e99 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/risch.py @@ -0,0 +1,1851 @@ +""" +The Risch Algorithm for transcendental function integration. + +The core algorithms for the Risch algorithm are here. The subproblem +algorithms are in the rde.py and prde.py files for the Risch +Differential Equation solver and the parametric problems solvers, +respectively. All important information concerning the differential extension +for an integrand is stored in a DifferentialExtension object, which in the code +is usually called DE. Throughout the code and Inside the DifferentialExtension +object, the conventions/attribute names are that the base domain is QQ and each +differential extension is x, t0, t1, ..., tn-1 = DE.t. DE.x is the variable of +integration (Dx == 1), DE.D is a list of the derivatives of +x, t1, t2, ..., tn-1 = t, DE.T is the list [x, t1, t2, ..., tn-1], DE.t is the +outer-most variable of the differential extension at the given level (the level +can be adjusted using DE.increment_level() and DE.decrement_level()), +k is the field C(x, t0, ..., tn-2), where C is the constant field. The +numerator of a fraction is denoted by a and the denominator by +d. If the fraction is named f, fa == numer(f) and fd == denom(f). +Fractions are returned as tuples (fa, fd). DE.d and DE.t are used to +represent the topmost derivation and extension variable, respectively. +The docstring of a function signifies whether an argument is in k[t], in +which case it will just return a Poly in t, or in k(t), in which case it +will return the fraction (fa, fd). Other variable names probably come +from the names used in Bronstein's book. +""" +from types import GeneratorType +from functools import reduce + +from sympy.core.function import Lambda +from sympy.core.mul import Mul +from sympy.core.intfunc import ilcm +from sympy.core.numbers import I +from sympy.core.power import Pow +from sympy.core.relational import Ne +from sympy.core.singleton import S +from sympy.core.sorting import ordered, default_sort_key +from sympy.core.symbol import Dummy, Symbol +from sympy.functions.elementary.exponential import log, exp +from sympy.functions.elementary.hyperbolic import (cosh, coth, sinh, + tanh) +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (atan, sin, cos, + tan, acot, cot, asin, acos) +from .integrals import integrate, Integral +from .heurisch import _symbols +from sympy.polys.polyerrors import PolynomialError +from sympy.polys.polytools import (real_roots, cancel, Poly, gcd, + reduced) +from sympy.polys.rootoftools import RootSum +from sympy.utilities.iterables import numbered_symbols + + +def integer_powers(exprs): + """ + Rewrites a list of expressions as integer multiples of each other. + + Explanation + =========== + + For example, if you have [x, x/2, x**2 + 1, 2*x/3], then you can rewrite + this as [(x/6) * 6, (x/6) * 3, (x**2 + 1) * 1, (x/6) * 4]. This is useful + in the Risch integration algorithm, where we must write exp(x) + exp(x/2) + as (exp(x/2))**2 + exp(x/2), but not as exp(x) + sqrt(exp(x)) (this is + because only the transcendental case is implemented and we therefore cannot + integrate algebraic extensions). The integer multiples returned by this + function for each term are the smallest possible (their content equals 1). + + Returns a list of tuples where the first element is the base term and the + second element is a list of `(item, factor)` terms, where `factor` is the + integer multiplicative factor that must multiply the base term to obtain + the original item. + + The easiest way to understand this is to look at an example: + + >>> from sympy.abc import x + >>> from sympy.integrals.risch import integer_powers + >>> integer_powers([x, x/2, x**2 + 1, 2*x/3]) + [(x/6, [(x, 6), (x/2, 3), (2*x/3, 4)]), (x**2 + 1, [(x**2 + 1, 1)])] + + We can see how this relates to the example at the beginning of the + docstring. It chose x/6 as the first base term. Then, x can be written as + (x/2) * 2, so we get (0, 2), and so on. Now only element (x**2 + 1) + remains, and there are no other terms that can be written as a rational + multiple of that, so we get that it can be written as (x**2 + 1) * 1. + + """ + # Here is the strategy: + + # First, go through each term and determine if it can be rewritten as a + # rational multiple of any of the terms gathered so far. + # cancel(a/b).is_Rational is sufficient for this. If it is a multiple, we + # add its multiple to the dictionary. + + terms = {} + for term in exprs: + for trm, trm_list in terms.items(): + a = cancel(term/trm) + if a.is_Rational: + trm_list.append((term, a)) + break + else: + terms[term] = [(term, S.One)] + + # After we have done this, we have all the like terms together, so we just + # need to find a common denominator so that we can get the base term and + # integer multiples such that each term can be written as an integer + # multiple of the base term, and the content of the integers is 1. + + newterms = {} + for term, term_list in terms.items(): + common_denom = reduce(ilcm, [i.as_numer_denom()[1] for _, i in + term_list]) + newterm = term/common_denom + newmults = [(i, j*common_denom) for i, j in term_list] + newterms[newterm] = newmults + + return sorted(iter(newterms.items()), key=lambda item: item[0].sort_key()) + + +class DifferentialExtension: + """ + A container for all the information relating to a differential extension. + + Explanation + =========== + + The attributes of this object are (see also the docstring of __init__): + + - f: The original (Expr) integrand. + - x: The variable of integration. + - T: List of variables in the extension. + - D: List of derivations in the extension; corresponds to the elements of T. + - fa: Poly of the numerator of the integrand. + - fd: Poly of the denominator of the integrand. + - Tfuncs: Lambda() representations of each element of T (except for x). + For back-substitution after integration. + - backsubs: A (possibly empty) list of further substitutions to be made on + the final integral to make it look more like the integrand. + - exts: + - extargs: + - cases: List of string representations of the cases of T. + - t: The top level extension variable, as defined by the current level + (see level below). + - d: The top level extension derivation, as defined by the current + derivation (see level below). + - case: The string representation of the case of self.d. + (Note that self.T and self.D will always contain the complete extension, + regardless of the level. Therefore, you should ALWAYS use DE.t and DE.d + instead of DE.T[-1] and DE.D[-1]. If you want to have a list of the + derivations or variables only up to the current level, use + DE.D[:len(DE.D) + DE.level + 1] and DE.T[:len(DE.T) + DE.level + 1]. Note + that, in particular, the derivation() function does this.) + + The following are also attributes, but will probably not be useful other + than in internal use: + - newf: Expr form of fa/fd. + - level: The number (between -1 and -len(self.T)) such that + self.T[self.level] == self.t and self.D[self.level] == self.d. + Use the methods self.increment_level() and self.decrement_level() to change + the current level. + """ + # __slots__ is defined mainly so we can iterate over all the attributes + # of the class easily (the memory use doesn't matter too much, since we + # only create one DifferentialExtension per integration). Also, it's nice + # to have a safeguard when debugging. + __slots__ = ('f', 'x', 'T', 'D', 'fa', 'fd', 'Tfuncs', 'backsubs', + 'exts', 'extargs', 'cases', 'case', 't', 'd', 'newf', 'level', + 'ts', 'dummy') + + def __init__(self, f=None, x=None, handle_first='log', dummy=False, extension=None, rewrite_complex=None): + """ + Tries to build a transcendental extension tower from ``f`` with respect to ``x``. + + Explanation + =========== + + If it is successful, creates a DifferentialExtension object with, among + others, the attributes fa, fd, D, T, Tfuncs, and backsubs such that + fa and fd are Polys in T[-1] with rational coefficients in T[:-1], + fa/fd == f, and D[i] is a Poly in T[i] with rational coefficients in + T[:i] representing the derivative of T[i] for each i from 1 to len(T). + Tfuncs is a list of Lambda objects for back replacing the functions + after integrating. Lambda() is only used (instead of lambda) to make + them easier to test and debug. Note that Tfuncs corresponds to the + elements of T, except for T[0] == x, but they should be back-substituted + in reverse order. backsubs is a (possibly empty) back-substitution list + that should be applied on the completed integral to make it look more + like the original integrand. + + If it is unsuccessful, it raises NotImplementedError. + + You can also create an object by manually setting the attributes as a + dictionary to the extension keyword argument. You must include at least + D. Warning, any attribute that is not given will be set to None. The + attributes T, t, d, cases, case, x, and level are set automatically and + do not need to be given. The functions in the Risch Algorithm will NOT + check to see if an attribute is None before using it. This also does not + check to see if the extension is valid (non-algebraic) or even if it is + self-consistent. Therefore, this should only be used for + testing/debugging purposes. + """ + # XXX: If you need to debug this function, set the break point here + + if extension: + if 'D' not in extension: + raise ValueError("At least the key D must be included with " + "the extension flag to DifferentialExtension.") + for attr in extension: + setattr(self, attr, extension[attr]) + + self._auto_attrs() + + return + elif f is None or x is None: + raise ValueError("Either both f and x or a manual extension must " + "be given.") + + if handle_first not in ('log', 'exp'): + raise ValueError("handle_first must be 'log' or 'exp', not %s." % + str(handle_first)) + + # f will be the original function, self.f might change if we reset + # (e.g., we pull out a constant from an exponential) + self.f = f + self.x = x + # setting the default value 'dummy' + self.dummy = dummy + self.reset() + exp_new_extension, log_new_extension = True, True + + # case of 'automatic' choosing + if rewrite_complex is None: + rewrite_complex = I in self.f.atoms() + + if rewrite_complex: + rewritables = { + (sin, cos, cot, tan, sinh, cosh, coth, tanh): exp, + (asin, acos, acot, atan): log, + } + # rewrite the trigonometric components + for candidates, rule in rewritables.items(): + self.newf = self.newf.rewrite(candidates, rule) + self.newf = cancel(self.newf) + else: + if any(i.has(x) for i in self.f.atoms(sin, cos, tan, atan, asin, acos)): + raise NotImplementedError("Trigonometric extensions are not " + "supported (yet!)") + + exps = set() + pows = set() + numpows = set() + sympows = set() + logs = set() + symlogs = set() + + while True: + if self.newf.is_rational_function(*self.T): + break + + if not exp_new_extension and not log_new_extension: + # We couldn't find a new extension on the last pass, so I guess + # we can't do it. + raise NotImplementedError("Couldn't find an elementary " + "transcendental extension for %s. Try using a " % str(f) + + "manual extension with the extension flag.") + + exps, pows, numpows, sympows, log_new_extension = \ + self._rewrite_exps_pows(exps, pows, numpows, sympows, log_new_extension) + + logs, symlogs = self._rewrite_logs(logs, symlogs) + + if handle_first == 'exp' or not log_new_extension: + exp_new_extension = self._exp_part(exps) + if exp_new_extension is None: + # reset and restart + self.f = self.newf + self.reset() + exp_new_extension = True + continue + + if handle_first == 'log' or not exp_new_extension: + log_new_extension = self._log_part(logs) + + self.fa, self.fd = frac_in(self.newf, self.t) + self._auto_attrs() + + return + + def __getattr__(self, attr): + # Avoid AttributeErrors when debugging + if attr not in self.__slots__: + raise AttributeError("%s has no attribute %s" % (repr(self), repr(attr))) + return None + + def _rewrite_exps_pows(self, exps, pows, numpows, + sympows, log_new_extension): + """ + Rewrite exps/pows for better processing. + """ + from .prde import is_deriv_k + + # Pre-preparsing. + ################# + # Get all exp arguments, so we can avoid ahead of time doing + # something like t1 = exp(x), t2 = exp(x/2) == sqrt(t1). + + # Things like sqrt(exp(x)) do not automatically simplify to + # exp(x/2), so they will be viewed as algebraic. The easiest way + # to handle this is to convert all instances of exp(a)**Rational + # to exp(Rational*a) before doing anything else. Note that the + # _exp_part code can generate terms of this form, so we do need to + # do this at each pass (or else modify it to not do that). + + ratpows = [i for i in self.newf.atoms(Pow) + if (isinstance(i.base, exp) and i.exp.is_Rational)] + + ratpows_repl = [ + (i, i.base.base**(i.exp*i.base.exp)) for i in ratpows] + self.backsubs += [(j, i) for i, j in ratpows_repl] + self.newf = self.newf.xreplace(dict(ratpows_repl)) + + # To make the process deterministic, the args are sorted + # so that functions with smaller op-counts are processed first. + # Ties are broken with the default_sort_key. + + # XXX Although the method is deterministic no additional work + # has been done to guarantee that the simplest solution is + # returned and that it would be affected be using different + # variables. Though it is possible that this is the case + # one should know that it has not been done intentionally, so + # further improvements may be possible. + + # TODO: This probably doesn't need to be completely recomputed at + # each pass. + exps = update_sets(exps, self.newf.atoms(exp), + lambda i: i.exp.is_rational_function(*self.T) and + i.exp.has(*self.T)) + pows = update_sets(pows, self.newf.atoms(Pow), + lambda i: i.exp.is_rational_function(*self.T) and + i.exp.has(*self.T)) + numpows = update_sets(numpows, set(pows), + lambda i: not i.base.has(*self.T)) + sympows = update_sets(sympows, set(pows) - set(numpows), + lambda i: i.base.is_rational_function(*self.T) and + not i.exp.is_Integer) + + # The easiest way to deal with non-base E powers is to convert them + # into base E, integrate, and then convert back. + for i in ordered(pows): + old = i + new = exp(i.exp*log(i.base)) + # If exp is ever changed to automatically reduce exp(x*log(2)) + # to 2**x, then this will break. The solution is to not change + # exp to do that :) + if i in sympows: + if i.exp.is_Rational: + raise NotImplementedError("Algebraic extensions are " + "not supported (%s)." % str(i)) + # We can add a**b only if log(a) in the extension, because + # a**b == exp(b*log(a)). + basea, based = frac_in(i.base, self.t) + A = is_deriv_k(basea, based, self) + if A is None: + # Nonelementary monomial (so far) + + # TODO: Would there ever be any benefit from just + # adding log(base) as a new monomial? + # ANSWER: Yes, otherwise we can't integrate x**x (or + # rather prove that it has no elementary integral) + # without first manually rewriting it as exp(x*log(x)) + self.newf = self.newf.xreplace({old: new}) + self.backsubs += [(new, old)] + log_new_extension = self._log_part([log(i.base)]) + exps = update_sets(exps, self.newf.atoms(exp), lambda i: + i.exp.is_rational_function(*self.T) and i.exp.has(*self.T)) + continue + ans, u, const = A + newterm = exp(i.exp*(log(const) + u)) + # Under the current implementation, exp kills terms + # only if they are of the form a*log(x), where a is a + # Number. This case should have already been killed by the + # above tests. Again, if this changes to kill more than + # that, this will break, which maybe is a sign that you + # shouldn't be changing that. Actually, if anything, this + # auto-simplification should be removed. See + # https://groups.google.com/group/sympy/browse_thread/thread/a61d48235f16867f + + self.newf = self.newf.xreplace({i: newterm}) + + elif i not in numpows: + continue + else: + # i in numpows + newterm = new + # TODO: Just put it in self.Tfuncs + self.backsubs.append((new, old)) + self.newf = self.newf.xreplace({old: newterm}) + exps.append(newterm) + + return exps, pows, numpows, sympows, log_new_extension + + def _rewrite_logs(self, logs, symlogs): + """ + Rewrite logs for better processing. + """ + atoms = self.newf.atoms(log) + logs = update_sets(logs, atoms, + lambda i: i.args[0].is_rational_function(*self.T) and + i.args[0].has(*self.T)) + symlogs = update_sets(symlogs, atoms, + lambda i: i.has(*self.T) and i.args[0].is_Pow and + i.args[0].base.is_rational_function(*self.T) and + not i.args[0].exp.is_Integer) + + # We can handle things like log(x**y) by converting it to y*log(x) + # This will fix not only symbolic exponents of the argument, but any + # non-Integer exponent, like log(sqrt(x)). The exponent can also + # depend on x, like log(x**x). + for i in ordered(symlogs): + # Unlike in the exponential case above, we do not ever + # potentially add new monomials (above we had to add log(a)). + # Therefore, there is no need to run any is_deriv functions + # here. Just convert log(a**b) to b*log(a) and let + # log_new_extension() handle it from there. + lbase = log(i.args[0].base) + logs.append(lbase) + new = i.args[0].exp*lbase + self.newf = self.newf.xreplace({i: new}) + self.backsubs.append((new, i)) + + # remove any duplicates + logs = sorted(set(logs), key=default_sort_key) + + return logs, symlogs + + def _auto_attrs(self): + """ + Set attributes that are generated automatically. + """ + if not self.T: + # i.e., when using the extension flag and T isn't given + self.T = [i.gen for i in self.D] + if not self.x: + self.x = self.T[0] + self.cases = [get_case(d, t) for d, t in zip(self.D, self.T)] + self.level = -1 + self.t = self.T[self.level] + self.d = self.D[self.level] + self.case = self.cases[self.level] + + def _exp_part(self, exps): + """ + Try to build an exponential extension. + + Returns + ======= + + Returns True if there was a new extension, False if there was no new + extension but it was able to rewrite the given exponentials in terms + of the existing extension, and None if the entire extension building + process should be restarted. If the process fails because there is no + way around an algebraic extension (e.g., exp(log(x)/2)), it will raise + NotImplementedError. + """ + from .prde import is_log_deriv_k_t_radical + new_extension = False + restart = False + expargs = [i.exp for i in exps] + ip = integer_powers(expargs) + for arg, others in ip: + # Minimize potential problems with algebraic substitution + others.sort(key=lambda i: i[1]) + + arga, argd = frac_in(arg, self.t) + A = is_log_deriv_k_t_radical(arga, argd, self) + + if A is not None: + ans, u, n, const = A + # if n is 1 or -1, it's algebraic, but we can handle it + if n == -1: + # This probably will never happen, because + # Rational.as_numer_denom() returns the negative term in + # the numerator. But in case that changes, reduce it to + # n == 1. + n = 1 + u **= -1 + const *= -1 + ans = [(i, -j) for i, j in ans] + + if n == 1: + # Example: exp(x + x**2) over QQ(x, exp(x), exp(x**2)) + self.newf = self.newf.xreplace({exp(arg): exp(const)*Mul(*[ + u**power for u, power in ans])}) + self.newf = self.newf.xreplace({exp(p*exparg): + exp(const*p) * Mul(*[u**power for u, power in ans]) + for exparg, p in others}) + # TODO: Add something to backsubs to put exp(const*p) + # back together. + + continue + + else: + # Bad news: we have an algebraic radical. But maybe we + # could still avoid it by choosing a different extension. + # For example, integer_powers() won't handle exp(x/2 + 1) + # over QQ(x, exp(x)), but if we pull out the exp(1), it + # will. Or maybe we have exp(x + x**2/2), over + # QQ(x, exp(x), exp(x**2)), which is exp(x)*sqrt(exp(x**2)), + # but if we use QQ(x, exp(x), exp(x**2/2)), then they will + # all work. + # + # So here is what we do: If there is a non-zero const, pull + # it out and retry. Also, if len(ans) > 1, then rewrite + # exp(arg) as the product of exponentials from ans, and + # retry that. If const == 0 and len(ans) == 1, then we + # assume that it would have been handled by either + # integer_powers() or n == 1 above if it could be handled, + # so we give up at that point. For example, you can never + # handle exp(log(x)/2) because it equals sqrt(x). + + if const or len(ans) > 1: + rad = Mul(*[term**(power/n) for term, power in ans]) + self.newf = self.newf.xreplace({exp(p*exparg): + exp(const*p)*rad for exparg, p in others}) + self.newf = self.newf.xreplace(dict(list(zip(reversed(self.T), + reversed([f(self.x) for f in self.Tfuncs]))))) + restart = True + break + else: + # TODO: give algebraic dependence in error string + raise NotImplementedError("Cannot integrate over " + "algebraic extensions.") + + else: + arga, argd = frac_in(arg, self.t) + darga = (argd*derivation(Poly(arga, self.t), self) - + arga*derivation(Poly(argd, self.t), self)) + dargd = argd**2 + darga, dargd = darga.cancel(dargd, include=True) + darg = darga.as_expr()/dargd.as_expr() + self.t = next(self.ts) + self.T.append(self.t) + self.extargs.append(arg) + self.exts.append('exp') + self.D.append(darg.as_poly(self.t, expand=False)*Poly(self.t, + self.t, expand=False)) + if self.dummy: + i = Dummy("i") + else: + i = Symbol('i') + self.Tfuncs += [Lambda(i, exp(arg.subs(self.x, i)))] + self.newf = self.newf.xreplace( + {exp(exparg): self.t**p for exparg, p in others}) + new_extension = True + + if restart: + return None + return new_extension + + def _log_part(self, logs): + """ + Try to build a logarithmic extension. + + Returns + ======= + + Returns True if there was a new extension and False if there was no new + extension but it was able to rewrite the given logarithms in terms + of the existing extension. Unlike with exponential extensions, there + is no way that a logarithm is not transcendental over and cannot be + rewritten in terms of an already existing extension in a non-algebraic + way, so this function does not ever return None or raise + NotImplementedError. + """ + from .prde import is_deriv_k + new_extension = False + logargs = [i.args[0] for i in logs] + for arg in ordered(logargs): + # The log case is easier, because whenever a logarithm is algebraic + # over the base field, it is of the form a1*t1 + ... an*tn + c, + # which is a polynomial, so we can just replace it with that. + # In other words, we don't have to worry about radicals. + arga, argd = frac_in(arg, self.t) + A = is_deriv_k(arga, argd, self) + if A is not None: + ans, u, const = A + newterm = log(const) + u + self.newf = self.newf.xreplace({log(arg): newterm}) + continue + + else: + arga, argd = frac_in(arg, self.t) + darga = (argd*derivation(Poly(arga, self.t), self) - + arga*derivation(Poly(argd, self.t), self)) + dargd = argd**2 + darg = darga.as_expr()/dargd.as_expr() + self.t = next(self.ts) + self.T.append(self.t) + self.extargs.append(arg) + self.exts.append('log') + self.D.append(cancel(darg.as_expr()/arg).as_poly(self.t, + expand=False)) + if self.dummy: + i = Dummy("i") + else: + i = Symbol('i') + self.Tfuncs += [Lambda(i, log(arg.subs(self.x, i)))] + self.newf = self.newf.xreplace({log(arg): self.t}) + new_extension = True + + return new_extension + + @property + def _important_attrs(self): + """ + Returns some of the more important attributes of self. + + Explanation + =========== + + Used for testing and debugging purposes. + + The attributes are (fa, fd, D, T, Tfuncs, backsubs, + exts, extargs). + """ + return (self.fa, self.fd, self.D, self.T, self.Tfuncs, + self.backsubs, self.exts, self.extargs) + + # NOTE: this printing doesn't follow the Python's standard + # eval(repr(DE)) == DE, where DE is the DifferentialExtension object, + # also this printing is supposed to contain all the important + # attributes of a DifferentialExtension object + def __repr__(self): + # no need to have GeneratorType object printed in it + r = [(attr, getattr(self, attr)) for attr in self.__slots__ + if not isinstance(getattr(self, attr), GeneratorType)] + return self.__class__.__name__ + '(dict(%r))' % (r) + + # fancy printing of DifferentialExtension object + def __str__(self): + return (self.__class__.__name__ + '({fa=%s, fd=%s, D=%s})' % + (self.fa, self.fd, self.D)) + + # should only be used for debugging purposes, internally + # f1 = f2 = log(x) at different places in code execution + # may return D1 != D2 as True, since 'level' or other attribute + # may differ + def __eq__(self, other): + for attr in self.__class__.__slots__: + d1, d2 = getattr(self, attr), getattr(other, attr) + if not (isinstance(d1, GeneratorType) or d1 == d2): + return False + return True + + def reset(self): + """ + Reset self to an initial state. Used by __init__. + """ + self.t = self.x + self.T = [self.x] + self.D = [Poly(1, self.x)] + self.level = -1 + self.exts = [None] + self.extargs = [None] + if self.dummy: + self.ts = numbered_symbols('t', cls=Dummy) + else: + # For testing + self.ts = numbered_symbols('t') + # For various things that we change to make things work that we need to + # change back when we are done. + self.backsubs = [] + self.Tfuncs = [] + self.newf = self.f + + def indices(self, extension): + """ + Parameters + ========== + + extension : str + Represents a valid extension type. + + Returns + ======= + + list: A list of indices of 'exts' where extension of + type 'extension' is present. + + Examples + ======== + + >>> from sympy.integrals.risch import DifferentialExtension + >>> from sympy import log, exp + >>> from sympy.abc import x + >>> DE = DifferentialExtension(log(x) + exp(x), x, handle_first='exp') + >>> DE.indices('log') + [2] + >>> DE.indices('exp') + [1] + + """ + return [i for i, ext in enumerate(self.exts) if ext == extension] + + def increment_level(self): + """ + Increment the level of self. + + Explanation + =========== + + This makes the working differential extension larger. self.level is + given relative to the end of the list (-1, -2, etc.), so we do not need + do worry about it when building the extension. + """ + if self.level >= -1: + raise ValueError("The level of the differential extension cannot " + "be incremented any further.") + + self.level += 1 + self.t = self.T[self.level] + self.d = self.D[self.level] + self.case = self.cases[self.level] + return None + + def decrement_level(self): + """ + Decrease the level of self. + + Explanation + =========== + + This makes the working differential extension smaller. self.level is + given relative to the end of the list (-1, -2, etc.), so we do not need + do worry about it when building the extension. + """ + if self.level <= -len(self.T): + raise ValueError("The level of the differential extension cannot " + "be decremented any further.") + + self.level -= 1 + self.t = self.T[self.level] + self.d = self.D[self.level] + self.case = self.cases[self.level] + return None + + +def update_sets(seq, atoms, func): + s = set(seq) + s = atoms.intersection(s) + new = atoms - s + s.update(list(filter(func, new))) + return list(s) + + +class DecrementLevel: + """ + A context manager for decrementing the level of a DifferentialExtension. + """ + __slots__ = ('DE',) + + def __init__(self, DE): + self.DE = DE + return + + def __enter__(self): + self.DE.decrement_level() + + def __exit__(self, exc_type, exc_value, traceback): + self.DE.increment_level() + + +class NonElementaryIntegralException(Exception): + """ + Exception used by subroutines within the Risch algorithm to indicate to one + another that the function being integrated does not have an elementary + integral in the given differential field. + """ + # TODO: Rewrite algorithms below to use this (?) + + # TODO: Pass through information about why the integral was nonelementary, + # and store that in the resulting NonElementaryIntegral somehow. + pass + + +def gcdex_diophantine(a, b, c): + """ + Extended Euclidean Algorithm, Diophantine version. + + Explanation + =========== + + Given ``a``, ``b`` in K[x] and ``c`` in (a, b), the ideal generated by ``a`` and + ``b``, return (s, t) such that s*a + t*b == c and either s == 0 or s.degree() + < b.degree(). + """ + # Extended Euclidean Algorithm (Diophantine Version) pg. 13 + # TODO: This should go in densetools.py. + # XXX: Bettter name? + + s, g = a.half_gcdex(b) + s *= c.exquo(g) # Inexact division means c is not in (a, b) + if s and s.degree() >= b.degree(): + _, s = s.div(b) + t = (c - s*a).exquo(b) + return (s, t) + + +def frac_in(f, t, *, cancel=False, **kwargs): + """ + Returns the tuple (fa, fd), where fa and fd are Polys in t. + + Explanation + =========== + + This is a common idiom in the Risch Algorithm functions, so we abstract + it out here. ``f`` should be a basic expression, a Poly, or a tuple (fa, fd), + where fa and fd are either basic expressions or Polys, and f == fa/fd. + **kwargs are applied to Poly. + """ + if isinstance(f, tuple): + fa, fd = f + f = fa.as_expr()/fd.as_expr() + fa, fd = f.as_expr().as_numer_denom() + fa, fd = fa.as_poly(t, **kwargs), fd.as_poly(t, **kwargs) + if cancel: + fa, fd = fa.cancel(fd, include=True) + if fa is None or fd is None: + raise ValueError("Could not turn %s into a fraction in %s." % (f, t)) + return (fa, fd) + + +def as_poly_1t(p, t, z): + """ + (Hackish) way to convert an element ``p`` of K[t, 1/t] to K[t, z]. + + In other words, ``z == 1/t`` will be a dummy variable that Poly can handle + better. + + See issue 5131. + + Examples + ======== + + >>> from sympy import random_poly + >>> from sympy.integrals.risch import as_poly_1t + >>> from sympy.abc import x, z + + >>> p1 = random_poly(x, 10, -10, 10) + >>> p2 = random_poly(x, 10, -10, 10) + >>> p = p1 + p2.subs(x, 1/x) + >>> as_poly_1t(p, x, z).as_expr().subs(z, 1/x) == p + True + """ + # TODO: Use this on the final result. That way, we can avoid answers like + # (...)*exp(-x). + pa, pd = frac_in(p, t, cancel=True) + if not pd.is_monomial: + # XXX: Is there a better Poly exception that we could raise here? + # Either way, if you see this (from the Risch Algorithm) it indicates + # a bug. + raise PolynomialError("%s is not an element of K[%s, 1/%s]." % (p, t, t)) + + t_part, remainder = pa.div(pd) + + ans = t_part.as_poly(t, z, expand=False) + + if remainder: + one = remainder.one + tp = t*one + r = pd.degree() - remainder.degree() + z_part = remainder.transform(one, tp) * tp**r + z_part = z_part.replace(t, z).to_field().quo_ground(pd.LC()) + ans += z_part.as_poly(t, z, expand=False) + + return ans + + +def derivation(p, DE, coefficientD=False, basic=False): + """ + Computes Dp. + + Explanation + =========== + + Given the derivation D with D = d/dx and p is a polynomial in t over + K(x), return Dp. + + If coefficientD is True, it computes the derivation kD + (kappaD), which is defined as kD(sum(ai*Xi**i, (i, 0, n))) == + sum(Dai*Xi**i, (i, 1, n)) (Definition 3.2.2, page 80). X in this case is + T[-1], so coefficientD computes the derivative just with respect to T[:-1], + with T[-1] treated as a constant. + + If ``basic=True``, the returns a Basic expression. Elements of D can still be + instances of Poly. + """ + if basic: + r = 0 + else: + r = Poly(0, DE.t) + + t = DE.t + if coefficientD: + if DE.level <= -len(DE.T): + # 'base' case, the answer is 0. + return r + DE.decrement_level() + + D = DE.D[:len(DE.D) + DE.level + 1] + T = DE.T[:len(DE.T) + DE.level + 1] + + for d, v in zip(D, T): + pv = p.as_poly(v) + if pv is None or basic: + pv = p.as_expr() + + if basic: + r += d.as_expr()*pv.diff(v) + else: + r += (d.as_expr()*pv.diff(v).as_expr()).as_poly(t) + + if basic: + r = cancel(r) + if coefficientD: + DE.increment_level() + + return r + + +def get_case(d, t): + """ + Returns the type of the derivation d. + + Returns one of {'exp', 'tan', 'base', 'primitive', 'other_linear', + 'other_nonlinear'}. + """ + if not d.expr.has(t): + if d.is_one: + return 'base' + return 'primitive' + if d.rem(Poly(t, t)).is_zero: + return 'exp' + if d.rem(Poly(1 + t**2, t)).is_zero: + return 'tan' + if d.degree(t) > 1: + return 'other_nonlinear' + return 'other_linear' + + +def splitfactor(p, DE, coefficientD=False, z=None): + """ + Splitting factorization. + + Explanation + =========== + + Given a derivation D on k[t] and ``p`` in k[t], return (p_n, p_s) in + k[t] x k[t] such that p = p_n*p_s, p_s is special, and each square + factor of p_n is normal. + + Page. 100 + """ + kinv = [1/x for x in DE.T[:DE.level]] + if z: + kinv.append(z) + + One = Poly(1, DE.t, domain=p.get_domain()) + Dp = derivation(p, DE, coefficientD=coefficientD) + # XXX: Is this right? + if p.is_zero: + return (p, One) + + if not p.expr.has(DE.t): + s = p.as_poly(*kinv).gcd(Dp.as_poly(*kinv)).as_poly(DE.t) + n = p.exquo(s) + return (n, s) + + if not Dp.is_zero: + h = p.gcd(Dp).to_field() + g = p.gcd(p.diff(DE.t)).to_field() + s = h.exquo(g) + + if s.degree(DE.t) == 0: + return (p, One) + + q_split = splitfactor(p.exquo(s), DE, coefficientD=coefficientD) + + return (q_split[0], q_split[1]*s) + else: + return (p, One) + + +def splitfactor_sqf(p, DE, coefficientD=False, z=None, basic=False): + """ + Splitting Square-free Factorization. + + Explanation + =========== + + Given a derivation D on k[t] and ``p`` in k[t], returns (N1, ..., Nm) + and (S1, ..., Sm) in k[t]^m such that p = + (N1*N2**2*...*Nm**m)*(S1*S2**2*...*Sm**m) is a splitting + factorization of ``p`` and the Ni and Si are square-free and coprime. + """ + # TODO: This algorithm appears to be faster in every case + # TODO: Verify this and splitfactor() for multiple extensions + kkinv = [1/x for x in DE.T[:DE.level]] + DE.T[:DE.level] + if z: + kkinv = [z] + + S = [] + N = [] + p_sqf = p.sqf_list_include() + if p.is_zero: + return (((p, 1),), ()) + + for pi, i in p_sqf: + Si = pi.as_poly(*kkinv).gcd(derivation(pi, DE, + coefficientD=coefficientD,basic=basic).as_poly(*kkinv)).as_poly(DE.t) + pi = Poly(pi, DE.t) + Si = Poly(Si, DE.t) + Ni = pi.exquo(Si) + if not Si.is_one: + S.append((Si, i)) + if not Ni.is_one: + N.append((Ni, i)) + + return (tuple(N), tuple(S)) + + +def canonical_representation(a, d, DE): + """ + Canonical Representation. + + Explanation + =========== + + Given a derivation D on k[t] and f = a/d in k(t), return (f_p, f_s, + f_n) in k[t] x k(t) x k(t) such that f = f_p + f_s + f_n is the + canonical representation of f (f_p is a polynomial, f_s is reduced + (has a special denominator), and f_n is simple (has a normal + denominator). + """ + # Make d monic + l = Poly(1/d.LC(), DE.t) + a, d = a.mul(l), d.mul(l) + + q, r = a.div(d) + dn, ds = splitfactor(d, DE) + + b, c = gcdex_diophantine(dn.as_poly(DE.t), ds.as_poly(DE.t), r.as_poly(DE.t)) + b, c = b.as_poly(DE.t), c.as_poly(DE.t) + + return (q, (b, ds), (c, dn)) + + +def hermite_reduce(a, d, DE): + """ + Hermite Reduction - Mack's Linear Version. + + Given a derivation D on k(t) and f = a/d in k(t), returns g, h, r in + k(t) such that f = Dg + h + r, h is simple, and r is reduced. + + """ + # Make d monic + l = Poly(1/d.LC(), DE.t) + a, d = a.mul(l), d.mul(l) + + fp, fs, fn = canonical_representation(a, d, DE) + a, d = fn + l = Poly(1/d.LC(), DE.t) + a, d = a.mul(l), d.mul(l) + + ga = Poly(0, DE.t) + gd = Poly(1, DE.t) + + dd = derivation(d, DE) + dm = gcd(d.to_field(), dd.to_field()).as_poly(DE.t) + ds, _ = d.div(dm) + + while dm.degree(DE.t) > 0: + + ddm = derivation(dm, DE) + dm2 = gcd(dm.to_field(), ddm.to_field()) + dms, _ = dm.div(dm2) + ds_ddm = ds.mul(ddm) + ds_ddm_dm, _ = ds_ddm.div(dm) + + b, c = gcdex_diophantine(-ds_ddm_dm.as_poly(DE.t), + dms.as_poly(DE.t), a.as_poly(DE.t)) + b, c = b.as_poly(DE.t), c.as_poly(DE.t) + + db = derivation(b, DE).as_poly(DE.t) + ds_dms, _ = ds.div(dms) + a = c.as_poly(DE.t) - db.mul(ds_dms).as_poly(DE.t) + + ga = ga*dm + b*gd + gd = gd*dm + ga, gd = ga.cancel(gd, include=True) + dm = dm2 + + q, r = a.div(ds) + ga, gd = ga.cancel(gd, include=True) + + r, d = r.cancel(ds, include=True) + rra = q*fs[1] + fp*fs[1] + fs[0] + rrd = fs[1] + rra, rrd = rra.cancel(rrd, include=True) + + return ((ga, gd), (r, d), (rra, rrd)) + + +def polynomial_reduce(p, DE): + """ + Polynomial Reduction. + + Explanation + =========== + + Given a derivation D on k(t) and p in k[t] where t is a nonlinear + monomial over k, return q, r in k[t] such that p = Dq + r, and + deg(r) < deg_t(Dt). + """ + q = Poly(0, DE.t) + while p.degree(DE.t) >= DE.d.degree(DE.t): + m = p.degree(DE.t) - DE.d.degree(DE.t) + 1 + q0 = Poly(DE.t**m, DE.t).mul(Poly(p.as_poly(DE.t).LC()/ + (m*DE.d.LC()), DE.t)) + q += q0 + p = p - derivation(q0, DE) + + return (q, p) + + +def laurent_series(a, d, F, n, DE): + """ + Contribution of ``F`` to the full partial fraction decomposition of A/D. + + Explanation + =========== + + Given a field K of characteristic 0 and ``A``,``D``,``F`` in K[x] with D monic, + nonzero, coprime with A, and ``F`` the factor of multiplicity n in the square- + free factorization of D, return the principal parts of the Laurent series of + A/D at all the zeros of ``F``. + """ + if F.degree()==0: + return 0 + Z = _symbols('z', n) + z = Symbol('z') + Z.insert(0, z) + delta_a = Poly(0, DE.t) + delta_d = Poly(1, DE.t) + + E = d.quo(F**n) + ha, hd = (a, E*Poly(z**n, DE.t)) + dF = derivation(F,DE) + B, _ = gcdex_diophantine(E, F, Poly(1,DE.t)) + C, _ = gcdex_diophantine(dF, F, Poly(1,DE.t)) + + # initialization + F_store = F + V, DE_D_list, H_list= [], [], [] + + for j in range(0, n): + # jth derivative of z would be substituted with dfnth/(j+1) where dfnth =(d^n)f/(dx)^n + F_store = derivation(F_store, DE) + v = (F_store.as_expr())/(j + 1) + V.append(v) + DE_D_list.append(Poly(Z[j + 1],Z[j])) + + DE_new = DifferentialExtension(extension = {'D': DE_D_list}) #a differential indeterminate + for j in range(0, n): + zEha = Poly(z**(n + j), DE.t)*E**(j + 1)*ha + zEhd = hd + Pa, Pd = cancel((zEha, zEhd))[1], cancel((zEha, zEhd))[2] + Q = Pa.quo(Pd) + for i in range(0, j + 1): + Q = Q.subs(Z[i], V[i]) + Dha = (hd*derivation(ha, DE, basic=True).as_poly(DE.t) + + ha*derivation(hd, DE, basic=True).as_poly(DE.t) + + hd*derivation(ha, DE_new, basic=True).as_poly(DE.t) + + ha*derivation(hd, DE_new, basic=True).as_poly(DE.t)) + Dhd = Poly(j + 1, DE.t)*hd**2 + ha, hd = Dha, Dhd + + Ff, _ = F.div(gcd(F, Q)) + F_stara, F_stard = frac_in(Ff, DE.t) + if F_stara.degree(DE.t) - F_stard.degree(DE.t) > 0: + QBC = Poly(Q, DE.t)*B**(1 + j)*C**(n + j) + H = QBC + H_list.append(H) + H = (QBC*F_stard).rem(F_stara) + alphas = real_roots(F_stara) + for alpha in list(alphas): + delta_a = delta_a*Poly((DE.t - alpha)**(n - j), DE.t) + Poly(H.eval(alpha), DE.t) + delta_d = delta_d*Poly((DE.t - alpha)**(n - j), DE.t) + return (delta_a, delta_d, H_list) + + +def recognize_derivative(a, d, DE, z=None): + """ + Compute the squarefree factorization of the denominator of f + and for each Di the polynomial H in K[x] (see Theorem 2.7.1), using the + LaurentSeries algorithm. Write Di = GiEi where Gj = gcd(Hn, Di) and + gcd(Ei,Hn) = 1. Since the residues of f at the roots of Gj are all 0, and + the residue of f at a root alpha of Ei is Hi(a) != 0, f is the derivative of a + rational function if and only if Ei = 1 for each i, which is equivalent to + Di | H[-1] for each i. + """ + flag =True + a, d = a.cancel(d, include=True) + _, r = a.div(d) + Np, Sp = splitfactor_sqf(d, DE, coefficientD=True, z=z) + + j = 1 + for s, _ in Sp: + delta_a, delta_d, H = laurent_series(r, d, s, j, DE) + g = gcd(d, H[-1]).as_poly() + if g is not d: + flag = False + break + j = j + 1 + return flag + + +def recognize_log_derivative(a, d, DE, z=None): + """ + There exists a v in K(x)* such that f = dv/v + where f a rational function if and only if f can be written as f = A/D + where D is squarefree,deg(A) < deg(D), gcd(A, D) = 1, + and all the roots of the Rothstein-Trager resultant are integers. In that case, + any of the Rothstein-Trager, Lazard-Rioboo-Trager or Czichowski algorithm + produces u in K(x) such that du/dx = uf. + """ + + z = z or Dummy('z') + a, d = a.cancel(d, include=True) + _, a = a.div(d) + + pz = Poly(z, DE.t) + Dd = derivation(d, DE) + q = a - pz*Dd + r, _ = d.resultant(q, includePRS=True) + r = Poly(r, z) + Np, Sp = splitfactor_sqf(r, DE, coefficientD=True, z=z) + + for s, _ in Sp: + # TODO also consider the complex roots which should + # turn the flag false + a = real_roots(s.as_poly(z)) + + if not all(j.is_Integer for j in a): + return False + return True + +def residue_reduce(a, d, DE, z=None, invert=True): + """ + Lazard-Rioboo-Rothstein-Trager resultant reduction. + + Explanation + =========== + + Given a derivation ``D`` on k(t) and f in k(t) simple, return g + elementary over k(t) and a Boolean b in {True, False} such that f - + Dg in k[t] if b == True or f + h and f + h - Dg do not have an + elementary integral over k(t) for any h in k (reduced) if b == + False. + + Returns (G, b), where G is a tuple of tuples of the form (s_i, S_i), + such that g = Add(*[RootSum(s_i, lambda z: z*log(S_i(z, t))) for + S_i, s_i in G]). f - Dg is the remaining integral, which is elementary + only if b == True, and hence the integral of f is elementary only if + b == True. + + f - Dg is not calculated in this function because that would require + explicitly calculating the RootSum. Use residue_reduce_derivation(). + """ + # TODO: Use log_to_atan() from rationaltools.py + # If r = residue_reduce(...), then the logarithmic part is given by: + # sum([RootSum(a[0].as_poly(z), lambda i: i*log(a[1].as_expr()).subs(z, + # i)).subs(t, log(x)) for a in r[0]]) + + z = z or Dummy('z') + a, d = a.cancel(d, include=True) + a, d = a.to_field().mul_ground(1/d.LC()), d.to_field().mul_ground(1/d.LC()) + kkinv = [1/x for x in DE.T[:DE.level]] + DE.T[:DE.level] + + if a.is_zero: + return ([], True) + _, a = a.div(d) + + pz = Poly(z, DE.t) + + Dd = derivation(d, DE) + q = a - pz*Dd + + if Dd.degree(DE.t) <= d.degree(DE.t): + r, R = d.resultant(q, includePRS=True) + else: + r, R = q.resultant(d, includePRS=True) + + R_map, H = {}, [] + for i in R: + R_map[i.degree()] = i + + r = Poly(r, z) + Np, Sp = splitfactor_sqf(r, DE, coefficientD=True, z=z) + + for s, i in Sp: + if i == d.degree(DE.t): + s = Poly(s, z).monic() + H.append((s, d)) + else: + h = R_map.get(i) + if h is None: + continue + h_lc = Poly(h.as_poly(DE.t).LC(), DE.t, field=True) + + h_lc_sqf = h_lc.sqf_list_include(all=True) + + for a, j in h_lc_sqf: + h = Poly(h, DE.t, field=True).exquo(Poly(gcd(a, s**j, *kkinv), + DE.t)) + + s = Poly(s, z).monic() + + if invert: + h_lc = Poly(h.as_poly(DE.t).LC(), DE.t, field=True, expand=False) + inv, coeffs = h_lc.as_poly(z, field=True).invert(s), [S.One] + + for coeff in h.coeffs()[1:]: + L = reduced(inv*coeff.as_poly(inv.gens), [s])[1] + coeffs.append(L.as_expr()) + + h = Poly(dict(list(zip(h.monoms(), coeffs))), DE.t) + + H.append((s, h)) + + b = not any(cancel(i.as_expr()).has(DE.t, z) for i, _ in Np) + + return (H, b) + + +def residue_reduce_to_basic(H, DE, z): + """ + Converts the tuple returned by residue_reduce() into a Basic expression. + """ + # TODO: check what Lambda does with RootOf + i = Dummy('i') + s = list(zip(reversed(DE.T), reversed([f(DE.x) for f in DE.Tfuncs]))) + + return sum(RootSum(a[0].as_poly(z), Lambda(i, i*log(a[1].as_expr()).subs( + {z: i}).subs(s))) for a in H) + + +def residue_reduce_derivation(H, DE, z): + """ + Computes the derivation of an expression returned by residue_reduce(). + + In general, this is a rational function in t, so this returns an + as_expr() result. + """ + # TODO: verify that this is correct for multiple extensions + i = Dummy('i') + return S(sum(RootSum(a[0].as_poly(z), Lambda(i, i*derivation(a[1], + DE).as_expr().subs(z, i)/a[1].as_expr().subs(z, i))) for a in H)) + + +def integrate_primitive_polynomial(p, DE): + """ + Integration of primitive polynomials. + + Explanation + =========== + + Given a primitive monomial t over k, and ``p`` in k[t], return q in k[t], + r in k, and a bool b in {True, False} such that r = p - Dq is in k if b is + True, or r = p - Dq does not have an elementary integral over k(t) if b is + False. + """ + Zero = Poly(0, DE.t) + q = Poly(0, DE.t) + + if not p.expr.has(DE.t): + return (Zero, p, True) + + from .prde import limited_integrate + while True: + if not p.expr.has(DE.t): + return (q, p, True) + + Dta, Dtb = frac_in(DE.d, DE.T[DE.level - 1]) + + with DecrementLevel(DE): # We had better be integrating the lowest extension (x) + # with ratint(). + a = p.LC() + aa, ad = frac_in(a, DE.t) + + try: + rv = limited_integrate(aa, ad, [(Dta, Dtb)], DE) + if rv is None: + raise NonElementaryIntegralException + (ba, bd), c = rv + except NonElementaryIntegralException: + return (q, p, False) + + m = p.degree(DE.t) + q0 = c[0].as_poly(DE.t)*Poly(DE.t**(m + 1)/(m + 1), DE.t) + \ + (ba.as_expr()/bd.as_expr()).as_poly(DE.t)*Poly(DE.t**m, DE.t) + + p = p - derivation(q0, DE) + q = q + q0 + + +def integrate_primitive(a, d, DE, z=None): + """ + Integration of primitive functions. + + Explanation + =========== + + Given a primitive monomial t over k and f in k(t), return g elementary over + k(t), i in k(t), and b in {True, False} such that i = f - Dg is in k if b + is True or i = f - Dg does not have an elementary integral over k(t) if b + is False. + + This function returns a Basic expression for the first argument. If b is + True, the second argument is Basic expression in k to recursively integrate. + If b is False, the second argument is an unevaluated Integral, which has + been proven to be nonelementary. + """ + # XXX: a and d must be canceled, or this might return incorrect results + z = z or Dummy("z") + s = list(zip(reversed(DE.T), reversed([f(DE.x) for f in DE.Tfuncs]))) + + g1, h, r = hermite_reduce(a, d, DE) + g2, b = residue_reduce(h[0], h[1], DE, z=z) + if not b: + i = cancel(a.as_expr()/d.as_expr() - (g1[1]*derivation(g1[0], DE) - + g1[0]*derivation(g1[1], DE)).as_expr()/(g1[1]**2).as_expr() - + residue_reduce_derivation(g2, DE, z)) + i = NonElementaryIntegral(cancel(i).subs(s), DE.x) + return ((g1[0].as_expr()/g1[1].as_expr()).subs(s) + + residue_reduce_to_basic(g2, DE, z), i, b) + + # h - Dg2 + r + p = cancel(h[0].as_expr()/h[1].as_expr() - residue_reduce_derivation(g2, + DE, z) + r[0].as_expr()/r[1].as_expr()) + p = p.as_poly(DE.t) + + q, i, b = integrate_primitive_polynomial(p, DE) + + ret = ((g1[0].as_expr()/g1[1].as_expr() + q.as_expr()).subs(s) + + residue_reduce_to_basic(g2, DE, z)) + if not b: + # TODO: This does not do the right thing when b is False + i = NonElementaryIntegral(cancel(i.as_expr()).subs(s), DE.x) + else: + i = cancel(i.as_expr()) + + return (ret, i, b) + + +def integrate_hyperexponential_polynomial(p, DE, z): + """ + Integration of hyperexponential polynomials. + + Explanation + =========== + + Given a hyperexponential monomial t over k and ``p`` in k[t, 1/t], return q in + k[t, 1/t] and a bool b in {True, False} such that p - Dq in k if b is True, + or p - Dq does not have an elementary integral over k(t) if b is False. + """ + t1 = DE.t + dtt = DE.d.exquo(Poly(DE.t, DE.t)) + qa = Poly(0, DE.t) + qd = Poly(1, DE.t) + b = True + + if p.is_zero: + return(qa, qd, b) + + from sympy.integrals.rde import rischDE + + with DecrementLevel(DE): + for i in range(-p.degree(z), p.degree(t1) + 1): + if not i: + continue + elif i < 0: + # If you get AttributeError: 'NoneType' object has no attribute 'nth' + # then this should really not have expand=False + # But it shouldn't happen because p is already a Poly in t and z + a = p.as_poly(z, expand=False).nth(-i) + else: + # If you get AttributeError: 'NoneType' object has no attribute 'nth' + # then this should really not have expand=False + a = p.as_poly(t1, expand=False).nth(i) + + aa, ad = frac_in(a, DE.t, field=True) + aa, ad = aa.cancel(ad, include=True) + iDt = Poly(i, t1)*dtt + iDta, iDtd = frac_in(iDt, DE.t, field=True) + try: + va, vd = rischDE(iDta, iDtd, Poly(aa, DE.t), Poly(ad, DE.t), DE) + va, vd = frac_in((va, vd), t1, cancel=True) + except NonElementaryIntegralException: + b = False + else: + qa = qa*vd + va*Poly(t1**i)*qd + qd *= vd + + return (qa, qd, b) + + +def integrate_hyperexponential(a, d, DE, z=None, conds='piecewise'): + """ + Integration of hyperexponential functions. + + Explanation + =========== + + Given a hyperexponential monomial t over k and f in k(t), return g + elementary over k(t), i in k(t), and a bool b in {True, False} such that + i = f - Dg is in k if b is True or i = f - Dg does not have an elementary + integral over k(t) if b is False. + + This function returns a Basic expression for the first argument. If b is + True, the second argument is Basic expression in k to recursively integrate. + If b is False, the second argument is an unevaluated Integral, which has + been proven to be nonelementary. + """ + # XXX: a and d must be canceled, or this might return incorrect results + z = z or Dummy("z") + s = list(zip(reversed(DE.T), reversed([f(DE.x) for f in DE.Tfuncs]))) + + g1, h, r = hermite_reduce(a, d, DE) + g2, b = residue_reduce(h[0], h[1], DE, z=z) + if not b: + i = cancel(a.as_expr()/d.as_expr() - (g1[1]*derivation(g1[0], DE) - + g1[0]*derivation(g1[1], DE)).as_expr()/(g1[1]**2).as_expr() - + residue_reduce_derivation(g2, DE, z)) + i = NonElementaryIntegral(cancel(i.subs(s)), DE.x) + return ((g1[0].as_expr()/g1[1].as_expr()).subs(s) + + residue_reduce_to_basic(g2, DE, z), i, b) + + # p should be a polynomial in t and 1/t, because Sirr == k[t, 1/t] + # h - Dg2 + r + p = cancel(h[0].as_expr()/h[1].as_expr() - residue_reduce_derivation(g2, + DE, z) + r[0].as_expr()/r[1].as_expr()) + pp = as_poly_1t(p, DE.t, z) + + qa, qd, b = integrate_hyperexponential_polynomial(pp, DE, z) + + i = pp.nth(0, 0) + + ret = ((g1[0].as_expr()/g1[1].as_expr()).subs(s) \ + + residue_reduce_to_basic(g2, DE, z)) + + qas = qa.as_expr().subs(s) + qds = qd.as_expr().subs(s) + if conds == 'piecewise' and DE.x not in qds.free_symbols: + # We have to be careful if the exponent is S.Zero! + + # XXX: Does qd = 0 always necessarily correspond to the exponential + # equaling 1? + ret += Piecewise( + (qas/qds, Ne(qds, 0)), + (integrate((p - i).subs(DE.t, 1).subs(s), DE.x), True) + ) + else: + ret += qas/qds + + if not b: + i = p - (qd*derivation(qa, DE) - qa*derivation(qd, DE)).as_expr()/\ + (qd**2).as_expr() + i = NonElementaryIntegral(cancel(i).subs(s), DE.x) + return (ret, i, b) + + +def integrate_hypertangent_polynomial(p, DE): + """ + Integration of hypertangent polynomials. + + Explanation + =========== + + Given a differential field k such that sqrt(-1) is not in k, a + hypertangent monomial t over k, and p in k[t], return q in k[t] and + c in k such that p - Dq - c*D(t**2 + 1)/(t**1 + 1) is in k and p - + Dq does not have an elementary integral over k(t) if Dc != 0. + """ + # XXX: Make sure that sqrt(-1) is not in k. + q, r = polynomial_reduce(p, DE) + a = DE.d.exquo(Poly(DE.t**2 + 1, DE.t)) + c = Poly(r.nth(1)/(2*a.as_expr()), DE.t) + return (q, c) + + +def integrate_nonlinear_no_specials(a, d, DE, z=None): + """ + Integration of nonlinear monomials with no specials. + + Explanation + =========== + + Given a nonlinear monomial t over k such that Sirr ({p in k[t] | p is + special, monic, and irreducible}) is empty, and f in k(t), returns g + elementary over k(t) and a Boolean b in {True, False} such that f - Dg is + in k if b == True, or f - Dg does not have an elementary integral over k(t) + if b == False. + + This function is applicable to all nonlinear extensions, but in the case + where it returns b == False, it will only have proven that the integral of + f - Dg is nonelementary if Sirr is empty. + + This function returns a Basic expression. + """ + # TODO: Integral from k? + # TODO: split out nonelementary integral + # XXX: a and d must be canceled, or this might not return correct results + z = z or Dummy("z") + s = list(zip(reversed(DE.T), reversed([f(DE.x) for f in DE.Tfuncs]))) + + g1, h, r = hermite_reduce(a, d, DE) + g2, b = residue_reduce(h[0], h[1], DE, z=z) + if not b: + return ((g1[0].as_expr()/g1[1].as_expr()).subs(s) + + residue_reduce_to_basic(g2, DE, z), b) + + # Because f has no specials, this should be a polynomial in t, or else + # there is a bug. + p = cancel(h[0].as_expr()/h[1].as_expr() - residue_reduce_derivation(g2, + DE, z).as_expr() + r[0].as_expr()/r[1].as_expr()).as_poly(DE.t) + q1, q2 = polynomial_reduce(p, DE) + + if q2.expr.has(DE.t): + b = False + else: + b = True + + ret = (cancel(g1[0].as_expr()/g1[1].as_expr() + q1.as_expr()).subs(s) + + residue_reduce_to_basic(g2, DE, z)) + return (ret, b) + + +class NonElementaryIntegral(Integral): + """ + Represents a nonelementary Integral. + + Explanation + =========== + + If the result of integrate() is an instance of this class, it is + guaranteed to be nonelementary. Note that integrate() by default will try + to find any closed-form solution, even in terms of special functions which + may themselves not be elementary. To make integrate() only give + elementary solutions, or, in the cases where it can prove the integral to + be nonelementary, instances of this class, use integrate(risch=True). + In this case, integrate() may raise NotImplementedError if it cannot make + such a determination. + + integrate() uses the deterministic Risch algorithm to integrate elementary + functions or prove that they have no elementary integral. In some cases, + this algorithm can split an integral into an elementary and nonelementary + part, so that the result of integrate will be the sum of an elementary + expression and a NonElementaryIntegral. + + Examples + ======== + + >>> from sympy import integrate, exp, log, Integral + >>> from sympy.abc import x + + >>> a = integrate(exp(-x**2), x, risch=True) + >>> print(a) + Integral(exp(-x**2), x) + >>> type(a) + + + >>> expr = (2*log(x)**2 - log(x) - x**2)/(log(x)**3 - x**2*log(x)) + >>> b = integrate(expr, x, risch=True) + >>> print(b) + -log(-x + log(x))/2 + log(x + log(x))/2 + Integral(1/log(x), x) + >>> type(b.atoms(Integral).pop()) + + + """ + # TODO: This is useful in and of itself, because isinstance(result, + # NonElementaryIntegral) will tell if the integral has been proven to be + # elementary. But should we do more? Perhaps a no-op .doit() if + # elementary=True? Or maybe some information on why the integral is + # nonelementary. + pass + + +def risch_integrate(f, x, extension=None, handle_first='log', + separate_integral=False, rewrite_complex=None, + conds='piecewise'): + r""" + The Risch Integration Algorithm. + + Explanation + =========== + + Only transcendental functions are supported. Currently, only exponentials + and logarithms are supported, but support for trigonometric functions is + forthcoming. + + If this function returns an unevaluated Integral in the result, it means + that it has proven that integral to be nonelementary. Any errors will + result in raising NotImplementedError. The unevaluated Integral will be + an instance of NonElementaryIntegral, a subclass of Integral. + + handle_first may be either 'exp' or 'log'. This changes the order in + which the extension is built, and may result in a different (but + equivalent) solution (for an example of this, see issue 5109). It is also + possible that the integral may be computed with one but not the other, + because not all cases have been implemented yet. It defaults to 'log' so + that the outer extension is exponential when possible, because more of the + exponential case has been implemented. + + If ``separate_integral`` is ``True``, the result is returned as a tuple (ans, i), + where the integral is ans + i, ans is elementary, and i is either a + NonElementaryIntegral or 0. This useful if you want to try further + integrating the NonElementaryIntegral part using other algorithms to + possibly get a solution in terms of special functions. It is False by + default. + + Examples + ======== + + >>> from sympy.integrals.risch import risch_integrate + >>> from sympy import exp, log, pprint + >>> from sympy.abc import x + + First, we try integrating exp(-x**2). Except for a constant factor of + 2/sqrt(pi), this is the famous error function. + + >>> pprint(risch_integrate(exp(-x**2), x)) + / + | + | 2 + | -x + | e dx + | + / + + The unevaluated Integral in the result means that risch_integrate() has + proven that exp(-x**2) does not have an elementary anti-derivative. + + In many cases, risch_integrate() can split out the elementary + anti-derivative part from the nonelementary anti-derivative part. + For example, + + >>> pprint(risch_integrate((2*log(x)**2 - log(x) - x**2)/(log(x)**3 - + ... x**2*log(x)), x)) + / + | + log(-x + log(x)) log(x + log(x)) | 1 + - ---------------- + --------------- + | ------ dx + 2 2 | log(x) + | + / + + This means that it has proven that the integral of 1/log(x) is + nonelementary. This function is also known as the logarithmic integral, + and is often denoted as Li(x). + + risch_integrate() currently only accepts purely transcendental functions + with exponentials and logarithms, though note that this can include + nested exponentials and logarithms, as well as exponentials with bases + other than E. + + >>> pprint(risch_integrate(exp(x)*exp(exp(x)), x)) + / x\ + \e / + e + >>> pprint(risch_integrate(exp(exp(x)), x)) + / + | + | / x\ + | \e / + | e dx + | + / + + >>> pprint(risch_integrate(x*x**x*log(x) + x**x + x*x**x, x)) + x + x*x + >>> pprint(risch_integrate(x**x, x)) + / + | + | x + | x dx + | + / + + >>> pprint(risch_integrate(-1/(x*log(x)*log(log(x))**2), x)) + 1 + ----------- + log(log(x)) + + """ + f = S(f) + + DE = extension or DifferentialExtension(f, x, handle_first=handle_first, + dummy=True, rewrite_complex=rewrite_complex) + fa, fd = DE.fa, DE.fd + + result = S.Zero + for case in reversed(DE.cases): + if not fa.expr.has(DE.t) and not fd.expr.has(DE.t) and not case == 'base': + DE.decrement_level() + fa, fd = frac_in((fa, fd), DE.t) + continue + + fa, fd = fa.cancel(fd, include=True) + if case == 'exp': + ans, i, b = integrate_hyperexponential(fa, fd, DE, conds=conds) + elif case == 'primitive': + ans, i, b = integrate_primitive(fa, fd, DE) + elif case == 'base': + # XXX: We can't call ratint() directly here because it doesn't + # handle polynomials correctly. + ans = integrate(fa.as_expr()/fd.as_expr(), DE.x, risch=False) + b = False + i = S.Zero + else: + raise NotImplementedError("Only exponential and logarithmic " + "extensions are currently supported.") + + result += ans + if b: + DE.decrement_level() + fa, fd = frac_in(i, DE.t) + else: + result = result.subs(DE.backsubs) + if not i.is_zero: + i = NonElementaryIntegral(i.function.subs(DE.backsubs),i.limits) + if not separate_integral: + result += i + return result + else: + + if isinstance(i, NonElementaryIntegral): + return (result, i) + else: + return (result, 0) diff --git a/lib/python3.12/site-packages/sympy/integrals/singularityfunctions.py b/lib/python3.12/site-packages/sympy/integrals/singularityfunctions.py new file mode 100644 index 0000000000000000000000000000000000000000..3e33d0542c45b67b193f17e00c25837f3a82109a --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/singularityfunctions.py @@ -0,0 +1,63 @@ +from sympy.functions import SingularityFunction, DiracDelta +from sympy.integrals import integrate + + +def singularityintegrate(f, x): + """ + This function handles the indefinite integrations of Singularity functions. + The ``integrate`` function calls this function internally whenever an + instance of SingularityFunction is passed as argument. + + Explanation + =========== + + The idea for integration is the following: + + - If we are dealing with a SingularityFunction expression, + i.e. ``SingularityFunction(x, a, n)``, we just return + ``SingularityFunction(x, a, n + 1)/(n + 1)`` if ``n >= 0`` and + ``SingularityFunction(x, a, n + 1)`` if ``n < 0``. + + - If the node is a multiplication or power node having a + SingularityFunction term we rewrite the whole expression in terms of + Heaviside and DiracDelta and then integrate the output. Lastly, we + rewrite the output of integration back in terms of SingularityFunction. + + - If none of the above case arises, we return None. + + Examples + ======== + + >>> from sympy.integrals.singularityfunctions import singularityintegrate + >>> from sympy import SingularityFunction, symbols, Function + >>> x, a, n, y = symbols('x a n y') + >>> f = Function('f') + >>> singularityintegrate(SingularityFunction(x, a, 3), x) + SingularityFunction(x, a, 4)/4 + >>> singularityintegrate(5*SingularityFunction(x, 5, -2), x) + 5*SingularityFunction(x, 5, -1) + >>> singularityintegrate(6*SingularityFunction(x, 5, -1), x) + 6*SingularityFunction(x, 5, 0) + >>> singularityintegrate(x*SingularityFunction(x, 0, -1), x) + 0 + >>> singularityintegrate(SingularityFunction(x, 1, -1) * f(x), x) + f(1)*SingularityFunction(x, 1, 0) + + """ + + if not f.has(SingularityFunction): + return None + + if isinstance(f, SingularityFunction): + x, a, n = f.args + if n.is_positive or n.is_zero: + return SingularityFunction(x, a, n + 1)/(n + 1) + elif n in (-1, -2, -3, -4): + return SingularityFunction(x, a, n + 1) + + if f.is_Mul or f.is_Pow: + + expr = f.rewrite(DiracDelta) + expr = integrate(expr, x) + return expr.rewrite(SingularityFunction) + return None diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_heurisch.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_heurisch.py new file mode 100644 index 0000000000000000000000000000000000000000..265186acd7fd444b4709e9385435555f4fc058b1 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_heurisch.py @@ -0,0 +1,389 @@ +from sympy.concrete.summations import Sum +from sympy.core.add import Add +from sympy.core.function import (Derivative, Function, diff) +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.relational import Eq, Ne +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.exponential import (LambertW, exp, log) +from sympy.functions.elementary.hyperbolic import (asinh, cosh, sinh, tanh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (acos, asin, atan, cos, sin, tan) +from sympy.functions.special.bessel import (besselj, besselk, bessely, jn) +from sympy.functions.special.error_functions import erf +from sympy.integrals.integrals import Integral +from sympy.logic.boolalg import And +from sympy.matrices import Matrix +from sympy.simplify.ratsimp import ratsimp +from sympy.simplify.simplify import simplify +from sympy.integrals.heurisch import components, heurisch, heurisch_wrapper +from sympy.testing.pytest import XFAIL, slow +from sympy.integrals.integrals import integrate +x, y, z, nu = symbols('x,y,z,nu') +f = Function('f') + + +def test_components(): + assert components(x*y, x) == {x} + assert components(1/(x + y), x) == {x} + assert components(sin(x), x) == {sin(x), x} + assert components(sin(x)*sqrt(log(x)), x) == \ + {log(x), sin(x), sqrt(log(x)), x} + assert components(x*sin(exp(x)*y), x) == \ + {sin(y*exp(x)), x, exp(x)} + assert components(x**Rational(17, 54)/sqrt(sin(x)), x) == \ + {sin(x), x**Rational(1, 54), sqrt(sin(x)), x} + + assert components(f(x), x) == \ + {x, f(x)} + assert components(Derivative(f(x), x), x) == \ + {x, f(x), Derivative(f(x), x)} + assert components(f(x)*diff(f(x), x), x) == \ + {x, f(x), Derivative(f(x), x), Derivative(f(x), x)} + + +def test_issue_10680(): + assert isinstance(integrate(x**log(x**log(x**log(x))),x), Integral) + + +def test_issue_21166(): + assert integrate(sin(x/sqrt(abs(x))), (x, -1, 1)) == 0 + + +def test_heurisch_polynomials(): + assert heurisch(1, x) == x + assert heurisch(x, x) == x**2/2 + assert heurisch(x**17, x) == x**18/18 + # For coverage + assert heurisch_wrapper(y, x) == y*x + + +def test_heurisch_fractions(): + assert heurisch(1/x, x) == log(x) + assert heurisch(1/(2 + x), x) == log(x + 2) + assert heurisch(1/(x + sin(y)), x) == log(x + sin(y)) + + # Up to a constant, where C = pi*I*Rational(5, 12), Mathematica gives identical + # result in the first case. The difference is because SymPy changes + # signs of expressions without any care. + # XXX ^ ^ ^ is this still correct? + assert heurisch(5*x**5/( + 2*x**6 - 5), x) in [5*log(2*x**6 - 5) / 12, 5*log(-2*x**6 + 5) / 12] + assert heurisch(5*x**5/(2*x**6 + 5), x) == 5*log(2*x**6 + 5) / 12 + + assert heurisch(1/x**2, x) == -1/x + assert heurisch(-1/x**5, x) == 1/(4*x**4) + + +def test_heurisch_log(): + assert heurisch(log(x), x) == x*log(x) - x + assert heurisch(log(3*x), x) == -x + x*log(3) + x*log(x) + assert heurisch(log(x**2), x) in [x*log(x**2) - 2*x, 2*x*log(x) - 2*x] + + +def test_heurisch_exp(): + assert heurisch(exp(x), x) == exp(x) + assert heurisch(exp(-x), x) == -exp(-x) + assert heurisch(exp(17*x), x) == exp(17*x) / 17 + assert heurisch(x*exp(x), x) == x*exp(x) - exp(x) + assert heurisch(x*exp(x**2), x) == exp(x**2) / 2 + + assert heurisch(exp(-x**2), x) is None + + assert heurisch(2**x, x) == 2**x/log(2) + assert heurisch(x*2**x, x) == x*2**x/log(2) - 2**x*log(2)**(-2) + + assert heurisch(Integral(x**z*y, (y, 1, 2), (z, 2, 3)).function, x) == (x*x**z*y)/(z+1) + assert heurisch(Sum(x**z, (z, 1, 2)).function, z) == x**z/log(x) + + # https://github.com/sympy/sympy/issues/23707 + anti = -exp(z)/(sqrt(x - y)*exp(z*sqrt(x - y)) - exp(z*sqrt(x - y))) + assert heurisch(exp(z)*exp(-z*sqrt(x - y)), z) == anti + + +def test_heurisch_trigonometric(): + assert heurisch(sin(x), x) == -cos(x) + assert heurisch(pi*sin(x) + 1, x) == x - pi*cos(x) + + assert heurisch(cos(x), x) == sin(x) + assert heurisch(tan(x), x) in [ + log(1 + tan(x)**2)/2, + log(tan(x) + I) + I*x, + log(tan(x) - I) - I*x, + ] + + assert heurisch(sin(x)*sin(y), x) == -cos(x)*sin(y) + assert heurisch(sin(x)*sin(y), y) == -cos(y)*sin(x) + + # gives sin(x) in answer when run via setup.py and cos(x) when run via py.test + assert heurisch(sin(x)*cos(x), x) in [sin(x)**2 / 2, -cos(x)**2 / 2] + assert heurisch(cos(x)/sin(x), x) == log(sin(x)) + + assert heurisch(x*sin(7*x), x) == sin(7*x) / 49 - x*cos(7*x) / 7 + assert heurisch(1/pi/4 * x**2*cos(x), x) == 1/pi/4*(x**2*sin(x) - + 2*sin(x) + 2*x*cos(x)) + + assert heurisch(acos(x/4) * asin(x/4), x) == 2*x - (sqrt(16 - x**2))*asin(x/4) \ + + (sqrt(16 - x**2))*acos(x/4) + x*asin(x/4)*acos(x/4) + + assert heurisch(sin(x)/(cos(x)**2+1), x) == -atan(cos(x)) #fixes issue 13723 + assert heurisch(1/(cos(x)+2), x) == 2*sqrt(3)*atan(sqrt(3)*tan(x/2)/3)/3 + assert heurisch(2*sin(x)*cos(x)/(sin(x)**4 + 1), x) == atan(sqrt(2)*sin(x) + - 1) - atan(sqrt(2)*sin(x) + 1) + + assert heurisch(1/cosh(x), x) == 2*atan(tanh(x/2)) + + +def test_heurisch_hyperbolic(): + assert heurisch(sinh(x), x) == cosh(x) + assert heurisch(cosh(x), x) == sinh(x) + + assert heurisch(x*sinh(x), x) == x*cosh(x) - sinh(x) + assert heurisch(x*cosh(x), x) == x*sinh(x) - cosh(x) + + assert heurisch( + x*asinh(x/2), x) == x**2*asinh(x/2)/2 + asinh(x/2) - x*sqrt(4 + x**2)/4 + + +def test_heurisch_mixed(): + assert heurisch(sin(x)*exp(x), x) == exp(x)*sin(x)/2 - exp(x)*cos(x)/2 + assert heurisch(sin(x/sqrt(-x)), x) == 2*x*cos(x/sqrt(-x))/sqrt(-x) - 2*sin(x/sqrt(-x)) + + +def test_heurisch_radicals(): + assert heurisch(1/sqrt(x), x) == 2*sqrt(x) + assert heurisch(1/sqrt(x)**3, x) == -2/sqrt(x) + assert heurisch(sqrt(x)**3, x) == 2*sqrt(x)**5/5 + + assert heurisch(sin(x)*sqrt(cos(x)), x) == -2*sqrt(cos(x))**3/3 + y = Symbol('y') + assert heurisch(sin(y*sqrt(x)), x) == 2/y**2*sin(y*sqrt(x)) - \ + 2*sqrt(x)*cos(y*sqrt(x))/y + assert heurisch_wrapper(sin(y*sqrt(x)), x) == Piecewise( + (-2*sqrt(x)*cos(sqrt(x)*y)/y + 2*sin(sqrt(x)*y)/y**2, Ne(y, 0)), + (0, True)) + y = Symbol('y', positive=True) + assert heurisch_wrapper(sin(y*sqrt(x)), x) == 2/y**2*sin(y*sqrt(x)) - \ + 2*sqrt(x)*cos(y*sqrt(x))/y + + +def test_heurisch_special(): + assert heurisch(erf(x), x) == x*erf(x) + exp(-x**2)/sqrt(pi) + assert heurisch(exp(-x**2)*erf(x), x) == sqrt(pi)*erf(x)**2 / 4 + + +def test_heurisch_symbolic_coeffs(): + assert heurisch(1/(x + y), x) == log(x + y) + assert heurisch(1/(x + sqrt(2)), x) == log(x + sqrt(2)) + assert simplify(diff(heurisch(log(x + y + z), y), y)) == log(x + y + z) + + +def test_heurisch_symbolic_coeffs_1130(): + y = Symbol('y') + assert heurisch_wrapper(1/(x**2 + y), x) == Piecewise( + (log(x - sqrt(-y))/(2*sqrt(-y)) - log(x + sqrt(-y))/(2*sqrt(-y)), + Ne(y, 0)), (-1/x, True)) + y = Symbol('y', positive=True) + assert heurisch_wrapper(1/(x**2 + y), x) == (atan(x/sqrt(y))/sqrt(y)) + + +def test_heurisch_hacking(): + assert heurisch(sqrt(1 + 7*x**2), x, hints=[]) == \ + x*sqrt(1 + 7*x**2)/2 + sqrt(7)*asinh(sqrt(7)*x)/14 + assert heurisch(sqrt(1 - 7*x**2), x, hints=[]) == \ + x*sqrt(1 - 7*x**2)/2 + sqrt(7)*asin(sqrt(7)*x)/14 + + assert heurisch(1/sqrt(1 + 7*x**2), x, hints=[]) == \ + sqrt(7)*asinh(sqrt(7)*x)/7 + assert heurisch(1/sqrt(1 - 7*x**2), x, hints=[]) == \ + sqrt(7)*asin(sqrt(7)*x)/7 + + assert heurisch(exp(-7*x**2), x, hints=[]) == \ + sqrt(7*pi)*erf(sqrt(7)*x)/14 + + assert heurisch(1/sqrt(9 - 4*x**2), x, hints=[]) == \ + asin(x*Rational(2, 3))/2 + + assert heurisch(1/sqrt(9 + 4*x**2), x, hints=[]) == \ + asinh(x*Rational(2, 3))/2 + + assert heurisch(1/sqrt(3*x**2-4), x, hints=[]) == \ + sqrt(3)*log(3*x + sqrt(3)*sqrt(3*x**2 - 4))/3 + + +def test_heurisch_function(): + assert heurisch(f(x), x) is None + +@XFAIL +def test_heurisch_function_derivative(): + # TODO: it looks like this used to work just by coincindence and + # thanks to sloppy implementation. Investigate why this used to + # work at all and if support for this can be restored. + + df = diff(f(x), x) + + assert heurisch(f(x)*df, x) == f(x)**2/2 + assert heurisch(f(x)**2*df, x) == f(x)**3/3 + assert heurisch(df/f(x), x) == log(f(x)) + + +def test_heurisch_wrapper(): + f = 1/(y + x) + assert heurisch_wrapper(f, x) == log(x + y) + f = 1/(y - x) + assert heurisch_wrapper(f, x) == -log(x - y) + f = 1/((y - x)*(y + x)) + assert heurisch_wrapper(f, x) == Piecewise( + (-log(x - y)/(2*y) + log(x + y)/(2*y), Ne(y, 0)), (1/x, True)) + # issue 6926 + f = sqrt(x**2/((y - x)*(y + x))) + assert heurisch_wrapper(f, x) == x*sqrt(-x**2/(x**2 - y**2)) \ + - y**2*sqrt(-x**2/(x**2 - y**2))/x + + +def test_issue_3609(): + assert heurisch(1/(x * (1 + log(x)**2)), x) == atan(log(x)) + +### These are examples from the Poor Man's Integrator +### http://www-sop.inria.fr/cafe/Manuel.Bronstein/pmint/examples/ + + +def test_pmint_rat(): + # TODO: heurisch() is off by a constant: -3/4. Possibly different permutation + # would give the optimal result? + + def drop_const(expr, x): + if expr.is_Add: + return Add(*[ arg for arg in expr.args if arg.has(x) ]) + else: + return expr + + f = (x**7 - 24*x**4 - 4*x**2 + 8*x - 8)/(x**8 + 6*x**6 + 12*x**4 + 8*x**2) + g = (4 + 8*x**2 + 6*x + 3*x**3)/(x**5 + 4*x**3 + 4*x) + log(x) + + assert drop_const(ratsimp(heurisch(f, x)), x) == g + + +def test_pmint_trig(): + f = (x - tan(x)) / tan(x)**2 + tan(x) + g = -x**2/2 - x/tan(x) + log(tan(x)**2 + 1)/2 + + assert heurisch(f, x) == g + + +def test_pmint_logexp(): + f = (1 + x + x*exp(x))*(x + log(x) + exp(x) - 1)/(x + log(x) + exp(x))**2/x + g = log(x + exp(x) + log(x)) + 1/(x + exp(x) + log(x)) + + assert ratsimp(heurisch(f, x)) == g + + +def test_pmint_erf(): + f = exp(-x**2)*erf(x)/(erf(x)**3 - erf(x)**2 - erf(x) + 1) + g = sqrt(pi)*log(erf(x) - 1)/8 - sqrt(pi)*log(erf(x) + 1)/8 - sqrt(pi)/(4*erf(x) - 4) + + assert ratsimp(heurisch(f, x)) == g + + +def test_pmint_LambertW(): + f = LambertW(x) + g = x*LambertW(x) - x + x/LambertW(x) + + assert heurisch(f, x) == g + + +def test_pmint_besselj(): + f = besselj(nu + 1, x)/besselj(nu, x) + g = nu*log(x) - log(besselj(nu, x)) + + assert heurisch(f, x) == g + + f = (nu*besselj(nu, x) - x*besselj(nu + 1, x))/x + g = besselj(nu, x) + + assert heurisch(f, x) == g + + f = jn(nu + 1, x)/jn(nu, x) + g = nu*log(x) - log(jn(nu, x)) + + assert heurisch(f, x) == g + + +@slow +def test_pmint_bessel_products(): + f = x*besselj(nu, x)*bessely(nu, 2*x) + g = -2*x*besselj(nu, x)*bessely(nu - 1, 2*x)/3 + x*besselj(nu - 1, x)*bessely(nu, 2*x)/3 + + assert heurisch(f, x) == g + + f = x*besselj(nu, x)*besselk(nu, 2*x) + g = -2*x*besselj(nu, x)*besselk(nu - 1, 2*x)/5 - x*besselj(nu - 1, x)*besselk(nu, 2*x)/5 + + assert heurisch(f, x) == g + + +def test_pmint_WrightOmega(): + def omega(x): + return LambertW(exp(x)) + + f = (1 + omega(x) * (2 + cos(omega(x)) * (x + omega(x))))/(1 + omega(x))/(x + omega(x)) + g = log(x + LambertW(exp(x))) + sin(LambertW(exp(x))) + + assert heurisch(f, x) == g + + +def test_RR(): + # Make sure the algorithm does the right thing if the ring is RR. See + # issue 8685. + assert heurisch(sqrt(1 + 0.25*x**2), x, hints=[]) == \ + 0.5*x*sqrt(0.25*x**2 + 1) + 1.0*asinh(0.5*x) + +# TODO: convert the rest of PMINT tests: +# Airy functions +# f = (x - AiryAi(x)*AiryAi(1, x)) / (x**2 - AiryAi(x)**2) +# g = Rational(1,2)*ln(x + AiryAi(x)) + Rational(1,2)*ln(x - AiryAi(x)) +# f = x**2 * AiryAi(x) +# g = -AiryAi(x) + AiryAi(1, x)*x +# Whittaker functions +# f = WhittakerW(mu + 1, nu, x) / (WhittakerW(mu, nu, x) * x) +# g = x/2 - mu*ln(x) - ln(WhittakerW(mu, nu, x)) + + +def test_issue_22527(): + t, R = symbols(r't R') + z = Function('z')(t) + def f(x): + return x/sqrt(R**2 - x**2) + Uz = integrate(f(z), z) + Ut = integrate(f(t), t) + assert Ut == Uz.subs(z, t) + + +def test_heurisch_complex_erf_issue_26338(): + r = symbols('r', real=True) + a = exp(-r**2/(2*(2 - I)**2)) + assert heurisch(a, r, hints=[]) is None # None, not a wrong soln + a = sqrt(pi)*erf((1 + I)/2)/2 + assert integrate(exp(-I*r**2/2), (r, 0, 1)) == a - I*a + + a = exp(-x**2/(2*(2 - I)**2)) + assert heurisch(a, x, hints=[]) is None # None, not a wrong soln + a = sqrt(pi)*erf((1 + I)/2)/2 + assert integrate(exp(-I*x**2/2), (x, 0, 1)) == a - I*a + + +def test_issue_15498(): + Z0 = Function('Z0') + k01, k10, t, s= symbols('k01 k10 t s', real=True, positive=True) + m = Matrix([[exp(-k10*t)]]) + _83 = Rational(83, 100) # 0.83 works, too + [a, b, c, d, e, f, g] = [100, 0.5, _83, 50, 0.6, 2, 120] + AIF_btf = a*(d*e*(1 - exp(-(t - b)/e)) + f*g*(1 - exp(-(t - b)/g))) + AIF_atf = a*(d*e*exp(-(t - b)/e)*(exp((c - b)/e) - 1 + ) + f*g*exp(-(t - b)/g)*(exp((c - b)/g) - 1)) + AIF_sym = Piecewise((0, t < b), (AIF_btf, And(b <= t, t < c)), (AIF_atf, c <= t)) + aif_eq = Eq(Z0(t), AIF_sym) + f_vec = Matrix([[k01*Z0(t)]]) + integrand = m*m.subs(t, s)**-1*f_vec.subs(aif_eq.lhs, aif_eq.rhs).subs(t, s) + solution = integrate(integrand[0], (s, 0, t)) + assert solution is not None # does not hang and takes less than 10 s diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_manual.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_manual.py new file mode 100644 index 0000000000000000000000000000000000000000..74cae4521ec97608a21553e0203be60c210387b3 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_manual.py @@ -0,0 +1,714 @@ +from sympy.core.expr import Expr +from sympy.core.mul import Mul +from sympy.core.function import (Derivative, Function, diff, expand) +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.relational import Ne +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, symbols) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.hyperbolic import (asinh, csch, cosh, coth, sech, sinh, tanh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise, piecewise_fold +from sympy.functions.elementary.trigonometric import (acos, acot, acsc, asec, asin, atan, cos, cot, csc, sec, sin, tan) +from sympy.functions.special.delta_functions import Heaviside, DiracDelta +from sympy.functions.special.elliptic_integrals import (elliptic_e, elliptic_f) +from sympy.functions.special.error_functions import (Chi, Ci, Ei, Shi, Si, erf, erfi, fresnelc, fresnels, li) +from sympy.functions.special.gamma_functions import uppergamma +from sympy.functions.special.polynomials import (assoc_laguerre, chebyshevt, chebyshevu, gegenbauer, hermite, jacobi, laguerre, legendre) +from sympy.functions.special.zeta_functions import polylog +from sympy.integrals.integrals import (Integral, integrate) +from sympy.logic.boolalg import And +from sympy.integrals.manualintegrate import (manualintegrate, find_substitutions, + _parts_rule, integral_steps, manual_subs) +from sympy.testing.pytest import raises, slow + +x, y, z, u, n, a, b, c, d, e = symbols('x y z u n a b c d e') +f = Function('f') + + +def assert_is_integral_of(f: Expr, F: Expr): + assert manualintegrate(f, x) == F + assert F.diff(x).equals(f) + + +def test_find_substitutions(): + assert find_substitutions((cot(x)**2 + 1)**2*csc(x)**2*cot(x)**2, x, u) == \ + [(cot(x), 1, -u**6 - 2*u**4 - u**2)] + assert find_substitutions((sec(x)**2 + tan(x) * sec(x)) / (sec(x) + tan(x)), + x, u) == [(sec(x) + tan(x), 1, 1/u)] + assert (-x**2, Rational(-1, 2), exp(u)) in find_substitutions(x * exp(-x**2), x, u) + assert not find_substitutions(Derivative(f(x), x)**2, x, u) + + +def test_manualintegrate_polynomials(): + assert manualintegrate(y, x) == x*y + assert manualintegrate(exp(2), x) == x * exp(2) + assert manualintegrate(x**2, x) == x**3 / 3 + assert manualintegrate(3 * x**2 + 4 * x**3, x) == x**3 + x**4 + + assert manualintegrate((x + 2)**3, x) == (x + 2)**4 / 4 + assert manualintegrate((3*x + 4)**2, x) == (3*x + 4)**3 / 9 + + assert manualintegrate((u + 2)**3, u) == (u + 2)**4 / 4 + assert manualintegrate((3*u + 4)**2, u) == (3*u + 4)**3 / 9 + + +def test_manualintegrate_exponentials(): + assert manualintegrate(exp(2*x), x) == exp(2*x) / 2 + assert manualintegrate(2**x, x) == (2 ** x) / log(2) + assert_is_integral_of(1/sqrt(1-exp(2*x)), + log(sqrt(1 - exp(2*x)) - 1)/2 - log(sqrt(1 - exp(2*x)) + 1)/2) + + assert manualintegrate(1 / x, x) == log(x) + assert manualintegrate(1 / (2*x + 3), x) == log(2*x + 3) / 2 + assert manualintegrate(log(x)**2 / x, x) == log(x)**3 / 3 + + assert_is_integral_of(x**x*(log(x)+1), x**x) + + +def test_manualintegrate_parts(): + assert manualintegrate(exp(x) * sin(x), x) == \ + (exp(x) * sin(x)) / 2 - (exp(x) * cos(x)) / 2 + assert manualintegrate(2*x*cos(x), x) == 2*x*sin(x) + 2*cos(x) + assert manualintegrate(x * log(x), x) == x**2*log(x)/2 - x**2/4 + assert manualintegrate(log(x), x) == x * log(x) - x + assert manualintegrate((3*x**2 + 5) * exp(x), x) == \ + 3*x**2*exp(x) - 6*x*exp(x) + 11*exp(x) + assert manualintegrate(atan(x), x) == x*atan(x) - log(x**2 + 1)/2 + + # Make sure _parts_rule doesn't pick u = constant but can pick dv = + # constant if necessary, e.g. for integrate(atan(x)) + assert _parts_rule(cos(x), x) == None + assert _parts_rule(exp(x), x) == None + assert _parts_rule(x**2, x) == None + result = _parts_rule(atan(x), x) + assert result[0] == atan(x) and result[1] == 1 + + +def test_manualintegrate_trigonometry(): + assert manualintegrate(sin(x), x) == -cos(x) + assert manualintegrate(tan(x), x) == -log(cos(x)) + + assert manualintegrate(sec(x), x) == log(sec(x) + tan(x)) + assert manualintegrate(csc(x), x) == -log(csc(x) + cot(x)) + + assert manualintegrate(sin(x) * cos(x), x) in [sin(x) ** 2 / 2, -cos(x)**2 / 2] + assert manualintegrate(-sec(x) * tan(x), x) == -sec(x) + assert manualintegrate(csc(x) * cot(x), x) == -csc(x) + assert manualintegrate(sec(x)**2, x) == tan(x) + assert manualintegrate(csc(x)**2, x) == -cot(x) + + assert manualintegrate(x * sec(x**2), x) == log(tan(x**2) + sec(x**2))/2 + assert manualintegrate(cos(x)*csc(sin(x)), x) == -log(cot(sin(x)) + csc(sin(x))) + assert manualintegrate(cos(3*x)*sec(x), x) == -x + sin(2*x) + assert manualintegrate(sin(3*x)*sec(x), x) == \ + -3*log(cos(x)) + 2*log(cos(x)**2) - 2*cos(x)**2 + + assert_is_integral_of(sinh(2*x), cosh(2*x)/2) + assert_is_integral_of(x*cosh(x**2), sinh(x**2)/2) + assert_is_integral_of(tanh(x), log(cosh(x))) + assert_is_integral_of(coth(x), log(sinh(x))) + f, F = sech(x), 2*atan(tanh(x/2)) + assert manualintegrate(f, x) == F + assert (F.diff(x) - f).rewrite(exp).simplify() == 0 # todo: equals returns None + f, F = csch(x), log(tanh(x/2)) + assert manualintegrate(f, x) == F + assert (F.diff(x) - f).rewrite(exp).simplify() == 0 + + +@slow +def test_manualintegrate_trigpowers(): + assert manualintegrate(sin(x)**2 * cos(x), x) == sin(x)**3 / 3 + assert manualintegrate(sin(x)**2 * cos(x) **2, x) == \ + x / 8 - sin(4*x) / 32 + assert manualintegrate(sin(x) * cos(x)**3, x) == -cos(x)**4 / 4 + assert manualintegrate(sin(x)**3 * cos(x)**2, x) == \ + cos(x)**5 / 5 - cos(x)**3 / 3 + + assert manualintegrate(tan(x)**3 * sec(x), x) == sec(x)**3/3 - sec(x) + assert manualintegrate(tan(x) * sec(x) **2, x) == sec(x)**2/2 + + assert manualintegrate(cot(x)**5 * csc(x), x) == \ + -csc(x)**5/5 + 2*csc(x)**3/3 - csc(x) + assert manualintegrate(cot(x)**2 * csc(x)**6, x) == \ + -cot(x)**7/7 - 2*cot(x)**5/5 - cot(x)**3/3 + + +@slow +def test_manualintegrate_inversetrig(): + # atan + assert manualintegrate(exp(x) / (1 + exp(2*x)), x) == atan(exp(x)) + assert manualintegrate(1 / (4 + 9 * x**2), x) == atan(3 * x/2) / 6 + assert manualintegrate(1 / (16 + 16 * x**2), x) == atan(x) / 16 + assert manualintegrate(1 / (4 + x**2), x) == atan(x / 2) / 2 + assert manualintegrate(1 / (1 + 4 * x**2), x) == atan(2*x) / 2 + ra = Symbol('a', real=True) + rb = Symbol('b', real=True) + assert manualintegrate(1/(ra + rb*x**2), x) == \ + Piecewise((atan(x/sqrt(ra/rb))/(rb*sqrt(ra/rb)), ra/rb > 0), + ((log(x - sqrt(-ra/rb)) - log(x + sqrt(-ra/rb)))/(2*sqrt(rb)*sqrt(-ra)), True)) + assert manualintegrate(1/(4 + rb*x**2), x) == \ + Piecewise((atan(x/(2*sqrt(1/rb)))/(2*rb*sqrt(1/rb)), 1/rb > 0), + (-I*(log(x - 2*sqrt(-1/rb)) - log(x + 2*sqrt(-1/rb)))/(4*sqrt(rb)), True)) + assert manualintegrate(1/(ra + 4*x**2), x) == \ + Piecewise((atan(2*x/sqrt(ra))/(2*sqrt(ra)), ra > 0), + ((log(x - sqrt(-ra)/2) - log(x + sqrt(-ra)/2))/(4*sqrt(-ra)), True)) + assert manualintegrate(1/(4 + 4*x**2), x) == atan(x) / 4 + + assert manualintegrate(1/(a + b*x**2), x) == Piecewise((atan(x/sqrt(a/b))/(b*sqrt(a/b)), Ne(a, 0)), + (-1/(b*x), True)) + + # asin + assert manualintegrate(1/sqrt(1-x**2), x) == asin(x) + assert manualintegrate(1/sqrt(4-4*x**2), x) == asin(x)/2 + assert manualintegrate(3/sqrt(1-9*x**2), x) == asin(3*x) + assert manualintegrate(1/sqrt(4-9*x**2), x) == asin(x*Rational(3, 2))/3 + + # asinh + assert manualintegrate(1/sqrt(x**2 + 1), x) == \ + asinh(x) + assert manualintegrate(1/sqrt(x**2 + 4), x) == \ + asinh(x/2) + assert manualintegrate(1/sqrt(4*x**2 + 4), x) == \ + asinh(x)/2 + assert manualintegrate(1/sqrt(4*x**2 + 1), x) == \ + asinh(2*x)/2 + assert manualintegrate(1/sqrt(ra*x**2 + 1), x) == \ + Piecewise((asin(x*sqrt(-ra))/sqrt(-ra), ra < 0), (asinh(sqrt(ra)*x)/sqrt(ra), ra > 0), (x, True)) + assert manualintegrate(1/sqrt(ra + x**2), x) == \ + Piecewise((asinh(x*sqrt(1/ra)), ra > 0), (log(2*x + 2*sqrt(ra + x**2)), True)) + + # log + assert manualintegrate(1/sqrt(x**2 - 1), x) == log(2*x + 2*sqrt(x**2 - 1)) + assert manualintegrate(1/sqrt(x**2 - 4), x) == log(2*x + 2*sqrt(x**2 - 4)) + assert manualintegrate(1/sqrt(4*x**2 - 4), x) == log(8*x + 4*sqrt(4*x**2 - 4))/2 + assert manualintegrate(1/sqrt(9*x**2 - 1), x) == log(18*x + 6*sqrt(9*x**2 - 1))/3 + assert manualintegrate(1/sqrt(ra*x**2 - 4), x) == \ + Piecewise((log(2*sqrt(ra)*sqrt(ra*x**2 - 4) + 2*ra*x)/sqrt(ra), Ne(ra, 0)), (-I*x/2, True)) + assert manualintegrate(1/sqrt(-ra + 4*x**2), x) == \ + Piecewise((asinh(2*x*sqrt(-1/ra))/2, ra < 0), (log(8*x + 4*sqrt(-ra + 4*x**2))/2, True)) + + # From https://www.wikiwand.com/en/List_of_integrals_of_inverse_trigonometric_functions + # asin + assert manualintegrate(asin(x), x) == x*asin(x) + sqrt(1 - x**2) + assert manualintegrate(asin(a*x), x) == Piecewise(((a*x*asin(a*x) + sqrt(-a**2*x**2 + 1))/a, Ne(a, 0)), (0, True)) + assert manualintegrate(x*asin(a*x), x) == \ + -a*Piecewise((-x*sqrt(-a**2*x**2 + 1)/(2*a**2) + + log(-2*a**2*x + 2*sqrt(-a**2)*sqrt(-a**2*x**2 + 1))/(2*a**2*sqrt(-a**2)), Ne(a**2, 0)), + (x**3/3, True))/2 + x**2*asin(a*x)/2 + # acos + assert manualintegrate(acos(x), x) == x*acos(x) - sqrt(1 - x**2) + assert manualintegrate(acos(a*x), x) == Piecewise(((a*x*acos(a*x) - sqrt(-a**2*x**2 + 1))/a, Ne(a, 0)), (pi*x/2, True)) + assert manualintegrate(x*acos(a*x), x) == \ + a*Piecewise((-x*sqrt(-a**2*x**2 + 1)/(2*a**2) + + log(-2*a**2*x + 2*sqrt(-a**2)*sqrt(-a**2*x**2 + 1))/(2*a**2*sqrt(-a**2)), Ne(a**2, 0)), + (x**3/3, True))/2 + x**2*acos(a*x)/2 + # atan + assert manualintegrate(atan(x), x) == x*atan(x) - log(x**2 + 1)/2 + assert manualintegrate(atan(a*x), x) == Piecewise(((a*x*atan(a*x) - log(a**2*x**2 + 1)/2)/a, Ne(a, 0)), (0, True)) + assert manualintegrate(x*atan(a*x), x) == -a*(x/a**2 - atan(x/sqrt(a**(-2)))/(a**4*sqrt(a**(-2))))/2 + x**2*atan(a*x)/2 + # acsc + assert manualintegrate(acsc(x), x) == x*acsc(x) + Integral(1/(x*sqrt(1 - 1/x**2)), x) + assert manualintegrate(acsc(a*x), x) == x*acsc(a*x) + Integral(1/(x*sqrt(1 - 1/(a**2*x**2))), x)/a + assert manualintegrate(x*acsc(a*x), x) == x**2*acsc(a*x)/2 + Integral(1/sqrt(1 - 1/(a**2*x**2)), x)/(2*a) + # asec + assert manualintegrate(asec(x), x) == x*asec(x) - Integral(1/(x*sqrt(1 - 1/x**2)), x) + assert manualintegrate(asec(a*x), x) == x*asec(a*x) - Integral(1/(x*sqrt(1 - 1/(a**2*x**2))), x)/a + assert manualintegrate(x*asec(a*x), x) == x**2*asec(a*x)/2 - Integral(1/sqrt(1 - 1/(a**2*x**2)), x)/(2*a) + # acot + assert manualintegrate(acot(x), x) == x*acot(x) + log(x**2 + 1)/2 + assert manualintegrate(acot(a*x), x) == Piecewise(((a*x*acot(a*x) + log(a**2*x**2 + 1)/2)/a, Ne(a, 0)), (pi*x/2, True)) + assert manualintegrate(x*acot(a*x), x) == a*(x/a**2 - atan(x/sqrt(a**(-2)))/(a**4*sqrt(a**(-2))))/2 + x**2*acot(a*x)/2 + + # piecewise + assert manualintegrate(1/sqrt(ra-rb*x**2), x) == \ + Piecewise((asin(x*sqrt(rb/ra))/sqrt(rb), And(-rb < 0, ra > 0)), + (asinh(x*sqrt(-rb/ra))/sqrt(-rb), And(-rb > 0, ra > 0)), + (log(-2*rb*x + 2*sqrt(-rb)*sqrt(ra - rb*x**2))/sqrt(-rb), Ne(rb, 0)), + (x/sqrt(ra), True)) + assert manualintegrate(1/sqrt(ra + rb*x**2), x) == \ + Piecewise((asin(x*sqrt(-rb/ra))/sqrt(-rb), And(ra > 0, rb < 0)), + (asinh(x*sqrt(rb/ra))/sqrt(rb), And(ra > 0, rb > 0)), + (log(2*sqrt(rb)*sqrt(ra + rb*x**2) + 2*rb*x)/sqrt(rb), Ne(rb, 0)), + (x/sqrt(ra), True)) + + +def test_manualintegrate_trig_substitution(): + assert manualintegrate(sqrt(16*x**2 - 9)/x, x) == \ + Piecewise((sqrt(16*x**2 - 9) - 3*acos(3/(4*x)), + And(x < Rational(3, 4), x > Rational(-3, 4)))) + assert manualintegrate(1/(x**4 * sqrt(25-x**2)), x) == \ + Piecewise((-sqrt(-x**2/25 + 1)/(125*x) - + (-x**2/25 + 1)**(3*S.Half)/(15*x**3), And(x < 5, x > -5))) + assert manualintegrate(x**7/(49*x**2 + 1)**(3 * S.Half), x) == \ + ((49*x**2 + 1)**(5*S.Half)/28824005 - + (49*x**2 + 1)**(3*S.Half)/5764801 + + 3*sqrt(49*x**2 + 1)/5764801 + 1/(5764801*sqrt(49*x**2 + 1))) + +def test_manualintegrate_trivial_substitution(): + assert manualintegrate((exp(x) - exp(-x))/x, x) == -Ei(-x) + Ei(x) + f = Function('f') + assert manualintegrate((f(x) - f(-x))/x, x) == \ + -Integral(f(-x)/x, x) + Integral(f(x)/x, x) + + +def test_manualintegrate_rational(): + assert manualintegrate(1/(4 - x**2), x) == -log(x - 2)/4 + log(x + 2)/4 + assert manualintegrate(1/(-1 + x**2), x) == log(x - 1)/2 - log(x + 1)/2 + + +def test_manualintegrate_special(): + f, F = 4*exp(-x**2/3), 2*sqrt(3)*sqrt(pi)*erf(sqrt(3)*x/3) + assert_is_integral_of(f, F) + f, F = 3*exp(4*x**2), 3*sqrt(pi)*erfi(2*x)/4 + assert_is_integral_of(f, F) + f, F = x**Rational(1, 3)*exp(-x/8), -16*uppergamma(Rational(4, 3), x/8) + assert_is_integral_of(f, F) + f, F = exp(2*x)/x, Ei(2*x) + assert_is_integral_of(f, F) + f, F = exp(1 + 2*x - x**2), sqrt(pi)*exp(2)*erf(x - 1)/2 + assert_is_integral_of(f, F) + f = sin(x**2 + 4*x + 1) + F = (sqrt(2)*sqrt(pi)*(-sin(3)*fresnelc(sqrt(2)*(2*x + 4)/(2*sqrt(pi))) + + cos(3)*fresnels(sqrt(2)*(2*x + 4)/(2*sqrt(pi))))/2) + assert_is_integral_of(f, F) + f, F = cos(4*x**2), sqrt(2)*sqrt(pi)*fresnelc(2*sqrt(2)*x/sqrt(pi))/4 + assert_is_integral_of(f, F) + f, F = sin(3*x + 2)/x, sin(2)*Ci(3*x) + cos(2)*Si(3*x) + assert_is_integral_of(f, F) + f, F = sinh(3*x - 2)/x, -sinh(2)*Chi(3*x) + cosh(2)*Shi(3*x) + assert_is_integral_of(f, F) + f, F = 5*cos(2*x - 3)/x, 5*cos(3)*Ci(2*x) + 5*sin(3)*Si(2*x) + assert_is_integral_of(f, F) + f, F = cosh(x/2)/x, Chi(x/2) + assert_is_integral_of(f, F) + f, F = cos(x**2)/x, Ci(x**2)/2 + assert_is_integral_of(f, F) + f, F = 1/log(2*x + 1), li(2*x + 1)/2 + assert_is_integral_of(f, F) + f, F = polylog(2, 5*x)/x, polylog(3, 5*x) + assert_is_integral_of(f, F) + f, F = 5/sqrt(3 - 2*sin(x)**2), 5*sqrt(3)*elliptic_f(x, Rational(2, 3))/3 + assert_is_integral_of(f, F) + f, F = sqrt(4 + 9*sin(x)**2), 2*elliptic_e(x, Rational(-9, 4)) + assert_is_integral_of(f, F) + + +def test_manualintegrate_derivative(): + assert manualintegrate(pi * Derivative(x**2 + 2*x + 3), x) == \ + pi * (x**2 + 2*x + 3) + assert manualintegrate(Derivative(x**2 + 2*x + 3, y), x) == \ + Integral(Derivative(x**2 + 2*x + 3, y)) + assert manualintegrate(Derivative(sin(x), x, x, x, y), x) == \ + Derivative(sin(x), x, x, y) + + +def test_manualintegrate_Heaviside(): + assert_is_integral_of(DiracDelta(3*x+2), Heaviside(3*x+2)/3) + assert_is_integral_of(DiracDelta(3*x, 0), Heaviside(3*x)/3) + assert manualintegrate(DiracDelta(a+b*x, 1), x) == \ + Piecewise((DiracDelta(a + b*x)/b, Ne(b, 0)), (x*DiracDelta(a, 1), True)) + assert_is_integral_of(DiracDelta(x/3-1, 2), 3*DiracDelta(x/3-1, 1)) + assert manualintegrate(Heaviside(x), x) == x*Heaviside(x) + assert manualintegrate(x*Heaviside(2), x) == x**2/2 + assert manualintegrate(x*Heaviside(-2), x) == 0 + assert manualintegrate(x*Heaviside( x), x) == x**2*Heaviside( x)/2 + assert manualintegrate(x*Heaviside(-x), x) == x**2*Heaviside(-x)/2 + assert manualintegrate(Heaviside(2*x + 4), x) == (x+2)*Heaviside(2*x + 4) + assert manualintegrate(x*Heaviside(x), x) == x**2*Heaviside(x)/2 + assert manualintegrate(Heaviside(x + 1)*Heaviside(1 - x)*x**2, x) == \ + ((x**3/3 + Rational(1, 3))*Heaviside(x + 1) - Rational(2, 3))*Heaviside(-x + 1) + + y = Symbol('y') + assert manualintegrate(sin(7 + x)*Heaviside(3*x - 7), x) == \ + (- cos(x + 7) + cos(Rational(28, 3)))*Heaviside(3*x - S(7)) + + assert manualintegrate(sin(y + x)*Heaviside(3*x - y), x) == \ + (cos(y*Rational(4, 3)) - cos(x + y))*Heaviside(3*x - y) + + +def test_manualintegrate_orthogonal_poly(): + n = symbols('n') + a, b = 7, Rational(5, 3) + polys = [jacobi(n, a, b, x), gegenbauer(n, a, x), chebyshevt(n, x), + chebyshevu(n, x), legendre(n, x), hermite(n, x), laguerre(n, x), + assoc_laguerre(n, a, x)] + for p in polys: + integral = manualintegrate(p, x) + for deg in [-2, -1, 0, 1, 3, 5, 8]: + # some accept negative "degree", some do not + try: + p_subbed = p.subs(n, deg) + except ValueError: + continue + assert (integral.subs(n, deg).diff(x) - p_subbed).expand() == 0 + + # can also integrate simple expressions with these polynomials + q = x*p.subs(x, 2*x + 1) + integral = manualintegrate(q, x) + for deg in [2, 4, 7]: + assert (integral.subs(n, deg).diff(x) - q.subs(n, deg)).expand() == 0 + + # cannot integrate with respect to any other parameter + t = symbols('t') + for i in range(len(p.args) - 1): + new_args = list(p.args) + new_args[i] = t + assert isinstance(manualintegrate(p.func(*new_args), t), Integral) + + +@slow +def test_issue_6799(): + r, x, phi = map(Symbol, 'r x phi'.split()) + n = Symbol('n', integer=True, positive=True) + + integrand = (cos(n*(x-phi))*cos(n*x)) + limits = (x, -pi, pi) + assert manualintegrate(integrand, x) == \ + ((n*x/2 + sin(2*n*x)/4)*cos(n*phi) - sin(n*phi)*cos(n*x)**2/2)/n + assert r * integrate(integrand, limits).trigsimp() / pi == r * cos(n * phi) + assert not integrate(integrand, limits).has(Dummy) + + +def test_issue_12251(): + assert manualintegrate(x**y, x) == Piecewise( + (x**(y + 1)/(y + 1), Ne(y, -1)), (log(x), True)) + + +def test_issue_3796(): + assert manualintegrate(diff(exp(x + x**2)), x) == exp(x + x**2) + assert integrate(x * exp(x**4), x, risch=False) == -I*sqrt(pi)*erf(I*x**2)/4 + + +def test_manual_true(): + assert integrate(exp(x) * sin(x), x, manual=True) == \ + (exp(x) * sin(x)) / 2 - (exp(x) * cos(x)) / 2 + assert integrate(sin(x) * cos(x), x, manual=True) in \ + [sin(x) ** 2 / 2, -cos(x)**2 / 2] + + +def test_issue_6746(): + y = Symbol('y') + n = Symbol('n') + assert manualintegrate(y**x, x) == Piecewise( + (y**x/log(y), Ne(log(y), 0)), (x, True)) + assert manualintegrate(y**(n*x), x) == Piecewise( + (Piecewise( + (y**(n*x)/log(y), Ne(log(y), 0)), + (n*x, True) + )/n, Ne(n, 0)), + (x, True)) + assert manualintegrate(exp(n*x), x) == Piecewise( + (exp(n*x)/n, Ne(n, 0)), (x, True)) + + y = Symbol('y', positive=True) + assert manualintegrate((y + 1)**x, x) == (y + 1)**x/log(y + 1) + y = Symbol('y', zero=True) + assert manualintegrate((y + 1)**x, x) == x + y = Symbol('y') + n = Symbol('n', nonzero=True) + assert manualintegrate(y**(n*x), x) == Piecewise( + (y**(n*x)/log(y), Ne(log(y), 0)), (n*x, True))/n + y = Symbol('y', positive=True) + assert manualintegrate((y + 1)**(n*x), x) == \ + (y + 1)**(n*x)/(n*log(y + 1)) + a = Symbol('a', negative=True) + b = Symbol('b') + assert manualintegrate(1/(a + b*x**2), x) == atan(x/sqrt(a/b))/(b*sqrt(a/b)) + b = Symbol('b', negative=True) + assert manualintegrate(1/(a + b*x**2), x) == \ + atan(x/(sqrt(-a)*sqrt(-1/b)))/(b*sqrt(-a)*sqrt(-1/b)) + assert manualintegrate(1/((x**a + y**b + 4)*sqrt(a*x**2 + 1)), x) == \ + y**(-b)*Integral(x**(-a)/(y**(-b)*sqrt(a*x**2 + 1) + + x**(-a)*sqrt(a*x**2 + 1) + 4*x**(-a)*y**(-b)*sqrt(a*x**2 + 1)), x) + assert manualintegrate(1/((x**2 + 4)*sqrt(4*x**2 + 1)), x) == \ + Integral(1/((x**2 + 4)*sqrt(4*x**2 + 1)), x) + assert manualintegrate(1/(x - a**x + x*b**2), x) == \ + Integral(1/(-a**x + b**2*x + x), x) + + +@slow +def test_issue_2850(): + assert manualintegrate(asin(x)*log(x), x) == -x*asin(x) - sqrt(-x**2 + 1) \ + + (x*asin(x) + sqrt(-x**2 + 1))*log(x) - Integral(sqrt(-x**2 + 1)/x, x) + assert manualintegrate(acos(x)*log(x), x) == -x*acos(x) + sqrt(-x**2 + 1) + \ + (x*acos(x) - sqrt(-x**2 + 1))*log(x) + Integral(sqrt(-x**2 + 1)/x, x) + assert manualintegrate(atan(x)*log(x), x) == -x*atan(x) + (x*atan(x) - \ + log(x**2 + 1)/2)*log(x) + log(x**2 + 1)/2 + Integral(log(x**2 + 1)/x, x)/2 + + +def test_issue_9462(): + assert manualintegrate(sin(2*x)*exp(x), x) == exp(x)*sin(2*x)/5 - 2*exp(x)*cos(2*x)/5 + assert not integral_steps(sin(2*x)*exp(x), x).contains_dont_know() + assert manualintegrate((x - 3) / (x**2 - 2*x + 2)**2, x) == \ + Integral(x/(x**4 - 4*x**3 + 8*x**2 - 8*x + 4), x) \ + - 3*Integral(1/(x**4 - 4*x**3 + 8*x**2 - 8*x + 4), x) + + +def test_cyclic_parts(): + f = cos(x)*exp(x/4) + F = 16*exp(x/4)*sin(x)/17 + 4*exp(x/4)*cos(x)/17 + assert manualintegrate(f, x) == F and F.diff(x) == f + f = x*cos(x)*exp(x/4) + F = (x*(16*exp(x/4)*sin(x)/17 + 4*exp(x/4)*cos(x)/17) - + 128*exp(x/4)*sin(x)/289 + 240*exp(x/4)*cos(x)/289) + assert manualintegrate(f, x) == F and F.diff(x) == f + + +@slow +def test_issue_10847_slow(): + assert manualintegrate((4*x**4 + 4*x**3 + 16*x**2 + 12*x + 8) + / (x**6 + 2*x**5 + 3*x**4 + 4*x**3 + 3*x**2 + 2*x + 1), x) == \ + 2*x/(x**2 + 1) + 3*atan(x) - 1/(x**2 + 1) - 3/(x + 1) + + +@slow +def test_issue_10847(): + + assert manualintegrate(x**2 / (x**2 - c), x) == \ + c*Piecewise((atan(x/sqrt(-c))/sqrt(-c), Ne(c, 0)), (-1/x, True)) + x + + rc = Symbol('c', real=True) + assert manualintegrate(x**2 / (x**2 - rc), x) == \ + rc*Piecewise((atan(x/sqrt(-rc))/sqrt(-rc), rc < 0), + ((log(-sqrt(rc) + x) - log(sqrt(rc) + x))/(2*sqrt(rc)), True)) + x + + assert manualintegrate(sqrt(x - y) * log(z / x), x) == \ + 4*y**2*Piecewise((atan(sqrt(x - y)/sqrt(y))/sqrt(y), Ne(y, 0)), + (-1/sqrt(x - y), True))/3 - 4*y*sqrt(x - y)/3 + \ + 2*(x - y)**Rational(3, 2)*log(z/x)/3 + 4*(x - y)**Rational(3, 2)/9 + ry = Symbol('y', real=True) + rz = Symbol('z', real=True) + assert manualintegrate(sqrt(x - ry) * log(rz / x), x) == \ + 4*ry**2*Piecewise((atan(sqrt(x - ry)/sqrt(ry))/sqrt(ry), ry > 0), + ((log(-sqrt(-ry) + sqrt(x - ry)) - log(sqrt(-ry) + sqrt(x - ry)))/(2*sqrt(-ry)), True))/3 \ + - 4*ry*sqrt(x - ry)/3 + 2*(x - ry)**Rational(3, 2)*log(rz/x)/3 \ + + 4*(x - ry)**Rational(3, 2)/9 + + assert manualintegrate(sqrt(x) * log(x), x) == 2*x**Rational(3, 2)*log(x)/3 - 4*x**Rational(3, 2)/9 + + result = manualintegrate(sqrt(a*x + b) / x, x) + assert result == Piecewise((-2*b*Piecewise( + (-atan(sqrt(a*x + b)/sqrt(-b))/sqrt(-b), Ne(b, 0)), + (1/sqrt(a*x + b), True)) + 2*sqrt(a*x + b), Ne(a, 0)), + (sqrt(b)*log(x), True)) + assert piecewise_fold(result) == Piecewise( + (2*b*atan(sqrt(a*x + b)/sqrt(-b))/sqrt(-b) + 2*sqrt(a*x + b), Ne(a, 0) & Ne(b, 0)), + (-2*b/sqrt(a*x + b) + 2*sqrt(a*x + b), Ne(a, 0)), + (sqrt(b)*log(x), True)) + + ra = Symbol('a', real=True) + rb = Symbol('b', real=True) + assert manualintegrate(sqrt(ra*x + rb) / x, x) == \ + Piecewise( + (-2*rb*Piecewise( + (-atan(sqrt(ra*x + rb)/sqrt(-rb))/sqrt(-rb), rb < 0), + (-I*(log(-sqrt(rb) + sqrt(ra*x + rb)) - log(sqrt(rb) + sqrt(ra*x + rb)))/(2*sqrt(-rb)), True)) + + 2*sqrt(ra*x + rb), Ne(ra, 0)), + (sqrt(rb)*log(x), True)) + + assert expand(manualintegrate(sqrt(ra*x + rb) / (x + rc), x)) == \ + Piecewise((-2*ra*rc*Piecewise((atan(sqrt(ra*x + rb)/sqrt(ra*rc - rb))/sqrt(ra*rc - rb), ra*rc - rb > 0), + (log(-sqrt(-ra*rc + rb) + sqrt(ra*x + rb))/(2*sqrt(-ra*rc + rb)) - + log(sqrt(-ra*rc + rb) + sqrt(ra*x + rb))/(2*sqrt(-ra*rc + rb)), True)) + + 2*rb*Piecewise((atan(sqrt(ra*x + rb)/sqrt(ra*rc - rb))/sqrt(ra*rc - rb), ra*rc - rb > 0), + (log(-sqrt(-ra*rc + rb) + sqrt(ra*x + rb))/(2*sqrt(-ra*rc + rb)) - + log(sqrt(-ra*rc + rb) + sqrt(ra*x + rb))/(2*sqrt(-ra*rc + rb)), True)) + + 2*sqrt(ra*x + rb), Ne(ra, 0)), (sqrt(rb)*log(rc + x), True)) + + assert manualintegrate(sqrt(2*x + 3) / (x + 1), x) == 2*sqrt(2*x + 3) - log(sqrt(2*x + 3) + 1) + log(sqrt(2*x + 3) - 1) + assert manualintegrate(sqrt(2*x + 3) / 2 * x, x) == (2*x + 3)**Rational(5, 2)/20 - (2*x + 3)**Rational(3, 2)/4 + assert manualintegrate(x**Rational(3,2) * log(x), x) == 2*x**Rational(5,2)*log(x)/5 - 4*x**Rational(5,2)/25 + assert manualintegrate(x**(-3) * log(x), x) == -log(x)/(2*x**2) - 1/(4*x**2) + assert manualintegrate(log(y)/(y**2*(1 - 1/y)), y) == \ + log(y)*log(-1 + 1/y) - Integral(log(-1 + 1/y)/y, y) + + +def test_issue_12899(): + assert manualintegrate(f(x,y).diff(x),y) == Integral(Derivative(f(x,y),x),y) + assert manualintegrate(f(x,y).diff(y).diff(x),y) == Derivative(f(x,y),x) + + +def test_constant_independent_of_symbol(): + assert manualintegrate(Integral(y, (x, 1, 2)), x) == \ + x*Integral(y, (x, 1, 2)) + + +def test_issue_12641(): + assert manualintegrate(sin(2*x), x) == -cos(2*x)/2 + assert manualintegrate(cos(x)*sin(2*x), x) == -2*cos(x)**3/3 + assert manualintegrate((sin(2*x)*cos(x))/(1 + cos(x)), x) == \ + -2*log(cos(x) + 1) - cos(x)**2 + 2*cos(x) + + +@slow +def test_issue_13297(): + assert manualintegrate(sin(x) * cos(x)**5, x) == -cos(x)**6 / 6 + + +def test_issue_14470(): + assert_is_integral_of(1/(x*sqrt(x + 1)), log(sqrt(x + 1) - 1) - log(sqrt(x + 1) + 1)) + + +@slow +def test_issue_9858(): + assert manualintegrate(exp(x)*cos(exp(x)), x) == sin(exp(x)) + assert manualintegrate(exp(2*x)*cos(exp(x)), x) == \ + exp(x)*sin(exp(x)) + cos(exp(x)) + res = manualintegrate(exp(10*x)*sin(exp(x)), x) + assert not res.has(Integral) + assert res.diff(x) == exp(10*x)*sin(exp(x)) + # an example with many similar integrations by parts + assert manualintegrate(sum(x*exp(k*x) for k in range(1, 8)), x) == ( + x*exp(7*x)/7 + x*exp(6*x)/6 + x*exp(5*x)/5 + x*exp(4*x)/4 + + x*exp(3*x)/3 + x*exp(2*x)/2 + x*exp(x) - exp(7*x)/49 -exp(6*x)/36 - + exp(5*x)/25 - exp(4*x)/16 - exp(3*x)/9 - exp(2*x)/4 - exp(x)) + + +def test_issue_8520(): + assert manualintegrate(x/(x**4 + 1), x) == atan(x**2)/2 + assert manualintegrate(x**2/(x**6 + 25), x) == atan(x**3/5)/15 + f = x/(9*x**4 + 4)**2 + assert manualintegrate(f, x).diff(x).factor() == f + + +def test_manual_subs(): + x, y = symbols('x y') + expr = log(x) + exp(x) + # if log(x) is y, then exp(y) is x + assert manual_subs(expr, log(x), y) == y + exp(exp(y)) + # if exp(x) is y, then log(y) need not be x + assert manual_subs(expr, exp(x), y) == log(x) + y + + raises(ValueError, lambda: manual_subs(expr, x)) + raises(ValueError, lambda: manual_subs(expr, exp(x), x, y)) + + +@slow +def test_issue_15471(): + f = log(x)*cos(log(x))/x**Rational(3, 4) + F = -128*x**Rational(1, 4)*sin(log(x))/289 + 240*x**Rational(1, 4)*cos(log(x))/289 + (16*x**Rational(1, 4)*sin(log(x))/17 + 4*x**Rational(1, 4)*cos(log(x))/17)*log(x) + assert_is_integral_of(f, F) + + +def test_quadratic_denom(): + f = (5*x + 2)/(3*x**2 - 2*x + 8) + assert manualintegrate(f, x) == 5*log(3*x**2 - 2*x + 8)/6 + 11*sqrt(23)*atan(3*sqrt(23)*(x - Rational(1, 3))/23)/69 + g = 3/(2*x**2 + 3*x + 1) + assert manualintegrate(g, x) == 3*log(4*x + 2) - 3*log(4*x + 4) + +def test_issue_22757(): + assert manualintegrate(sin(x), y) == y * sin(x) + + +def test_issue_23348(): + steps = integral_steps(tan(x), x) + constant_times_step = steps.substep.substep + assert constant_times_step.integrand == constant_times_step.constant * constant_times_step.other + + +def test_issue_23566(): + i = Integral(1/sqrt(x**2 - 1), (x, -2, -1)).doit(manual=True) + assert i == -log(4 - 2*sqrt(3)) + log(2) + assert str(i.n()) == '1.31695789692482' + + +def test_issue_25093(): + ap = Symbol('ap', positive=True) + an = Symbol('an', negative=True) + assert manualintegrate(exp(a*x**2 + b), x) == sqrt(pi)*exp(b)*erfi(sqrt(a)*x)/(2*sqrt(a)) + assert manualintegrate(exp(ap*x**2 + b), x) == sqrt(pi)*exp(b)*erfi(sqrt(ap)*x)/(2*sqrt(ap)) + assert manualintegrate(exp(an*x**2 + b), x) == -sqrt(pi)*exp(b)*erf(an*x/sqrt(-an))/(2*sqrt(-an)) + assert manualintegrate(sin(a*x**2 + b), x) == ( + sqrt(2)*sqrt(pi)*(sin(b)*fresnelc(sqrt(2)*sqrt(a)*x/sqrt(pi)) + + cos(b)*fresnels(sqrt(2)*sqrt(a)*x/sqrt(pi)))/(2*sqrt(a))) + assert manualintegrate(cos(a*x**2 + b), x) == ( + sqrt(2)*sqrt(pi)*(-sin(b)*fresnels(sqrt(2)*sqrt(a)*x/sqrt(pi)) + + cos(b)*fresnelc(sqrt(2)*sqrt(a)*x/sqrt(pi)))/(2*sqrt(a))) + + +def test_nested_pow(): + assert_is_integral_of(sqrt(x**2), x*sqrt(x**2)/2) + assert_is_integral_of(sqrt(x**(S(5)/3)), 6*x*sqrt(x**(S(5)/3))/11) + assert_is_integral_of(1/sqrt(x**2), x*log(x)/sqrt(x**2)) + assert_is_integral_of(x*sqrt(x**(-4)), x**2*sqrt(x**-4)*log(x)) + f = (c*(a+b*x)**d)**e + F1 = (c*(a + b*x)**d)**e*(a/b + x)/(d*e + 1) + F2 = (c*(a + b*x)**d)**e*(a/b + x)*log(a/b + x) + assert manualintegrate(f, x) == \ + Piecewise((Piecewise((F1, Ne(d*e, -1)), (F2, True)), Ne(b, 0)), (x*(a**d*c)**e, True)) + assert F1.diff(x).equals(f) + assert F2.diff(x).subs(d*e, -1).equals(f) + + +def test_manualintegrate_sqrt_linear(): + assert_is_integral_of((5*x**3+4)/sqrt(2+3*x), + 10*(3*x + 2)**(S(7)/2)/567 - 4*(3*x + 2)**(S(5)/2)/27 + + 40*(3*x + 2)**(S(3)/2)/81 + 136*sqrt(3*x + 2)/81) + assert manualintegrate(x/sqrt(a+b*x)**3, x) == \ + Piecewise((Mul(2, b**-2, a/sqrt(a + b*x) + sqrt(a + b*x)), Ne(b, 0)), (x**2/(2*a**(S(3)/2)), True)) + assert_is_integral_of((sqrt(3*x+3)+1)/((2*x+2)**(1/S(3))+1), + 3*sqrt(6)*(2*x + 2)**(S(7)/6)/14 - 3*sqrt(6)*(2*x + 2)**(S(5)/6)/10 - + 3*sqrt(6)*(2*x + 2)**(S.One/6)/2 + 3*(2*x + 2)**(S(2)/3)/4 - 3*(2*x + 2)**(S.One/3)/2 + + sqrt(6)*sqrt(2*x + 2)/2 + 3*log((2*x + 2)**(S.One/3) + 1)/2 + + 3*sqrt(6)*atan((2*x + 2)**(S.One/6))/2) + assert_is_integral_of(sqrt(x+sqrt(x)), + 2*sqrt(sqrt(x) + x)*(sqrt(x)/12 + x/3 - S(1)/8) + log(2*sqrt(x) + 2*sqrt(sqrt(x) + x) + 1)/8) + assert_is_integral_of(sqrt(2*x+3+sqrt(4*x+5))**3, + sqrt(2*x + sqrt(4*x + 5) + 3) * + (9*x/10 + 11*(4*x + 5)**(S(3)/2)/40 + sqrt(4*x + 5)/40 + (4*x + 5)**2/10 + S(11)/10)/2) + + +def test_manualintegrate_sqrt_quadratic(): + assert_is_integral_of(1/sqrt((x - I)**2-1), log(2*x + 2*sqrt(x**2 - 2*I*x - 2) - 2*I)) + assert_is_integral_of(1/sqrt(3*x**2+4*x+5), sqrt(3)*asinh(3*sqrt(11)*(x + S(2)/3)/11)/3) + assert_is_integral_of(1/sqrt(-3*x**2+4*x+5), sqrt(3)*asin(3*sqrt(19)*(x - S(2)/3)/19)/3) + assert_is_integral_of(1/sqrt(3*x**2+4*x-5), sqrt(3)*log(6*x + 2*sqrt(3)*sqrt(3*x**2 + 4*x - 5) + 4)/3) + assert_is_integral_of(1/sqrt(4*x**2-4*x+1), (x - S.Half)*log(x - S.Half)/(2*sqrt((x - S.Half)**2))) + assert manualintegrate(1/sqrt(a+b*x+c*x**2), x) == \ + Piecewise((log(b + 2*sqrt(c)*sqrt(a + b*x + c*x**2) + 2*c*x)/sqrt(c), Ne(c, 0) & Ne(a - b**2/(4*c), 0)), + ((b/(2*c) + x)*log(b/(2*c) + x)/sqrt(c*(b/(2*c) + x)**2), Ne(c, 0)), + (2*sqrt(a + b*x)/b, Ne(b, 0)), (x/sqrt(a), True)) + + assert_is_integral_of((7*x+6)/sqrt(3*x**2+4*x+5), + 7*sqrt(3*x**2 + 4*x + 5)/3 + 4*sqrt(3)*asinh(3*sqrt(11)*(x + S(2)/3)/11)/9) + assert_is_integral_of((7*x+6)/sqrt(-3*x**2+4*x+5), + -7*sqrt(-3*x**2 + 4*x + 5)/3 + 32*sqrt(3)*asin(3*sqrt(19)*(x - S(2)/3)/19)/9) + assert_is_integral_of((7*x+6)/sqrt(3*x**2+4*x-5), + 7*sqrt(3*x**2 + 4*x - 5)/3 + 4*sqrt(3)*log(6*x + 2*sqrt(3)*sqrt(3*x**2 + 4*x - 5) + 4)/9) + assert manualintegrate((d+e*x)/sqrt(a+b*x+c*x**2), x) == \ + Piecewise(((-b*e/(2*c) + d) * + Piecewise((log(b + 2*sqrt(c)*sqrt(a + b*x + c*x**2) + 2*c*x)/sqrt(c), Ne(a - b**2/(4*c), 0)), + ((b/(2*c) + x)*log(b/(2*c) + x)/sqrt(c*(b/(2*c) + x)**2), True)) + + e*sqrt(a + b*x + c*x**2)/c, Ne(c, 0)), + ((2*d*sqrt(a + b*x) + 2*e*(-a*sqrt(a + b*x) + (a + b*x)**(S(3)/2)/3)/b)/b, Ne(b, 0)), + ((d*x + e*x**2/2)/sqrt(a), True)) + + assert manualintegrate((3*x**3-x**2+2*x-4)/sqrt(x**2-3*x+2), x) == \ + sqrt(x**2 - 3*x + 2)*(x**2 + 13*x/4 + S(101)/8) + 135*log(2*x + 2*sqrt(x**2 - 3*x + 2) - 3)/16 + + assert_is_integral_of(sqrt(53225*x**2-66732*x+23013), + (x/2 - S(16683)/53225)*sqrt(53225*x**2 - 66732*x + 23013) + + 111576969*sqrt(2129)*asinh(53225*x/10563 - S(11122)/3521)/1133160250) + assert manualintegrate(sqrt(a+c*x**2), x) == \ + Piecewise((a*Piecewise((log(2*sqrt(c)*sqrt(a + c*x**2) + 2*c*x)/sqrt(c), Ne(a, 0)), + (x*log(x)/sqrt(c*x**2), True))/2 + x*sqrt(a + c*x**2)/2, Ne(c, 0)), + (sqrt(a)*x, True)) + assert manualintegrate(sqrt(a+b*x+c*x**2), x) == \ + Piecewise(((a/2 - b**2/(8*c)) * + Piecewise((log(b + 2*sqrt(c)*sqrt(a + b*x + c*x**2) + 2*c*x)/sqrt(c), Ne(a - b**2/(4*c), 0)), + ((b/(2*c) + x)*log(b/(2*c) + x)/sqrt(c*(b/(2*c) + x)**2), True)) + + (b/(4*c) + x/2)*sqrt(a + b*x + c*x**2), Ne(c, 0)), + (2*(a + b*x)**(S(3)/2)/(3*b), Ne(b, 0)), + (sqrt(a)*x, True)) + + assert_is_integral_of(x*sqrt(x**2+2*x+4), + (x**2/3 + x/6 + S(5)/6)*sqrt(x**2 + 2*x + 4) - 3*asinh(sqrt(3)*(x + 1)/3)/2) + + +def test_mul_pow_derivative(): + assert_is_integral_of(x*sec(x)*tan(x), x*sec(x) - log(tan(x) + sec(x))) + assert_is_integral_of(x*sec(x)**2, x*tan(x) + log(cos(x))) + assert_is_integral_of(x**3*Derivative(f(x), (x, 4)), + x**3*Derivative(f(x), (x, 3)) - 3*x**2*Derivative(f(x), (x, 2)) + + 6*x*Derivative(f(x), x) - 6*f(x)) diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_prde.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_prde.py new file mode 100644 index 0000000000000000000000000000000000000000..a7429ea8634c742eb77cdb26f99b2cb15853cd42 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_prde.py @@ -0,0 +1,322 @@ +"""Most of these tests come from the examples in Bronstein's book.""" +from sympy.integrals.risch import DifferentialExtension, derivation +from sympy.integrals.prde import (prde_normal_denom, prde_special_denom, + prde_linear_constraints, constant_system, prde_spde, prde_no_cancel_b_large, + prde_no_cancel_b_small, limited_integrate_reduce, limited_integrate, + is_deriv_k, is_log_deriv_k_t_radical, parametric_log_deriv_heu, + is_log_deriv_k_t_radical_in_field, param_poly_rischDE, param_rischDE, + prde_cancel_liouvillian) + +from sympy.polys.polymatrix import PolyMatrix as Matrix + +from sympy.core.numbers import Rational +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.polys.domains.rationalfield import QQ +from sympy.polys.polytools import Poly +from sympy.abc import x, t, n + +t0, t1, t2, t3, k = symbols('t:4 k') + + +def test_prde_normal_denom(): + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 + t**2, t)]}) + fa = Poly(1, t) + fd = Poly(x, t) + G = [(Poly(t, t), Poly(1 + t**2, t)), (Poly(1, t), Poly(x + x*t**2, t))] + assert prde_normal_denom(fa, fd, G, DE) == \ + (Poly(x, t, domain='ZZ(x)'), (Poly(1, t, domain='ZZ(x)'), Poly(1, t, + domain='ZZ(x)')), [(Poly(x*t, t, domain='ZZ(x)'), + Poly(t**2 + 1, t, domain='ZZ(x)')), (Poly(1, t, domain='ZZ(x)'), + Poly(t**2 + 1, t, domain='ZZ(x)'))], Poly(1, t, domain='ZZ(x)')) + G = [(Poly(t, t), Poly(t**2 + 2*t + 1, t)), (Poly(x*t, t), + Poly(t**2 + 2*t + 1, t)), (Poly(x*t**2, t), Poly(t**2 + 2*t + 1, t))] + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + assert prde_normal_denom(Poly(x, t), Poly(1, t), G, DE) == \ + (Poly(t + 1, t), (Poly((-1 + x)*t + x, t), Poly(1, t, domain='ZZ[x]')), [(Poly(t, t), + Poly(1, t)), (Poly(x*t, t), Poly(1, t, domain='ZZ[x]')), (Poly(x*t**2, t), + Poly(1, t, domain='ZZ[x]'))], Poly(t + 1, t)) + + +def test_prde_special_denom(): + a = Poly(t + 1, t) + ba = Poly(t**2, t) + bd = Poly(1, t) + G = [(Poly(t, t), Poly(1, t)), (Poly(t**2, t), Poly(1, t)), (Poly(t**3, t), Poly(1, t))] + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + assert prde_special_denom(a, ba, bd, G, DE) == \ + (Poly(t + 1, t), Poly(t**2, t), [(Poly(t, t), Poly(1, t)), + (Poly(t**2, t), Poly(1, t)), (Poly(t**3, t), Poly(1, t))], Poly(1, t)) + G = [(Poly(t, t), Poly(1, t)), (Poly(1, t), Poly(t, t))] + assert prde_special_denom(Poly(1, t), Poly(t**2, t), Poly(1, t), G, DE) == \ + (Poly(1, t), Poly(t**2 - 1, t), [(Poly(t**2, t), Poly(1, t)), + (Poly(1, t), Poly(1, t))], Poly(t, t)) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-2*x*t0, t0)]}) + DE.decrement_level() + G = [(Poly(t, t), Poly(t**2, t)), (Poly(2*t, t), Poly(t, t))] + assert prde_special_denom(Poly(5*x*t + 1, t), Poly(t**2 + 2*x**3*t, t), Poly(t**3 + 2, t), G, DE) == \ + (Poly(5*x*t + 1, t), Poly(0, t, domain='ZZ[x]'), [(Poly(t, t), Poly(t**2, t)), + (Poly(2*t, t), Poly(t, t))], Poly(1, x)) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly((t**2 + 1)*2*x, t)]}) + G = [(Poly(t + x, t), Poly(t*x, t)), (Poly(2*t, t), Poly(x**2, x))] + assert prde_special_denom(Poly(5*x*t + 1, t), Poly(t**2 + 2*x**3*t, t), Poly(t**3, t), G, DE) == \ + (Poly(5*x*t + 1, t), Poly(0, t, domain='ZZ[x]'), [(Poly(t + x, t), Poly(x*t, t)), + (Poly(2*t, t, x), Poly(x**2, t, x))], Poly(1, t)) + assert prde_special_denom(Poly(t + 1, t), Poly(t**2, t), Poly(t**3, t), G, DE) == \ + (Poly(t + 1, t), Poly(0, t, domain='ZZ[x]'), [(Poly(t + x, t), Poly(x*t, t)), (Poly(2*t, t, x), + Poly(x**2, t, x))], Poly(1, t)) + + +def test_prde_linear_constraints(): + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + G = [(Poly(2*x**3 + 3*x + 1, x), Poly(x**2 - 1, x)), (Poly(1, x), Poly(x - 1, x)), + (Poly(1, x), Poly(x + 1, x))] + assert prde_linear_constraints(Poly(1, x), Poly(0, x), G, DE) == \ + ((Poly(2*x, x, domain='QQ'), Poly(0, x, domain='QQ'), Poly(0, x, domain='QQ')), + Matrix([[1, 1, -1], [5, 1, 1]], x)) + G = [(Poly(t, t), Poly(1, t)), (Poly(t**2, t), Poly(1, t)), (Poly(t**3, t), Poly(1, t))] + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + assert prde_linear_constraints(Poly(t + 1, t), Poly(t**2, t), G, DE) == \ + ((Poly(t, t, domain='QQ'), Poly(t**2, t, domain='QQ'), Poly(t**3, t, domain='QQ')), + Matrix(0, 3, [], t)) + G = [(Poly(2*x, t), Poly(t, t)), (Poly(-x, t), Poly(t, t))] + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + assert prde_linear_constraints(Poly(1, t), Poly(0, t), G, DE) == \ + ((Poly(0, t, domain='QQ[x]'), Poly(0, t, domain='QQ[x]')), Matrix([[2*x, -x]], t)) + + +def test_constant_system(): + A = Matrix([[-(x + 3)/(x - 1), (x + 1)/(x - 1), 1], + [-x - 3, x + 1, x - 1], + [2*(x + 3)/(x - 1), 0, 0]], t) + u = Matrix([[(x + 1)/(x - 1)], [x + 1], [0]], t) + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + R = QQ.frac_field(x)[t] + assert constant_system(A, u, DE) == \ + (Matrix([[1, 0, 0], + [0, 1, 0], + [0, 0, 0], + [0, 0, 1]], ring=R), Matrix([0, 1, 0, 0], ring=R)) + + +def test_prde_spde(): + D = [Poly(x, t), Poly(-x*t, t)] + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + # TODO: when bound_degree() can handle this, test degree bound from that too + assert prde_spde(Poly(t, t), Poly(-1/x, t), D, n, DE) == \ + (Poly(t, t), Poly(0, t, domain='ZZ(x)'), + [Poly(2*x, t, domain='ZZ(x)'), Poly(-x, t, domain='ZZ(x)')], + [Poly(-x**2, t, domain='ZZ(x)'), Poly(0, t, domain='ZZ(x)')], n - 1) + + +def test_prde_no_cancel(): + # b large + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + assert prde_no_cancel_b_large(Poly(1, x), [Poly(x**2, x), Poly(1, x)], 2, DE) == \ + ([Poly(x**2 - 2*x + 2, x), Poly(1, x)], Matrix([[1, 0, -1, 0], + [0, 1, 0, -1]], x)) + assert prde_no_cancel_b_large(Poly(1, x), [Poly(x**3, x), Poly(1, x)], 3, DE) == \ + ([Poly(x**3 - 3*x**2 + 6*x - 6, x), Poly(1, x)], Matrix([[1, 0, -1, 0], + [0, 1, 0, -1]], x)) + assert prde_no_cancel_b_large(Poly(x, x), [Poly(x**2, x), Poly(1, x)], 1, DE) == \ + ([Poly(x, x, domain='ZZ'), Poly(0, x, domain='ZZ')], Matrix([[1, -1, 0, 0], + [1, 0, -1, 0], + [0, 1, 0, -1]], x)) + # b small + # XXX: Is there a better example of a monomial with D.degree() > 2? + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t**3 + 1, t)]}) + + # My original q was t**4 + t + 1, but this solution implies q == t**4 + # (c1 = 4), with some of the ci for the original q equal to 0. + G = [Poly(t**6, t), Poly(x*t**5, t), Poly(t**3, t), Poly(x*t**2, t), Poly(1 + x, t)] + R = QQ.frac_field(x)[t] + assert prde_no_cancel_b_small(Poly(x*t, t), G, 4, DE) == \ + ([Poly(t**4/4 - x/12*t**3 + x**2/24*t**2 + (Rational(-11, 12) - x**3/24)*t + x/24, t), + Poly(x/3*t**3 - x**2/6*t**2 + (Rational(-1, 3) + x**3/6)*t - x/6, t), Poly(t, t), + Poly(0, t), Poly(0, t)], Matrix([[1, 0, -1, 0, 0, 0, 0, 0, 0, 0], + [0, 1, Rational(-1, 4), 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, -1, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, -1, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, -1, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, -1, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, -1]], ring=R)) + + # TODO: Add test for deg(b) <= 0 with b small + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 + t**2, t)]}) + b = Poly(-1/x**2, t, field=True) # deg(b) == 0 + q = [Poly(x**i*t**j, t, field=True) for i in range(2) for j in range(3)] + h, A = prde_no_cancel_b_small(b, q, 3, DE) + V = A.nullspace() + R = QQ.frac_field(x)[t] + assert len(V) == 1 + assert V[0] == Matrix([Rational(-1, 2), 0, 0, 1, 0, 0]*3, ring=R) + assert (Matrix([h])*V[0][6:, :])[0] == Poly(x**2/2, t, domain='QQ(x)') + assert (Matrix([q])*V[0][:6, :])[0] == Poly(x - S.Half, t, domain='QQ(x)') + + +def test_prde_cancel_liouvillian(): + ### 1. case == 'primitive' + # used when integrating f = log(x) - log(x - 1) + # Not taken from 'the' book + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + p0 = Poly(0, t, field=True) + p1 = Poly((x - 1)*t, t, domain='ZZ(x)') + p2 = Poly(x - 1, t, domain='ZZ(x)') + p3 = Poly(-x**2 + x, t, domain='ZZ(x)') + h, A = prde_cancel_liouvillian(Poly(-1/(x - 1), t), [Poly(-x + 1, t), Poly(1, t)], 1, DE) + V = A.nullspace() + assert h == [p0, p0, p1, p0, p0, p0, p0, p0, p0, p0, p2, p3, p0, p0, p0, p0] + assert A.rank() == 16 + assert (Matrix([h])*V[0][:16, :]) == Matrix([[Poly(0, t, domain='QQ(x)')]]) + + ### 2. case == 'exp' + # used when integrating log(x/exp(x) + 1) + # Not taken from book + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-t, t)]}) + assert prde_cancel_liouvillian(Poly(0, t, domain='QQ[x]'), [Poly(1, t, domain='QQ(x)')], 0, DE) == \ + ([Poly(1, t, domain='QQ'), Poly(x, t, domain='ZZ(x)')], Matrix([[-1, 0, 1]], DE.t)) + + +def test_param_poly_rischDE(): + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + a = Poly(x**2 - x, x, field=True) + b = Poly(1, x, field=True) + q = [Poly(x, x, field=True), Poly(x**2, x, field=True)] + h, A = param_poly_rischDE(a, b, q, 3, DE) + + assert A.nullspace() == [Matrix([0, 1, 1, 1], DE.t)] # c1, c2, d1, d2 + # Solution of a*Dp + b*p = c1*q1 + c2*q2 = q2 = x**2 + # is d1*h1 + d2*h2 = h1 + h2 = x. + assert h[0] + h[1] == Poly(x, x, domain='QQ') + # a*Dp + b*p = q1 = x has no solution. + + a = Poly(x**2 - x, x, field=True) + b = Poly(x**2 - 5*x + 3, x, field=True) + q = [Poly(1, x, field=True), Poly(x, x, field=True), + Poly(x**2, x, field=True)] + h, A = param_poly_rischDE(a, b, q, 3, DE) + + assert A.nullspace() == [Matrix([3, -5, 1, -5, 1, 1], DE.t)] + p = -Poly(5, DE.t)*h[0] + h[1] + h[2] # Poly(1, x) + assert a*derivation(p, DE) + b*p == Poly(x**2 - 5*x + 3, x, domain='QQ') + + +def test_param_rischDE(): + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + p1, px = Poly(1, x, field=True), Poly(x, x, field=True) + G = [(p1, px), (p1, p1), (px, p1)] # [1/x, 1, x] + h, A = param_rischDE(-p1, Poly(x**2, x, field=True), G, DE) + assert len(h) == 3 + p = [hi[0].as_expr()/hi[1].as_expr() for hi in h] + V = A.nullspace() + assert len(V) == 2 + assert V[0] == Matrix([-1, 1, 0, -1, 1, 0], DE.t) + y = -p[0] + p[1] + 0*p[2] # x + assert y.diff(x) - y/x**2 == 1 - 1/x # Dy + f*y == -G0 + G1 + 0*G2 + + # the below test computation takes place while computing the integral + # of 'f = log(log(x + exp(x)))' + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t, t)]}) + G = [(Poly(t + x, t, domain='ZZ(x)'), Poly(1, t, domain='QQ')), (Poly(0, t, domain='QQ'), Poly(1, t, domain='QQ'))] + h, A = param_rischDE(Poly(-t - 1, t, field=True), Poly(t + x, t, field=True), G, DE) + assert len(h) == 5 + p = [hi[0].as_expr()/hi[1].as_expr() for hi in h] + V = A.nullspace() + assert len(V) == 3 + assert V[0] == Matrix([0, 0, 0, 0, 1, 0, 0], DE.t) + y = 0*p[0] + 0*p[1] + 1*p[2] + 0*p[3] + 0*p[4] + assert y.diff(t) - y/(t + x) == 0 # Dy + f*y = 0*G0 + 0*G1 + + +def test_limited_integrate_reduce(): + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + assert limited_integrate_reduce(Poly(x, t), Poly(t**2, t), [(Poly(x, t), + Poly(t, t))], DE) == \ + (Poly(t, t), Poly(-1/x, t), Poly(t, t), 1, (Poly(x, t), Poly(1, t, domain='ZZ[x]')), + [(Poly(-x*t, t), Poly(1, t, domain='ZZ[x]'))]) + + +def test_limited_integrate(): + DE = DifferentialExtension(extension={'D': [Poly(1, x)]}) + G = [(Poly(x, x), Poly(x + 1, x))] + assert limited_integrate(Poly(-(1 + x + 5*x**2 - 3*x**3), x), + Poly(1 - x - x**2 + x**3, x), G, DE) == \ + ((Poly(x**2 - x + 2, x), Poly(x - 1, x, domain='QQ')), [2]) + G = [(Poly(1, x), Poly(x, x))] + assert limited_integrate(Poly(5*x**2, x), Poly(3, x), G, DE) == \ + ((Poly(5*x**3/9, x), Poly(1, x, domain='QQ')), [0]) + + +def test_is_log_deriv_k_t_radical(): + DE = DifferentialExtension(extension={'D': [Poly(1, x)], 'exts': [None], + 'extargs': [None]}) + assert is_log_deriv_k_t_radical(Poly(2*x, x), Poly(1, x), DE) is None + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(2*t1, t1), Poly(1/x, t2)], + 'exts': [None, 'exp', 'log'], 'extargs': [None, 2*x, x]}) + assert is_log_deriv_k_t_radical(Poly(x + t2/2, t2), Poly(1, t2), DE) == \ + ([(t1, 1), (x, 1)], t1*x, 2, 0) + # TODO: Add more tests + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(t0, t0), Poly(1/x, t)], + 'exts': [None, 'exp', 'log'], 'extargs': [None, x, x]}) + assert is_log_deriv_k_t_radical(Poly(x + t/2 + 3, t), Poly(1, t), DE) == \ + ([(t0, 2), (x, 1)], x*t0**2, 2, 3) + + +def test_is_deriv_k(): + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t1), Poly(1/(x + 1), t2)], + 'exts': [None, 'log', 'log'], 'extargs': [None, x, x + 1]}) + assert is_deriv_k(Poly(2*x**2 + 2*x, t2), Poly(1, t2), DE) == \ + ([(t1, 1), (t2, 1)], t1 + t2, 2) + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t1), Poly(t2, t2)], + 'exts': [None, 'log', 'exp'], 'extargs': [None, x, x]}) + assert is_deriv_k(Poly(x**2*t2**3, t2), Poly(1, t2), DE) == \ + ([(x, 3), (t1, 2)], 2*t1 + 3*x, 1) + # TODO: Add more tests, including ones with exponentials + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(2/x, t1)], + 'exts': [None, 'log'], 'extargs': [None, x**2]}) + assert is_deriv_k(Poly(x, t1), Poly(1, t1), DE) == \ + ([(t1, S.Half)], t1/2, 1) + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(2/(1 + x), t0)], + 'exts': [None, 'log'], 'extargs': [None, x**2 + 2*x + 1]}) + assert is_deriv_k(Poly(1 + x, t0), Poly(1, t0), DE) == \ + ([(t0, S.Half)], t0/2, 1) + + # Issue 10798 + # DE = DifferentialExtension(log(1/x), x) + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-1/x, t)], + 'exts': [None, 'log'], 'extargs': [None, 1/x]}) + assert is_deriv_k(Poly(1, t), Poly(x, t), DE) == ([(t, 1)], t, 1) + + +def test_is_log_deriv_k_t_radical_in_field(): + # NOTE: any potential constant factor in the second element of the result + # doesn't matter, because it cancels in Da/a. + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + assert is_log_deriv_k_t_radical_in_field(Poly(5*t + 1, t), Poly(2*t*x, t), DE) == \ + (2, t*x**5) + assert is_log_deriv_k_t_radical_in_field(Poly(2 + 3*t, t), Poly(5*x*t, t), DE) == \ + (5, x**3*t**2) + + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-t/x**2, t)]}) + assert is_log_deriv_k_t_radical_in_field(Poly(-(1 + 2*t), t), + Poly(2*x**2 + 2*x**2*t, t), DE) == \ + (2, t + t**2) + assert is_log_deriv_k_t_radical_in_field(Poly(-1, t), Poly(x**2, t), DE) == \ + (1, t) + assert is_log_deriv_k_t_radical_in_field(Poly(1, t), Poly(2*x**2, t), DE) == \ + (2, 1/t) + + +def test_parametric_log_deriv(): + DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) + assert parametric_log_deriv_heu(Poly(5*t**2 + t - 6, t), Poly(2*x*t**2, t), + Poly(-1, t), Poly(x*t**2, t), DE) == \ + (2, 6, t*x**5) diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_transforms.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_transforms.py new file mode 100644 index 0000000000000000000000000000000000000000..ad299fea910e88d7f31e2c783f939c04d9fd146d --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_transforms.py @@ -0,0 +1,636 @@ +from sympy.integrals.transforms import ( + mellin_transform, inverse_mellin_transform, + fourier_transform, inverse_fourier_transform, + sine_transform, inverse_sine_transform, + cosine_transform, inverse_cosine_transform, + hankel_transform, inverse_hankel_transform, + FourierTransform, SineTransform, CosineTransform, InverseFourierTransform, + InverseSineTransform, InverseCosineTransform, IntegralTransformError) +from sympy.integrals.laplace import ( + laplace_transform, inverse_laplace_transform) +from sympy.core.function import Function, expand_mul +from sympy.core import EulerGamma +from sympy.core.numbers import I, Rational, oo, pi +from sympy.core.singleton import S +from sympy.core.symbol import Symbol, symbols +from sympy.functions.combinatorial.factorials import factorial +from sympy.functions.elementary.complexes import re, unpolarify +from sympy.functions.elementary.exponential import exp, exp_polar, log +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import atan, cos, sin, tan +from sympy.functions.special.bessel import besseli, besselj, besselk, bessely +from sympy.functions.special.delta_functions import Heaviside +from sympy.functions.special.error_functions import erf, expint +from sympy.functions.special.gamma_functions import gamma +from sympy.functions.special.hyper import meijerg +from sympy.simplify.gammasimp import gammasimp +from sympy.simplify.hyperexpand import hyperexpand +from sympy.simplify.trigsimp import trigsimp +from sympy.testing.pytest import XFAIL, slow, skip, raises +from sympy.abc import x, s, a, b, c, d + + +nu, beta, rho = symbols('nu beta rho') + + +def test_undefined_function(): + from sympy.integrals.transforms import MellinTransform + f = Function('f') + assert mellin_transform(f(x), x, s) == MellinTransform(f(x), x, s) + assert mellin_transform(f(x) + exp(-x), x, s) == \ + (MellinTransform(f(x), x, s) + gamma(s + 1)/s, (0, oo), True) + + +def test_free_symbols(): + f = Function('f') + assert mellin_transform(f(x), x, s).free_symbols == {s} + assert mellin_transform(f(x)*a, x, s).free_symbols == {s, a} + + +def test_as_integral(): + from sympy.integrals.integrals import Integral + f = Function('f') + assert mellin_transform(f(x), x, s).rewrite('Integral') == \ + Integral(x**(s - 1)*f(x), (x, 0, oo)) + assert fourier_transform(f(x), x, s).rewrite('Integral') == \ + Integral(f(x)*exp(-2*I*pi*s*x), (x, -oo, oo)) + assert laplace_transform(f(x), x, s, noconds=True).rewrite('Integral') == \ + Integral(f(x)*exp(-s*x), (x, 0, oo)) + assert str(2*pi*I*inverse_mellin_transform(f(s), s, x, (a, b)).rewrite('Integral')) \ + == "Integral(f(s)/x**s, (s, _c - oo*I, _c + oo*I))" + assert str(2*pi*I*inverse_laplace_transform(f(s), s, x).rewrite('Integral')) == \ + "Integral(f(s)*exp(s*x), (s, _c - oo*I, _c + oo*I))" + assert inverse_fourier_transform(f(s), s, x).rewrite('Integral') == \ + Integral(f(s)*exp(2*I*pi*s*x), (s, -oo, oo)) + +# NOTE this is stuck in risch because meijerint cannot handle it + + +@slow +@XFAIL +def test_mellin_transform_fail(): + skip("Risch takes forever.") + + MT = mellin_transform + + bpos = symbols('b', positive=True) + # bneg = symbols('b', negative=True) + + expr = (sqrt(x + b**2) + b)**a/sqrt(x + b**2) + # TODO does not work with bneg, argument wrong. Needs changes to matching. + assert MT(expr.subs(b, -bpos), x, s) == \ + ((-1)**(a + 1)*2**(a + 2*s)*bpos**(a + 2*s - 1)*gamma(a + s) + *gamma(1 - a - 2*s)/gamma(1 - s), + (-re(a), -re(a)/2 + S.Half), True) + + expr = (sqrt(x + b**2) + b)**a + assert MT(expr.subs(b, -bpos), x, s) == \ + ( + 2**(a + 2*s)*a*bpos**(a + 2*s)*gamma(-a - 2* + s)*gamma(a + s)/gamma(-s + 1), + (-re(a), -re(a)/2), True) + + # Test exponent 1: + assert MT(expr.subs({b: -bpos, a: 1}), x, s) == \ + (-bpos**(2*s + 1)*gamma(s)*gamma(-s - S.Half)/(2*sqrt(pi)), + (-1, Rational(-1, 2)), True) + + +def test_mellin_transform(): + from sympy.functions.elementary.miscellaneous import (Max, Min) + MT = mellin_transform + + bpos = symbols('b', positive=True) + + # 8.4.2 + assert MT(x**nu*Heaviside(x - 1), x, s) == \ + (-1/(nu + s), (-oo, -re(nu)), True) + assert MT(x**nu*Heaviside(1 - x), x, s) == \ + (1/(nu + s), (-re(nu), oo), True) + + assert MT((1 - x)**(beta - 1)*Heaviside(1 - x), x, s) == \ + (gamma(beta)*gamma(s)/gamma(beta + s), (0, oo), re(beta) > 0) + assert MT((x - 1)**(beta - 1)*Heaviside(x - 1), x, s) == \ + (gamma(beta)*gamma(1 - beta - s)/gamma(1 - s), + (-oo, 1 - re(beta)), re(beta) > 0) + + assert MT((1 + x)**(-rho), x, s) == \ + (gamma(s)*gamma(rho - s)/gamma(rho), (0, re(rho)), True) + + assert MT(abs(1 - x)**(-rho), x, s) == ( + 2*sin(pi*rho/2)*gamma(1 - rho)* + cos(pi*(s - rho/2))*gamma(s)*gamma(rho-s)/pi, + (0, re(rho)), re(rho) < 1) + mt = MT((1 - x)**(beta - 1)*Heaviside(1 - x) + + a*(x - 1)**(beta - 1)*Heaviside(x - 1), x, s) + assert mt[1], mt[2] == ((0, -re(beta) + 1), re(beta) > 0) + + assert MT((x**a - b**a)/(x - b), x, s)[0] == \ + pi*b**(a + s - 1)*sin(pi*a)/(sin(pi*s)*sin(pi*(a + s))) + assert MT((x**a - bpos**a)/(x - bpos), x, s) == \ + (pi*bpos**(a + s - 1)*sin(pi*a)/(sin(pi*s)*sin(pi*(a + s))), + (Max(0, -re(a)), Min(1, 1 - re(a))), True) + + expr = (sqrt(x + b**2) + b)**a + assert MT(expr.subs(b, bpos), x, s) == \ + (-a*(2*bpos)**(a + 2*s)*gamma(s)*gamma(-a - 2*s)/gamma(-a - s + 1), + (0, -re(a)/2), True) + + expr = (sqrt(x + b**2) + b)**a/sqrt(x + b**2) + assert MT(expr.subs(b, bpos), x, s) == \ + (2**(a + 2*s)*bpos**(a + 2*s - 1)*gamma(s) + *gamma(1 - a - 2*s)/gamma(1 - a - s), + (0, -re(a)/2 + S.Half), True) + + # 8.4.2 + assert MT(exp(-x), x, s) == (gamma(s), (0, oo), True) + assert MT(exp(-1/x), x, s) == (gamma(-s), (-oo, 0), True) + + # 8.4.5 + assert MT(log(x)**4*Heaviside(1 - x), x, s) == (24/s**5, (0, oo), True) + assert MT(log(x)**3*Heaviside(x - 1), x, s) == (6/s**4, (-oo, 0), True) + assert MT(log(x + 1), x, s) == (pi/(s*sin(pi*s)), (-1, 0), True) + assert MT(log(1/x + 1), x, s) == (pi/(s*sin(pi*s)), (0, 1), True) + assert MT(log(abs(1 - x)), x, s) == (pi/(s*tan(pi*s)), (-1, 0), True) + assert MT(log(abs(1 - 1/x)), x, s) == (pi/(s*tan(pi*s)), (0, 1), True) + + # 8.4.14 + assert MT(erf(sqrt(x)), x, s) == \ + (-gamma(s + S.Half)/(sqrt(pi)*s), (Rational(-1, 2), 0), True) + + +def test_mellin_transform2(): + MT = mellin_transform + # TODO we cannot currently do these (needs summation of 3F2(-1)) + # this also implies that they cannot be written as a single g-function + # (although this is possible) + mt = MT(log(x)/(x + 1), x, s) + assert mt[1:] == ((0, 1), True) + assert not hyperexpand(mt[0], allow_hyper=True).has(meijerg) + mt = MT(log(x)**2/(x + 1), x, s) + assert mt[1:] == ((0, 1), True) + assert not hyperexpand(mt[0], allow_hyper=True).has(meijerg) + mt = MT(log(x)/(x + 1)**2, x, s) + assert mt[1:] == ((0, 2), True) + assert not hyperexpand(mt[0], allow_hyper=True).has(meijerg) + + +@slow +def test_mellin_transform_bessel(): + from sympy.functions.elementary.miscellaneous import Max + MT = mellin_transform + + # 8.4.19 + assert MT(besselj(a, 2*sqrt(x)), x, s) == \ + (gamma(a/2 + s)/gamma(a/2 - s + 1), (-re(a)/2, Rational(3, 4)), True) + assert MT(sin(sqrt(x))*besselj(a, sqrt(x)), x, s) == \ + (2**a*gamma(-2*s + S.Half)*gamma(a/2 + s + S.Half)/( + gamma(-a/2 - s + 1)*gamma(a - 2*s + 1)), ( + -re(a)/2 - S.Half, Rational(1, 4)), True) + assert MT(cos(sqrt(x))*besselj(a, sqrt(x)), x, s) == \ + (2**a*gamma(a/2 + s)*gamma(-2*s + S.Half)/( + gamma(-a/2 - s + S.Half)*gamma(a - 2*s + 1)), ( + -re(a)/2, Rational(1, 4)), True) + assert MT(besselj(a, sqrt(x))**2, x, s) == \ + (gamma(a + s)*gamma(S.Half - s) + / (sqrt(pi)*gamma(1 - s)*gamma(1 + a - s)), + (-re(a), S.Half), True) + assert MT(besselj(a, sqrt(x))*besselj(-a, sqrt(x)), x, s) == \ + (gamma(s)*gamma(S.Half - s) + / (sqrt(pi)*gamma(1 - a - s)*gamma(1 + a - s)), + (0, S.Half), True) + # NOTE: prudnikov gives the strip below as (1/2 - re(a), 1). As far as + # I can see this is wrong (since besselj(z) ~ 1/sqrt(z) for z large) + assert MT(besselj(a - 1, sqrt(x))*besselj(a, sqrt(x)), x, s) == \ + (gamma(1 - s)*gamma(a + s - S.Half) + / (sqrt(pi)*gamma(Rational(3, 2) - s)*gamma(a - s + S.Half)), + (S.Half - re(a), S.Half), True) + assert MT(besselj(a, sqrt(x))*besselj(b, sqrt(x)), x, s) == \ + (4**s*gamma(1 - 2*s)*gamma((a + b)/2 + s) + / (gamma(1 - s + (b - a)/2)*gamma(1 - s + (a - b)/2) + *gamma( 1 - s + (a + b)/2)), + (-(re(a) + re(b))/2, S.Half), True) + assert MT(besselj(a, sqrt(x))**2 + besselj(-a, sqrt(x))**2, x, s)[1:] == \ + ((Max(re(a), -re(a)), S.Half), True) + + # Section 8.4.20 + assert MT(bessely(a, 2*sqrt(x)), x, s) == \ + (-cos(pi*(a/2 - s))*gamma(s - a/2)*gamma(s + a/2)/pi, + (Max(-re(a)/2, re(a)/2), Rational(3, 4)), True) + assert MT(sin(sqrt(x))*bessely(a, sqrt(x)), x, s) == \ + (-4**s*sin(pi*(a/2 - s))*gamma(S.Half - 2*s) + * gamma((1 - a)/2 + s)*gamma((1 + a)/2 + s) + / (sqrt(pi)*gamma(1 - s - a/2)*gamma(1 - s + a/2)), + (Max(-(re(a) + 1)/2, (re(a) - 1)/2), Rational(1, 4)), True) + assert MT(cos(sqrt(x))*bessely(a, sqrt(x)), x, s) == \ + (-4**s*cos(pi*(a/2 - s))*gamma(s - a/2)*gamma(s + a/2)*gamma(S.Half - 2*s) + / (sqrt(pi)*gamma(S.Half - s - a/2)*gamma(S.Half - s + a/2)), + (Max(-re(a)/2, re(a)/2), Rational(1, 4)), True) + assert MT(besselj(a, sqrt(x))*bessely(a, sqrt(x)), x, s) == \ + (-cos(pi*s)*gamma(s)*gamma(a + s)*gamma(S.Half - s) + / (pi**S('3/2')*gamma(1 + a - s)), + (Max(-re(a), 0), S.Half), True) + assert MT(besselj(a, sqrt(x))*bessely(b, sqrt(x)), x, s) == \ + (-4**s*cos(pi*(a/2 - b/2 + s))*gamma(1 - 2*s) + * gamma(a/2 - b/2 + s)*gamma(a/2 + b/2 + s) + / (pi*gamma(a/2 - b/2 - s + 1)*gamma(a/2 + b/2 - s + 1)), + (Max((-re(a) + re(b))/2, (-re(a) - re(b))/2), S.Half), True) + # NOTE bessely(a, sqrt(x))**2 and bessely(a, sqrt(x))*bessely(b, sqrt(x)) + # are a mess (no matter what way you look at it ...) + assert MT(bessely(a, sqrt(x))**2, x, s)[1:] == \ + ((Max(-re(a), 0, re(a)), S.Half), True) + + # Section 8.4.22 + # TODO we can't do any of these (delicate cancellation) + + # Section 8.4.23 + assert MT(besselk(a, 2*sqrt(x)), x, s) == \ + (gamma( + s - a/2)*gamma(s + a/2)/2, (Max(-re(a)/2, re(a)/2), oo), True) + assert MT(besselj(a, 2*sqrt(2*sqrt(x)))*besselk( + a, 2*sqrt(2*sqrt(x))), x, s) == (4**(-s)*gamma(2*s)* + gamma(a/2 + s)/(2*gamma(a/2 - s + 1)), (Max(0, -re(a)/2), oo), True) + # TODO bessely(a, x)*besselk(a, x) is a mess + assert MT(besseli(a, sqrt(x))*besselk(a, sqrt(x)), x, s) == \ + (gamma(s)*gamma( + a + s)*gamma(-s + S.Half)/(2*sqrt(pi)*gamma(a - s + 1)), + (Max(-re(a), 0), S.Half), True) + assert MT(besseli(b, sqrt(x))*besselk(a, sqrt(x)), x, s) == \ + (2**(2*s - 1)*gamma(-2*s + 1)*gamma(-a/2 + b/2 + s)* \ + gamma(a/2 + b/2 + s)/(gamma(-a/2 + b/2 - s + 1)* \ + gamma(a/2 + b/2 - s + 1)), (Max(-re(a)/2 - re(b)/2, \ + re(a)/2 - re(b)/2), S.Half), True) + + # TODO products of besselk are a mess + + mt = MT(exp(-x/2)*besselk(a, x/2), x, s) + mt0 = gammasimp(trigsimp(gammasimp(mt[0].expand(func=True)))) + assert mt0 == 2*pi**Rational(3, 2)*cos(pi*s)*gamma(S.Half - s)/( + (cos(2*pi*a) - cos(2*pi*s))*gamma(-a - s + 1)*gamma(a - s + 1)) + assert mt[1:] == ((Max(-re(a), re(a)), oo), True) + # TODO exp(x/2)*besselk(a, x/2) [etc] cannot currently be done + # TODO various strange products of special orders + + +@slow +def test_expint(): + from sympy.functions.elementary.miscellaneous import Max + from sympy.functions.special.error_functions import Ci, E1, Si + from sympy.simplify.simplify import simplify + + aneg = Symbol('a', negative=True) + u = Symbol('u', polar=True) + + assert mellin_transform(E1(x), x, s) == (gamma(s)/s, (0, oo), True) + assert inverse_mellin_transform(gamma(s)/s, s, x, + (0, oo)).rewrite(expint).expand() == E1(x) + assert mellin_transform(expint(a, x), x, s) == \ + (gamma(s)/(a + s - 1), (Max(1 - re(a), 0), oo), True) + # XXX IMT has hickups with complicated strips ... + assert simplify(unpolarify( + inverse_mellin_transform(gamma(s)/(aneg + s - 1), s, x, + (1 - aneg, oo)).rewrite(expint).expand(func=True))) == \ + expint(aneg, x) + + assert mellin_transform(Si(x), x, s) == \ + (-2**s*sqrt(pi)*gamma(s/2 + S.Half)/( + 2*s*gamma(-s/2 + 1)), (-1, 0), True) + assert inverse_mellin_transform(-2**s*sqrt(pi)*gamma((s + 1)/2) + /(2*s*gamma(-s/2 + 1)), s, x, (-1, 0)) \ + == Si(x) + + assert mellin_transform(Ci(sqrt(x)), x, s) == \ + (-2**(2*s - 1)*sqrt(pi)*gamma(s)/(s*gamma(-s + S.Half)), (0, 1), True) + assert inverse_mellin_transform( + -4**s*sqrt(pi)*gamma(s)/(2*s*gamma(-s + S.Half)), + s, u, (0, 1)).expand() == Ci(sqrt(u)) + + +@slow +def test_inverse_mellin_transform(): + from sympy.core.function import expand + from sympy.functions.elementary.miscellaneous import (Max, Min) + from sympy.functions.elementary.trigonometric import cot + from sympy.simplify.powsimp import powsimp + from sympy.simplify.simplify import simplify + IMT = inverse_mellin_transform + + assert IMT(gamma(s), s, x, (0, oo)) == exp(-x) + assert IMT(gamma(-s), s, x, (-oo, 0)) == exp(-1/x) + assert simplify(IMT(s/(2*s**2 - 2), s, x, (2, oo))) == \ + (x**2 + 1)*Heaviside(1 - x)/(4*x) + + # test passing "None" + assert IMT(1/(s**2 - 1), s, x, (-1, None)) == \ + -x*Heaviside(-x + 1)/2 - Heaviside(x - 1)/(2*x) + assert IMT(1/(s**2 - 1), s, x, (None, 1)) == \ + -x*Heaviside(-x + 1)/2 - Heaviside(x - 1)/(2*x) + + # test expansion of sums + assert IMT(gamma(s) + gamma(s - 1), s, x, (1, oo)) == (x + 1)*exp(-x)/x + + # test factorisation of polys + r = symbols('r', real=True) + assert IMT(1/(s**2 + 1), s, exp(-x), (None, oo) + ).subs(x, r).rewrite(sin).simplify() \ + == sin(r)*Heaviside(1 - exp(-r)) + + # test multiplicative substitution + _a, _b = symbols('a b', positive=True) + assert IMT(_b**(-s/_a)*factorial(s/_a)/s, s, x, (0, oo)) == exp(-_b*x**_a) + assert IMT(factorial(_a/_b + s/_b)/(_a + s), s, x, (-_a, oo)) == x**_a*exp(-x**_b) + + def simp_pows(expr): + return simplify(powsimp(expand_mul(expr, deep=False), force=True)).replace(exp_polar, exp) + + # Now test the inverses of all direct transforms tested above + + # Section 8.4.2 + nu = symbols('nu', real=True) + assert IMT(-1/(nu + s), s, x, (-oo, None)) == x**nu*Heaviside(x - 1) + assert IMT(1/(nu + s), s, x, (None, oo)) == x**nu*Heaviside(1 - x) + assert simp_pows(IMT(gamma(beta)*gamma(s)/gamma(s + beta), s, x, (0, oo))) \ + == (1 - x)**(beta - 1)*Heaviside(1 - x) + assert simp_pows(IMT(gamma(beta)*gamma(1 - beta - s)/gamma(1 - s), + s, x, (-oo, None))) \ + == (x - 1)**(beta - 1)*Heaviside(x - 1) + assert simp_pows(IMT(gamma(s)*gamma(rho - s)/gamma(rho), s, x, (0, None))) \ + == (1/(x + 1))**rho + assert simp_pows(IMT(d**c*d**(s - 1)*sin(pi*c) + *gamma(s)*gamma(s + c)*gamma(1 - s)*gamma(1 - s - c)/pi, + s, x, (Max(-re(c), 0), Min(1 - re(c), 1)))) \ + == (x**c - d**c)/(x - d) + + assert simplify(IMT(1/sqrt(pi)*(-c/2)*gamma(s)*gamma((1 - c)/2 - s) + *gamma(-c/2 - s)/gamma(1 - c - s), + s, x, (0, -re(c)/2))) == \ + (1 + sqrt(x + 1))**c + assert simplify(IMT(2**(a + 2*s)*b**(a + 2*s - 1)*gamma(s)*gamma(1 - a - 2*s) + /gamma(1 - a - s), s, x, (0, (-re(a) + 1)/2))) == \ + b**(a - 1)*(b**2*(sqrt(1 + x/b**2) + 1)**a + x*(sqrt(1 + x/b**2) + 1 + )**(a - 1))/(b**2 + x) + assert simplify(IMT(-2**(c + 2*s)*c*b**(c + 2*s)*gamma(s)*gamma(-c - 2*s) + / gamma(-c - s + 1), s, x, (0, -re(c)/2))) == \ + b**c*(sqrt(1 + x/b**2) + 1)**c + + # Section 8.4.5 + assert IMT(24/s**5, s, x, (0, oo)) == log(x)**4*Heaviside(1 - x) + assert expand(IMT(6/s**4, s, x, (-oo, 0)), force=True) == \ + log(x)**3*Heaviside(x - 1) + assert IMT(pi/(s*sin(pi*s)), s, x, (-1, 0)) == log(x + 1) + assert IMT(pi/(s*sin(pi*s/2)), s, x, (-2, 0)) == log(x**2 + 1) + assert IMT(pi/(s*sin(2*pi*s)), s, x, (Rational(-1, 2), 0)) == log(sqrt(x) + 1) + assert IMT(pi/(s*sin(pi*s)), s, x, (0, 1)) == log(1 + 1/x) + + # TODO + def mysimp(expr): + from sympy.core.function import expand + from sympy.simplify.powsimp import powsimp + from sympy.simplify.simplify import logcombine + return expand( + powsimp(logcombine(expr, force=True), force=True, deep=True), + force=True).replace(exp_polar, exp) + + assert mysimp(mysimp(IMT(pi/(s*tan(pi*s)), s, x, (-1, 0)))) in [ + log(1 - x)*Heaviside(1 - x) + log(x - 1)*Heaviside(x - 1), + log(x)*Heaviside(x - 1) + log(1 - 1/x)*Heaviside(x - 1) + log(-x + + 1)*Heaviside(-x + 1)] + # test passing cot + assert mysimp(IMT(pi*cot(pi*s)/s, s, x, (0, 1))) in [ + log(1/x - 1)*Heaviside(1 - x) + log(1 - 1/x)*Heaviside(x - 1), + -log(x)*Heaviside(-x + 1) + log(1 - 1/x)*Heaviside(x - 1) + log(-x + + 1)*Heaviside(-x + 1), ] + + # 8.4.14 + assert IMT(-gamma(s + S.Half)/(sqrt(pi)*s), s, x, (Rational(-1, 2), 0)) == \ + erf(sqrt(x)) + + # 8.4.19 + assert simplify(IMT(gamma(a/2 + s)/gamma(a/2 - s + 1), s, x, (-re(a)/2, Rational(3, 4)))) \ + == besselj(a, 2*sqrt(x)) + assert simplify(IMT(2**a*gamma(S.Half - 2*s)*gamma(s + (a + 1)/2) + / (gamma(1 - s - a/2)*gamma(1 - 2*s + a)), + s, x, (-(re(a) + 1)/2, Rational(1, 4)))) == \ + sin(sqrt(x))*besselj(a, sqrt(x)) + assert simplify(IMT(2**a*gamma(a/2 + s)*gamma(S.Half - 2*s) + / (gamma(S.Half - s - a/2)*gamma(1 - 2*s + a)), + s, x, (-re(a)/2, Rational(1, 4)))) == \ + cos(sqrt(x))*besselj(a, sqrt(x)) + # TODO this comes out as an amazing mess, but simplifies nicely + assert simplify(IMT(gamma(a + s)*gamma(S.Half - s) + / (sqrt(pi)*gamma(1 - s)*gamma(1 + a - s)), + s, x, (-re(a), S.Half))) == \ + besselj(a, sqrt(x))**2 + assert simplify(IMT(gamma(s)*gamma(S.Half - s) + / (sqrt(pi)*gamma(1 - s - a)*gamma(1 + a - s)), + s, x, (0, S.Half))) == \ + besselj(-a, sqrt(x))*besselj(a, sqrt(x)) + assert simplify(IMT(4**s*gamma(-2*s + 1)*gamma(a/2 + b/2 + s) + / (gamma(-a/2 + b/2 - s + 1)*gamma(a/2 - b/2 - s + 1) + *gamma(a/2 + b/2 - s + 1)), + s, x, (-(re(a) + re(b))/2, S.Half))) == \ + besselj(a, sqrt(x))*besselj(b, sqrt(x)) + + # Section 8.4.20 + # TODO this can be further simplified! + assert simplify(IMT(-2**(2*s)*cos(pi*a/2 - pi*b/2 + pi*s)*gamma(-2*s + 1) * + gamma(a/2 - b/2 + s)*gamma(a/2 + b/2 + s) / + (pi*gamma(a/2 - b/2 - s + 1)*gamma(a/2 + b/2 - s + 1)), + s, x, + (Max(-re(a)/2 - re(b)/2, -re(a)/2 + re(b)/2), S.Half))) == \ + besselj(a, sqrt(x))*-(besselj(-b, sqrt(x)) - + besselj(b, sqrt(x))*cos(pi*b))/sin(pi*b) + # TODO more + + # for coverage + + assert IMT(pi/cos(pi*s), s, x, (0, S.Half)) == sqrt(x)/(x + 1) + + +def test_fourier_transform(): + from sympy.core.function import (expand, expand_complex, expand_trig) + from sympy.polys.polytools import factor + from sympy.simplify.simplify import simplify + FT = fourier_transform + IFT = inverse_fourier_transform + + def simp(x): + return simplify(expand_trig(expand_complex(expand(x)))) + + def sinc(x): + return sin(pi*x)/(pi*x) + k = symbols('k', real=True) + f = Function("f") + + # TODO for this to work with real a, need to expand abs(a*x) to abs(a)*abs(x) + a = symbols('a', positive=True) + b = symbols('b', positive=True) + + posk = symbols('posk', positive=True) + + # Test unevaluated form + assert fourier_transform(f(x), x, k) == FourierTransform(f(x), x, k) + assert inverse_fourier_transform( + f(k), k, x) == InverseFourierTransform(f(k), k, x) + + # basic examples from wikipedia + assert simp(FT(Heaviside(1 - abs(2*a*x)), x, k)) == sinc(k/a)/a + # TODO IFT is a *mess* + assert simp(FT(Heaviside(1 - abs(a*x))*(1 - abs(a*x)), x, k)) == sinc(k/a)**2/a + # TODO IFT + + assert factor(FT(exp(-a*x)*Heaviside(x), x, k), extension=I) == \ + 1/(a + 2*pi*I*k) + # NOTE: the ift comes out in pieces + assert IFT(1/(a + 2*pi*I*x), x, posk, + noconds=False) == (exp(-a*posk), True) + assert IFT(1/(a + 2*pi*I*x), x, -posk, + noconds=False) == (0, True) + assert IFT(1/(a + 2*pi*I*x), x, symbols('k', negative=True), + noconds=False) == (0, True) + # TODO IFT without factoring comes out as meijer g + + assert factor(FT(x*exp(-a*x)*Heaviside(x), x, k), extension=I) == \ + 1/(a + 2*pi*I*k)**2 + assert FT(exp(-a*x)*sin(b*x)*Heaviside(x), x, k) == \ + b/(b**2 + (a + 2*I*pi*k)**2) + + assert FT(exp(-a*x**2), x, k) == sqrt(pi)*exp(-pi**2*k**2/a)/sqrt(a) + assert IFT(sqrt(pi/a)*exp(-(pi*k)**2/a), k, x) == exp(-a*x**2) + assert FT(exp(-a*abs(x)), x, k) == 2*a/(a**2 + 4*pi**2*k**2) + # TODO IFT (comes out as meijer G) + + # TODO besselj(n, x), n an integer > 0 actually can be done... + + # TODO are there other common transforms (no distributions!)? + + +def test_sine_transform(): + t = symbols("t") + w = symbols("w") + a = symbols("a") + f = Function("f") + + # Test unevaluated form + assert sine_transform(f(t), t, w) == SineTransform(f(t), t, w) + assert inverse_sine_transform( + f(w), w, t) == InverseSineTransform(f(w), w, t) + + assert sine_transform(1/sqrt(t), t, w) == 1/sqrt(w) + assert inverse_sine_transform(1/sqrt(w), w, t) == 1/sqrt(t) + + assert sine_transform((1/sqrt(t))**3, t, w) == 2*sqrt(w) + + assert sine_transform(t**(-a), t, w) == 2**( + -a + S.Half)*w**(a - 1)*gamma(-a/2 + 1)/gamma((a + 1)/2) + assert inverse_sine_transform(2**(-a + S( + 1)/2)*w**(a - 1)*gamma(-a/2 + 1)/gamma(a/2 + S.Half), w, t) == t**(-a) + + assert sine_transform( + exp(-a*t), t, w) == sqrt(2)*w/(sqrt(pi)*(a**2 + w**2)) + assert inverse_sine_transform( + sqrt(2)*w/(sqrt(pi)*(a**2 + w**2)), w, t) == exp(-a*t) + + assert sine_transform( + log(t)/t, t, w) == sqrt(2)*sqrt(pi)*-(log(w**2) + 2*EulerGamma)/4 + + assert sine_transform( + t*exp(-a*t**2), t, w) == sqrt(2)*w*exp(-w**2/(4*a))/(4*a**Rational(3, 2)) + assert inverse_sine_transform( + sqrt(2)*w*exp(-w**2/(4*a))/(4*a**Rational(3, 2)), w, t) == t*exp(-a*t**2) + + +def test_cosine_transform(): + from sympy.functions.special.error_functions import (Ci, Si) + + t = symbols("t") + w = symbols("w") + a = symbols("a") + f = Function("f") + + # Test unevaluated form + assert cosine_transform(f(t), t, w) == CosineTransform(f(t), t, w) + assert inverse_cosine_transform( + f(w), w, t) == InverseCosineTransform(f(w), w, t) + + assert cosine_transform(1/sqrt(t), t, w) == 1/sqrt(w) + assert inverse_cosine_transform(1/sqrt(w), w, t) == 1/sqrt(t) + + assert cosine_transform(1/( + a**2 + t**2), t, w) == sqrt(2)*sqrt(pi)*exp(-a*w)/(2*a) + + assert cosine_transform(t**( + -a), t, w) == 2**(-a + S.Half)*w**(a - 1)*gamma((-a + 1)/2)/gamma(a/2) + assert inverse_cosine_transform(2**(-a + S( + 1)/2)*w**(a - 1)*gamma(-a/2 + S.Half)/gamma(a/2), w, t) == t**(-a) + + assert cosine_transform( + exp(-a*t), t, w) == sqrt(2)*a/(sqrt(pi)*(a**2 + w**2)) + assert inverse_cosine_transform( + sqrt(2)*a/(sqrt(pi)*(a**2 + w**2)), w, t) == exp(-a*t) + + assert cosine_transform(exp(-a*sqrt(t))*cos(a*sqrt( + t)), t, w) == a*exp(-a**2/(2*w))/(2*w**Rational(3, 2)) + + assert cosine_transform(1/(a + t), t, w) == sqrt(2)*( + (-2*Si(a*w) + pi)*sin(a*w)/2 - cos(a*w)*Ci(a*w))/sqrt(pi) + assert inverse_cosine_transform(sqrt(2)*meijerg(((S.Half, 0), ()), ( + (S.Half, 0, 0), (S.Half,)), a**2*w**2/4)/(2*pi), w, t) == 1/(a + t) + + assert cosine_transform(1/sqrt(a**2 + t**2), t, w) == sqrt(2)*meijerg( + ((S.Half,), ()), ((0, 0), (S.Half,)), a**2*w**2/4)/(2*sqrt(pi)) + assert inverse_cosine_transform(sqrt(2)*meijerg(((S.Half,), ()), ((0, 0), (S.Half,)), a**2*w**2/4)/(2*sqrt(pi)), w, t) == 1/(t*sqrt(a**2/t**2 + 1)) + + +def test_hankel_transform(): + r = Symbol("r") + k = Symbol("k") + nu = Symbol("nu") + m = Symbol("m") + a = symbols("a") + + assert hankel_transform(1/r, r, k, 0) == 1/k + assert inverse_hankel_transform(1/k, k, r, 0) == 1/r + + assert hankel_transform( + 1/r**m, r, k, 0) == 2**(-m + 1)*k**(m - 2)*gamma(-m/2 + 1)/gamma(m/2) + assert inverse_hankel_transform( + 2**(-m + 1)*k**(m - 2)*gamma(-m/2 + 1)/gamma(m/2), k, r, 0) == r**(-m) + + assert hankel_transform(1/r**m, r, k, nu) == ( + 2*2**(-m)*k**(m - 2)*gamma(-m/2 + nu/2 + 1)/gamma(m/2 + nu/2)) + assert inverse_hankel_transform(2**(-m + 1)*k**( + m - 2)*gamma(-m/2 + nu/2 + 1)/gamma(m/2 + nu/2), k, r, nu) == r**(-m) + + assert hankel_transform(r**nu*exp(-a*r), r, k, nu) == \ + 2**(nu + 1)*a*k**(-nu - 3)*(a**2/k**2 + 1)**(-nu - S( + 3)/2)*gamma(nu + Rational(3, 2))/sqrt(pi) + assert inverse_hankel_transform( + 2**(nu + 1)*a*k**(-nu - 3)*(a**2/k**2 + 1)**(-nu - Rational(3, 2))*gamma( + nu + Rational(3, 2))/sqrt(pi), k, r, nu) == r**nu*exp(-a*r) + + +def test_issue_7181(): + assert mellin_transform(1/(1 - x), x, s) != None + + +def test_issue_8882(): + # This is the original test. + # from sympy import diff, Integral, integrate + # r = Symbol('r') + # psi = 1/r*sin(r)*exp(-(a0*r)) + # h = -1/2*diff(psi, r, r) - 1/r*psi + # f = 4*pi*psi*h*r**2 + # assert integrate(f, (r, -oo, 3), meijerg=True).has(Integral) == True + + # To save time, only the critical part is included. + F = -a**(-s + 1)*(4 + 1/a**2)**(-s/2)*sqrt(1/a**2)*exp(-s*I*pi)* \ + sin(s*atan(sqrt(1/a**2)/2))*gamma(s) + raises(IntegralTransformError, lambda: + inverse_mellin_transform(F, s, x, (-1, oo), + **{'as_meijerg': True, 'needeval': True})) + + +def test_issue_12591(): + x, y = symbols("x y", real=True) + assert fourier_transform(exp(x), x, y) == FourierTransform(exp(x), x, y) diff --git a/lib/python3.12/site-packages/sympy/integrals/tests/test_trigonometry.py b/lib/python3.12/site-packages/sympy/integrals/tests/test_trigonometry.py new file mode 100644 index 0000000000000000000000000000000000000000..857c8503c5aa690d66e9cdab49730b4ea655a52c --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/tests/test_trigonometry.py @@ -0,0 +1,98 @@ +from sympy.core import Ne, Rational, Symbol +from sympy.functions import sin, cos, tan, csc, sec, cot, log, Piecewise +from sympy.integrals.trigonometry import trigintegrate + +x = Symbol('x') + + +def test_trigintegrate_odd(): + assert trigintegrate(Rational(1), x) == x + assert trigintegrate(x, x) is None + assert trigintegrate(x**2, x) is None + + assert trigintegrate(sin(x), x) == -cos(x) + assert trigintegrate(cos(x), x) == sin(x) + + assert trigintegrate(sin(3*x), x) == -cos(3*x)/3 + assert trigintegrate(cos(3*x), x) == sin(3*x)/3 + + y = Symbol('y') + assert trigintegrate(sin(y*x), x) == Piecewise( + (-cos(y*x)/y, Ne(y, 0)), (0, True)) + assert trigintegrate(cos(y*x), x) == Piecewise( + (sin(y*x)/y, Ne(y, 0)), (x, True)) + assert trigintegrate(sin(y*x)**2, x) == Piecewise( + ((x*y/2 - sin(x*y)*cos(x*y)/2)/y, Ne(y, 0)), (0, True)) + assert trigintegrate(sin(y*x)*cos(y*x), x) == Piecewise( + (sin(x*y)**2/(2*y), Ne(y, 0)), (0, True)) + assert trigintegrate(cos(y*x)**2, x) == Piecewise( + ((x*y/2 + sin(x*y)*cos(x*y)/2)/y, Ne(y, 0)), (x, True)) + + y = Symbol('y', positive=True) + # TODO: remove conds='none' below. For this to work we would have to rule + # out (e.g. by trying solve) the condition y = 0, incompatible with + # y.is_positive being True. + assert trigintegrate(sin(y*x), x, conds='none') == -cos(y*x)/y + assert trigintegrate(cos(y*x), x, conds='none') == sin(y*x)/y + + assert trigintegrate(sin(x)*cos(x), x) == sin(x)**2/2 + assert trigintegrate(sin(x)*cos(x)**2, x) == -cos(x)**3/3 + assert trigintegrate(sin(x)**2*cos(x), x) == sin(x)**3/3 + + # check if it selects right function to substitute, + # so the result is kept simple + assert trigintegrate(sin(x)**7 * cos(x), x) == sin(x)**8/8 + assert trigintegrate(sin(x) * cos(x)**7, x) == -cos(x)**8/8 + + assert trigintegrate(sin(x)**7 * cos(x)**3, x) == \ + -sin(x)**10/10 + sin(x)**8/8 + assert trigintegrate(sin(x)**3 * cos(x)**7, x) == \ + cos(x)**10/10 - cos(x)**8/8 + + # both n, m are odd and -ve, and not necessarily equal + assert trigintegrate(sin(x)**-1*cos(x)**-1, x) == \ + -log(sin(x)**2 - 1)/2 + log(sin(x)) + + +def test_trigintegrate_even(): + assert trigintegrate(sin(x)**2, x) == x/2 - cos(x)*sin(x)/2 + assert trigintegrate(cos(x)**2, x) == x/2 + cos(x)*sin(x)/2 + + assert trigintegrate(sin(3*x)**2, x) == x/2 - cos(3*x)*sin(3*x)/6 + assert trigintegrate(cos(3*x)**2, x) == x/2 + cos(3*x)*sin(3*x)/6 + assert trigintegrate(sin(x)**2 * cos(x)**2, x) == \ + x/8 - sin(2*x)*cos(2*x)/16 + + assert trigintegrate(sin(x)**4 * cos(x)**2, x) == \ + x/16 - sin(x) *cos(x)/16 - sin(x)**3*cos(x)/24 + \ + sin(x)**5*cos(x)/6 + + assert trigintegrate(sin(x)**2 * cos(x)**4, x) == \ + x/16 + cos(x) *sin(x)/16 + cos(x)**3*sin(x)/24 - \ + cos(x)**5*sin(x)/6 + + assert trigintegrate(sin(x)**(-4), x) == -2*cos(x)/(3*sin(x)) \ + - cos(x)/(3*sin(x)**3) + + assert trigintegrate(cos(x)**(-6), x) == sin(x)/(5*cos(x)**5) \ + + 4*sin(x)/(15*cos(x)**3) + 8*sin(x)/(15*cos(x)) + + +def test_trigintegrate_mixed(): + assert trigintegrate(sin(x)*sec(x), x) == -log(cos(x)) + assert trigintegrate(sin(x)*csc(x), x) == x + assert trigintegrate(sin(x)*cot(x), x) == sin(x) + + assert trigintegrate(cos(x)*sec(x), x) == x + assert trigintegrate(cos(x)*csc(x), x) == log(sin(x)) + assert trigintegrate(cos(x)*tan(x), x) == -cos(x) + assert trigintegrate(cos(x)*cot(x), x) == log(cos(x) - 1)/2 \ + - log(cos(x) + 1)/2 + cos(x) + assert trigintegrate(cot(x)*cos(x)**2, x) == log(sin(x)) - sin(x)**2/2 + + +def test_trigintegrate_symbolic(): + n = Symbol('n', integer=True) + assert trigintegrate(cos(x)**n, x) is None + assert trigintegrate(sin(x)**n, x) is None + assert trigintegrate(cot(x)**n, x) is None diff --git a/lib/python3.12/site-packages/sympy/integrals/transforms.py b/lib/python3.12/site-packages/sympy/integrals/transforms.py new file mode 100644 index 0000000000000000000000000000000000000000..1cd8eefbb3b1fa7749955cf2ad349a35a71debc3 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/transforms.py @@ -0,0 +1,1590 @@ +""" Integral Transforms """ +from functools import reduce, wraps +from itertools import repeat +from sympy.core import S, pi +from sympy.core.add import Add +from sympy.core.function import ( + AppliedUndef, count_ops, expand, expand_mul, Function) +from sympy.core.mul import Mul +from sympy.core.intfunc import igcd, ilcm +from sympy.core.sorting import default_sort_key +from sympy.core.symbol import Dummy +from sympy.core.traversal import postorder_traversal +from sympy.functions.combinatorial.factorials import factorial, rf +from sympy.functions.elementary.complexes import re, arg, Abs +from sympy.functions.elementary.exponential import exp, exp_polar +from sympy.functions.elementary.hyperbolic import cosh, coth, sinh, tanh +from sympy.functions.elementary.integers import ceiling +from sympy.functions.elementary.miscellaneous import Max, Min, sqrt +from sympy.functions.elementary.piecewise import piecewise_fold +from sympy.functions.elementary.trigonometric import cos, cot, sin, tan +from sympy.functions.special.bessel import besselj +from sympy.functions.special.delta_functions import Heaviside +from sympy.functions.special.gamma_functions import gamma +from sympy.functions.special.hyper import meijerg +from sympy.integrals import integrate, Integral +from sympy.integrals.meijerint import _dummy +from sympy.logic.boolalg import to_cnf, conjuncts, disjuncts, Or, And +from sympy.polys.polyroots import roots +from sympy.polys.polytools import factor, Poly +from sympy.polys.rootoftools import CRootOf +from sympy.utilities.iterables import iterable +from sympy.utilities.misc import debug + + +########################################################################## +# Helpers / Utilities +########################################################################## + + +class IntegralTransformError(NotImplementedError): + """ + Exception raised in relation to problems computing transforms. + + Explanation + =========== + + This class is mostly used internally; if integrals cannot be computed + objects representing unevaluated transforms are usually returned. + + The hint ``needeval=True`` can be used to disable returning transform + objects, and instead raise this exception if an integral cannot be + computed. + """ + def __init__(self, transform, function, msg): + super().__init__( + "%s Transform could not be computed: %s." % (transform, msg)) + self.function = function + + +class IntegralTransform(Function): + """ + Base class for integral transforms. + + Explanation + =========== + + This class represents unevaluated transforms. + + To implement a concrete transform, derive from this class and implement + the ``_compute_transform(f, x, s, **hints)`` and ``_as_integral(f, x, s)`` + functions. If the transform cannot be computed, raise :obj:`IntegralTransformError`. + + Also set ``cls._name``. For instance, + + >>> from sympy import LaplaceTransform + >>> LaplaceTransform._name + 'Laplace' + + Implement ``self._collapse_extra`` if your function returns more than just a + number and possibly a convergence condition. + """ + + @property + def function(self): + """ The function to be transformed. """ + return self.args[0] + + @property + def function_variable(self): + """ The dependent variable of the function to be transformed. """ + return self.args[1] + + @property + def transform_variable(self): + """ The independent transform variable. """ + return self.args[2] + + @property + def free_symbols(self): + """ + This method returns the symbols that will exist when the transform + is evaluated. + """ + return self.function.free_symbols.union({self.transform_variable}) \ + - {self.function_variable} + + def _compute_transform(self, f, x, s, **hints): + raise NotImplementedError + + def _as_integral(self, f, x, s): + raise NotImplementedError + + def _collapse_extra(self, extra): + cond = And(*extra) + if cond == False: + raise IntegralTransformError(self.__class__.name, None, '') + return cond + + def _try_directly(self, **hints): + T = None + try_directly = not any(func.has(self.function_variable) + for func in self.function.atoms(AppliedUndef)) + if try_directly: + try: + T = self._compute_transform(self.function, + self.function_variable, self.transform_variable, **hints) + except IntegralTransformError: + debug('[IT _try ] Caught IntegralTransformError, returns None') + T = None + + fn = self.function + if not fn.is_Add: + fn = expand_mul(fn) + return fn, T + + def doit(self, **hints): + """ + Try to evaluate the transform in closed form. + + Explanation + =========== + + This general function handles linearity, but apart from that leaves + pretty much everything to _compute_transform. + + Standard hints are the following: + + - ``simplify``: whether or not to simplify the result + - ``noconds``: if True, do not return convergence conditions + - ``needeval``: if True, raise IntegralTransformError instead of + returning IntegralTransform objects + + The default values of these hints depend on the concrete transform, + usually the default is + ``(simplify, noconds, needeval) = (True, False, False)``. + """ + needeval = hints.pop('needeval', False) + simplify = hints.pop('simplify', True) + hints['simplify'] = simplify + + fn, T = self._try_directly(**hints) + + if T is not None: + return T + + if fn.is_Add: + hints['needeval'] = needeval + res = [self.__class__(*([x] + list(self.args[1:]))).doit(**hints) + for x in fn.args] + extra = [] + ress = [] + for x in res: + if not isinstance(x, tuple): + x = [x] + ress.append(x[0]) + if len(x) == 2: + # only a condition + extra.append(x[1]) + elif len(x) > 2: + # some region parameters and a condition (Mellin, Laplace) + extra += [x[1:]] + if simplify==True: + res = Add(*ress).simplify() + else: + res = Add(*ress) + if not extra: + return res + try: + extra = self._collapse_extra(extra) + if iterable(extra): + return (res,) + tuple(extra) + else: + return (res, extra) + except IntegralTransformError: + pass + + if needeval: + raise IntegralTransformError( + self.__class__._name, self.function, 'needeval') + + # TODO handle derivatives etc + + # pull out constant coefficients + coeff, rest = fn.as_coeff_mul(self.function_variable) + return coeff*self.__class__(*([Mul(*rest)] + list(self.args[1:]))) + + @property + def as_integral(self): + return self._as_integral(self.function, self.function_variable, + self.transform_variable) + + def _eval_rewrite_as_Integral(self, *args, **kwargs): + return self.as_integral + + +def _simplify(expr, doit): + if doit: + from sympy.simplify import simplify + from sympy.simplify.powsimp import powdenest + return simplify(powdenest(piecewise_fold(expr), polar=True)) + return expr + + +def _noconds_(default): + """ + This is a decorator generator for dropping convergence conditions. + + Explanation + =========== + + Suppose you define a function ``transform(*args)`` which returns a tuple of + the form ``(result, cond1, cond2, ...)``. + + Decorating it ``@_noconds_(default)`` will add a new keyword argument + ``noconds`` to it. If ``noconds=True``, the return value will be altered to + be only ``result``, whereas if ``noconds=False`` the return value will not + be altered. + + The default value of the ``noconds`` keyword will be ``default`` (i.e. the + argument of this function). + """ + def make_wrapper(func): + @wraps(func) + def wrapper(*args, noconds=default, **kwargs): + res = func(*args, **kwargs) + if noconds: + return res[0] + return res + return wrapper + return make_wrapper +_noconds = _noconds_(False) + + +########################################################################## +# Mellin Transform +########################################################################## + +def _default_integrator(f, x): + return integrate(f, (x, S.Zero, S.Infinity)) + + +@_noconds +def _mellin_transform(f, x, s_, integrator=_default_integrator, simplify=True): + """ Backend function to compute Mellin transforms. """ + # We use a fresh dummy, because assumptions on s might drop conditions on + # convergence of the integral. + s = _dummy('s', 'mellin-transform', f) + F = integrator(x**(s - 1) * f, x) + + if not F.has(Integral): + return _simplify(F.subs(s, s_), simplify), (S.NegativeInfinity, S.Infinity), S.true + + if not F.is_Piecewise: # XXX can this work if integration gives continuous result now? + raise IntegralTransformError('Mellin', f, 'could not compute integral') + + F, cond = F.args[0] + if F.has(Integral): + raise IntegralTransformError( + 'Mellin', f, 'integral in unexpected form') + + def process_conds(cond): + """ + Turn ``cond`` into a strip (a, b), and auxiliary conditions. + """ + from sympy.solvers.inequalities import _solve_inequality + a = S.NegativeInfinity + b = S.Infinity + aux = S.true + conds = conjuncts(to_cnf(cond)) + t = Dummy('t', real=True) + for c in conds: + a_ = S.Infinity + b_ = S.NegativeInfinity + aux_ = [] + for d in disjuncts(c): + d_ = d.replace( + re, lambda x: x.as_real_imag()[0]).subs(re(s), t) + if not d.is_Relational or \ + d.rel_op in ('==', '!=') \ + or d_.has(s) or not d_.has(t): + aux_ += [d] + continue + soln = _solve_inequality(d_, t) + if not soln.is_Relational or \ + soln.rel_op in ('==', '!='): + aux_ += [d] + continue + if soln.lts == t: + b_ = Max(soln.gts, b_) + else: + a_ = Min(soln.lts, a_) + if a_ is not S.Infinity and a_ != b: + a = Max(a_, a) + elif b_ is not S.NegativeInfinity and b_ != a: + b = Min(b_, b) + else: + aux = And(aux, Or(*aux_)) + return a, b, aux + + conds = [process_conds(c) for c in disjuncts(cond)] + conds = [x for x in conds if x[2] != False] + conds.sort(key=lambda x: (x[0] - x[1], count_ops(x[2]))) + + if not conds: + raise IntegralTransformError('Mellin', f, 'no convergence found') + + a, b, aux = conds[0] + return _simplify(F.subs(s, s_), simplify), (a, b), aux + + +class MellinTransform(IntegralTransform): + """ + Class representing unevaluated Mellin transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute Mellin transforms, see the :func:`mellin_transform` + docstring. + """ + + _name = 'Mellin' + + def _compute_transform(self, f, x, s, **hints): + return _mellin_transform(f, x, s, **hints) + + def _as_integral(self, f, x, s): + return Integral(f*x**(s - 1), (x, S.Zero, S.Infinity)) + + def _collapse_extra(self, extra): + a = [] + b = [] + cond = [] + for (sa, sb), c in extra: + a += [sa] + b += [sb] + cond += [c] + res = (Max(*a), Min(*b)), And(*cond) + if (res[0][0] >= res[0][1]) == True or res[1] == False: + raise IntegralTransformError( + 'Mellin', None, 'no combined convergence.') + return res + + +def mellin_transform(f, x, s, **hints): + r""" + Compute the Mellin transform `F(s)` of `f(x)`, + + .. math :: F(s) = \int_0^\infty x^{s-1} f(x) \mathrm{d}x. + + For all "sensible" functions, this converges absolutely in a strip + `a < \operatorname{Re}(s) < b`. + + Explanation + =========== + + The Mellin transform is related via change of variables to the Fourier + transform, and also to the (bilateral) Laplace transform. + + This function returns ``(F, (a, b), cond)`` + where ``F`` is the Mellin transform of ``f``, ``(a, b)`` is the fundamental strip + (as above), and ``cond`` are auxiliary convergence conditions. + + If the integral cannot be computed in closed form, this function returns + an unevaluated :class:`MellinTransform` object. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. If ``noconds=False``, + then only `F` will be returned (i.e. not ``cond``, and also not the strip + ``(a, b)``). + + Examples + ======== + + >>> from sympy import mellin_transform, exp + >>> from sympy.abc import x, s + >>> mellin_transform(exp(-x), x, s) + (gamma(s), (0, oo), True) + + See Also + ======== + + inverse_mellin_transform, laplace_transform, fourier_transform + hankel_transform, inverse_hankel_transform + """ + return MellinTransform(f, x, s).doit(**hints) + + +def _rewrite_sin(m_n, s, a, b): + """ + Re-write the sine function ``sin(m*s + n)`` as gamma functions, compatible + with the strip (a, b). + + Return ``(gamma1, gamma2, fac)`` so that ``f == fac/(gamma1 * gamma2)``. + + Examples + ======== + + >>> from sympy.integrals.transforms import _rewrite_sin + >>> from sympy import pi, S + >>> from sympy.abc import s + >>> _rewrite_sin((pi, 0), s, 0, 1) + (gamma(s), gamma(1 - s), pi) + >>> _rewrite_sin((pi, 0), s, 1, 0) + (gamma(s - 1), gamma(2 - s), -pi) + >>> _rewrite_sin((pi, 0), s, -1, 0) + (gamma(s + 1), gamma(-s), -pi) + >>> _rewrite_sin((pi, pi/2), s, S(1)/2, S(3)/2) + (gamma(s - 1/2), gamma(3/2 - s), -pi) + >>> _rewrite_sin((pi, pi), s, 0, 1) + (gamma(s), gamma(1 - s), -pi) + >>> _rewrite_sin((2*pi, 0), s, 0, S(1)/2) + (gamma(2*s), gamma(1 - 2*s), pi) + >>> _rewrite_sin((2*pi, 0), s, S(1)/2, 1) + (gamma(2*s - 1), gamma(2 - 2*s), -pi) + """ + # (This is a separate function because it is moderately complicated, + # and I want to doctest it.) + # We want to use pi/sin(pi*x) = gamma(x)*gamma(1-x). + # But there is one comlication: the gamma functions determine the + # inegration contour in the definition of the G-function. Usually + # it would not matter if this is slightly shifted, unless this way + # we create an undefined function! + # So we try to write this in such a way that the gammas are + # eminently on the right side of the strip. + m, n = m_n + + m = expand_mul(m/pi) + n = expand_mul(n/pi) + r = ceiling(-m*a - n.as_real_imag()[0]) # Don't use re(n), does not expand + return gamma(m*s + n + r), gamma(1 - n - r - m*s), (-1)**r*pi + + +class MellinTransformStripError(ValueError): + """ + Exception raised by _rewrite_gamma. Mainly for internal use. + """ + pass + + +def _rewrite_gamma(f, s, a, b): + """ + Try to rewrite the product f(s) as a product of gamma functions, + so that the inverse Mellin transform of f can be expressed as a meijer + G function. + + Explanation + =========== + + Return (an, ap), (bm, bq), arg, exp, fac such that + G((an, ap), (bm, bq), arg/z**exp)*fac is the inverse Mellin transform of f(s). + + Raises IntegralTransformError or MellinTransformStripError on failure. + + It is asserted that f has no poles in the fundamental strip designated by + (a, b). One of a and b is allowed to be None. The fundamental strip is + important, because it determines the inversion contour. + + This function can handle exponentials, linear factors, trigonometric + functions. + + This is a helper function for inverse_mellin_transform that will not + attempt any transformations on f. + + Examples + ======== + + >>> from sympy.integrals.transforms import _rewrite_gamma + >>> from sympy.abc import s + >>> from sympy import oo + >>> _rewrite_gamma(s*(s+3)*(s-1), s, -oo, oo) + (([], [-3, 0, 1]), ([-2, 1, 2], []), 1, 1, -1) + >>> _rewrite_gamma((s-1)**2, s, -oo, oo) + (([], [1, 1]), ([2, 2], []), 1, 1, 1) + + Importance of the fundamental strip: + + >>> _rewrite_gamma(1/s, s, 0, oo) + (([1], []), ([], [0]), 1, 1, 1) + >>> _rewrite_gamma(1/s, s, None, oo) + (([1], []), ([], [0]), 1, 1, 1) + >>> _rewrite_gamma(1/s, s, 0, None) + (([1], []), ([], [0]), 1, 1, 1) + >>> _rewrite_gamma(1/s, s, -oo, 0) + (([], [1]), ([0], []), 1, 1, -1) + >>> _rewrite_gamma(1/s, s, None, 0) + (([], [1]), ([0], []), 1, 1, -1) + >>> _rewrite_gamma(1/s, s, -oo, None) + (([], [1]), ([0], []), 1, 1, -1) + + >>> _rewrite_gamma(2**(-s+3), s, -oo, oo) + (([], []), ([], []), 1/2, 1, 8) + """ + # Our strategy will be as follows: + # 1) Guess a constant c such that the inversion integral should be + # performed wrt s'=c*s (instead of plain s). Write s for s'. + # 2) Process all factors, rewrite them independently as gamma functions in + # argument s, or exponentials of s. + # 3) Try to transform all gamma functions s.t. they have argument + # a+s or a-s. + # 4) Check that the resulting G function parameters are valid. + # 5) Combine all the exponentials. + + a_, b_ = S([a, b]) + + def left(c, is_numer): + """ + Decide whether pole at c lies to the left of the fundamental strip. + """ + # heuristically, this is the best chance for us to solve the inequalities + c = expand(re(c)) + if a_ is None and b_ is S.Infinity: + return True + if a_ is None: + return c < b_ + if b_ is None: + return c <= a_ + if (c >= b_) == True: + return False + if (c <= a_) == True: + return True + if is_numer: + return None + if a_.free_symbols or b_.free_symbols or c.free_symbols: + return None # XXX + #raise IntegralTransformError('Inverse Mellin', f, + # 'Could not determine position of singularity %s' + # ' relative to fundamental strip' % c) + raise MellinTransformStripError('Pole inside critical strip?') + + # 1) + s_multipliers = [] + for g in f.atoms(gamma): + if not g.has(s): + continue + arg = g.args[0] + if arg.is_Add: + arg = arg.as_independent(s)[1] + coeff, _ = arg.as_coeff_mul(s) + s_multipliers += [coeff] + for g in f.atoms(sin, cos, tan, cot): + if not g.has(s): + continue + arg = g.args[0] + if arg.is_Add: + arg = arg.as_independent(s)[1] + coeff, _ = arg.as_coeff_mul(s) + s_multipliers += [coeff/pi] + s_multipliers = [Abs(x) if x.is_extended_real else x for x in s_multipliers] + common_coefficient = S.One + for x in s_multipliers: + if not x.is_Rational: + common_coefficient = x + break + s_multipliers = [x/common_coefficient for x in s_multipliers] + if not (all(x.is_Rational for x in s_multipliers) and + common_coefficient.is_extended_real): + raise IntegralTransformError("Gamma", None, "Nonrational multiplier") + s_multiplier = common_coefficient/reduce(ilcm, [S(x.q) + for x in s_multipliers], S.One) + if s_multiplier == common_coefficient: + if len(s_multipliers) == 0: + s_multiplier = common_coefficient + else: + s_multiplier = common_coefficient \ + *reduce(igcd, [S(x.p) for x in s_multipliers]) + + f = f.subs(s, s/s_multiplier) + fac = S.One/s_multiplier + exponent = S.One/s_multiplier + if a_ is not None: + a_ *= s_multiplier + if b_ is not None: + b_ *= s_multiplier + + # 2) + numer, denom = f.as_numer_denom() + numer = Mul.make_args(numer) + denom = Mul.make_args(denom) + args = list(zip(numer, repeat(True))) + list(zip(denom, repeat(False))) + + facs = [] + dfacs = [] + # *_gammas will contain pairs (a, c) representing Gamma(a*s + c) + numer_gammas = [] + denom_gammas = [] + # exponentials will contain bases for exponentials of s + exponentials = [] + + def exception(fact): + return IntegralTransformError("Inverse Mellin", f, "Unrecognised form '%s'." % fact) + while args: + fact, is_numer = args.pop() + if is_numer: + ugammas, lgammas = numer_gammas, denom_gammas + ufacs = facs + else: + ugammas, lgammas = denom_gammas, numer_gammas + ufacs = dfacs + + def linear_arg(arg): + """ Test if arg is of form a*s+b, raise exception if not. """ + if not arg.is_polynomial(s): + raise exception(fact) + p = Poly(arg, s) + if p.degree() != 1: + raise exception(fact) + return p.all_coeffs() + + # constants + if not fact.has(s): + ufacs += [fact] + # exponentials + elif fact.is_Pow or isinstance(fact, exp): + if fact.is_Pow: + base = fact.base + exp_ = fact.exp + else: + base = exp_polar(1) + exp_ = fact.exp + if exp_.is_Integer: + cond = is_numer + if exp_ < 0: + cond = not cond + args += [(base, cond)]*Abs(exp_) + continue + elif not base.has(s): + a, b = linear_arg(exp_) + if not is_numer: + base = 1/base + exponentials += [base**a] + facs += [base**b] + else: + raise exception(fact) + # linear factors + elif fact.is_polynomial(s): + p = Poly(fact, s) + if p.degree() != 1: + # We completely factor the poly. For this we need the roots. + # Now roots() only works in some cases (low degree), and CRootOf + # only works without parameters. So try both... + coeff = p.LT()[1] + rs = roots(p, s) + if len(rs) != p.degree(): + rs = CRootOf.all_roots(p) + ufacs += [coeff] + args += [(s - c, is_numer) for c in rs] + continue + a, c = p.all_coeffs() + ufacs += [a] + c /= -a + # Now need to convert s - c + if left(c, is_numer): + ugammas += [(S.One, -c + 1)] + lgammas += [(S.One, -c)] + else: + ufacs += [-1] + ugammas += [(S.NegativeOne, c + 1)] + lgammas += [(S.NegativeOne, c)] + elif isinstance(fact, gamma): + a, b = linear_arg(fact.args[0]) + if is_numer: + if (a > 0 and (left(-b/a, is_numer) == False)) or \ + (a < 0 and (left(-b/a, is_numer) == True)): + raise NotImplementedError( + 'Gammas partially over the strip.') + ugammas += [(a, b)] + elif isinstance(fact, sin): + # We try to re-write all trigs as gammas. This is not in + # general the best strategy, since sometimes this is impossible, + # but rewriting as exponentials would work. However trig functions + # in inverse mellin transforms usually all come from simplifying + # gamma terms, so this should work. + a = fact.args[0] + if is_numer: + # No problem with the poles. + gamma1, gamma2, fac_ = gamma(a/pi), gamma(1 - a/pi), pi + else: + gamma1, gamma2, fac_ = _rewrite_sin(linear_arg(a), s, a_, b_) + args += [(gamma1, not is_numer), (gamma2, not is_numer)] + ufacs += [fac_] + elif isinstance(fact, tan): + a = fact.args[0] + args += [(sin(a, evaluate=False), is_numer), + (sin(pi/2 - a, evaluate=False), not is_numer)] + elif isinstance(fact, cos): + a = fact.args[0] + args += [(sin(pi/2 - a, evaluate=False), is_numer)] + elif isinstance(fact, cot): + a = fact.args[0] + args += [(sin(pi/2 - a, evaluate=False), is_numer), + (sin(a, evaluate=False), not is_numer)] + else: + raise exception(fact) + + fac *= Mul(*facs)/Mul(*dfacs) + + # 3) + an, ap, bm, bq = [], [], [], [] + for gammas, plus, minus, is_numer in [(numer_gammas, an, bm, True), + (denom_gammas, bq, ap, False)]: + while gammas: + a, c = gammas.pop() + if a != -1 and a != +1: + # We use the gamma function multiplication theorem. + p = Abs(S(a)) + newa = a/p + newc = c/p + if not a.is_Integer: + raise TypeError("a is not an integer") + for k in range(p): + gammas += [(newa, newc + k/p)] + if is_numer: + fac *= (2*pi)**((1 - p)/2) * p**(c - S.Half) + exponentials += [p**a] + else: + fac /= (2*pi)**((1 - p)/2) * p**(c - S.Half) + exponentials += [p**(-a)] + continue + if a == +1: + plus.append(1 - c) + else: + minus.append(c) + + # 4) + # TODO + + # 5) + arg = Mul(*exponentials) + + # for testability, sort the arguments + an.sort(key=default_sort_key) + ap.sort(key=default_sort_key) + bm.sort(key=default_sort_key) + bq.sort(key=default_sort_key) + + return (an, ap), (bm, bq), arg, exponent, fac + + +@_noconds_(True) +def _inverse_mellin_transform(F, s, x_, strip, as_meijerg=False): + """ A helper for the real inverse_mellin_transform function, this one here + assumes x to be real and positive. """ + x = _dummy('t', 'inverse-mellin-transform', F, positive=True) + # Actually, we won't try integration at all. Instead we use the definition + # of the Meijer G function as a fairly general inverse mellin transform. + F = F.rewrite(gamma) + for g in [factor(F), expand_mul(F), expand(F)]: + if g.is_Add: + # do all terms separately + ress = [_inverse_mellin_transform(G, s, x, strip, as_meijerg, + noconds=False) + for G in g.args] + conds = [p[1] for p in ress] + ress = [p[0] for p in ress] + res = Add(*ress) + if not as_meijerg: + res = factor(res, gens=res.atoms(Heaviside)) + return res.subs(x, x_), And(*conds) + + try: + a, b, C, e, fac = _rewrite_gamma(g, s, strip[0], strip[1]) + except IntegralTransformError: + continue + try: + G = meijerg(a, b, C/x**e) + except ValueError: + continue + if as_meijerg: + h = G + else: + try: + from sympy.simplify import hyperexpand + h = hyperexpand(G) + except NotImplementedError: + raise IntegralTransformError( + 'Inverse Mellin', F, 'Could not calculate integral') + + if h.is_Piecewise and len(h.args) == 3: + # XXX we break modularity here! + h = Heaviside(x - Abs(C))*h.args[0].args[0] \ + + Heaviside(Abs(C) - x)*h.args[1].args[0] + # We must ensure that the integral along the line we want converges, + # and return that value. + # See [L], 5.2 + cond = [Abs(arg(G.argument)) < G.delta*pi] + # Note: we allow ">=" here, this corresponds to convergence if we let + # limits go to oo symmetrically. ">" corresponds to absolute convergence. + cond += [And(Or(len(G.ap) != len(G.bq), 0 >= re(G.nu) + 1), + Abs(arg(G.argument)) == G.delta*pi)] + cond = Or(*cond) + if cond == False: + raise IntegralTransformError( + 'Inverse Mellin', F, 'does not converge') + return (h*fac).subs(x, x_), cond + + raise IntegralTransformError('Inverse Mellin', F, '') + +_allowed = None + + +class InverseMellinTransform(IntegralTransform): + """ + Class representing unevaluated inverse Mellin transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute inverse Mellin transforms, see the + :func:`inverse_mellin_transform` docstring. + """ + + _name = 'Inverse Mellin' + _none_sentinel = Dummy('None') + _c = Dummy('c') + + def __new__(cls, F, s, x, a, b, **opts): + if a is None: + a = InverseMellinTransform._none_sentinel + if b is None: + b = InverseMellinTransform._none_sentinel + return IntegralTransform.__new__(cls, F, s, x, a, b, **opts) + + @property + def fundamental_strip(self): + a, b = self.args[3], self.args[4] + if a is InverseMellinTransform._none_sentinel: + a = None + if b is InverseMellinTransform._none_sentinel: + b = None + return a, b + + def _compute_transform(self, F, s, x, **hints): + # IntegralTransform's doit will cause this hint to exist, but + # InverseMellinTransform should ignore it + hints.pop('simplify', True) + global _allowed + if _allowed is None: + _allowed = { + exp, gamma, sin, cos, tan, cot, cosh, sinh, tanh, coth, + factorial, rf} + for f in postorder_traversal(F): + if f.is_Function and f.has(s) and f.func not in _allowed: + raise IntegralTransformError('Inverse Mellin', F, + 'Component %s not recognised.' % f) + strip = self.fundamental_strip + return _inverse_mellin_transform(F, s, x, strip, **hints) + + def _as_integral(self, F, s, x): + c = self.__class__._c + return Integral(F*x**(-s), (s, c - S.ImaginaryUnit*S.Infinity, c + + S.ImaginaryUnit*S.Infinity))/(2*S.Pi*S.ImaginaryUnit) + + +def inverse_mellin_transform(F, s, x, strip, **hints): + r""" + Compute the inverse Mellin transform of `F(s)` over the fundamental + strip given by ``strip=(a, b)``. + + Explanation + =========== + + This can be defined as + + .. math:: f(x) = \frac{1}{2\pi i} \int_{c - i\infty}^{c + i\infty} x^{-s} F(s) \mathrm{d}s, + + for any `c` in the fundamental strip. Under certain regularity + conditions on `F` and/or `f`, + this recovers `f` from its Mellin transform `F` + (and vice versa), for positive real `x`. + + One of `a` or `b` may be passed as ``None``; a suitable `c` will be + inferred. + + If the integral cannot be computed in closed form, this function returns + an unevaluated :class:`InverseMellinTransform` object. + + Note that this function will assume x to be positive and real, regardless + of the SymPy assumptions! + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + + Examples + ======== + + >>> from sympy import inverse_mellin_transform, oo, gamma + >>> from sympy.abc import x, s + >>> inverse_mellin_transform(gamma(s), s, x, (0, oo)) + exp(-x) + + The fundamental strip matters: + + >>> f = 1/(s**2 - 1) + >>> inverse_mellin_transform(f, s, x, (-oo, -1)) + x*(1 - 1/x**2)*Heaviside(x - 1)/2 + >>> inverse_mellin_transform(f, s, x, (-1, 1)) + -x*Heaviside(1 - x)/2 - Heaviside(x - 1)/(2*x) + >>> inverse_mellin_transform(f, s, x, (1, oo)) + (1/2 - x**2/2)*Heaviside(1 - x)/x + + See Also + ======== + + mellin_transform + hankel_transform, inverse_hankel_transform + """ + return InverseMellinTransform(F, s, x, strip[0], strip[1]).doit(**hints) + + +########################################################################## +# Fourier Transform +########################################################################## + +@_noconds_(True) +def _fourier_transform(f, x, k, a, b, name, simplify=True): + r""" + Compute a general Fourier-type transform + + .. math:: + + F(k) = a \int_{-\infty}^{\infty} e^{bixk} f(x)\, dx. + + For suitable choice of *a* and *b*, this reduces to the standard Fourier + and inverse Fourier transforms. + """ + F = integrate(a*f*exp(b*S.ImaginaryUnit*x*k), (x, S.NegativeInfinity, S.Infinity)) + + if not F.has(Integral): + return _simplify(F, simplify), S.true + + integral_f = integrate(f, (x, S.NegativeInfinity, S.Infinity)) + if integral_f in (S.NegativeInfinity, S.Infinity, S.NaN) or integral_f.has(Integral): + raise IntegralTransformError(name, f, 'function not integrable on real axis') + + if not F.is_Piecewise: + raise IntegralTransformError(name, f, 'could not compute integral') + + F, cond = F.args[0] + if F.has(Integral): + raise IntegralTransformError(name, f, 'integral in unexpected form') + + return _simplify(F, simplify), cond + + +class FourierTypeTransform(IntegralTransform): + """ Base class for Fourier transforms.""" + + def a(self): + raise NotImplementedError( + "Class %s must implement a(self) but does not" % self.__class__) + + def b(self): + raise NotImplementedError( + "Class %s must implement b(self) but does not" % self.__class__) + + def _compute_transform(self, f, x, k, **hints): + return _fourier_transform(f, x, k, + self.a(), self.b(), + self.__class__._name, **hints) + + def _as_integral(self, f, x, k): + a = self.a() + b = self.b() + return Integral(a*f*exp(b*S.ImaginaryUnit*x*k), (x, S.NegativeInfinity, S.Infinity)) + + +class FourierTransform(FourierTypeTransform): + """ + Class representing unevaluated Fourier transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute Fourier transforms, see the :func:`fourier_transform` + docstring. + """ + + _name = 'Fourier' + + def a(self): + return 1 + + def b(self): + return -2*S.Pi + + +def fourier_transform(f, x, k, **hints): + r""" + Compute the unitary, ordinary-frequency Fourier transform of ``f``, defined + as + + .. math:: F(k) = \int_{-\infty}^\infty f(x) e^{-2\pi i x k} \mathrm{d} x. + + Explanation + =========== + + If the transform cannot be computed in closed form, this + function returns an unevaluated :class:`FourierTransform` object. + + For other Fourier transform conventions, see the function + :func:`sympy.integrals.transforms._fourier_transform`. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + Note that for this transform, by default ``noconds=True``. + + Examples + ======== + + >>> from sympy import fourier_transform, exp + >>> from sympy.abc import x, k + >>> fourier_transform(exp(-x**2), x, k) + sqrt(pi)*exp(-pi**2*k**2) + >>> fourier_transform(exp(-x**2), x, k, noconds=False) + (sqrt(pi)*exp(-pi**2*k**2), True) + + See Also + ======== + + inverse_fourier_transform + sine_transform, inverse_sine_transform + cosine_transform, inverse_cosine_transform + hankel_transform, inverse_hankel_transform + mellin_transform, laplace_transform + """ + return FourierTransform(f, x, k).doit(**hints) + + +class InverseFourierTransform(FourierTypeTransform): + """ + Class representing unevaluated inverse Fourier transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute inverse Fourier transforms, see the + :func:`inverse_fourier_transform` docstring. + """ + + _name = 'Inverse Fourier' + + def a(self): + return 1 + + def b(self): + return 2*S.Pi + + +def inverse_fourier_transform(F, k, x, **hints): + r""" + Compute the unitary, ordinary-frequency inverse Fourier transform of `F`, + defined as + + .. math:: f(x) = \int_{-\infty}^\infty F(k) e^{2\pi i x k} \mathrm{d} k. + + Explanation + =========== + + If the transform cannot be computed in closed form, this + function returns an unevaluated :class:`InverseFourierTransform` object. + + For other Fourier transform conventions, see the function + :func:`sympy.integrals.transforms._fourier_transform`. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + Note that for this transform, by default ``noconds=True``. + + Examples + ======== + + >>> from sympy import inverse_fourier_transform, exp, sqrt, pi + >>> from sympy.abc import x, k + >>> inverse_fourier_transform(sqrt(pi)*exp(-(pi*k)**2), k, x) + exp(-x**2) + >>> inverse_fourier_transform(sqrt(pi)*exp(-(pi*k)**2), k, x, noconds=False) + (exp(-x**2), True) + + See Also + ======== + + fourier_transform + sine_transform, inverse_sine_transform + cosine_transform, inverse_cosine_transform + hankel_transform, inverse_hankel_transform + mellin_transform, laplace_transform + """ + return InverseFourierTransform(F, k, x).doit(**hints) + + +########################################################################## +# Fourier Sine and Cosine Transform +########################################################################## + +@_noconds_(True) +def _sine_cosine_transform(f, x, k, a, b, K, name, simplify=True): + """ + Compute a general sine or cosine-type transform + F(k) = a int_0^oo b*sin(x*k) f(x) dx. + F(k) = a int_0^oo b*cos(x*k) f(x) dx. + + For suitable choice of a and b, this reduces to the standard sine/cosine + and inverse sine/cosine transforms. + """ + F = integrate(a*f*K(b*x*k), (x, S.Zero, S.Infinity)) + + if not F.has(Integral): + return _simplify(F, simplify), S.true + + if not F.is_Piecewise: + raise IntegralTransformError(name, f, 'could not compute integral') + + F, cond = F.args[0] + if F.has(Integral): + raise IntegralTransformError(name, f, 'integral in unexpected form') + + return _simplify(F, simplify), cond + + +class SineCosineTypeTransform(IntegralTransform): + """ + Base class for sine and cosine transforms. + Specify cls._kern. + """ + + def a(self): + raise NotImplementedError( + "Class %s must implement a(self) but does not" % self.__class__) + + def b(self): + raise NotImplementedError( + "Class %s must implement b(self) but does not" % self.__class__) + + + def _compute_transform(self, f, x, k, **hints): + return _sine_cosine_transform(f, x, k, + self.a(), self.b(), + self.__class__._kern, + self.__class__._name, **hints) + + def _as_integral(self, f, x, k): + a = self.a() + b = self.b() + K = self.__class__._kern + return Integral(a*f*K(b*x*k), (x, S.Zero, S.Infinity)) + + +class SineTransform(SineCosineTypeTransform): + """ + Class representing unevaluated sine transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute sine transforms, see the :func:`sine_transform` + docstring. + """ + + _name = 'Sine' + _kern = sin + + def a(self): + return sqrt(2)/sqrt(pi) + + def b(self): + return S.One + + +def sine_transform(f, x, k, **hints): + r""" + Compute the unitary, ordinary-frequency sine transform of `f`, defined + as + + .. math:: F(k) = \sqrt{\frac{2}{\pi}} \int_{0}^\infty f(x) \sin(2\pi x k) \mathrm{d} x. + + Explanation + =========== + + If the transform cannot be computed in closed form, this + function returns an unevaluated :class:`SineTransform` object. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + Note that for this transform, by default ``noconds=True``. + + Examples + ======== + + >>> from sympy import sine_transform, exp + >>> from sympy.abc import x, k, a + >>> sine_transform(x*exp(-a*x**2), x, k) + sqrt(2)*k*exp(-k**2/(4*a))/(4*a**(3/2)) + >>> sine_transform(x**(-a), x, k) + 2**(1/2 - a)*k**(a - 1)*gamma(1 - a/2)/gamma(a/2 + 1/2) + + See Also + ======== + + fourier_transform, inverse_fourier_transform + inverse_sine_transform + cosine_transform, inverse_cosine_transform + hankel_transform, inverse_hankel_transform + mellin_transform, laplace_transform + """ + return SineTransform(f, x, k).doit(**hints) + + +class InverseSineTransform(SineCosineTypeTransform): + """ + Class representing unevaluated inverse sine transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute inverse sine transforms, see the + :func:`inverse_sine_transform` docstring. + """ + + _name = 'Inverse Sine' + _kern = sin + + def a(self): + return sqrt(2)/sqrt(pi) + + def b(self): + return S.One + + +def inverse_sine_transform(F, k, x, **hints): + r""" + Compute the unitary, ordinary-frequency inverse sine transform of `F`, + defined as + + .. math:: f(x) = \sqrt{\frac{2}{\pi}} \int_{0}^\infty F(k) \sin(2\pi x k) \mathrm{d} k. + + Explanation + =========== + + If the transform cannot be computed in closed form, this + function returns an unevaluated :class:`InverseSineTransform` object. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + Note that for this transform, by default ``noconds=True``. + + Examples + ======== + + >>> from sympy import inverse_sine_transform, exp, sqrt, gamma + >>> from sympy.abc import x, k, a + >>> inverse_sine_transform(2**((1-2*a)/2)*k**(a - 1)* + ... gamma(-a/2 + 1)/gamma((a+1)/2), k, x) + x**(-a) + >>> inverse_sine_transform(sqrt(2)*k*exp(-k**2/(4*a))/(4*sqrt(a)**3), k, x) + x*exp(-a*x**2) + + See Also + ======== + + fourier_transform, inverse_fourier_transform + sine_transform + cosine_transform, inverse_cosine_transform + hankel_transform, inverse_hankel_transform + mellin_transform, laplace_transform + """ + return InverseSineTransform(F, k, x).doit(**hints) + + +class CosineTransform(SineCosineTypeTransform): + """ + Class representing unevaluated cosine transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute cosine transforms, see the :func:`cosine_transform` + docstring. + """ + + _name = 'Cosine' + _kern = cos + + def a(self): + return sqrt(2)/sqrt(pi) + + def b(self): + return S.One + + +def cosine_transform(f, x, k, **hints): + r""" + Compute the unitary, ordinary-frequency cosine transform of `f`, defined + as + + .. math:: F(k) = \sqrt{\frac{2}{\pi}} \int_{0}^\infty f(x) \cos(2\pi x k) \mathrm{d} x. + + Explanation + =========== + + If the transform cannot be computed in closed form, this + function returns an unevaluated :class:`CosineTransform` object. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + Note that for this transform, by default ``noconds=True``. + + Examples + ======== + + >>> from sympy import cosine_transform, exp, sqrt, cos + >>> from sympy.abc import x, k, a + >>> cosine_transform(exp(-a*x), x, k) + sqrt(2)*a/(sqrt(pi)*(a**2 + k**2)) + >>> cosine_transform(exp(-a*sqrt(x))*cos(a*sqrt(x)), x, k) + a*exp(-a**2/(2*k))/(2*k**(3/2)) + + See Also + ======== + + fourier_transform, inverse_fourier_transform, + sine_transform, inverse_sine_transform + inverse_cosine_transform + hankel_transform, inverse_hankel_transform + mellin_transform, laplace_transform + """ + return CosineTransform(f, x, k).doit(**hints) + + +class InverseCosineTransform(SineCosineTypeTransform): + """ + Class representing unevaluated inverse cosine transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute inverse cosine transforms, see the + :func:`inverse_cosine_transform` docstring. + """ + + _name = 'Inverse Cosine' + _kern = cos + + def a(self): + return sqrt(2)/sqrt(pi) + + def b(self): + return S.One + + +def inverse_cosine_transform(F, k, x, **hints): + r""" + Compute the unitary, ordinary-frequency inverse cosine transform of `F`, + defined as + + .. math:: f(x) = \sqrt{\frac{2}{\pi}} \int_{0}^\infty F(k) \cos(2\pi x k) \mathrm{d} k. + + Explanation + =========== + + If the transform cannot be computed in closed form, this + function returns an unevaluated :class:`InverseCosineTransform` object. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + Note that for this transform, by default ``noconds=True``. + + Examples + ======== + + >>> from sympy import inverse_cosine_transform, sqrt, pi + >>> from sympy.abc import x, k, a + >>> inverse_cosine_transform(sqrt(2)*a/(sqrt(pi)*(a**2 + k**2)), k, x) + exp(-a*x) + >>> inverse_cosine_transform(1/sqrt(k), k, x) + 1/sqrt(x) + + See Also + ======== + + fourier_transform, inverse_fourier_transform, + sine_transform, inverse_sine_transform + cosine_transform + hankel_transform, inverse_hankel_transform + mellin_transform, laplace_transform + """ + return InverseCosineTransform(F, k, x).doit(**hints) + + +########################################################################## +# Hankel Transform +########################################################################## + +@_noconds_(True) +def _hankel_transform(f, r, k, nu, name, simplify=True): + r""" + Compute a general Hankel transform + + .. math:: F_\nu(k) = \int_{0}^\infty f(r) J_\nu(k r) r \mathrm{d} r. + """ + F = integrate(f*besselj(nu, k*r)*r, (r, S.Zero, S.Infinity)) + + if not F.has(Integral): + return _simplify(F, simplify), S.true + + if not F.is_Piecewise: + raise IntegralTransformError(name, f, 'could not compute integral') + + F, cond = F.args[0] + if F.has(Integral): + raise IntegralTransformError(name, f, 'integral in unexpected form') + + return _simplify(F, simplify), cond + + +class HankelTypeTransform(IntegralTransform): + """ + Base class for Hankel transforms. + """ + + def doit(self, **hints): + return self._compute_transform(self.function, + self.function_variable, + self.transform_variable, + self.args[3], + **hints) + + def _compute_transform(self, f, r, k, nu, **hints): + return _hankel_transform(f, r, k, nu, self._name, **hints) + + def _as_integral(self, f, r, k, nu): + return Integral(f*besselj(nu, k*r)*r, (r, S.Zero, S.Infinity)) + + @property + def as_integral(self): + return self._as_integral(self.function, + self.function_variable, + self.transform_variable, + self.args[3]) + + +class HankelTransform(HankelTypeTransform): + """ + Class representing unevaluated Hankel transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute Hankel transforms, see the :func:`hankel_transform` + docstring. + """ + + _name = 'Hankel' + + +def hankel_transform(f, r, k, nu, **hints): + r""" + Compute the Hankel transform of `f`, defined as + + .. math:: F_\nu(k) = \int_{0}^\infty f(r) J_\nu(k r) r \mathrm{d} r. + + Explanation + =========== + + If the transform cannot be computed in closed form, this + function returns an unevaluated :class:`HankelTransform` object. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + Note that for this transform, by default ``noconds=True``. + + Examples + ======== + + >>> from sympy import hankel_transform, inverse_hankel_transform + >>> from sympy import exp + >>> from sympy.abc import r, k, m, nu, a + + >>> ht = hankel_transform(1/r**m, r, k, nu) + >>> ht + 2*k**(m - 2)*gamma(-m/2 + nu/2 + 1)/(2**m*gamma(m/2 + nu/2)) + + >>> inverse_hankel_transform(ht, k, r, nu) + r**(-m) + + >>> ht = hankel_transform(exp(-a*r), r, k, 0) + >>> ht + a/(k**3*(a**2/k**2 + 1)**(3/2)) + + >>> inverse_hankel_transform(ht, k, r, 0) + exp(-a*r) + + See Also + ======== + + fourier_transform, inverse_fourier_transform + sine_transform, inverse_sine_transform + cosine_transform, inverse_cosine_transform + inverse_hankel_transform + mellin_transform, laplace_transform + """ + return HankelTransform(f, r, k, nu).doit(**hints) + + +class InverseHankelTransform(HankelTypeTransform): + """ + Class representing unevaluated inverse Hankel transforms. + + For usage of this class, see the :class:`IntegralTransform` docstring. + + For how to compute inverse Hankel transforms, see the + :func:`inverse_hankel_transform` docstring. + """ + + _name = 'Inverse Hankel' + + +def inverse_hankel_transform(F, k, r, nu, **hints): + r""" + Compute the inverse Hankel transform of `F` defined as + + .. math:: f(r) = \int_{0}^\infty F_\nu(k) J_\nu(k r) k \mathrm{d} k. + + Explanation + =========== + + If the transform cannot be computed in closed form, this + function returns an unevaluated :class:`InverseHankelTransform` object. + + For a description of possible hints, refer to the docstring of + :func:`sympy.integrals.transforms.IntegralTransform.doit`. + Note that for this transform, by default ``noconds=True``. + + Examples + ======== + + >>> from sympy import hankel_transform, inverse_hankel_transform + >>> from sympy import exp + >>> from sympy.abc import r, k, m, nu, a + + >>> ht = hankel_transform(1/r**m, r, k, nu) + >>> ht + 2*k**(m - 2)*gamma(-m/2 + nu/2 + 1)/(2**m*gamma(m/2 + nu/2)) + + >>> inverse_hankel_transform(ht, k, r, nu) + r**(-m) + + >>> ht = hankel_transform(exp(-a*r), r, k, 0) + >>> ht + a/(k**3*(a**2/k**2 + 1)**(3/2)) + + >>> inverse_hankel_transform(ht, k, r, 0) + exp(-a*r) + + See Also + ======== + + fourier_transform, inverse_fourier_transform + sine_transform, inverse_sine_transform + cosine_transform, inverse_cosine_transform + hankel_transform + mellin_transform, laplace_transform + """ + return InverseHankelTransform(F, k, r, nu).doit(**hints) + + +########################################################################## +# Laplace Transform +########################################################################## + +# Stub classes and functions that used to be here +import sympy.integrals.laplace as _laplace + +LaplaceTransform = _laplace.LaplaceTransform +laplace_transform = _laplace.laplace_transform +laplace_correspondence = _laplace.laplace_correspondence +laplace_initial_conds = _laplace.laplace_initial_conds +InverseLaplaceTransform = _laplace.InverseLaplaceTransform +inverse_laplace_transform = _laplace.inverse_laplace_transform diff --git a/lib/python3.12/site-packages/sympy/integrals/trigonometry.py b/lib/python3.12/site-packages/sympy/integrals/trigonometry.py new file mode 100644 index 0000000000000000000000000000000000000000..dd6389bcc79f28ed6c255546685da1a0e061c327 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/integrals/trigonometry.py @@ -0,0 +1,335 @@ +from sympy.core import cacheit, Dummy, Ne, Integer, Rational, S, Wild +from sympy.functions import binomial, sin, cos, Piecewise, Abs +from .integrals import integrate + +# TODO sin(a*x)*cos(b*x) -> sin((a+b)x) + sin((a-b)x) ? + +# creating, each time, Wild's and sin/cos/Mul is expensive. Also, our match & +# subs are very slow when not cached, and if we create Wild each time, we +# effectively block caching. +# +# so we cache the pattern + +# need to use a function instead of lamda since hash of lambda changes on +# each call to _pat_sincos +def _integer_instance(n): + return isinstance(n, Integer) + +@cacheit +def _pat_sincos(x): + a = Wild('a', exclude=[x]) + n, m = [Wild(s, exclude=[x], properties=[_integer_instance]) + for s in 'nm'] + pat = sin(a*x)**n * cos(a*x)**m + return pat, a, n, m + +_u = Dummy('u') + + +def trigintegrate(f, x, conds='piecewise'): + """ + Integrate f = Mul(trig) over x. + + Examples + ======== + + >>> from sympy import sin, cos, tan, sec + >>> from sympy.integrals.trigonometry import trigintegrate + >>> from sympy.abc import x + + >>> trigintegrate(sin(x)*cos(x), x) + sin(x)**2/2 + + >>> trigintegrate(sin(x)**2, x) + x/2 - sin(x)*cos(x)/2 + + >>> trigintegrate(tan(x)*sec(x), x) + 1/cos(x) + + >>> trigintegrate(sin(x)*tan(x), x) + -log(sin(x) - 1)/2 + log(sin(x) + 1)/2 - sin(x) + + References + ========== + + .. [1] https://en.wikibooks.org/wiki/Calculus/Integration_techniques + + See Also + ======== + + sympy.integrals.integrals.Integral.doit + sympy.integrals.integrals.Integral + """ + pat, a, n, m = _pat_sincos(x) + + f = f.rewrite('sincos') + M = f.match(pat) + + if M is None: + return + + n, m = M[n], M[m] + if n.is_zero and m.is_zero: + return x + zz = x if n.is_zero else S.Zero + + a = M[a] + + if n.is_odd or m.is_odd: + u = _u + n_, m_ = n.is_odd, m.is_odd + + # take smallest n or m -- to choose simplest substitution + if n_ and m_: + + # Make sure to choose the positive one + # otherwise an incorrect integral can occur. + if n < 0 and m > 0: + m_ = True + n_ = False + elif m < 0 and n > 0: + n_ = True + m_ = False + # Both are negative so choose the smallest n or m + # in absolute value for simplest substitution. + elif (n < 0 and m < 0): + n_ = n > m + m_ = not (n > m) + + # Both n and m are odd and positive + else: + n_ = (n < m) # NB: careful here, one of the + m_ = not (n < m) # conditions *must* be true + + # n m u=C (n-1)/2 m + # S(x) * C(x) dx --> -(1-u^2) * u du + if n_: + ff = -(1 - u**2)**((n - 1)/2) * u**m + uu = cos(a*x) + + # n m u=S n (m-1)/2 + # S(x) * C(x) dx --> u * (1-u^2) du + elif m_: + ff = u**n * (1 - u**2)**((m - 1)/2) + uu = sin(a*x) + + fi = integrate(ff, u) # XXX cyclic deps + fx = fi.subs(u, uu) + if conds == 'piecewise': + return Piecewise((fx / a, Ne(a, 0)), (zz, True)) + return fx / a + + # n & m are both even + # + # 2k 2m 2l 2l + # we transform S (x) * C (x) into terms with only S (x) or C (x) + # + # example: + # 100 4 100 2 2 100 4 2 + # S (x) * C (x) = S (x) * (1-S (x)) = S (x) * (1 + S (x) - 2*S (x)) + # + # 104 102 100 + # = S (x) - 2*S (x) + S (x) + # 2k + # then S is integrated with recursive formula + + # take largest n or m -- to choose simplest substitution + n_ = (Abs(n) > Abs(m)) + m_ = (Abs(m) > Abs(n)) + res = S.Zero + + if n_: + # 2k 2 k i 2i + # C = (1 - S ) = sum(i, (-) * B(k, i) * S ) + if m > 0: + for i in range(0, m//2 + 1): + res += (S.NegativeOne**i * binomial(m//2, i) * + _sin_pow_integrate(n + 2*i, x)) + + elif m == 0: + res = _sin_pow_integrate(n, x) + else: + + # m < 0 , |n| > |m| + # / + # | + # | m n + # | cos (x) sin (x) dx = + # | + # | + #/ + # / + # | + # -1 m+1 n-1 n - 1 | m+2 n-2 + # ________ cos (x) sin (x) + _______ | cos (x) sin (x) dx + # | + # m + 1 m + 1 | + # / + + res = (Rational(-1, m + 1) * cos(x)**(m + 1) * sin(x)**(n - 1) + + Rational(n - 1, m + 1) * + trigintegrate(cos(x)**(m + 2)*sin(x)**(n - 2), x)) + + elif m_: + # 2k 2 k i 2i + # S = (1 - C ) = sum(i, (-) * B(k, i) * C ) + if n > 0: + + # / / + # | | + # | m n | -m n + # | cos (x)*sin (x) dx or | cos (x) * sin (x) dx + # | | + # / / + # + # |m| > |n| ; m, n >0 ; m, n belong to Z - {0} + # n 2 + # sin (x) term is expanded here in terms of cos (x), + # and then integrated. + # + + for i in range(0, n//2 + 1): + res += (S.NegativeOne**i * binomial(n//2, i) * + _cos_pow_integrate(m + 2*i, x)) + + elif n == 0: + + # / + # | + # | 1 + # | _ _ _ + # | m + # | cos (x) + # / + # + + res = _cos_pow_integrate(m, x) + else: + + # n < 0 , |m| > |n| + # / + # | + # | m n + # | cos (x) sin (x) dx = + # | + # | + #/ + # / + # | + # 1 m-1 n+1 m - 1 | m-2 n+2 + # _______ cos (x) sin (x) + _______ | cos (x) sin (x) dx + # | + # n + 1 n + 1 | + # / + + res = (Rational(1, n + 1) * cos(x)**(m - 1)*sin(x)**(n + 1) + + Rational(m - 1, n + 1) * + trigintegrate(cos(x)**(m - 2)*sin(x)**(n + 2), x)) + + else: + if m == n: + ##Substitute sin(2x)/2 for sin(x)cos(x) and then Integrate. + res = integrate((sin(2*x)*S.Half)**m, x) + elif (m == -n): + if n < 0: + # Same as the scheme described above. + # the function argument to integrate in the end will + # be 1, this cannot be integrated by trigintegrate. + # Hence use sympy.integrals.integrate. + res = (Rational(1, n + 1) * cos(x)**(m - 1) * sin(x)**(n + 1) + + Rational(m - 1, n + 1) * + integrate(cos(x)**(m - 2) * sin(x)**(n + 2), x)) + else: + res = (Rational(-1, m + 1) * cos(x)**(m + 1) * sin(x)**(n - 1) + + Rational(n - 1, m + 1) * + integrate(cos(x)**(m + 2)*sin(x)**(n - 2), x)) + if conds == 'piecewise': + return Piecewise((res.subs(x, a*x) / a, Ne(a, 0)), (zz, True)) + return res.subs(x, a*x) / a + + +def _sin_pow_integrate(n, x): + if n > 0: + if n == 1: + #Recursion break + return -cos(x) + + # n > 0 + # / / + # | | + # | n -1 n-1 n - 1 | n-2 + # | sin (x) dx = ______ cos (x) sin (x) + _______ | sin (x) dx + # | | + # | n n | + #/ / + # + # + + return (Rational(-1, n) * cos(x) * sin(x)**(n - 1) + + Rational(n - 1, n) * _sin_pow_integrate(n - 2, x)) + + if n < 0: + if n == -1: + ##Make sure this does not come back here again. + ##Recursion breaks here or at n==0. + return trigintegrate(1/sin(x), x) + + # n < 0 + # / / + # | | + # | n 1 n+1 n + 2 | n+2 + # | sin (x) dx = _______ cos (x) sin (x) + _______ | sin (x) dx + # | | + # | n + 1 n + 1 | + #/ / + # + + return (Rational(1, n + 1) * cos(x) * sin(x)**(n + 1) + + Rational(n + 2, n + 1) * _sin_pow_integrate(n + 2, x)) + + else: + #n == 0 + #Recursion break. + return x + + +def _cos_pow_integrate(n, x): + if n > 0: + if n == 1: + #Recursion break. + return sin(x) + + # n > 0 + # / / + # | | + # | n 1 n-1 n - 1 | n-2 + # | sin (x) dx = ______ sin (x) cos (x) + _______ | cos (x) dx + # | | + # | n n | + #/ / + # + + return (Rational(1, n) * sin(x) * cos(x)**(n - 1) + + Rational(n - 1, n) * _cos_pow_integrate(n - 2, x)) + + if n < 0: + if n == -1: + ##Recursion break + return trigintegrate(1/cos(x), x) + + # n < 0 + # / / + # | | + # | n -1 n+1 n + 2 | n+2 + # | cos (x) dx = _______ sin (x) cos (x) + _______ | cos (x) dx + # | | + # | n + 1 n + 1 | + #/ / + # + + return (Rational(-1, n + 1) * sin(x) * cos(x)**(n + 1) + + Rational(n + 2, n + 1) * _cos_pow_integrate(n + 2, x)) + else: + # n == 0 + #Recursion Break. + return x diff --git a/lib/python3.12/site-packages/sympy/solvers/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b868b9eb485be78b32d2d63749172fbe327c388 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/__pycache__/__init__.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/__pycache__/bivariate.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/__pycache__/bivariate.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e4b6a1b2b21cbeba486aca74c03daf8924c6c84 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/__pycache__/bivariate.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/__pycache__/decompogen.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/__pycache__/decompogen.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56a5c33f86d88f2322295ee9a83db77475ba15e3 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/__pycache__/decompogen.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/__pycache__/deutils.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/__pycache__/deutils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4c42d95ec2faa573b5dd957a29f31e712ae691f Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/__pycache__/deutils.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/__pycache__/inequalities.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/__pycache__/inequalities.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed45df241bcdebe61f23d008ba129cdf03830197 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/__pycache__/inequalities.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/__pycache__/pde.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/__pycache__/pde.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b64b2e4d0c562f047978678acd4a1e0d82b9066 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/__pycache__/pde.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/__pycache__/polysys.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/__pycache__/polysys.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74b03da7c3b81d1599cbd59f26c8ed164aafdf77 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/__pycache__/polysys.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/__pycache__/recurr.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/__pycache__/recurr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8a37968b94054bf2906c171966759354f5911f6 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/__pycache__/recurr.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/__pycache__/simplex.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/__pycache__/simplex.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f542265aad5daea34cc6f978846ec3286815ce3 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/__pycache__/simplex.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/benchmarks/__init__.py b/lib/python3.12/site-packages/sympy/solvers/benchmarks/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/lib/python3.12/site-packages/sympy/solvers/benchmarks/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/benchmarks/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8da0fcf745bc39c4607464f38d1a5bea758cab7 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/benchmarks/__pycache__/__init__.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/benchmarks/__pycache__/bench_solvers.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/benchmarks/__pycache__/bench_solvers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..98028776ca79c363b63f58c93b56ae9092b83116 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/benchmarks/__pycache__/bench_solvers.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/benchmarks/bench_solvers.py b/lib/python3.12/site-packages/sympy/solvers/benchmarks/bench_solvers.py new file mode 100644 index 0000000000000000000000000000000000000000..d18102873f7efcde1d111e0e8eca12e208f94663 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/benchmarks/bench_solvers.py @@ -0,0 +1,12 @@ +from sympy.core.symbol import Symbol +from sympy.matrices.dense import (eye, zeros) +from sympy.solvers.solvers import solve_linear_system + +N = 8 +M = zeros(N, N + 1) +M[:, :N] = eye(N) +S = [Symbol('A%i' % i) for i in range(N)] + + +def timeit_linsolve_trivial(): + solve_linear_system(M, *S) diff --git a/lib/python3.12/site-packages/sympy/solvers/diophantine/__init__.py b/lib/python3.12/site-packages/sympy/solvers/diophantine/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..23c21242208d6f520c130250ecdce43382b9d868 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/diophantine/__init__.py @@ -0,0 +1,5 @@ +from .diophantine import diophantine, classify_diop, diop_solve + +__all__ = [ + 'diophantine', 'classify_diop', 'diop_solve' +] diff --git a/lib/python3.12/site-packages/sympy/solvers/diophantine/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/diophantine/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0fe99d0d77cc991f0bacae0ec8991e53b77bba7e Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/diophantine/__pycache__/__init__.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/diophantine/diophantine.py b/lib/python3.12/site-packages/sympy/solvers/diophantine/diophantine.py new file mode 100644 index 0000000000000000000000000000000000000000..3df4fe9b0df137828233a9243d2e1e604af309fd --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/diophantine/diophantine.py @@ -0,0 +1,3960 @@ +from __future__ import annotations + +from sympy.core.add import Add +from sympy.core.assumptions import check_assumptions +from sympy.core.containers import Tuple +from sympy.core.exprtools import factor_terms +from sympy.core.function import _mexpand +from sympy.core.mul import Mul +from sympy.core.numbers import Rational, int_valued +from sympy.core.intfunc import igcdex, ilcm, igcd, integer_nthroot, isqrt +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.sorting import default_sort_key, ordered +from sympy.core.symbol import Symbol, symbols +from sympy.core.sympify import _sympify +from sympy.external.gmpy import jacobi, remove, invert, iroot +from sympy.functions.elementary.complexes import sign +from sympy.functions.elementary.integers import floor +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.matrices.dense import MutableDenseMatrix as Matrix +from sympy.ntheory.factor_ import divisors, factorint, perfect_power +from sympy.ntheory.generate import nextprime +from sympy.ntheory.primetest import is_square, isprime +from sympy.ntheory.modular import symmetric_residue +from sympy.ntheory.residue_ntheory import sqrt_mod, sqrt_mod_iter +from sympy.polys.polyerrors import GeneratorsNeeded +from sympy.polys.polytools import Poly, factor_list +from sympy.simplify.simplify import signsimp +from sympy.solvers.solveset import solveset_real +from sympy.utilities import numbered_symbols +from sympy.utilities.misc import as_int, filldedent +from sympy.utilities.iterables import (is_sequence, subsets, permute_signs, + signed_permutations, ordered_partitions) + + +# these are imported with 'from sympy.solvers.diophantine import * +__all__ = ['diophantine', 'classify_diop'] + + +class DiophantineSolutionSet(set): + """ + Container for a set of solutions to a particular diophantine equation. + + The base representation is a set of tuples representing each of the solutions. + + Parameters + ========== + + symbols : list + List of free symbols in the original equation. + parameters: list + List of parameters to be used in the solution. + + Examples + ======== + + Adding solutions: + + >>> from sympy.solvers.diophantine.diophantine import DiophantineSolutionSet + >>> from sympy.abc import x, y, t, u + >>> s1 = DiophantineSolutionSet([x, y], [t, u]) + >>> s1 + set() + >>> s1.add((2, 3)) + >>> s1.add((-1, u)) + >>> s1 + {(-1, u), (2, 3)} + >>> s2 = DiophantineSolutionSet([x, y], [t, u]) + >>> s2.add((3, 4)) + >>> s1.update(*s2) + >>> s1 + {(-1, u), (2, 3), (3, 4)} + + Conversion of solutions into dicts: + + >>> list(s1.dict_iterator()) + [{x: -1, y: u}, {x: 2, y: 3}, {x: 3, y: 4}] + + Substituting values: + + >>> s3 = DiophantineSolutionSet([x, y], [t, u]) + >>> s3.add((t**2, t + u)) + >>> s3 + {(t**2, t + u)} + >>> s3.subs({t: 2, u: 3}) + {(4, 5)} + >>> s3.subs(t, -1) + {(1, u - 1)} + >>> s3.subs(t, 3) + {(9, u + 3)} + + Evaluation at specific values. Positional arguments are given in the same order as the parameters: + + >>> s3(-2, 3) + {(4, 1)} + >>> s3(5) + {(25, u + 5)} + >>> s3(None, 2) + {(t**2, t + 2)} + """ + + def __init__(self, symbols_seq, parameters): + super().__init__() + + if not is_sequence(symbols_seq): + raise ValueError("Symbols must be given as a sequence.") + + if not is_sequence(parameters): + raise ValueError("Parameters must be given as a sequence.") + + self.symbols = tuple(symbols_seq) + self.parameters = tuple(parameters) + + def add(self, solution): + if len(solution) != len(self.symbols): + raise ValueError("Solution should have a length of %s, not %s" % (len(self.symbols), len(solution))) + # make solution canonical wrt sign (i.e. no -x unless x is also present as an arg) + args = set(solution) + for i in range(len(solution)): + x = solution[i] + if not type(x) is int and (-x).is_Symbol and -x not in args: + solution = [_.subs(-x, x) for _ in solution] + super().add(Tuple(*solution)) + + def update(self, *solutions): + for solution in solutions: + self.add(solution) + + def dict_iterator(self): + for solution in ordered(self): + yield dict(zip(self.symbols, solution)) + + def subs(self, *args, **kwargs): + result = DiophantineSolutionSet(self.symbols, self.parameters) + for solution in self: + result.add(solution.subs(*args, **kwargs)) + return result + + def __call__(self, *args): + if len(args) > len(self.parameters): + raise ValueError("Evaluation should have at most %s values, not %s" % (len(self.parameters), len(args))) + rep = {p: v for p, v in zip(self.parameters, args) if v is not None} + return self.subs(rep) + + +class DiophantineEquationType: + """ + Internal representation of a particular diophantine equation type. + + Parameters + ========== + + equation : + The diophantine equation that is being solved. + free_symbols : list (optional) + The symbols being solved for. + + Attributes + ========== + + total_degree : + The maximum of the degrees of all terms in the equation + homogeneous : + Does the equation contain a term of degree 0 + homogeneous_order : + Does the equation contain any coefficient that is in the symbols being solved for + dimension : + The number of symbols being solved for + """ + name = None # type: str + + def __init__(self, equation, free_symbols=None): + self.equation = _sympify(equation).expand(force=True) + + if free_symbols is not None: + self.free_symbols = free_symbols + else: + self.free_symbols = list(self.equation.free_symbols) + self.free_symbols.sort(key=default_sort_key) + + if not self.free_symbols: + raise ValueError('equation should have 1 or more free symbols') + + self.coeff = self.equation.as_coefficients_dict() + if not all(int_valued(c) for c in self.coeff.values()): + raise TypeError("Coefficients should be Integers") + + self.total_degree = Poly(self.equation).total_degree() + self.homogeneous = 1 not in self.coeff + self.homogeneous_order = not (set(self.coeff) & set(self.free_symbols)) + self.dimension = len(self.free_symbols) + self._parameters = None + + def matches(self): + """ + Determine whether the given equation can be matched to the particular equation type. + """ + return False + + @property + def n_parameters(self): + return self.dimension + + @property + def parameters(self): + if self._parameters is None: + self._parameters = symbols('t_:%i' % (self.n_parameters,), integer=True) + return self._parameters + + def solve(self, parameters=None, limit=None) -> DiophantineSolutionSet: + raise NotImplementedError('No solver has been written for %s.' % self.name) + + def pre_solve(self, parameters=None): + if not self.matches(): + raise ValueError("This equation does not match the %s equation type." % self.name) + + if parameters is not None: + if len(parameters) != self.n_parameters: + raise ValueError("Expected %s parameter(s) but got %s" % (self.n_parameters, len(parameters))) + + self._parameters = parameters + + +class Univariate(DiophantineEquationType): + """ + Representation of a univariate diophantine equation. + + A univariate diophantine equation is an equation of the form + `a_{0} + a_{1}x + a_{2}x^2 + .. + a_{n}x^n = 0` where `a_{1}, a_{2}, ..a_{n}` are + integer constants and `x` is an integer variable. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import Univariate + >>> from sympy.abc import x + >>> Univariate((x - 2)*(x - 3)**2).solve() # solves equation (x - 2)*(x - 3)**2 == 0 + {(2,), (3,)} + + """ + + name = 'univariate' + + def matches(self): + return self.dimension == 1 + + def solve(self, parameters=None, limit=None): + self.pre_solve(parameters) + + result = DiophantineSolutionSet(self.free_symbols, parameters=self.parameters) + for i in solveset_real(self.equation, self.free_symbols[0]).intersect(S.Integers): + result.add((i,)) + return result + + +class Linear(DiophantineEquationType): + """ + Representation of a linear diophantine equation. + + A linear diophantine equation is an equation of the form `a_{1}x_{1} + + a_{2}x_{2} + .. + a_{n}x_{n} = 0` where `a_{1}, a_{2}, ..a_{n}` are + integer constants and `x_{1}, x_{2}, ..x_{n}` are integer variables. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import Linear + >>> from sympy.abc import x, y, z + >>> l1 = Linear(2*x - 3*y - 5) + >>> l1.matches() # is this equation linear + True + >>> l1.solve() # solves equation 2*x - 3*y - 5 == 0 + {(3*t_0 - 5, 2*t_0 - 5)} + + Here x = -3*t_0 - 5 and y = -2*t_0 - 5 + + >>> Linear(2*x - 3*y - 4*z -3).solve() + {(t_0, 2*t_0 + 4*t_1 + 3, -t_0 - 3*t_1 - 3)} + + """ + + name = 'linear' + + def matches(self): + return self.total_degree == 1 + + def solve(self, parameters=None, limit=None): + self.pre_solve(parameters) + + coeff = self.coeff + var = self.free_symbols + + if 1 in coeff: + # negate coeff[] because input is of the form: ax + by + c == 0 + # but is used as: ax + by == -c + c = -coeff[1] + else: + c = 0 + + result = DiophantineSolutionSet(var, parameters=self.parameters) + params = result.parameters + + if len(var) == 1: + q, r = divmod(c, coeff[var[0]]) + if not r: + result.add((q,)) + return result + + ''' + base_solution_linear() can solve diophantine equations of the form: + + a*x + b*y == c + + We break down multivariate linear diophantine equations into a + series of bivariate linear diophantine equations which can then + be solved individually by base_solution_linear(). + + Consider the following: + + a_0*x_0 + a_1*x_1 + a_2*x_2 == c + + which can be re-written as: + + a_0*x_0 + g_0*y_0 == c + + where + + g_0 == gcd(a_1, a_2) + + and + + y == (a_1*x_1)/g_0 + (a_2*x_2)/g_0 + + This leaves us with two binary linear diophantine equations. + For the first equation: + + a == a_0 + b == g_0 + c == c + + For the second: + + a == a_1/g_0 + b == a_2/g_0 + c == the solution we find for y_0 in the first equation. + + The arrays A and B are the arrays of integers used for + 'a' and 'b' in each of the n-1 bivariate equations we solve. + ''' + + A = [coeff[v] for v in var] + B = [] + if len(var) > 2: + B.append(igcd(A[-2], A[-1])) + A[-2] = A[-2] // B[0] + A[-1] = A[-1] // B[0] + for i in range(len(A) - 3, 0, -1): + gcd = igcd(B[0], A[i]) + B[0] = B[0] // gcd + A[i] = A[i] // gcd + B.insert(0, gcd) + B.append(A[-1]) + + ''' + Consider the trivariate linear equation: + + 4*x_0 + 6*x_1 + 3*x_2 == 2 + + This can be re-written as: + + 4*x_0 + 3*y_0 == 2 + + where + + y_0 == 2*x_1 + x_2 + (Note that gcd(3, 6) == 3) + + The complete integral solution to this equation is: + + x_0 == 2 + 3*t_0 + y_0 == -2 - 4*t_0 + + where 't_0' is any integer. + + Now that we have a solution for 'x_0', find 'x_1' and 'x_2': + + 2*x_1 + x_2 == -2 - 4*t_0 + + We can then solve for '-2' and '-4' independently, + and combine the results: + + 2*x_1a + x_2a == -2 + x_1a == 0 + t_0 + x_2a == -2 - 2*t_0 + + 2*x_1b + x_2b == -4*t_0 + x_1b == 0*t_0 + t_1 + x_2b == -4*t_0 - 2*t_1 + + ==> + + x_1 == t_0 + t_1 + x_2 == -2 - 6*t_0 - 2*t_1 + + where 't_0' and 't_1' are any integers. + + Note that: + + 4*(2 + 3*t_0) + 6*(t_0 + t_1) + 3*(-2 - 6*t_0 - 2*t_1) == 2 + + for any integral values of 't_0', 't_1'; as required. + + This method is generalised for many variables, below. + + ''' + solutions = [] + for Ai, Bi in zip(A, B): + tot_x, tot_y = [], [] + + for arg in Add.make_args(c): + if arg.is_Integer: + # example: 5 -> k = 5 + k, p = arg, S.One + pnew = params[0] + else: # arg is a Mul or Symbol + # example: 3*t_1 -> k = 3 + # example: t_0 -> k = 1 + k, p = arg.as_coeff_Mul() + pnew = params[params.index(p) + 1] + + sol = sol_x, sol_y = base_solution_linear(k, Ai, Bi, pnew) + + if p is S.One: + if None in sol: + return result + else: + # convert a + b*pnew -> a*p + b*pnew + if isinstance(sol_x, Add): + sol_x = sol_x.args[0]*p + sol_x.args[1] + if isinstance(sol_y, Add): + sol_y = sol_y.args[0]*p + sol_y.args[1] + + tot_x.append(sol_x) + tot_y.append(sol_y) + + solutions.append(Add(*tot_x)) + c = Add(*tot_y) + + solutions.append(c) + result.add(solutions) + return result + + +class BinaryQuadratic(DiophantineEquationType): + """ + Representation of a binary quadratic diophantine equation. + + A binary quadratic diophantine equation is an equation of the + form `Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0`, where `A, B, C, D, E, + F` are integer constants and `x` and `y` are integer variables. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy.solvers.diophantine.diophantine import BinaryQuadratic + >>> b1 = BinaryQuadratic(x**3 + y**2 + 1) + >>> b1.matches() + False + >>> b2 = BinaryQuadratic(x**2 + y**2 + 2*x + 2*y + 2) + >>> b2.matches() + True + >>> b2.solve() + {(-1, -1)} + + References + ========== + + .. [1] Methods to solve Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0, [online], + Available: https://www.alpertron.com.ar/METHODS.HTM + .. [2] Solving the equation ax^2+ bxy + cy^2 + dx + ey + f= 0, [online], + Available: https://web.archive.org/web/20160323033111/http://www.jpr2718.org/ax2p.pdf + + """ + + name = 'binary_quadratic' + + def matches(self): + return self.total_degree == 2 and self.dimension == 2 + + def solve(self, parameters=None, limit=None) -> DiophantineSolutionSet: + self.pre_solve(parameters) + + var = self.free_symbols + coeff = self.coeff + + x, y = var + + A = coeff[x**2] + B = coeff[x*y] + C = coeff[y**2] + D = coeff[x] + E = coeff[y] + F = coeff[S.One] + + A, B, C, D, E, F = [as_int(i) for i in _remove_gcd(A, B, C, D, E, F)] + + # (1) Simple-Hyperbolic case: A = C = 0, B != 0 + # In this case equation can be converted to (Bx + E)(By + D) = DE - BF + # We consider two cases; DE - BF = 0 and DE - BF != 0 + # More details, https://www.alpertron.com.ar/METHODS.HTM#SHyperb + + result = DiophantineSolutionSet(var, self.parameters) + t, u = result.parameters + + discr = B**2 - 4*A*C + if A == 0 and C == 0 and B != 0: + + if D*E - B*F == 0: + q, r = divmod(E, B) + if not r: + result.add((-q, t)) + q, r = divmod(D, B) + if not r: + result.add((t, -q)) + else: + div = divisors(D*E - B*F) + div = div + [-term for term in div] + for d in div: + x0, r = divmod(d - E, B) + if not r: + q, r = divmod(D*E - B*F, d) + if not r: + y0, r = divmod(q - D, B) + if not r: + result.add((x0, y0)) + + # (2) Parabolic case: B**2 - 4*A*C = 0 + # There are two subcases to be considered in this case. + # sqrt(c)D - sqrt(a)E = 0 and sqrt(c)D - sqrt(a)E != 0 + # More Details, https://www.alpertron.com.ar/METHODS.HTM#Parabol + + elif discr == 0: + + if A == 0: + s = BinaryQuadratic(self.equation, free_symbols=[y, x]).solve(parameters=[t, u]) + for soln in s: + result.add((soln[1], soln[0])) + + else: + g = sign(A)*igcd(A, C) + a = A // g + c = C // g + e = sign(B / A) + + sqa = isqrt(a) + sqc = isqrt(c) + _c = e*sqc*D - sqa*E + if not _c: + z = Symbol("z", real=True) + eq = sqa*g*z**2 + D*z + sqa*F + roots = solveset_real(eq, z).intersect(S.Integers) + for root in roots: + ans = diop_solve(sqa*x + e*sqc*y - root) + result.add((ans[0], ans[1])) + + elif int_valued(c): + solve_x = lambda u: -e*sqc*g*_c*t**2 - (E + 2*e*sqc*g*u)*t \ + - (e*sqc*g*u**2 + E*u + e*sqc*F) // _c + + solve_y = lambda u: sqa*g*_c*t**2 + (D + 2*sqa*g*u)*t \ + + (sqa*g*u**2 + D*u + sqa*F) // _c + + for z0 in range(0, abs(_c)): + # Check if the coefficients of y and x obtained are integers or not + if (divisible(sqa*g*z0**2 + D*z0 + sqa*F, _c) and + divisible(e*sqc*g*z0**2 + E*z0 + e*sqc*F, _c)): + result.add((solve_x(z0), solve_y(z0))) + + # (3) Method used when B**2 - 4*A*C is a square, is described in p. 6 of the below paper + # by John P. Robertson. + # https://web.archive.org/web/20160323033111/http://www.jpr2718.org/ax2p.pdf + + elif is_square(discr): + if A != 0: + r = sqrt(discr) + u, v = symbols("u, v", integer=True) + eq = _mexpand( + 4*A*r*u*v + 4*A*D*(B*v + r*u + r*v - B*u) + + 2*A*4*A*E*(u - v) + 4*A*r*4*A*F) + + solution = diop_solve(eq, t) + + for s0, t0 in solution: + + num = B*t0 + r*s0 + r*t0 - B*s0 + x_0 = S(num) / (4*A*r) + y_0 = S(s0 - t0) / (2*r) + if isinstance(s0, Symbol) or isinstance(t0, Symbol): + if len(check_param(x_0, y_0, 4*A*r, parameters)) > 0: + ans = check_param(x_0, y_0, 4*A*r, parameters) + result.update(*ans) + elif x_0.is_Integer and y_0.is_Integer: + if is_solution_quad(var, coeff, x_0, y_0): + result.add((x_0, y_0)) + + else: + s = BinaryQuadratic(self.equation, free_symbols=var[::-1]).solve(parameters=[t, u]) # Interchange x and y + while s: + result.add(s.pop()[::-1]) # and solution <--------+ + + # (4) B**2 - 4*A*C > 0 and B**2 - 4*A*C not a square or B**2 - 4*A*C < 0 + + else: + + P, Q = _transformation_to_DN(var, coeff) + D, N = _find_DN(var, coeff) + solns_pell = diop_DN(D, N) + + if D < 0: + for x0, y0 in solns_pell: + for x in [-x0, x0]: + for y in [-y0, y0]: + s = P*Matrix([x, y]) + Q + try: + result.add([as_int(_) for _ in s]) + except ValueError: + pass + else: + # In this case equation can be transformed into a Pell equation + + solns_pell = set(solns_pell) + solns_pell.update((-X, -Y) for X, Y in list(solns_pell)) + + a = diop_DN(D, 1) + T = a[0][0] + U = a[0][1] + + if all(int_valued(_) for _ in P[:4] + Q[:2]): + for r, s in solns_pell: + _a = (r + s*sqrt(D))*(T + U*sqrt(D))**t + _b = (r - s*sqrt(D))*(T - U*sqrt(D))**t + x_n = _mexpand(S(_a + _b) / 2) + y_n = _mexpand(S(_a - _b) / (2*sqrt(D))) + s = P*Matrix([x_n, y_n]) + Q + result.add(s) + + else: + L = ilcm(*[_.q for _ in P[:4] + Q[:2]]) + + k = 1 + + T_k = T + U_k = U + + while (T_k - 1) % L != 0 or U_k % L != 0: + T_k, U_k = T_k*T + D*U_k*U, T_k*U + U_k*T + k += 1 + + for X, Y in solns_pell: + + for i in range(k): + if all(int_valued(_) for _ in P*Matrix([X, Y]) + Q): + _a = (X + sqrt(D)*Y)*(T_k + sqrt(D)*U_k)**t + _b = (X - sqrt(D)*Y)*(T_k - sqrt(D)*U_k)**t + Xt = S(_a + _b) / 2 + Yt = S(_a - _b) / (2*sqrt(D)) + s = P*Matrix([Xt, Yt]) + Q + result.add(s) + + X, Y = X*T + D*U*Y, X*U + Y*T + + return result + + +class InhomogeneousTernaryQuadratic(DiophantineEquationType): + """ + + Representation of an inhomogeneous ternary quadratic. + + No solver is currently implemented for this equation type. + + """ + + name = 'inhomogeneous_ternary_quadratic' + + def matches(self): + if not (self.total_degree == 2 and self.dimension == 3): + return False + if not self.homogeneous: + return False + return not self.homogeneous_order + + +class HomogeneousTernaryQuadraticNormal(DiophantineEquationType): + """ + Representation of a homogeneous ternary quadratic normal diophantine equation. + + Examples + ======== + + >>> from sympy.abc import x, y, z + >>> from sympy.solvers.diophantine.diophantine import HomogeneousTernaryQuadraticNormal + >>> HomogeneousTernaryQuadraticNormal(4*x**2 - 5*y**2 + z**2).solve() + {(1, 2, 4)} + + """ + + name = 'homogeneous_ternary_quadratic_normal' + + def matches(self): + if not (self.total_degree == 2 and self.dimension == 3): + return False + if not self.homogeneous: + return False + if not self.homogeneous_order: + return False + + nonzero = [k for k in self.coeff if self.coeff[k]] + return len(nonzero) == 3 and all(i**2 in nonzero for i in self.free_symbols) + + def solve(self, parameters=None, limit=None) -> DiophantineSolutionSet: + self.pre_solve(parameters) + + var = self.free_symbols + coeff = self.coeff + + x, y, z = var + + a = coeff[x**2] + b = coeff[y**2] + c = coeff[z**2] + + (sqf_of_a, sqf_of_b, sqf_of_c), (a_1, b_1, c_1), (a_2, b_2, c_2) = \ + sqf_normal(a, b, c, steps=True) + + A = -a_2*c_2 + B = -b_2*c_2 + + result = DiophantineSolutionSet(var, parameters=self.parameters) + + # If following two conditions are satisfied then there are no solutions + if A < 0 and B < 0: + return result + + if ( + sqrt_mod(-b_2*c_2, a_2) is None or + sqrt_mod(-c_2*a_2, b_2) is None or + sqrt_mod(-a_2*b_2, c_2) is None): + return result + + z_0, x_0, y_0 = descent(A, B) + + z_0, q = _rational_pq(z_0, abs(c_2)) + x_0 *= q + y_0 *= q + + x_0, y_0, z_0 = _remove_gcd(x_0, y_0, z_0) + + # Holzer reduction + if sign(a) == sign(b): + x_0, y_0, z_0 = holzer(x_0, y_0, z_0, abs(a_2), abs(b_2), abs(c_2)) + elif sign(a) == sign(c): + x_0, z_0, y_0 = holzer(x_0, z_0, y_0, abs(a_2), abs(c_2), abs(b_2)) + else: + y_0, z_0, x_0 = holzer(y_0, z_0, x_0, abs(b_2), abs(c_2), abs(a_2)) + + x_0 = reconstruct(b_1, c_1, x_0) + y_0 = reconstruct(a_1, c_1, y_0) + z_0 = reconstruct(a_1, b_1, z_0) + + sq_lcm = ilcm(sqf_of_a, sqf_of_b, sqf_of_c) + + x_0 = abs(x_0*sq_lcm // sqf_of_a) + y_0 = abs(y_0*sq_lcm // sqf_of_b) + z_0 = abs(z_0*sq_lcm // sqf_of_c) + + result.add(_remove_gcd(x_0, y_0, z_0)) + return result + + +class HomogeneousTernaryQuadratic(DiophantineEquationType): + """ + Representation of a homogeneous ternary quadratic diophantine equation. + + Examples + ======== + + >>> from sympy.abc import x, y, z + >>> from sympy.solvers.diophantine.diophantine import HomogeneousTernaryQuadratic + >>> HomogeneousTernaryQuadratic(x**2 + y**2 - 3*z**2 + x*y).solve() + {(-1, 2, 1)} + >>> HomogeneousTernaryQuadratic(3*x**2 + y**2 - 3*z**2 + 5*x*y + y*z).solve() + {(3, 12, 13)} + + """ + + name = 'homogeneous_ternary_quadratic' + + def matches(self): + if not (self.total_degree == 2 and self.dimension == 3): + return False + if not self.homogeneous: + return False + if not self.homogeneous_order: + return False + + nonzero = [k for k in self.coeff if self.coeff[k]] + return not (len(nonzero) == 3 and all(i**2 in nonzero for i in self.free_symbols)) + + def solve(self, parameters=None, limit=None): + self.pre_solve(parameters) + + _var = self.free_symbols + coeff = self.coeff + + x, y, z = _var + var = [x, y, z] + + # Equations of the form B*x*y + C*z*x + E*y*z = 0 and At least two of the + # coefficients A, B, C are non-zero. + # There are infinitely many solutions for the equation. + # Ex: (0, 0, t), (0, t, 0), (t, 0, 0) + # Equation can be re-written as y*(B*x + E*z) = -C*x*z and we can find rather + # unobvious solutions. Set y = -C and B*x + E*z = x*z. The latter can be solved by + # using methods for binary quadratic diophantine equations. Let's select the + # solution which minimizes |x| + |z| + + result = DiophantineSolutionSet(var, parameters=self.parameters) + + def unpack_sol(sol): + if len(sol) > 0: + return list(sol)[0] + return None, None, None + + if not any(coeff[i**2] for i in var): + if coeff[x*z]: + sols = diophantine(coeff[x*y]*x + coeff[y*z]*z - x*z) + s = min(sols, key=lambda r: abs(r[0]) + abs(r[1])) + result.add(_remove_gcd(s[0], -coeff[x*z], s[1])) + return result + + var[0], var[1] = _var[1], _var[0] + y_0, x_0, z_0 = unpack_sol(_diop_ternary_quadratic(var, coeff)) + if x_0 is not None: + result.add((x_0, y_0, z_0)) + return result + + if coeff[x**2] == 0: + # If the coefficient of x is zero change the variables + if coeff[y**2] == 0: + var[0], var[2] = _var[2], _var[0] + z_0, y_0, x_0 = unpack_sol(_diop_ternary_quadratic(var, coeff)) + + else: + var[0], var[1] = _var[1], _var[0] + y_0, x_0, z_0 = unpack_sol(_diop_ternary_quadratic(var, coeff)) + + else: + if coeff[x*y] or coeff[x*z]: + # Apply the transformation x --> X - (B*y + C*z)/(2*A) + A = coeff[x**2] + B = coeff[x*y] + C = coeff[x*z] + D = coeff[y**2] + E = coeff[y*z] + F = coeff[z**2] + + _coeff = {} + + _coeff[x**2] = 4*A**2 + _coeff[y**2] = 4*A*D - B**2 + _coeff[z**2] = 4*A*F - C**2 + _coeff[y*z] = 4*A*E - 2*B*C + _coeff[x*y] = 0 + _coeff[x*z] = 0 + + x_0, y_0, z_0 = unpack_sol(_diop_ternary_quadratic(var, _coeff)) + + if x_0 is None: + return result + + p, q = _rational_pq(B*y_0 + C*z_0, 2*A) + x_0, y_0, z_0 = x_0*q - p, y_0*q, z_0*q + + elif coeff[z*y] != 0: + if coeff[y**2] == 0: + if coeff[z**2] == 0: + # Equations of the form A*x**2 + E*yz = 0. + A = coeff[x**2] + E = coeff[y*z] + + b, a = _rational_pq(-E, A) + + x_0, y_0, z_0 = b, a, b + + else: + # Ax**2 + E*y*z + F*z**2 = 0 + var[0], var[2] = _var[2], _var[0] + z_0, y_0, x_0 = unpack_sol(_diop_ternary_quadratic(var, coeff)) + + else: + # A*x**2 + D*y**2 + E*y*z + F*z**2 = 0, C may be zero + var[0], var[1] = _var[1], _var[0] + y_0, x_0, z_0 = unpack_sol(_diop_ternary_quadratic(var, coeff)) + + else: + # Ax**2 + D*y**2 + F*z**2 = 0, C may be zero + x_0, y_0, z_0 = unpack_sol(_diop_ternary_quadratic_normal(var, coeff)) + + if x_0 is None: + return result + + result.add(_remove_gcd(x_0, y_0, z_0)) + return result + + +class InhomogeneousGeneralQuadratic(DiophantineEquationType): + """ + + Representation of an inhomogeneous general quadratic. + + No solver is currently implemented for this equation type. + + """ + + name = 'inhomogeneous_general_quadratic' + + def matches(self): + if not (self.total_degree == 2 and self.dimension >= 3): + return False + if not self.homogeneous_order: + return True + # there may be Pow keys like x**2 or Mul keys like x*y + return any(k.is_Mul for k in self.coeff) and not self.homogeneous + + +class HomogeneousGeneralQuadratic(DiophantineEquationType): + """ + + Representation of a homogeneous general quadratic. + + No solver is currently implemented for this equation type. + + """ + + name = 'homogeneous_general_quadratic' + + def matches(self): + if not (self.total_degree == 2 and self.dimension >= 3): + return False + if not self.homogeneous_order: + return False + # there may be Pow keys like x**2 or Mul keys like x*y + return any(k.is_Mul for k in self.coeff) and self.homogeneous + + +class GeneralSumOfSquares(DiophantineEquationType): + r""" + Representation of the diophantine equation + + `x_{1}^2 + x_{2}^2 + . . . + x_{n}^2 - k = 0`. + + Details + ======= + + When `n = 3` if `k = 4^a(8m + 7)` for some `a, m \in Z` then there will be + no solutions. Refer [1]_ for more details. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import GeneralSumOfSquares + >>> from sympy.abc import a, b, c, d, e + >>> GeneralSumOfSquares(a**2 + b**2 + c**2 + d**2 + e**2 - 2345).solve() + {(15, 22, 22, 24, 24)} + + By default only 1 solution is returned. Use the `limit` keyword for more: + + >>> sorted(GeneralSumOfSquares(a**2 + b**2 + c**2 + d**2 + e**2 - 2345).solve(limit=3)) + [(15, 22, 22, 24, 24), (16, 19, 24, 24, 24), (16, 20, 22, 23, 26)] + + References + ========== + + .. [1] Representing an integer as a sum of three squares, [online], + Available: + https://www.proofwiki.org/wiki/Integer_as_Sum_of_Three_Squares + """ + + name = 'general_sum_of_squares' + + def matches(self): + if not (self.total_degree == 2 and self.dimension >= 3): + return False + if not self.homogeneous_order: + return False + if any(k.is_Mul for k in self.coeff): + return False + return all(self.coeff[k] == 1 for k in self.coeff if k != 1) + + def solve(self, parameters=None, limit=1): + self.pre_solve(parameters) + + var = self.free_symbols + k = -int(self.coeff[1]) + n = self.dimension + + result = DiophantineSolutionSet(var, parameters=self.parameters) + + if k < 0 or limit < 1: + return result + + signs = [-1 if x.is_nonpositive else 1 for x in var] + negs = signs.count(-1) != 0 + + took = 0 + for t in sum_of_squares(k, n, zeros=True): + if negs: + result.add([signs[i]*j for i, j in enumerate(t)]) + else: + result.add(t) + took += 1 + if took == limit: + break + return result + + +class GeneralPythagorean(DiophantineEquationType): + """ + Representation of the general pythagorean equation, + `a_{1}^2x_{1}^2 + a_{2}^2x_{2}^2 + . . . + a_{n}^2x_{n}^2 - a_{n + 1}^2x_{n + 1}^2 = 0`. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import GeneralPythagorean + >>> from sympy.abc import a, b, c, d, e, x, y, z, t + >>> GeneralPythagorean(a**2 + b**2 + c**2 - d**2).solve() + {(t_0**2 + t_1**2 - t_2**2, 2*t_0*t_2, 2*t_1*t_2, t_0**2 + t_1**2 + t_2**2)} + >>> GeneralPythagorean(9*a**2 - 4*b**2 + 16*c**2 + 25*d**2 + e**2).solve(parameters=[x, y, z, t]) + {(-10*t**2 + 10*x**2 + 10*y**2 + 10*z**2, 15*t**2 + 15*x**2 + 15*y**2 + 15*z**2, 15*t*x, 12*t*y, 60*t*z)} + """ + + name = 'general_pythagorean' + + def matches(self): + if not (self.total_degree == 2 and self.dimension >= 3): + return False + if not self.homogeneous_order: + return False + if any(k.is_Mul for k in self.coeff): + return False + if all(self.coeff[k] == 1 for k in self.coeff if k != 1): + return False + if not all(is_square(abs(self.coeff[k])) for k in self.coeff): + return False + # all but one has the same sign + # e.g. 4*x**2 + y**2 - 4*z**2 + return abs(sum(sign(self.coeff[k]) for k in self.coeff)) == self.dimension - 2 + + @property + def n_parameters(self): + return self.dimension - 1 + + def solve(self, parameters=None, limit=1): + self.pre_solve(parameters) + + coeff = self.coeff + var = self.free_symbols + n = self.dimension + + if sign(coeff[var[0] ** 2]) + sign(coeff[var[1] ** 2]) + sign(coeff[var[2] ** 2]) < 0: + for key in coeff.keys(): + coeff[key] = -coeff[key] + + result = DiophantineSolutionSet(var, parameters=self.parameters) + + index = 0 + + for i, v in enumerate(var): + if sign(coeff[v ** 2]) == -1: + index = i + + m = result.parameters + + ith = sum(m_i ** 2 for m_i in m) + L = [ith - 2 * m[n - 2] ** 2] + L.extend([2 * m[i] * m[n - 2] for i in range(n - 2)]) + sol = L[:index] + [ith] + L[index:] + + lcm = 1 + for i, v in enumerate(var): + if i == index or (index > 0 and i == 0) or (index == 0 and i == 1): + lcm = ilcm(lcm, sqrt(abs(coeff[v ** 2]))) + else: + s = sqrt(coeff[v ** 2]) + lcm = ilcm(lcm, s if _odd(s) else s // 2) + + for i, v in enumerate(var): + sol[i] = (lcm * sol[i]) / sqrt(abs(coeff[v ** 2])) + + result.add(sol) + return result + + +class CubicThue(DiophantineEquationType): + """ + Representation of a cubic Thue diophantine equation. + + A cubic Thue diophantine equation is a polynomial of the form + `f(x, y) = r` of degree 3, where `x` and `y` are integers + and `r` is a rational number. + + No solver is currently implemented for this equation type. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy.solvers.diophantine.diophantine import CubicThue + >>> c1 = CubicThue(x**3 + y**2 + 1) + >>> c1.matches() + True + + """ + + name = 'cubic_thue' + + def matches(self): + return self.total_degree == 3 and self.dimension == 2 + + +class GeneralSumOfEvenPowers(DiophantineEquationType): + """ + Representation of the diophantine equation + + `x_{1}^e + x_{2}^e + . . . + x_{n}^e - k = 0` + + where `e` is an even, integer power. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import GeneralSumOfEvenPowers + >>> from sympy.abc import a, b + >>> GeneralSumOfEvenPowers(a**4 + b**4 - (2**4 + 3**4)).solve() + {(2, 3)} + + """ + + name = 'general_sum_of_even_powers' + + def matches(self): + if not self.total_degree > 3: + return False + if self.total_degree % 2 != 0: + return False + if not all(k.is_Pow and k.exp == self.total_degree for k in self.coeff if k != 1): + return False + return all(self.coeff[k] == 1 for k in self.coeff if k != 1) + + def solve(self, parameters=None, limit=1): + self.pre_solve(parameters) + + var = self.free_symbols + coeff = self.coeff + + p = None + for q in coeff.keys(): + if q.is_Pow and coeff[q]: + p = q.exp + + k = len(var) + n = -coeff[1] + + result = DiophantineSolutionSet(var, parameters=self.parameters) + + if n < 0 or limit < 1: + return result + + sign = [-1 if x.is_nonpositive else 1 for x in var] + negs = sign.count(-1) != 0 + + took = 0 + for t in power_representation(n, p, k): + if negs: + result.add([sign[i]*j for i, j in enumerate(t)]) + else: + result.add(t) + took += 1 + if took == limit: + break + return result + +# these types are known (but not necessarily handled) +# note that order is important here (in the current solver state) +all_diop_classes = [ + Linear, + Univariate, + BinaryQuadratic, + InhomogeneousTernaryQuadratic, + HomogeneousTernaryQuadraticNormal, + HomogeneousTernaryQuadratic, + InhomogeneousGeneralQuadratic, + HomogeneousGeneralQuadratic, + GeneralSumOfSquares, + GeneralPythagorean, + CubicThue, + GeneralSumOfEvenPowers, +] + +diop_known = {diop_class.name for diop_class in all_diop_classes} + + +def _remove_gcd(*x): + try: + g = igcd(*x) + except ValueError: + fx = list(filter(None, x)) + if len(fx) < 2: + return x + g = igcd(*[i.as_content_primitive()[0] for i in fx]) + except TypeError: + raise TypeError('_remove_gcd(a,b,c) or _remove_gcd(*container)') + if g == 1: + return x + return tuple([i//g for i in x]) + + +def _rational_pq(a, b): + # return `(numer, denom)` for a/b; sign in numer and gcd removed + return _remove_gcd(sign(b)*a, abs(b)) + + +def _nint_or_floor(p, q): + # return nearest int to p/q; in case of tie return floor(p/q) + w, r = divmod(p, q) + if abs(r) <= abs(q)//2: + return w + return w + 1 + + +def _odd(i): + return i % 2 != 0 + + +def _even(i): + return i % 2 == 0 + + +def diophantine(eq, param=symbols("t", integer=True), syms=None, + permute=False): + """ + Simplify the solution procedure of diophantine equation ``eq`` by + converting it into a product of terms which should equal zero. + + Explanation + =========== + + For example, when solving, `x^2 - y^2 = 0` this is treated as + `(x + y)(x - y) = 0` and `x + y = 0` and `x - y = 0` are solved + independently and combined. Each term is solved by calling + ``diop_solve()``. (Although it is possible to call ``diop_solve()`` + directly, one must be careful to pass an equation in the correct + form and to interpret the output correctly; ``diophantine()`` is + the public-facing function to use in general.) + + Output of ``diophantine()`` is a set of tuples. The elements of the + tuple are the solutions for each variable in the equation and + are arranged according to the alphabetic ordering of the variables. + e.g. For an equation with two variables, `a` and `b`, the first + element of the tuple is the solution for `a` and the second for `b`. + + Usage + ===== + + ``diophantine(eq, t, syms)``: Solve the diophantine + equation ``eq``. + ``t`` is the optional parameter to be used by ``diop_solve()``. + ``syms`` is an optional list of symbols which determines the + order of the elements in the returned tuple. + + By default, only the base solution is returned. If ``permute`` is set to + True then permutations of the base solution and/or permutations of the + signs of the values will be returned when applicable. + + Details + ======= + + ``eq`` should be an expression which is assumed to be zero. + ``t`` is the parameter to be used in the solution. + + Examples + ======== + + >>> from sympy import diophantine + >>> from sympy.abc import a, b + >>> eq = a**4 + b**4 - (2**4 + 3**4) + >>> diophantine(eq) + {(2, 3)} + >>> diophantine(eq, permute=True) + {(-3, -2), (-3, 2), (-2, -3), (-2, 3), (2, -3), (2, 3), (3, -2), (3, 2)} + + >>> from sympy.abc import x, y, z + >>> diophantine(x**2 - y**2) + {(t_0, -t_0), (t_0, t_0)} + + >>> diophantine(x*(2*x + 3*y - z)) + {(0, n1, n2), (t_0, t_1, 2*t_0 + 3*t_1)} + >>> diophantine(x**2 + 3*x*y + 4*x) + {(0, n1), (-3*t_0 - 4, t_0)} + + See Also + ======== + + diop_solve + sympy.utilities.iterables.permute_signs + sympy.utilities.iterables.signed_permutations + """ + + eq = _sympify(eq) + + if isinstance(eq, Eq): + eq = eq.lhs - eq.rhs + + try: + var = list(eq.expand(force=True).free_symbols) + var.sort(key=default_sort_key) + if syms: + if not is_sequence(syms): + raise TypeError( + 'syms should be given as a sequence, e.g. a list') + syms = [i for i in syms if i in var] + if syms != var: + dict_sym_index = dict(zip(syms, range(len(syms)))) + return {tuple([t[dict_sym_index[i]] for i in var]) + for t in diophantine(eq, param, permute=permute)} + n, d = eq.as_numer_denom() + if n.is_number: + return set() + if not d.is_number: + dsol = diophantine(d) + good = diophantine(n) - dsol + return {s for s in good if _mexpand(d.subs(zip(var, s)))} + eq = factor_terms(n) + assert not eq.is_number + eq = eq.as_independent(*var, as_Add=False)[1] + p = Poly(eq) + assert not any(g.is_number for g in p.gens) + eq = p.as_expr() + assert eq.is_polynomial() + except (GeneratorsNeeded, AssertionError): + raise TypeError(filldedent(''' + Equation should be a polynomial with Rational coefficients.''')) + + # permute only sign + do_permute_signs = False + # permute sign and values + do_permute_signs_var = False + # permute few signs + permute_few_signs = False + try: + # if we know that factoring should not be attempted, skip + # the factoring step + v, c, t = classify_diop(eq) + + # check for permute sign + if permute: + len_var = len(v) + permute_signs_for = [ + GeneralSumOfSquares.name, + GeneralSumOfEvenPowers.name] + permute_signs_check = [ + HomogeneousTernaryQuadratic.name, + HomogeneousTernaryQuadraticNormal.name, + BinaryQuadratic.name] + if t in permute_signs_for: + do_permute_signs_var = True + elif t in permute_signs_check: + # if all the variables in eq have even powers + # then do_permute_sign = True + if len_var == 3: + var_mul = list(subsets(v, 2)) + # here var_mul is like [(x, y), (x, z), (y, z)] + xy_coeff = True + x_coeff = True + var1_mul_var2 = (a[0]*a[1] for a in var_mul) + # if coeff(y*z), coeff(y*x), coeff(x*z) is not 0 then + # `xy_coeff` => True and do_permute_sign => False. + # Means no permuted solution. + for v1_mul_v2 in var1_mul_var2: + try: + coeff = c[v1_mul_v2] + except KeyError: + coeff = 0 + xy_coeff = bool(xy_coeff) and bool(coeff) + var_mul = list(subsets(v, 1)) + # here var_mul is like [(x,), (y, )] + for v1 in var_mul: + try: + coeff = c[v1[0]] + except KeyError: + coeff = 0 + x_coeff = bool(x_coeff) and bool(coeff) + if not any((xy_coeff, x_coeff)): + # means only x**2, y**2, z**2, const is present + do_permute_signs = True + elif not x_coeff: + permute_few_signs = True + elif len_var == 2: + var_mul = list(subsets(v, 2)) + # here var_mul is like [(x, y)] + xy_coeff = True + x_coeff = True + var1_mul_var2 = (x[0]*x[1] for x in var_mul) + for v1_mul_v2 in var1_mul_var2: + try: + coeff = c[v1_mul_v2] + except KeyError: + coeff = 0 + xy_coeff = bool(xy_coeff) and bool(coeff) + var_mul = list(subsets(v, 1)) + # here var_mul is like [(x,), (y, )] + for v1 in var_mul: + try: + coeff = c[v1[0]] + except KeyError: + coeff = 0 + x_coeff = bool(x_coeff) and bool(coeff) + if not any((xy_coeff, x_coeff)): + # means only x**2, y**2 and const is present + # so we can get more soln by permuting this soln. + do_permute_signs = True + elif not x_coeff: + # when coeff(x), coeff(y) is not present then signs of + # x, y can be permuted such that their sign are same + # as sign of x*y. + # e.g 1. (x_val,y_val)=> (x_val,y_val), (-x_val,-y_val) + # 2. (-x_vall, y_val)=> (-x_val,y_val), (x_val,-y_val) + permute_few_signs = True + if t == 'general_sum_of_squares': + # trying to factor such expressions will sometimes hang + terms = [(eq, 1)] + else: + raise TypeError + except (TypeError, NotImplementedError): + fl = factor_list(eq) + if fl[0].is_Rational and fl[0] != 1: + return diophantine(eq/fl[0], param=param, syms=syms, permute=permute) + terms = fl[1] + + sols = set() + + for term in terms: + + base, _ = term + var_t, _, eq_type = classify_diop(base, _dict=False) + _, base = signsimp(base, evaluate=False).as_coeff_Mul() + solution = diop_solve(base, param) + + if eq_type in [ + Linear.name, + HomogeneousTernaryQuadratic.name, + HomogeneousTernaryQuadraticNormal.name, + GeneralPythagorean.name]: + sols.add(merge_solution(var, var_t, solution)) + + elif eq_type in [ + BinaryQuadratic.name, + GeneralSumOfSquares.name, + GeneralSumOfEvenPowers.name, + Univariate.name]: + sols.update(merge_solution(var, var_t, sol) for sol in solution) + + else: + raise NotImplementedError('unhandled type: %s' % eq_type) + + # remove null merge results + if () in sols: + sols.remove(()) + null = tuple([0]*len(var)) + # if there is no solution, return trivial solution + if not sols and eq.subs(zip(var, null)).is_zero: + sols.add(null) + final_soln = set() + for sol in sols: + if all(int_valued(s) for s in sol): + if do_permute_signs: + permuted_sign = set(permute_signs(sol)) + final_soln.update(permuted_sign) + elif permute_few_signs: + lst = list(permute_signs(sol)) + lst = list(filter(lambda x: x[0]*x[1] == sol[1]*sol[0], lst)) + permuted_sign = set(lst) + final_soln.update(permuted_sign) + elif do_permute_signs_var: + permuted_sign_var = set(signed_permutations(sol)) + final_soln.update(permuted_sign_var) + else: + final_soln.add(sol) + else: + final_soln.add(sol) + return final_soln + + +def merge_solution(var, var_t, solution): + """ + This is used to construct the full solution from the solutions of sub + equations. + + Explanation + =========== + + For example when solving the equation `(x - y)(x^2 + y^2 - z^2) = 0`, + solutions for each of the equations `x - y = 0` and `x^2 + y^2 - z^2` are + found independently. Solutions for `x - y = 0` are `(x, y) = (t, t)`. But + we should introduce a value for z when we output the solution for the + original equation. This function converts `(t, t)` into `(t, t, n_{1})` + where `n_{1}` is an integer parameter. + """ + sol = [] + + if None in solution: + return () + + solution = iter(solution) + params = numbered_symbols("n", integer=True, start=1) + for v in var: + if v in var_t: + sol.append(next(solution)) + else: + sol.append(next(params)) + + for val, symb in zip(sol, var): + if check_assumptions(val, **symb.assumptions0) is False: + return () + + return tuple(sol) + + +def _diop_solve(eq, params=None): + for diop_type in all_diop_classes: + if diop_type(eq).matches(): + return diop_type(eq).solve(parameters=params) + + +def diop_solve(eq, param=symbols("t", integer=True)): + """ + Solves the diophantine equation ``eq``. + + Explanation + =========== + + Unlike ``diophantine()``, factoring of ``eq`` is not attempted. Uses + ``classify_diop()`` to determine the type of the equation and calls + the appropriate solver function. + + Use of ``diophantine()`` is recommended over other helper functions. + ``diop_solve()`` can return either a set or a tuple depending on the + nature of the equation. + + Usage + ===== + + ``diop_solve(eq, t)``: Solve diophantine equation, ``eq`` using ``t`` + as a parameter if needed. + + Details + ======= + + ``eq`` should be an expression which is assumed to be zero. + ``t`` is a parameter to be used in the solution. + + Examples + ======== + + >>> from sympy.solvers.diophantine import diop_solve + >>> from sympy.abc import x, y, z, w + >>> diop_solve(2*x + 3*y - 5) + (3*t_0 - 5, 5 - 2*t_0) + >>> diop_solve(4*x + 3*y - 4*z + 5) + (t_0, 8*t_0 + 4*t_1 + 5, 7*t_0 + 3*t_1 + 5) + >>> diop_solve(x + 3*y - 4*z + w - 6) + (t_0, t_0 + t_1, 6*t_0 + 5*t_1 + 4*t_2 - 6, 5*t_0 + 4*t_1 + 3*t_2 - 6) + >>> diop_solve(x**2 + y**2 - 5) + {(-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)} + + + See Also + ======== + + diophantine() + """ + var, coeff, eq_type = classify_diop(eq, _dict=False) + + if eq_type == Linear.name: + return diop_linear(eq, param) + + elif eq_type == BinaryQuadratic.name: + return diop_quadratic(eq, param) + + elif eq_type == HomogeneousTernaryQuadratic.name: + return diop_ternary_quadratic(eq, parameterize=True) + + elif eq_type == HomogeneousTernaryQuadraticNormal.name: + return diop_ternary_quadratic_normal(eq, parameterize=True) + + elif eq_type == GeneralPythagorean.name: + return diop_general_pythagorean(eq, param) + + elif eq_type == Univariate.name: + return diop_univariate(eq) + + elif eq_type == GeneralSumOfSquares.name: + return diop_general_sum_of_squares(eq, limit=S.Infinity) + + elif eq_type == GeneralSumOfEvenPowers.name: + return diop_general_sum_of_even_powers(eq, limit=S.Infinity) + + if eq_type is not None and eq_type not in diop_known: + raise ValueError(filldedent(''' + Although this type of equation was identified, it is not yet + handled. It should, however, be listed in `diop_known` at the + top of this file. Developers should see comments at the end of + `classify_diop`. + ''')) # pragma: no cover + else: + raise NotImplementedError( + 'No solver has been written for %s.' % eq_type) + + +def classify_diop(eq, _dict=True): + # docstring supplied externally + + matched = False + diop_type = None + for diop_class in all_diop_classes: + diop_type = diop_class(eq) + if diop_type.matches(): + matched = True + break + + if matched: + return diop_type.free_symbols, dict(diop_type.coeff) if _dict else diop_type.coeff, diop_type.name + + # new diop type instructions + # -------------------------- + # if this error raises and the equation *can* be classified, + # * it should be identified in the if-block above + # * the type should be added to the diop_known + # if a solver can be written for it, + # * a dedicated handler should be written (e.g. diop_linear) + # * it should be passed to that handler in diop_solve + raise NotImplementedError(filldedent(''' + This equation is not yet recognized or else has not been + simplified sufficiently to put it in a form recognized by + diop_classify().''')) + + +classify_diop.func_doc = ( # type: ignore + ''' + Helper routine used by diop_solve() to find information about ``eq``. + + Explanation + =========== + + Returns a tuple containing the type of the diophantine equation + along with the variables (free symbols) and their coefficients. + Variables are returned as a list and coefficients are returned + as a dict with the key being the respective term and the constant + term is keyed to 1. The type is one of the following: + + * %s + + Usage + ===== + + ``classify_diop(eq)``: Return variables, coefficients and type of the + ``eq``. + + Details + ======= + + ``eq`` should be an expression which is assumed to be zero. + ``_dict`` is for internal use: when True (default) a dict is returned, + otherwise a defaultdict which supplies 0 for missing keys is returned. + + Examples + ======== + + >>> from sympy.solvers.diophantine import classify_diop + >>> from sympy.abc import x, y, z, w, t + >>> classify_diop(4*x + 6*y - 4) + ([x, y], {1: -4, x: 4, y: 6}, 'linear') + >>> classify_diop(x + 3*y -4*z + 5) + ([x, y, z], {1: 5, x: 1, y: 3, z: -4}, 'linear') + >>> classify_diop(x**2 + y**2 - x*y + x + 5) + ([x, y], {1: 5, x: 1, x**2: 1, y**2: 1, x*y: -1}, 'binary_quadratic') + ''' % ('\n * '.join(sorted(diop_known)))) + + +def diop_linear(eq, param=symbols("t", integer=True)): + """ + Solves linear diophantine equations. + + A linear diophantine equation is an equation of the form `a_{1}x_{1} + + a_{2}x_{2} + .. + a_{n}x_{n} = 0` where `a_{1}, a_{2}, ..a_{n}` are + integer constants and `x_{1}, x_{2}, ..x_{n}` are integer variables. + + Usage + ===== + + ``diop_linear(eq)``: Returns a tuple containing solutions to the + diophantine equation ``eq``. Values in the tuple is arranged in the same + order as the sorted variables. + + Details + ======= + + ``eq`` is a linear diophantine equation which is assumed to be zero. + ``param`` is the parameter to be used in the solution. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import diop_linear + >>> from sympy.abc import x, y, z + >>> diop_linear(2*x - 3*y - 5) # solves equation 2*x - 3*y - 5 == 0 + (3*t_0 - 5, 2*t_0 - 5) + + Here x = -3*t_0 - 5 and y = -2*t_0 - 5 + + >>> diop_linear(2*x - 3*y - 4*z -3) + (t_0, 2*t_0 + 4*t_1 + 3, -t_0 - 3*t_1 - 3) + + See Also + ======== + + diop_quadratic(), diop_ternary_quadratic(), diop_general_pythagorean(), + diop_general_sum_of_squares() + """ + var, coeff, diop_type = classify_diop(eq, _dict=False) + + if diop_type == Linear.name: + parameters = None + if param is not None: + parameters = symbols('%s_0:%i' % (param, len(var)), integer=True) + + result = Linear(eq).solve(parameters=parameters) + + if param is None: + result = result(*[0]*len(result.parameters)) + + if len(result) > 0: + return list(result)[0] + else: + return tuple([None]*len(result.parameters)) + + +def base_solution_linear(c, a, b, t=None): + """ + Return the base solution for the linear equation, `ax + by = c`. + + Explanation + =========== + + Used by ``diop_linear()`` to find the base solution of a linear + Diophantine equation. If ``t`` is given then the parametrized solution is + returned. + + Usage + ===== + + ``base_solution_linear(c, a, b, t)``: ``a``, ``b``, ``c`` are coefficients + in `ax + by = c` and ``t`` is the parameter to be used in the solution. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import base_solution_linear + >>> from sympy.abc import t + >>> base_solution_linear(5, 2, 3) # equation 2*x + 3*y = 5 + (-5, 5) + >>> base_solution_linear(0, 5, 7) # equation 5*x + 7*y = 0 + (0, 0) + >>> base_solution_linear(5, 2, 3, t) # equation 2*x + 3*y = 5 + (3*t - 5, 5 - 2*t) + >>> base_solution_linear(0, 5, 7, t) # equation 5*x + 7*y = 0 + (7*t, -5*t) + """ + a, b, c = _remove_gcd(a, b, c) + + if c == 0: + if t is None: + return (0, 0) + if b < 0: + t = -t + return (b*t, -a*t) + + x0, y0, d = igcdex(abs(a), abs(b)) + x0 *= sign(a) + y0 *= sign(b) + if c % d: + return (None, None) + if t is None: + return (c*x0, c*y0) + if b < 0: + t = -t + return (c*x0 + b*t, c*y0 - a*t) + + +def diop_univariate(eq): + """ + Solves a univariate diophantine equations. + + Explanation + =========== + + A univariate diophantine equation is an equation of the form + `a_{0} + a_{1}x + a_{2}x^2 + .. + a_{n}x^n = 0` where `a_{1}, a_{2}, ..a_{n}` are + integer constants and `x` is an integer variable. + + Usage + ===== + + ``diop_univariate(eq)``: Returns a set containing solutions to the + diophantine equation ``eq``. + + Details + ======= + + ``eq`` is a univariate diophantine equation which is assumed to be zero. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import diop_univariate + >>> from sympy.abc import x + >>> diop_univariate((x - 2)*(x - 3)**2) # solves equation (x - 2)*(x - 3)**2 == 0 + {(2,), (3,)} + + """ + var, coeff, diop_type = classify_diop(eq, _dict=False) + + if diop_type == Univariate.name: + return {(int(i),) for i in solveset_real( + eq, var[0]).intersect(S.Integers)} + + +def divisible(a, b): + """ + Returns `True` if ``a`` is divisible by ``b`` and `False` otherwise. + """ + return not a % b + + +def diop_quadratic(eq, param=symbols("t", integer=True)): + """ + Solves quadratic diophantine equations. + + i.e. equations of the form `Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0`. Returns a + set containing the tuples `(x, y)` which contains the solutions. If there + are no solutions then `(None, None)` is returned. + + Usage + ===== + + ``diop_quadratic(eq, param)``: ``eq`` is a quadratic binary diophantine + equation. ``param`` is used to indicate the parameter to be used in the + solution. + + Details + ======= + + ``eq`` should be an expression which is assumed to be zero. + ``param`` is a parameter to be used in the solution. + + Examples + ======== + + >>> from sympy.abc import x, y, t + >>> from sympy.solvers.diophantine.diophantine import diop_quadratic + >>> diop_quadratic(x**2 + y**2 + 2*x + 2*y + 2, t) + {(-1, -1)} + + References + ========== + + .. [1] Methods to solve Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0, [online], + Available: https://www.alpertron.com.ar/METHODS.HTM + .. [2] Solving the equation ax^2+ bxy + cy^2 + dx + ey + f= 0, [online], + Available: https://web.archive.org/web/20160323033111/http://www.jpr2718.org/ax2p.pdf + + See Also + ======== + + diop_linear(), diop_ternary_quadratic(), diop_general_sum_of_squares(), + diop_general_pythagorean() + """ + var, coeff, diop_type = classify_diop(eq, _dict=False) + + if diop_type == BinaryQuadratic.name: + if param is not None: + parameters = [param, Symbol("u", integer=True)] + else: + parameters = None + return set(BinaryQuadratic(eq).solve(parameters=parameters)) + + +def is_solution_quad(var, coeff, u, v): + """ + Check whether `(u, v)` is solution to the quadratic binary diophantine + equation with the variable list ``var`` and coefficient dictionary + ``coeff``. + + Not intended for use by normal users. + """ + reps = dict(zip(var, (u, v))) + eq = Add(*[j*i.xreplace(reps) for i, j in coeff.items()]) + return _mexpand(eq) == 0 + + +def diop_DN(D, N, t=symbols("t", integer=True)): + """ + Solves the equation `x^2 - Dy^2 = N`. + + Explanation + =========== + + Mainly concerned with the case `D > 0, D` is not a perfect square, + which is the same as the generalized Pell equation. The LMM + algorithm [1]_ is used to solve this equation. + + Returns one solution tuple, (`x, y)` for each class of the solutions. + Other solutions of the class can be constructed according to the + values of ``D`` and ``N``. + + Usage + ===== + + ``diop_DN(D, N, t)``: D and N are integers as in `x^2 - Dy^2 = N` and + ``t`` is the parameter to be used in the solutions. + + Details + ======= + + ``D`` and ``N`` correspond to D and N in the equation. + ``t`` is the parameter to be used in the solutions. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import diop_DN + >>> diop_DN(13, -4) # Solves equation x**2 - 13*y**2 = -4 + [(3, 1), (393, 109), (36, 10)] + + The output can be interpreted as follows: There are three fundamental + solutions to the equation `x^2 - 13y^2 = -4` given by (3, 1), (393, 109) + and (36, 10). Each tuple is in the form (x, y), i.e. solution (3, 1) means + that `x = 3` and `y = 1`. + + >>> diop_DN(986, 1) # Solves equation x**2 - 986*y**2 = 1 + [(49299, 1570)] + + See Also + ======== + + find_DN(), diop_bf_DN() + + References + ========== + + .. [1] Solving the generalized Pell equation x**2 - D*y**2 = N, John P. + Robertson, July 31, 2004, Pages 16 - 17. [online], Available: + https://web.archive.org/web/20160323033128/http://www.jpr2718.org/pell.pdf + """ + if D < 0: + if N == 0: + return [(0, 0)] + if N < 0: + return [] + # N > 0: + sol = [] + for d in divisors(square_factor(N), generator=True): + for x, y in cornacchia(1, int(-D), int(N // d**2)): + sol.append((d*x, d*y)) + if D == -1: + sol.append((d*y, d*x)) + return sol + + if D == 0: + if N < 0: + return [] + if N == 0: + return [(0, t)] + sN, _exact = integer_nthroot(N, 2) + if _exact: + return [(sN, t)] + return [] + + # D > 0 + sD, _exact = integer_nthroot(D, 2) + if _exact: + if N == 0: + return [(sD*t, t)] + + sol = [] + for y in range(floor(sign(N)*(N - 1)/(2*sD)) + 1): + try: + sq, _exact = integer_nthroot(D*y**2 + N, 2) + except ValueError: + _exact = False + if _exact: + sol.append((sq, y)) + return sol + + if 1 < N**2 < D: + # It is much faster to call `_special_diop_DN`. + return _special_diop_DN(D, N) + + if N == 0: + return [(0, 0)] + + sol = [] + if abs(N) == 1: + pqa = PQa(0, 1, D) + *_, prev_B, prev_G = next(pqa) + for j, (*_, a, _, _B, _G) in enumerate(pqa): + if a == 2*sD: + break + prev_B, prev_G = _B, _G + if j % 2: + if N == 1: + sol.append((prev_G, prev_B)) + return sol + if N == -1: + return [(prev_G, prev_B)] + for _ in range(j): + *_, _B, _G = next(pqa) + return [(_G, _B)] + + for f in divisors(square_factor(N), generator=True): + m = N // f**2 + am = abs(m) + for sqm in sqrt_mod(D, am, all_roots=True): + z = symmetric_residue(sqm, am) + pqa = PQa(z, am, D) + *_, prev_B, prev_G = next(pqa) + for _ in range(length(z, am, D) - 1): + _, q, *_, _B, _G = next(pqa) + if abs(q) == 1: + if prev_G**2 - D*prev_B**2 == m: + sol.append((f*prev_G, f*prev_B)) + elif a := diop_DN(D, -1): + sol.append((f*(prev_G*a[0][0] + prev_B*D*a[0][1]), + f*(prev_G*a[0][1] + prev_B*a[0][0]))) + break + prev_B, prev_G = _B, _G + return sol + + +def _special_diop_DN(D, N): + """ + Solves the equation `x^2 - Dy^2 = N` for the special case where + `1 < N**2 < D` and `D` is not a perfect square. + It is better to call `diop_DN` rather than this function, as + the former checks the condition `1 < N**2 < D`, and calls the latter only + if appropriate. + + Usage + ===== + + WARNING: Internal method. Do not call directly! + + ``_special_diop_DN(D, N)``: D and N are integers as in `x^2 - Dy^2 = N`. + + Details + ======= + + ``D`` and ``N`` correspond to D and N in the equation. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import _special_diop_DN + >>> _special_diop_DN(13, -3) # Solves equation x**2 - 13*y**2 = -3 + [(7, 2), (137, 38)] + + The output can be interpreted as follows: There are two fundamental + solutions to the equation `x^2 - 13y^2 = -3` given by (7, 2) and + (137, 38). Each tuple is in the form (x, y), i.e. solution (7, 2) means + that `x = 7` and `y = 2`. + + >>> _special_diop_DN(2445, -20) # Solves equation x**2 - 2445*y**2 = -20 + [(445, 9), (17625560, 356454), (698095554475, 14118073569)] + + See Also + ======== + + diop_DN() + + References + ========== + + .. [1] Section 4.4.4 of the following book: + Quadratic Diophantine Equations, T. Andreescu and D. Andrica, + Springer, 2015. + """ + + # The following assertion was removed for efficiency, with the understanding + # that this method is not called directly. The parent method, `diop_DN` + # is responsible for performing the appropriate checks. + # + # assert (1 < N**2 < D) and (not integer_nthroot(D, 2)[1]) + + sqrt_D = isqrt(D) + F = {N // f**2: f for f in divisors(square_factor(abs(N)), generator=True)} + P = 0 + Q = 1 + G0, G1 = 0, 1 + B0, B1 = 1, 0 + + solutions = [] + while True: + for _ in range(2): + a = (P + sqrt_D) // Q + P = a*Q - P + Q = (D - P**2) // Q + G0, G1 = G1, a*G1 + G0 + B0, B1 = B1, a*B1 + B0 + if (s := G1**2 - D*B1**2) in F: + f = F[s] + solutions.append((f*G1, f*B1)) + if Q == 1: + break + return solutions + + +def cornacchia(a:int, b:int, m:int) -> set[tuple[int, int]]: + r""" + Solves `ax^2 + by^2 = m` where `\gcd(a, b) = 1 = gcd(a, m)` and `a, b > 0`. + + Explanation + =========== + + Uses the algorithm due to Cornacchia. The method only finds primitive + solutions, i.e. ones with `\gcd(x, y) = 1`. So this method cannot be used to + find the solutions of `x^2 + y^2 = 20` since the only solution to former is + `(x, y) = (4, 2)` and it is not primitive. When `a = b`, only the + solutions with `x \leq y` are found. For more details, see the References. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import cornacchia + >>> cornacchia(2, 3, 35) # equation 2x**2 + 3y**2 = 35 + {(2, 3), (4, 1)} + >>> cornacchia(1, 1, 25) # equation x**2 + y**2 = 25 + {(4, 3)} + + References + =========== + + .. [1] A. Nitaj, "L'algorithme de Cornacchia" + .. [2] Solving the diophantine equation ax**2 + by**2 = m by Cornacchia's + method, [online], Available: + http://www.numbertheory.org/php/cornacchia.html + + See Also + ======== + + sympy.utilities.iterables.signed_permutations + """ + # Assume gcd(a, b) = gcd(a, m) = 1 and a, b > 0 but no error checking + sols = set() + for t in sqrt_mod_iter(-b*invert(a, m), m): + if t < m // 2: + continue + u, r = m, t + while (m1 := m - a*r**2) <= 0: + u, r = r, u % r + m1, _r = divmod(m1, b) + if _r: + continue + s, _exact = iroot(m1, 2) + if _exact: + if a == b and r < s: + r, s = s, r + sols.add((int(r), int(s))) + return sols + + +def PQa(P_0, Q_0, D): + r""" + Returns useful information needed to solve the Pell equation. + + Explanation + =========== + + There are six sequences of integers defined related to the continued + fraction representation of `\\frac{P + \sqrt{D}}{Q}`, namely {`P_{i}`}, + {`Q_{i}`}, {`a_{i}`},{`A_{i}`}, {`B_{i}`}, {`G_{i}`}. ``PQa()`` Returns + these values as a 6-tuple in the same order as mentioned above. Refer [1]_ + for more detailed information. + + Usage + ===== + + ``PQa(P_0, Q_0, D)``: ``P_0``, ``Q_0`` and ``D`` are integers corresponding + to `P_{0}`, `Q_{0}` and `D` in the continued fraction + `\\frac{P_{0} + \sqrt{D}}{Q_{0}}`. + Also it's assumed that `P_{0}^2 == D mod(|Q_{0}|)` and `D` is square free. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import PQa + >>> pqa = PQa(13, 4, 5) # (13 + sqrt(5))/4 + >>> next(pqa) # (P_0, Q_0, a_0, A_0, B_0, G_0) + (13, 4, 3, 3, 1, -1) + >>> next(pqa) # (P_1, Q_1, a_1, A_1, B_1, G_1) + (-1, 1, 1, 4, 1, 3) + + References + ========== + + .. [1] Solving the generalized Pell equation x^2 - Dy^2 = N, John P. + Robertson, July 31, 2004, Pages 4 - 8. https://web.archive.org/web/20160323033128/http://www.jpr2718.org/pell.pdf + """ + sqD = isqrt(D) + A2 = B1 = 0 + A1 = B2 = 1 + G1 = Q_0 + G2 = -P_0 + P_i = P_0 + Q_i = Q_0 + + while True: + a_i = (P_i + sqD) // Q_i + A1, A2 = a_i*A1 + A2, A1 + B1, B2 = a_i*B1 + B2, B1 + G1, G2 = a_i*G1 + G2, G1 + yield P_i, Q_i, a_i, A1, B1, G1 + + P_i = a_i*Q_i - P_i + Q_i = (D - P_i**2) // Q_i + + +def diop_bf_DN(D, N, t=symbols("t", integer=True)): + r""" + Uses brute force to solve the equation, `x^2 - Dy^2 = N`. + + Explanation + =========== + + Mainly concerned with the generalized Pell equation which is the case when + `D > 0, D` is not a perfect square. For more information on the case refer + [1]_. Let `(t, u)` be the minimal positive solution of the equation + `x^2 - Dy^2 = 1`. Then this method requires + `\sqrt{\\frac{\mid N \mid (t \pm 1)}{2D}}` to be small. + + Usage + ===== + + ``diop_bf_DN(D, N, t)``: ``D`` and ``N`` are coefficients in + `x^2 - Dy^2 = N` and ``t`` is the parameter to be used in the solutions. + + Details + ======= + + ``D`` and ``N`` correspond to D and N in the equation. + ``t`` is the parameter to be used in the solutions. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import diop_bf_DN + >>> diop_bf_DN(13, -4) + [(3, 1), (-3, 1), (36, 10)] + >>> diop_bf_DN(986, 1) + [(49299, 1570)] + + See Also + ======== + + diop_DN() + + References + ========== + + .. [1] Solving the generalized Pell equation x**2 - D*y**2 = N, John P. + Robertson, July 31, 2004, Page 15. https://web.archive.org/web/20160323033128/http://www.jpr2718.org/pell.pdf + """ + D = as_int(D) + N = as_int(N) + + sol = [] + a = diop_DN(D, 1) + u = a[0][0] + + if N == 0: + if D < 0: + return [(0, 0)] + if D == 0: + return [(0, t)] + sD, _exact = integer_nthroot(D, 2) + if _exact: + return [(sD*t, t), (-sD*t, t)] + return [(0, 0)] + + if abs(N) == 1: + return diop_DN(D, N) + + if N > 1: + L1 = 0 + L2 = integer_nthroot(int(N*(u - 1)/(2*D)), 2)[0] + 1 + else: # N < -1 + L1, _exact = integer_nthroot(-int(N/D), 2) + if not _exact: + L1 += 1 + L2 = integer_nthroot(-int(N*(u + 1)/(2*D)), 2)[0] + 1 + + for y in range(L1, L2): + try: + x, _exact = integer_nthroot(N + D*y**2, 2) + except ValueError: + _exact = False + if _exact: + sol.append((x, y)) + if not equivalent(x, y, -x, y, D, N): + sol.append((-x, y)) + + return sol + + +def equivalent(u, v, r, s, D, N): + """ + Returns True if two solutions `(u, v)` and `(r, s)` of `x^2 - Dy^2 = N` + belongs to the same equivalence class and False otherwise. + + Explanation + =========== + + Two solutions `(u, v)` and `(r, s)` to the above equation fall to the same + equivalence class iff both `(ur - Dvs)` and `(us - vr)` are divisible by + `N`. See reference [1]_. No test is performed to test whether `(u, v)` and + `(r, s)` are actually solutions to the equation. User should take care of + this. + + Usage + ===== + + ``equivalent(u, v, r, s, D, N)``: `(u, v)` and `(r, s)` are two solutions + of the equation `x^2 - Dy^2 = N` and all parameters involved are integers. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import equivalent + >>> equivalent(18, 5, -18, -5, 13, -1) + True + >>> equivalent(3, 1, -18, 393, 109, -4) + False + + References + ========== + + .. [1] Solving the generalized Pell equation x**2 - D*y**2 = N, John P. + Robertson, July 31, 2004, Page 12. https://web.archive.org/web/20160323033128/http://www.jpr2718.org/pell.pdf + + """ + return divisible(u*r - D*v*s, N) and divisible(u*s - v*r, N) + + +def length(P, Q, D): + r""" + Returns the (length of aperiodic part + length of periodic part) of + continued fraction representation of `\\frac{P + \sqrt{D}}{Q}`. + + It is important to remember that this does NOT return the length of the + periodic part but the sum of the lengths of the two parts as mentioned + above. + + Usage + ===== + + ``length(P, Q, D)``: ``P``, ``Q`` and ``D`` are integers corresponding to + the continued fraction `\\frac{P + \sqrt{D}}{Q}`. + + Details + ======= + + ``P``, ``D`` and ``Q`` corresponds to P, D and Q in the continued fraction, + `\\frac{P + \sqrt{D}}{Q}`. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import length + >>> length(-2, 4, 5) # (-2 + sqrt(5))/4 + 3 + >>> length(-5, 4, 17) # (-5 + sqrt(17))/4 + 4 + + See Also + ======== + sympy.ntheory.continued_fraction.continued_fraction_periodic + """ + from sympy.ntheory.continued_fraction import continued_fraction_periodic + v = continued_fraction_periodic(P, Q, D) + if isinstance(v[-1], list): + rpt = len(v[-1]) + nonrpt = len(v) - 1 + else: + rpt = 0 + nonrpt = len(v) + return rpt + nonrpt + + +def transformation_to_DN(eq): + """ + This function transforms general quadratic, + `ax^2 + bxy + cy^2 + dx + ey + f = 0` + to more easy to deal with `X^2 - DY^2 = N` form. + + Explanation + =========== + + This is used to solve the general quadratic equation by transforming it to + the latter form. Refer to [1]_ for more detailed information on the + transformation. This function returns a tuple (A, B) where A is a 2 X 2 + matrix and B is a 2 X 1 matrix such that, + + Transpose([x y]) = A * Transpose([X Y]) + B + + Usage + ===== + + ``transformation_to_DN(eq)``: where ``eq`` is the quadratic to be + transformed. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy.solvers.diophantine.diophantine import transformation_to_DN + >>> A, B = transformation_to_DN(x**2 - 3*x*y - y**2 - 2*y + 1) + >>> A + Matrix([ + [1/26, 3/26], + [ 0, 1/13]]) + >>> B + Matrix([ + [-6/13], + [-4/13]]) + + A, B returned are such that Transpose((x y)) = A * Transpose((X Y)) + B. + Substituting these values for `x` and `y` and a bit of simplifying work + will give an equation of the form `x^2 - Dy^2 = N`. + + >>> from sympy.abc import X, Y + >>> from sympy import Matrix, simplify + >>> u = (A*Matrix([X, Y]) + B)[0] # Transformation for x + >>> u + X/26 + 3*Y/26 - 6/13 + >>> v = (A*Matrix([X, Y]) + B)[1] # Transformation for y + >>> v + Y/13 - 4/13 + + Next we will substitute these formulas for `x` and `y` and do + ``simplify()``. + + >>> eq = simplify((x**2 - 3*x*y - y**2 - 2*y + 1).subs(zip((x, y), (u, v)))) + >>> eq + X**2/676 - Y**2/52 + 17/13 + + By multiplying the denominator appropriately, we can get a Pell equation + in the standard form. + + >>> eq * 676 + X**2 - 13*Y**2 + 884 + + If only the final equation is needed, ``find_DN()`` can be used. + + See Also + ======== + + find_DN() + + References + ========== + + .. [1] Solving the equation ax^2 + bxy + cy^2 + dx + ey + f = 0, + John P.Robertson, May 8, 2003, Page 7 - 11. + https://web.archive.org/web/20160323033111/http://www.jpr2718.org/ax2p.pdf + """ + + var, coeff, diop_type = classify_diop(eq, _dict=False) + if diop_type == BinaryQuadratic.name: + return _transformation_to_DN(var, coeff) + + +def _transformation_to_DN(var, coeff): + + x, y = var + + a = coeff[x**2] + b = coeff[x*y] + c = coeff[y**2] + d = coeff[x] + e = coeff[y] + f = coeff[1] + + a, b, c, d, e, f = [as_int(i) for i in _remove_gcd(a, b, c, d, e, f)] + + X, Y = symbols("X, Y", integer=True) + + if b: + B, C = _rational_pq(2*a, b) + A, T = _rational_pq(a, B**2) + + # eq_1 = A*B*X**2 + B*(c*T - A*C**2)*Y**2 + d*T*X + (B*e*T - d*T*C)*Y + f*T*B + coeff = {X**2: A*B, X*Y: 0, Y**2: B*(c*T - A*C**2), X: d*T, Y: B*e*T - d*T*C, 1: f*T*B} + A_0, B_0 = _transformation_to_DN([X, Y], coeff) + return Matrix(2, 2, [S.One/B, -S(C)/B, 0, 1])*A_0, Matrix(2, 2, [S.One/B, -S(C)/B, 0, 1])*B_0 + + if d: + B, C = _rational_pq(2*a, d) + A, T = _rational_pq(a, B**2) + + # eq_2 = A*X**2 + c*T*Y**2 + e*T*Y + f*T - A*C**2 + coeff = {X**2: A, X*Y: 0, Y**2: c*T, X: 0, Y: e*T, 1: f*T - A*C**2} + A_0, B_0 = _transformation_to_DN([X, Y], coeff) + return Matrix(2, 2, [S.One/B, 0, 0, 1])*A_0, Matrix(2, 2, [S.One/B, 0, 0, 1])*B_0 + Matrix([-S(C)/B, 0]) + + if e: + B, C = _rational_pq(2*c, e) + A, T = _rational_pq(c, B**2) + + # eq_3 = a*T*X**2 + A*Y**2 + f*T - A*C**2 + coeff = {X**2: a*T, X*Y: 0, Y**2: A, X: 0, Y: 0, 1: f*T - A*C**2} + A_0, B_0 = _transformation_to_DN([X, Y], coeff) + return Matrix(2, 2, [1, 0, 0, S.One/B])*A_0, Matrix(2, 2, [1, 0, 0, S.One/B])*B_0 + Matrix([0, -S(C)/B]) + + # TODO: pre-simplification: Not necessary but may simplify + # the equation. + return Matrix(2, 2, [S.One/a, 0, 0, 1]), Matrix([0, 0]) + + +def find_DN(eq): + """ + This function returns a tuple, `(D, N)` of the simplified form, + `x^2 - Dy^2 = N`, corresponding to the general quadratic, + `ax^2 + bxy + cy^2 + dx + ey + f = 0`. + + Solving the general quadratic is then equivalent to solving the equation + `X^2 - DY^2 = N` and transforming the solutions by using the transformation + matrices returned by ``transformation_to_DN()``. + + Usage + ===== + + ``find_DN(eq)``: where ``eq`` is the quadratic to be transformed. + + Examples + ======== + + >>> from sympy.abc import x, y + >>> from sympy.solvers.diophantine.diophantine import find_DN + >>> find_DN(x**2 - 3*x*y - y**2 - 2*y + 1) + (13, -884) + + Interpretation of the output is that we get `X^2 -13Y^2 = -884` after + transforming `x^2 - 3xy - y^2 - 2y + 1` using the transformation returned + by ``transformation_to_DN()``. + + See Also + ======== + + transformation_to_DN() + + References + ========== + + .. [1] Solving the equation ax^2 + bxy + cy^2 + dx + ey + f = 0, + John P.Robertson, May 8, 2003, Page 7 - 11. + https://web.archive.org/web/20160323033111/http://www.jpr2718.org/ax2p.pdf + """ + var, coeff, diop_type = classify_diop(eq, _dict=False) + if diop_type == BinaryQuadratic.name: + return _find_DN(var, coeff) + + +def _find_DN(var, coeff): + + x, y = var + X, Y = symbols("X, Y", integer=True) + A, B = _transformation_to_DN(var, coeff) + + u = (A*Matrix([X, Y]) + B)[0] + v = (A*Matrix([X, Y]) + B)[1] + eq = x**2*coeff[x**2] + x*y*coeff[x*y] + y**2*coeff[y**2] + x*coeff[x] + y*coeff[y] + coeff[1] + + simplified = _mexpand(eq.subs(zip((x, y), (u, v)))) + + coeff = simplified.as_coefficients_dict() + + return -coeff[Y**2]/coeff[X**2], -coeff[1]/coeff[X**2] + + +def check_param(x, y, a, params): + """ + If there is a number modulo ``a`` such that ``x`` and ``y`` are both + integers, then return a parametric representation for ``x`` and ``y`` + else return (None, None). + + Here ``x`` and ``y`` are functions of ``t``. + """ + from sympy.simplify.simplify import clear_coefficients + + if x.is_number and not x.is_Integer: + return DiophantineSolutionSet([x, y], parameters=params) + + if y.is_number and not y.is_Integer: + return DiophantineSolutionSet([x, y], parameters=params) + + m, n = symbols("m, n", integer=True) + c, p = (m*x + n*y).as_content_primitive() + if a % c.q: + return DiophantineSolutionSet([x, y], parameters=params) + + # clear_coefficients(mx + b, R)[1] -> (R - b)/m + eq = clear_coefficients(x, m)[1] - clear_coefficients(y, n)[1] + junk, eq = eq.as_content_primitive() + + return _diop_solve(eq, params=params) + + +def diop_ternary_quadratic(eq, parameterize=False): + """ + Solves the general quadratic ternary form, + `ax^2 + by^2 + cz^2 + fxy + gyz + hxz = 0`. + + Returns a tuple `(x, y, z)` which is a base solution for the above + equation. If there are no solutions, `(None, None, None)` is returned. + + Usage + ===== + + ``diop_ternary_quadratic(eq)``: Return a tuple containing a basic solution + to ``eq``. + + Details + ======= + + ``eq`` should be an homogeneous expression of degree two in three variables + and it is assumed to be zero. + + Examples + ======== + + >>> from sympy.abc import x, y, z + >>> from sympy.solvers.diophantine.diophantine import diop_ternary_quadratic + >>> diop_ternary_quadratic(x**2 + 3*y**2 - z**2) + (1, 0, 1) + >>> diop_ternary_quadratic(4*x**2 + 5*y**2 - z**2) + (1, 0, 2) + >>> diop_ternary_quadratic(45*x**2 - 7*y**2 - 8*x*y - z**2) + (28, 45, 105) + >>> diop_ternary_quadratic(x**2 - 49*y**2 - z**2 + 13*z*y -8*x*y) + (9, 1, 5) + """ + var, coeff, diop_type = classify_diop(eq, _dict=False) + + if diop_type in ( + HomogeneousTernaryQuadratic.name, + HomogeneousTernaryQuadraticNormal.name): + sol = _diop_ternary_quadratic(var, coeff) + if len(sol) > 0: + x_0, y_0, z_0 = list(sol)[0] + else: + x_0, y_0, z_0 = None, None, None + + if parameterize: + return _parametrize_ternary_quadratic( + (x_0, y_0, z_0), var, coeff) + return x_0, y_0, z_0 + + +def _diop_ternary_quadratic(_var, coeff): + eq = sum(i*coeff[i] for i in coeff) + if HomogeneousTernaryQuadratic(eq).matches(): + return HomogeneousTernaryQuadratic(eq, free_symbols=_var).solve() + elif HomogeneousTernaryQuadraticNormal(eq).matches(): + return HomogeneousTernaryQuadraticNormal(eq, free_symbols=_var).solve() + + +def transformation_to_normal(eq): + """ + Returns the transformation Matrix that converts a general ternary + quadratic equation ``eq`` (`ax^2 + by^2 + cz^2 + dxy + eyz + fxz`) + to a form without cross terms: `ax^2 + by^2 + cz^2 = 0`. This is + not used in solving ternary quadratics; it is only implemented for + the sake of completeness. + """ + var, coeff, diop_type = classify_diop(eq, _dict=False) + + if diop_type in ( + "homogeneous_ternary_quadratic", + "homogeneous_ternary_quadratic_normal"): + return _transformation_to_normal(var, coeff) + + +def _transformation_to_normal(var, coeff): + + _var = list(var) # copy + x, y, z = var + + if not any(coeff[i**2] for i in var): + # https://math.stackexchange.com/questions/448051/transform-quadratic-ternary-form-to-normal-form/448065#448065 + a = coeff[x*y] + b = coeff[y*z] + c = coeff[x*z] + swap = False + if not a: # b can't be 0 or else there aren't 3 vars + swap = True + a, b = b, a + T = Matrix(((1, 1, -b/a), (1, -1, -c/a), (0, 0, 1))) + if swap: + T.row_swap(0, 1) + T.col_swap(0, 1) + return T + + if coeff[x**2] == 0: + # If the coefficient of x is zero change the variables + if coeff[y**2] == 0: + _var[0], _var[2] = var[2], var[0] + T = _transformation_to_normal(_var, coeff) + T.row_swap(0, 2) + T.col_swap(0, 2) + return T + + _var[0], _var[1] = var[1], var[0] + T = _transformation_to_normal(_var, coeff) + T.row_swap(0, 1) + T.col_swap(0, 1) + return T + + # Apply the transformation x --> X - (B*Y + C*Z)/(2*A) + if coeff[x*y] != 0 or coeff[x*z] != 0: + A = coeff[x**2] + B = coeff[x*y] + C = coeff[x*z] + D = coeff[y**2] + E = coeff[y*z] + F = coeff[z**2] + + _coeff = {} + + _coeff[x**2] = 4*A**2 + _coeff[y**2] = 4*A*D - B**2 + _coeff[z**2] = 4*A*F - C**2 + _coeff[y*z] = 4*A*E - 2*B*C + _coeff[x*y] = 0 + _coeff[x*z] = 0 + + T_0 = _transformation_to_normal(_var, _coeff) + return Matrix(3, 3, [1, S(-B)/(2*A), S(-C)/(2*A), 0, 1, 0, 0, 0, 1])*T_0 + + elif coeff[y*z] != 0: + if coeff[y**2] == 0: + if coeff[z**2] == 0: + # Equations of the form A*x**2 + E*yz = 0. + # Apply transformation y -> Y + Z ans z -> Y - Z + return Matrix(3, 3, [1, 0, 0, 0, 1, 1, 0, 1, -1]) + + # Ax**2 + E*y*z + F*z**2 = 0 + _var[0], _var[2] = var[2], var[0] + T = _transformation_to_normal(_var, coeff) + T.row_swap(0, 2) + T.col_swap(0, 2) + return T + + # A*x**2 + D*y**2 + E*y*z + F*z**2 = 0, F may be zero + _var[0], _var[1] = var[1], var[0] + T = _transformation_to_normal(_var, coeff) + T.row_swap(0, 1) + T.col_swap(0, 1) + return T + + return Matrix.eye(3) + + +def parametrize_ternary_quadratic(eq): + """ + Returns the parametrized general solution for the ternary quadratic + equation ``eq`` which has the form + `ax^2 + by^2 + cz^2 + fxy + gyz + hxz = 0`. + + Examples + ======== + + >>> from sympy import Tuple, ordered + >>> from sympy.abc import x, y, z + >>> from sympy.solvers.diophantine.diophantine import parametrize_ternary_quadratic + + The parametrized solution may be returned with three parameters: + + >>> parametrize_ternary_quadratic(2*x**2 + y**2 - 2*z**2) + (p**2 - 2*q**2, -2*p**2 + 4*p*q - 4*p*r - 4*q**2, p**2 - 4*p*q + 2*q**2 - 4*q*r) + + There might also be only two parameters: + + >>> parametrize_ternary_quadratic(4*x**2 + 2*y**2 - 3*z**2) + (2*p**2 - 3*q**2, -4*p**2 + 12*p*q - 6*q**2, 4*p**2 - 8*p*q + 6*q**2) + + Notes + ===== + + Consider ``p`` and ``q`` in the previous 2-parameter + solution and observe that more than one solution can be represented + by a given pair of parameters. If `p` and ``q`` are not coprime, this is + trivially true since the common factor will also be a common factor of the + solution values. But it may also be true even when ``p`` and + ``q`` are coprime: + + >>> sol = Tuple(*_) + >>> p, q = ordered(sol.free_symbols) + >>> sol.subs([(p, 3), (q, 2)]) + (6, 12, 12) + >>> sol.subs([(q, 1), (p, 1)]) + (-1, 2, 2) + >>> sol.subs([(q, 0), (p, 1)]) + (2, -4, 4) + >>> sol.subs([(q, 1), (p, 0)]) + (-3, -6, 6) + + Except for sign and a common factor, these are equivalent to + the solution of (1, 2, 2). + + References + ========== + + .. [1] The algorithmic resolution of Diophantine equations, Nigel P. Smart, + London Mathematical Society Student Texts 41, Cambridge University + Press, Cambridge, 1998. + + """ + var, coeff, diop_type = classify_diop(eq, _dict=False) + + if diop_type in ( + "homogeneous_ternary_quadratic", + "homogeneous_ternary_quadratic_normal"): + x_0, y_0, z_0 = list(_diop_ternary_quadratic(var, coeff))[0] + return _parametrize_ternary_quadratic( + (x_0, y_0, z_0), var, coeff) + + +def _parametrize_ternary_quadratic(solution, _var, coeff): + # called for a*x**2 + b*y**2 + c*z**2 + d*x*y + e*y*z + f*x*z = 0 + assert 1 not in coeff + + x_0, y_0, z_0 = solution + + v = list(_var) # copy + + if x_0 is None: + return (None, None, None) + + if solution.count(0) >= 2: + # if there are 2 zeros the equation reduces + # to k*X**2 == 0 where X is x, y, or z so X must + # be zero, too. So there is only the trivial + # solution. + return (None, None, None) + + if x_0 == 0: + v[0], v[1] = v[1], v[0] + y_p, x_p, z_p = _parametrize_ternary_quadratic( + (y_0, x_0, z_0), v, coeff) + return x_p, y_p, z_p + + x, y, z = v + r, p, q = symbols("r, p, q", integer=True) + + eq = sum(k*v for k, v in coeff.items()) + eq_1 = _mexpand(eq.subs(zip( + (x, y, z), (r*x_0, r*y_0 + p, r*z_0 + q)))) + A, B = eq_1.as_independent(r, as_Add=True) + + + x = A*x_0 + y = (A*y_0 - _mexpand(B/r*p)) + z = (A*z_0 - _mexpand(B/r*q)) + + return _remove_gcd(x, y, z) + + +def diop_ternary_quadratic_normal(eq, parameterize=False): + """ + Solves the quadratic ternary diophantine equation, + `ax^2 + by^2 + cz^2 = 0`. + + Explanation + =========== + + Here the coefficients `a`, `b`, and `c` should be non zero. Otherwise the + equation will be a quadratic binary or univariate equation. If solvable, + returns a tuple `(x, y, z)` that satisfies the given equation. If the + equation does not have integer solutions, `(None, None, None)` is returned. + + Usage + ===== + + ``diop_ternary_quadratic_normal(eq)``: where ``eq`` is an equation of the form + `ax^2 + by^2 + cz^2 = 0`. + + Examples + ======== + + >>> from sympy.abc import x, y, z + >>> from sympy.solvers.diophantine.diophantine import diop_ternary_quadratic_normal + >>> diop_ternary_quadratic_normal(x**2 + 3*y**2 - z**2) + (1, 0, 1) + >>> diop_ternary_quadratic_normal(4*x**2 + 5*y**2 - z**2) + (1, 0, 2) + >>> diop_ternary_quadratic_normal(34*x**2 - 3*y**2 - 301*z**2) + (4, 9, 1) + """ + var, coeff, diop_type = classify_diop(eq, _dict=False) + if diop_type == HomogeneousTernaryQuadraticNormal.name: + sol = _diop_ternary_quadratic_normal(var, coeff) + if len(sol) > 0: + x_0, y_0, z_0 = list(sol)[0] + else: + x_0, y_0, z_0 = None, None, None + if parameterize: + return _parametrize_ternary_quadratic( + (x_0, y_0, z_0), var, coeff) + return x_0, y_0, z_0 + + +def _diop_ternary_quadratic_normal(var, coeff): + eq = sum(i * coeff[i] for i in coeff) + return HomogeneousTernaryQuadraticNormal(eq, free_symbols=var).solve() + + +def sqf_normal(a, b, c, steps=False): + """ + Return `a', b', c'`, the coefficients of the square-free normal + form of `ax^2 + by^2 + cz^2 = 0`, where `a', b', c'` are pairwise + prime. If `steps` is True then also return three tuples: + `sq`, `sqf`, and `(a', b', c')` where `sq` contains the square + factors of `a`, `b` and `c` after removing the `gcd(a, b, c)`; + `sqf` contains the values of `a`, `b` and `c` after removing + both the `gcd(a, b, c)` and the square factors. + + The solutions for `ax^2 + by^2 + cz^2 = 0` can be + recovered from the solutions of `a'x^2 + b'y^2 + c'z^2 = 0`. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import sqf_normal + >>> sqf_normal(2 * 3**2 * 5, 2 * 5 * 11, 2 * 7**2 * 11) + (11, 1, 5) + >>> sqf_normal(2 * 3**2 * 5, 2 * 5 * 11, 2 * 7**2 * 11, True) + ((3, 1, 7), (5, 55, 11), (11, 1, 5)) + + References + ========== + + .. [1] Legendre's Theorem, Legrange's Descent, + https://public.csusm.edu/aitken_html/notes/legendre.pdf + + + See Also + ======== + + reconstruct() + """ + ABC = _remove_gcd(a, b, c) + sq = tuple(square_factor(i) for i in ABC) + sqf = A, B, C = tuple([i//j**2 for i,j in zip(ABC, sq)]) + pc = igcd(A, B) + A /= pc + B /= pc + pa = igcd(B, C) + B /= pa + C /= pa + pb = igcd(A, C) + A /= pb + B /= pb + + A *= pa + B *= pb + C *= pc + + if steps: + return (sq, sqf, (A, B, C)) + else: + return A, B, C + + +def square_factor(a): + r""" + Returns an integer `c` s.t. `a = c^2k, \ c,k \in Z`. Here `k` is square + free. `a` can be given as an integer or a dictionary of factors. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import square_factor + >>> square_factor(24) + 2 + >>> square_factor(-36*3) + 6 + >>> square_factor(1) + 1 + >>> square_factor({3: 2, 2: 1, -1: 1}) # -18 + 3 + + See Also + ======== + sympy.ntheory.factor_.core + """ + f = a if isinstance(a, dict) else factorint(a) + return Mul(*[p**(e//2) for p, e in f.items()]) + + +def reconstruct(A, B, z): + """ + Reconstruct the `z` value of an equivalent solution of `ax^2 + by^2 + cz^2` + from the `z` value of a solution of the square-free normal form of the + equation, `a'*x^2 + b'*y^2 + c'*z^2`, where `a'`, `b'` and `c'` are square + free and `gcd(a', b', c') == 1`. + """ + f = factorint(igcd(A, B)) + for p, e in f.items(): + if e != 1: + raise ValueError('a and b should be square-free') + z *= p + return z + + +def ldescent(A, B): + """ + Return a non-trivial solution to `w^2 = Ax^2 + By^2` using + Lagrange's method; return None if there is no such solution. + + Parameters + ========== + + A : Integer + B : Integer + non-zero integer + + Returns + ======= + + (int, int, int) | None : a tuple `(w_0, x_0, y_0)` which is a solution to the above equation. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import ldescent + >>> ldescent(1, 1) # w^2 = x^2 + y^2 + (1, 1, 0) + >>> ldescent(4, -7) # w^2 = 4x^2 - 7y^2 + (2, -1, 0) + + This means that `x = -1, y = 0` and `w = 2` is a solution to the equation + `w^2 = 4x^2 - 7y^2` + + >>> ldescent(5, -1) # w^2 = 5x^2 - y^2 + (2, 1, -1) + + References + ========== + + .. [1] The algorithmic resolution of Diophantine equations, Nigel P. Smart, + London Mathematical Society Student Texts 41, Cambridge University + Press, Cambridge, 1998. + .. [2] Cremona, J. E., Rusin, D. (2003). Efficient Solution of Rational Conics. + Mathematics of Computation, 72(243), 1417-1441. + https://doi.org/10.1090/S0025-5718-02-01480-1 + """ + if A == 0 or B == 0: + raise ValueError("A and B must be non-zero integers") + if abs(A) > abs(B): + w, y, x = ldescent(B, A) + return w, x, y + if A == 1: + return (1, 1, 0) + if B == 1: + return (1, 0, 1) + if B == -1: # and A == -1 + return + + r = sqrt_mod(A, B) + if r is None: + return + Q = (r**2 - A) // B + if Q == 0: + return r, -1, 0 + for i in divisors(Q): + d, _exact = integer_nthroot(abs(Q) // i, 2) + if _exact: + B_0 = sign(Q)*i + W, X, Y = ldescent(A, B_0) + return _remove_gcd(-A*X + r*W, r*X - W, Y*B_0*d) + + +def descent(A, B): + """ + Returns a non-trivial solution, (x, y, z), to `x^2 = Ay^2 + Bz^2` + using Lagrange's descent method with lattice-reduction. `A` and `B` + are assumed to be valid for such a solution to exist. + + This is faster than the normal Lagrange's descent algorithm because + the Gaussian reduction is used. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import descent + >>> descent(3, 1) # x**2 = 3*y**2 + z**2 + (1, 0, 1) + + `(x, y, z) = (1, 0, 1)` is a solution to the above equation. + + >>> descent(41, -113) + (-16, -3, 1) + + References + ========== + + .. [1] Cremona, J. E., Rusin, D. (2003). Efficient Solution of Rational Conics. + Mathematics of Computation, 72(243), 1417-1441. + https://doi.org/10.1090/S0025-5718-02-01480-1 + """ + if abs(A) > abs(B): + x, y, z = descent(B, A) + return x, z, y + + if B == 1: + return (1, 0, 1) + if A == 1: + return (1, 1, 0) + if B == -A: + return (0, 1, 1) + if B == A: + x, z, y = descent(-1, A) + return (A*y, z, x) + + w = sqrt_mod(A, B) + x_0, z_0 = gaussian_reduce(w, A, B) + + t = (x_0**2 - A*z_0**2) // B + t_2 = square_factor(t) + t_1 = t // t_2**2 + + x_1, z_1, y_1 = descent(A, t_1) + + return _remove_gcd(x_0*x_1 + A*z_0*z_1, z_0*x_1 + x_0*z_1, t_1*t_2*y_1) + + +def gaussian_reduce(w:int, a:int, b:int) -> tuple[int, int]: + r""" + Returns a reduced solution `(x, z)` to the congruence + `X^2 - aZ^2 \equiv 0 \pmod{b}` so that `x^2 + |a|z^2` is as small as possible. + Here ``w`` is a solution of the congruence `x^2 \equiv a \pmod{b}`. + + This function is intended to be used only for ``descent()``. + + Explanation + =========== + + The Gaussian reduction can find the shortest vector for any norm. + So we define the special norm for the vectors `u = (u_1, u_2)` and `v = (v_1, v_2)` as follows. + + .. math :: + u \cdot v := (wu_1 + bu_2)(wv_1 + bv_2) + |a|u_1v_1 + + Note that, given the mapping `f: (u_1, u_2) \to (wu_1 + bu_2, u_1)`, + `f((u_1,u_2))` is the solution to `X^2 - aZ^2 \equiv 0 \pmod{b}`. + In other words, finding the shortest vector in this norm will yield a solution with smaller `X^2 + |a|Z^2`. + The algorithm starts from basis vectors `(0, 1)` and `(1, 0)` + (corresponding to solutions `(b, 0)` and `(w, 1)`, respectively) and finds the shortest vector. + The shortest vector does not necessarily correspond to the smallest solution, + but since ``descent()`` only wants the smallest possible solution, it is sufficient. + + Parameters + ========== + + w : int + ``w`` s.t. `w^2 \equiv a \pmod{b}` + a : int + square-free nonzero integer + b : int + square-free nonzero integer + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import gaussian_reduce + >>> from sympy.ntheory.residue_ntheory import sqrt_mod + >>> a, b = 19, 101 + >>> gaussian_reduce(sqrt_mod(a, b), a, b) # 1**2 - 19*(-4)**2 = -303 + (1, -4) + >>> a, b = 11, 14 + >>> x, z = gaussian_reduce(sqrt_mod(a, b), a, b) + >>> (x**2 - a*z**2) % b == 0 + True + + It does not always return the smallest solution. + + >>> a, b = 6, 95 + >>> min_x, min_z = 1, 4 + >>> x, z = gaussian_reduce(sqrt_mod(a, b), a, b) + >>> (x**2 - a*z**2) % b == 0 and (min_x**2 - a*min_z**2) % b == 0 + True + >>> min_x**2 + abs(a)*min_z**2 < x**2 + abs(a)*z**2 + True + + References + ========== + + .. [1] Gaussian lattice Reduction [online]. Available: + https://web.archive.org/web/20201021115213/http://home.ie.cuhk.edu.hk/~wkshum/wordpress/?p=404 + .. [2] Cremona, J. E., Rusin, D. (2003). Efficient Solution of Rational Conics. + Mathematics of Computation, 72(243), 1417-1441. + https://doi.org/10.1090/S0025-5718-02-01480-1 + """ + a = abs(a) + def _dot(u, v): + return u[0]*v[0] + a*u[1]*v[1] + + u = (b, 0) + v = (w, 1) if b*w >= 0 else (-w, -1) + # i.e., _dot(u, v) >= 0 + + if b**2 < w**2 + a: + u, v = v, u + # i.e., norm(u) >= norm(v), where norm(u) := sqrt(_dot(u, u)) + + while _dot(u, u) > (dv := _dot(v, v)): + k = _dot(u, v) // dv + u, v = v, (u[0] - k*v[0], u[1] - k*v[1]) + c = (v[0] - u[0], v[1] - u[1]) + if _dot(c, c) <= _dot(u, u) <= 2*_dot(u, v): + return c + return u + + +def holzer(x, y, z, a, b, c): + r""" + Simplify the solution `(x, y, z)` of the equation + `ax^2 + by^2 = cz^2` with `a, b, c > 0` and `z^2 \geq \mid ab \mid` to + a new reduced solution `(x', y', z')` such that `z'^2 \leq \mid ab \mid`. + + The algorithm is an interpretation of Mordell's reduction as described + on page 8 of Cremona and Rusin's paper [1]_ and the work of Mordell in + reference [2]_. + + References + ========== + + .. [1] Cremona, J. E., Rusin, D. (2003). Efficient Solution of Rational Conics. + Mathematics of Computation, 72(243), 1417-1441. + https://doi.org/10.1090/S0025-5718-02-01480-1 + .. [2] Diophantine Equations, L. J. Mordell, page 48. + + """ + + if _odd(c): + k = 2*c + else: + k = c//2 + + small = a*b*c + step = 0 + while True: + t1, t2, t3 = a*x**2, b*y**2, c*z**2 + # check that it's a solution + if t1 + t2 != t3: + if step == 0: + raise ValueError('bad starting solution') + break + x_0, y_0, z_0 = x, y, z + if max(t1, t2, t3) <= small: + # Holzer condition + break + + uv = u, v = base_solution_linear(k, y_0, -x_0) + if None in uv: + break + + p, q = -(a*u*x_0 + b*v*y_0), c*z_0 + r = Rational(p, q) + if _even(c): + w = _nint_or_floor(p, q) + assert abs(w - r) <= S.Half + else: + w = p//q # floor + if _odd(a*u + b*v + c*w): + w += 1 + assert abs(w - r) <= S.One + + A = (a*u**2 + b*v**2 + c*w**2) + B = (a*u*x_0 + b*v*y_0 + c*w*z_0) + x = Rational(x_0*A - 2*u*B, k) + y = Rational(y_0*A - 2*v*B, k) + z = Rational(z_0*A - 2*w*B, k) + assert all(i.is_Integer for i in (x, y, z)) + step += 1 + + return tuple([int(i) for i in (x_0, y_0, z_0)]) + + +def diop_general_pythagorean(eq, param=symbols("m", integer=True)): + """ + Solves the general pythagorean equation, + `a_{1}^2x_{1}^2 + a_{2}^2x_{2}^2 + . . . + a_{n}^2x_{n}^2 - a_{n + 1}^2x_{n + 1}^2 = 0`. + + Returns a tuple which contains a parametrized solution to the equation, + sorted in the same order as the input variables. + + Usage + ===== + + ``diop_general_pythagorean(eq, param)``: where ``eq`` is a general + pythagorean equation which is assumed to be zero and ``param`` is the base + parameter used to construct other parameters by subscripting. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import diop_general_pythagorean + >>> from sympy.abc import a, b, c, d, e + >>> diop_general_pythagorean(a**2 + b**2 + c**2 - d**2) + (m1**2 + m2**2 - m3**2, 2*m1*m3, 2*m2*m3, m1**2 + m2**2 + m3**2) + >>> diop_general_pythagorean(9*a**2 - 4*b**2 + 16*c**2 + 25*d**2 + e**2) + (10*m1**2 + 10*m2**2 + 10*m3**2 - 10*m4**2, 15*m1**2 + 15*m2**2 + 15*m3**2 + 15*m4**2, 15*m1*m4, 12*m2*m4, 60*m3*m4) + """ + var, coeff, diop_type = classify_diop(eq, _dict=False) + + if diop_type == GeneralPythagorean.name: + if param is None: + params = None + else: + params = symbols('%s1:%i' % (param, len(var)), integer=True) + return list(GeneralPythagorean(eq).solve(parameters=params))[0] + + +def diop_general_sum_of_squares(eq, limit=1): + r""" + Solves the equation `x_{1}^2 + x_{2}^2 + . . . + x_{n}^2 - k = 0`. + + Returns at most ``limit`` number of solutions. + + Usage + ===== + + ``general_sum_of_squares(eq, limit)`` : Here ``eq`` is an expression which + is assumed to be zero. Also, ``eq`` should be in the form, + `x_{1}^2 + x_{2}^2 + . . . + x_{n}^2 - k = 0`. + + Details + ======= + + When `n = 3` if `k = 4^a(8m + 7)` for some `a, m \in Z` then there will be + no solutions. Refer to [1]_ for more details. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import diop_general_sum_of_squares + >>> from sympy.abc import a, b, c, d, e + >>> diop_general_sum_of_squares(a**2 + b**2 + c**2 + d**2 + e**2 - 2345) + {(15, 22, 22, 24, 24)} + + Reference + ========= + + .. [1] Representing an integer as a sum of three squares, [online], + Available: + https://www.proofwiki.org/wiki/Integer_as_Sum_of_Three_Squares + """ + var, coeff, diop_type = classify_diop(eq, _dict=False) + + if diop_type == GeneralSumOfSquares.name: + return set(GeneralSumOfSquares(eq).solve(limit=limit)) + + +def diop_general_sum_of_even_powers(eq, limit=1): + """ + Solves the equation `x_{1}^e + x_{2}^e + . . . + x_{n}^e - k = 0` + where `e` is an even, integer power. + + Returns at most ``limit`` number of solutions. + + Usage + ===== + + ``general_sum_of_even_powers(eq, limit)`` : Here ``eq`` is an expression which + is assumed to be zero. Also, ``eq`` should be in the form, + `x_{1}^e + x_{2}^e + . . . + x_{n}^e - k = 0`. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import diop_general_sum_of_even_powers + >>> from sympy.abc import a, b + >>> diop_general_sum_of_even_powers(a**4 + b**4 - (2**4 + 3**4)) + {(2, 3)} + + See Also + ======== + + power_representation + """ + var, coeff, diop_type = classify_diop(eq, _dict=False) + + if diop_type == GeneralSumOfEvenPowers.name: + return set(GeneralSumOfEvenPowers(eq).solve(limit=limit)) + + +## Functions below this comment can be more suitably grouped under +## an Additive number theory module rather than the Diophantine +## equation module. + + +def partition(n, k=None, zeros=False): + """ + Returns a generator that can be used to generate partitions of an integer + `n`. + + Explanation + =========== + + A partition of `n` is a set of positive integers which add up to `n`. For + example, partitions of 3 are 3, 1 + 2, 1 + 1 + 1. A partition is returned + as a tuple. If ``k`` equals None, then all possible partitions are returned + irrespective of their size, otherwise only the partitions of size ``k`` are + returned. If the ``zero`` parameter is set to True then a suitable + number of zeros are added at the end of every partition of size less than + ``k``. + + ``zero`` parameter is considered only if ``k`` is not None. When the + partitions are over, the last `next()` call throws the ``StopIteration`` + exception, so this function should always be used inside a try - except + block. + + Details + ======= + + ``partition(n, k)``: Here ``n`` is a positive integer and ``k`` is the size + of the partition which is also positive integer. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import partition + >>> f = partition(5) + >>> next(f) + (1, 1, 1, 1, 1) + >>> next(f) + (1, 1, 1, 2) + >>> g = partition(5, 3) + >>> next(g) + (1, 1, 3) + >>> next(g) + (1, 2, 2) + >>> g = partition(5, 3, zeros=True) + >>> next(g) + (0, 0, 5) + + """ + if not zeros or k is None: + for i in ordered_partitions(n, k): + yield tuple(i) + else: + for m in range(1, k + 1): + for i in ordered_partitions(n, m): + i = tuple(i) + yield (0,)*(k - len(i)) + i + + +def prime_as_sum_of_two_squares(p): + """ + Represent a prime `p` as a unique sum of two squares; this can + only be done if the prime is congruent to 1 mod 4. + + Parameters + ========== + + p : Integer + A prime that is congruent to 1 mod 4 + + Returns + ======= + + (int, int) | None : Pair of positive integers ``(x, y)`` satisfying ``x**2 + y**2 = p``. + None if ``p`` is not congruent to 1 mod 4. + + Raises + ====== + + ValueError + If ``p`` is not prime number + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import prime_as_sum_of_two_squares + >>> prime_as_sum_of_two_squares(7) # can't be done + >>> prime_as_sum_of_two_squares(5) + (1, 2) + + Reference + ========= + + .. [1] Representing a number as a sum of four squares, [online], + Available: https://schorn.ch/lagrange.html + + See Also + ======== + + sum_of_squares + + """ + p = as_int(p) + if p % 4 != 1: + return + if not isprime(p): + raise ValueError("p should be a prime number") + + if p % 8 == 5: + # Legendre symbol (2/p) == -1 if p % 8 in [3, 5] + b = 2 + elif p % 12 == 5: + # Legendre symbol (3/p) == -1 if p % 12 in [5, 7] + b = 3 + elif p % 5 in [2, 3]: + # Legendre symbol (5/p) == -1 if p % 5 in [2, 3] + b = 5 + else: + b = 7 + while jacobi(b, p) == 1: + b = nextprime(b) + + b = pow(b, p >> 2, p) + a = p + while b**2 > p: + a, b = b, a % b + return (int(a % b), int(b)) # convert from long + + +def sum_of_three_squares(n): + r""" + Returns a 3-tuple $(a, b, c)$ such that $a^2 + b^2 + c^2 = n$ and + $a, b, c \geq 0$. + + Returns None if $n = 4^a(8m + 7)$ for some `a, m \in \mathbb{Z}`. See + [1]_ for more details. + + Parameters + ========== + + n : Integer + non-negative integer + + Returns + ======= + + (int, int, int) | None : 3-tuple non-negative integers ``(a, b, c)`` satisfying ``a**2 + b**2 + c**2 = n``. + a,b,c are sorted in ascending order. ``None`` if no such ``(a,b,c)``. + + Raises + ====== + + ValueError + If ``n`` is a negative integer + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import sum_of_three_squares + >>> sum_of_three_squares(44542) + (18, 37, 207) + + References + ========== + + .. [1] Representing a number as a sum of three squares, [online], + Available: https://schorn.ch/lagrange.html + + See Also + ======== + + power_representation : + ``sum_of_three_squares(n)`` is one of the solutions output by ``power_representation(n, 2, 3, zeros=True)`` + + """ + # https://math.stackexchange.com/questions/483101/rabin-and-shallit-algorithm/651425#651425 + # discusses these numbers (except for 1, 2, 3) as the exceptions of H&L's conjecture that + # Every sufficiently large number n is either a square or the sum of a prime and a square. + special = {1: (0, 0, 1), 2: (0, 1, 1), 3: (1, 1, 1), 10: (0, 1, 3), 34: (3, 3, 4), + 58: (0, 3, 7), 85: (0, 6, 7), 130: (0, 3, 11), 214: (3, 6, 13), 226: (8, 9, 9), + 370: (8, 9, 15), 526: (6, 7, 21), 706: (15, 15, 16), 730: (0, 1, 27), + 1414: (6, 17, 33), 1906: (13, 21, 36), 2986: (21, 32, 39), 9634: (56, 57, 57)} + n = as_int(n) + if n < 0: + raise ValueError("n should be a non-negative integer") + if n == 0: + return (0, 0, 0) + n, v = remove(n, 4) + v = 1 << v + if n % 8 == 7: + return + if n in special: + return tuple([v*i for i in special[n]]) + + s, _exact = integer_nthroot(n, 2) + if _exact: + return (0, 0, v*s) + if n % 8 == 3: + if not s % 2: + s -= 1 + for x in range(s, -1, -2): + N = (n - x**2) // 2 + if isprime(N): + # n % 8 == 3 and x % 2 == 1 => N % 4 == 1 + y, z = prime_as_sum_of_two_squares(N) + return tuple(sorted([v*x, v*(y + z), v*abs(y - z)])) + # We will never reach this point because there must be a solution. + assert False + + # assert n % 4 in [1, 2] + if not((n % 2) ^ (s % 2)): + s -= 1 + for x in range(s, -1, -2): + N = n - x**2 + if isprime(N): + # assert N % 4 == 1 + y, z = prime_as_sum_of_two_squares(N) + return tuple(sorted([v*x, v*y, v*z])) + # We will never reach this point because there must be a solution. + assert False + + +def sum_of_four_squares(n): + r""" + Returns a 4-tuple `(a, b, c, d)` such that `a^2 + b^2 + c^2 + d^2 = n`. + Here `a, b, c, d \geq 0`. + + Parameters + ========== + + n : Integer + non-negative integer + + Returns + ======= + + (int, int, int, int) : 4-tuple non-negative integers ``(a, b, c, d)`` satisfying ``a**2 + b**2 + c**2 + d**2 = n``. + a,b,c,d are sorted in ascending order. + + Raises + ====== + + ValueError + If ``n`` is a negative integer + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import sum_of_four_squares + >>> sum_of_four_squares(3456) + (8, 8, 32, 48) + >>> sum_of_four_squares(1294585930293) + (0, 1234, 2161, 1137796) + + References + ========== + + .. [1] Representing a number as a sum of four squares, [online], + Available: https://schorn.ch/lagrange.html + + See Also + ======== + + power_representation : + ``sum_of_four_squares(n)`` is one of the solutions output by ``power_representation(n, 2, 4, zeros=True)`` + + """ + n = as_int(n) + if n < 0: + raise ValueError("n should be a non-negative integer") + if n == 0: + return (0, 0, 0, 0) + # remove factors of 4 since a solution in terms of 3 squares is + # going to be returned; this is also done in sum_of_three_squares, + # but it needs to be done here to select d + n, v = remove(n, 4) + v = 1 << v + if n % 8 == 7: + d = 2 + n = n - 4 + elif n % 8 in (2, 6): + d = 1 + n = n - 1 + else: + d = 0 + x, y, z = sum_of_three_squares(n) # sorted + return tuple(sorted([v*d, v*x, v*y, v*z])) + + +def power_representation(n, p, k, zeros=False): + r""" + Returns a generator for finding k-tuples of integers, + `(n_{1}, n_{2}, . . . n_{k})`, such that + `n = n_{1}^p + n_{2}^p + . . . n_{k}^p`. + + Usage + ===== + + ``power_representation(n, p, k, zeros)``: Represent non-negative number + ``n`` as a sum of ``k`` ``p``\ th powers. If ``zeros`` is true, then the + solutions is allowed to contain zeros. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import power_representation + + Represent 1729 as a sum of two cubes: + + >>> f = power_representation(1729, 3, 2) + >>> next(f) + (9, 10) + >>> next(f) + (1, 12) + + If the flag `zeros` is True, the solution may contain tuples with + zeros; any such solutions will be generated after the solutions + without zeros: + + >>> list(power_representation(125, 2, 3, zeros=True)) + [(5, 6, 8), (3, 4, 10), (0, 5, 10), (0, 2, 11)] + + For even `p` the `permute_sign` function can be used to get all + signed values: + + >>> from sympy.utilities.iterables import permute_signs + >>> list(permute_signs((1, 12))) + [(1, 12), (-1, 12), (1, -12), (-1, -12)] + + All possible signed permutations can also be obtained: + + >>> from sympy.utilities.iterables import signed_permutations + >>> list(signed_permutations((1, 12))) + [(1, 12), (-1, 12), (1, -12), (-1, -12), (12, 1), (-12, 1), (12, -1), (-12, -1)] + """ + n, p, k = [as_int(i) for i in (n, p, k)] + + if n < 0: + if p % 2: + for t in power_representation(-n, p, k, zeros): + yield tuple(-i for i in t) + return + + if p < 1 or k < 1: + raise ValueError(filldedent(''' + Expecting positive integers for `(p, k)`, but got `(%s, %s)`''' + % (p, k))) + + if n == 0: + if zeros: + yield (0,)*k + return + + if k == 1: + if p == 1: + yield (n,) + elif n == 1: + yield (1,) + else: + be = perfect_power(n) + if be: + b, e = be + d, r = divmod(e, p) + if not r: + yield (b**d,) + return + + if p == 1: + yield from partition(n, k, zeros=zeros) + return + + if p == 2: + if k == 3: + n, v = remove(n, 4) + if v: + v = 1 << v + for t in power_representation(n, p, k, zeros): + yield tuple(i*v for i in t) + return + feasible = _can_do_sum_of_squares(n, k) + if not feasible: + return + if not zeros: + if n > 33 and k >= 5 and k <= n and n - k in ( + 13, 10, 7, 5, 4, 2, 1): + '''Todd G. Will, "When Is n^2 a Sum of k Squares?", [online]. + Available: https://www.maa.org/sites/default/files/Will-MMz-201037918.pdf''' + return + # quick tests since feasibility includes the possiblity of 0 + if k == 4 and (n in (1, 3, 5, 9, 11, 17, 29, 41) or remove(n, 4)[0] in (2, 6, 14)): + # A000534 + return + if k == 3 and n in (1, 2, 5, 10, 13, 25, 37, 58, 85, 130): # or n = some number >= 5*10**10 + # A051952 + return + if feasible is not True: # it's prime and k == 2 + yield prime_as_sum_of_two_squares(n) + return + + if k == 2 and p > 2: + be = perfect_power(n) + if be and be[1] % p == 0: + return # Fermat: a**n + b**n = c**n has no solution for n > 2 + + if n >= k: + a = integer_nthroot(n - (k - 1), p)[0] + for t in pow_rep_recursive(a, k, n, [], p): + yield tuple(reversed(t)) + + if zeros: + a = integer_nthroot(n, p)[0] + for i in range(1, k): + for t in pow_rep_recursive(a, i, n, [], p): + yield tuple(reversed(t + (0,)*(k - i))) + + +sum_of_powers = power_representation + + +def pow_rep_recursive(n_i, k, n_remaining, terms, p): + # Invalid arguments + if n_i <= 0 or k <= 0: + return + + # No solutions may exist + if n_remaining < k: + return + if k * pow(n_i, p) < n_remaining: + return + + if k == 0 and n_remaining == 0: + yield tuple(terms) + + elif k == 1: + # next_term^p must equal to n_remaining + next_term, exact = integer_nthroot(n_remaining, p) + if exact and next_term <= n_i: + yield tuple(terms + [next_term]) + return + + else: + # TODO: Fall back to diop_DN when k = 2 + if n_i >= 1 and k > 0: + for next_term in range(1, n_i + 1): + residual = n_remaining - pow(next_term, p) + if residual < 0: + break + yield from pow_rep_recursive(next_term, k - 1, residual, terms + [next_term], p) + + +def sum_of_squares(n, k, zeros=False): + """Return a generator that yields the k-tuples of nonnegative + values, the squares of which sum to n. If zeros is False (default) + then the solution will not contain zeros. The nonnegative + elements of a tuple are sorted. + + * If k == 1 and n is square, (n,) is returned. + + * If k == 2 then n can only be written as a sum of squares if + every prime in the factorization of n that has the form + 4*k + 3 has an even multiplicity. If n is prime then + it can only be written as a sum of two squares if it is + in the form 4*k + 1. + + * if k == 3 then n can be written as a sum of squares if it does + not have the form 4**m*(8*k + 7). + + * all integers can be written as the sum of 4 squares. + + * if k > 4 then n can be partitioned and each partition can + be written as a sum of 4 squares; if n is not evenly divisible + by 4 then n can be written as a sum of squares only if the + an additional partition can be written as sum of squares. + For example, if k = 6 then n is partitioned into two parts, + the first being written as a sum of 4 squares and the second + being written as a sum of 2 squares -- which can only be + done if the condition above for k = 2 can be met, so this will + automatically reject certain partitions of n. + + Examples + ======== + + >>> from sympy.solvers.diophantine.diophantine import sum_of_squares + >>> list(sum_of_squares(25, 2)) + [(3, 4)] + >>> list(sum_of_squares(25, 2, True)) + [(3, 4), (0, 5)] + >>> list(sum_of_squares(25, 4)) + [(1, 2, 2, 4)] + + See Also + ======== + + sympy.utilities.iterables.signed_permutations + """ + yield from power_representation(n, 2, k, zeros) + + +def _can_do_sum_of_squares(n, k): + """Return True if n can be written as the sum of k squares, + False if it cannot, or 1 if ``k == 2`` and ``n`` is prime (in which + case it *can* be written as a sum of two squares). A False + is returned only if it cannot be written as ``k``-squares, even + if 0s are allowed. + """ + if k < 1: + return False + if n < 0: + return False + if n == 0: + return True + if k == 1: + return is_square(n) + if k == 2: + if n in (1, 2): + return True + if isprime(n): + if n % 4 == 1: + return 1 # signal that it was prime + return False + # n is a composite number + # we can proceed iff no prime factor in the form 4*k + 3 + # has an odd multiplicity + return all(p % 4 !=3 or m % 2 == 0 for p, m in factorint(n).items()) + if k == 3: + return remove(n, 4)[0] % 8 != 7 + # every number can be written as a sum of 4 squares; for k > 4 partitions + # can be 0 + return True diff --git a/lib/python3.12/site-packages/sympy/solvers/diophantine/tests/__init__.py b/lib/python3.12/site-packages/sympy/solvers/diophantine/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/lib/python3.12/site-packages/sympy/solvers/diophantine/tests/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/diophantine/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2784cd229bd6fe1c141e72bc32dc69d754f1e47 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/diophantine/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/diophantine/tests/__pycache__/test_diophantine.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/diophantine/tests/__pycache__/test_diophantine.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45f706093aa8909ed94e865fb083d458fd16d506 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/diophantine/tests/__pycache__/test_diophantine.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/diophantine/tests/test_diophantine.py b/lib/python3.12/site-packages/sympy/solvers/diophantine/tests/test_diophantine.py new file mode 100644 index 0000000000000000000000000000000000000000..094770b7bba795aef306ea71831388414fde935e --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/diophantine/tests/test_diophantine.py @@ -0,0 +1,1051 @@ +from sympy.core.add import Add +from sympy.core.mul import Mul +from sympy.core.numbers import (Rational, oo, pi) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.matrices.dense import Matrix +from sympy.ntheory.factor_ import factorint +from sympy.simplify.powsimp import powsimp +from sympy.core.function import _mexpand +from sympy.core.sorting import default_sort_key, ordered +from sympy.functions.elementary.trigonometric import sin +from sympy.solvers.diophantine import diophantine +from sympy.solvers.diophantine.diophantine import (diop_DN, + diop_solve, diop_ternary_quadratic_normal, + diop_general_pythagorean, diop_ternary_quadratic, diop_linear, + diop_quadratic, diop_general_sum_of_squares, diop_general_sum_of_even_powers, + descent, diop_bf_DN, divisible, equivalent, find_DN, ldescent, length, + reconstruct, partition, power_representation, + prime_as_sum_of_two_squares, square_factor, sum_of_four_squares, + sum_of_three_squares, transformation_to_DN, transformation_to_normal, + classify_diop, base_solution_linear, cornacchia, sqf_normal, gaussian_reduce, holzer, + check_param, parametrize_ternary_quadratic, sum_of_powers, sum_of_squares, + _diop_ternary_quadratic_normal, _nint_or_floor, + _odd, _even, _remove_gcd, _can_do_sum_of_squares, DiophantineSolutionSet, GeneralPythagorean, + BinaryQuadratic) + +from sympy.testing.pytest import slow, raises, XFAIL +from sympy.utilities.iterables import ( + signed_permutations) + +a, b, c, d, p, q, x, y, z, w, t, u, v, X, Y, Z = symbols( + "a, b, c, d, p, q, x, y, z, w, t, u, v, X, Y, Z", integer=True) +t_0, t_1, t_2, t_3, t_4, t_5, t_6 = symbols("t_:7", integer=True) +m1, m2, m3 = symbols('m1:4', integer=True) +n1 = symbols('n1', integer=True) + + +def diop_simplify(eq): + return _mexpand(powsimp(_mexpand(eq))) + + +def test_input_format(): + raises(TypeError, lambda: diophantine(sin(x))) + raises(TypeError, lambda: diophantine(x/pi - 3)) + + +def test_nosols(): + # diophantine should sympify eq so that these are equivalent + assert diophantine(3) == set() + assert diophantine(S(3)) == set() + + +def test_univariate(): + assert diop_solve((x - 1)*(x - 2)**2) == {(1,), (2,)} + assert diop_solve((x - 1)*(x - 2)) == {(1,), (2,)} + + +def test_classify_diop(): + raises(TypeError, lambda: classify_diop(x**2/3 - 1)) + raises(ValueError, lambda: classify_diop(1)) + raises(NotImplementedError, lambda: classify_diop(w*x*y*z - 1)) + raises(NotImplementedError, lambda: classify_diop(x**3 + y**3 + z**4 - 90)) + assert classify_diop(14*x**2 + 15*x - 42) == ( + [x], {1: -42, x: 15, x**2: 14}, 'univariate') + assert classify_diop(x*y + z) == ( + [x, y, z], {x*y: 1, z: 1}, 'inhomogeneous_ternary_quadratic') + assert classify_diop(x*y + z + w + x**2) == ( + [w, x, y, z], {x*y: 1, w: 1, x**2: 1, z: 1}, 'inhomogeneous_general_quadratic') + assert classify_diop(x*y + x*z + x**2 + 1) == ( + [x, y, z], {x*y: 1, x*z: 1, x**2: 1, 1: 1}, 'inhomogeneous_general_quadratic') + assert classify_diop(x*y + z + w + 42) == ( + [w, x, y, z], {x*y: 1, w: 1, 1: 42, z: 1}, 'inhomogeneous_general_quadratic') + assert classify_diop(x*y + z*w) == ( + [w, x, y, z], {x*y: 1, w*z: 1}, 'homogeneous_general_quadratic') + assert classify_diop(x*y**2 + 1) == ( + [x, y], {x*y**2: 1, 1: 1}, 'cubic_thue') + assert classify_diop(x**4 + y**4 + z**4 - (1 + 16 + 81)) == ( + [x, y, z], {1: -98, x**4: 1, z**4: 1, y**4: 1}, 'general_sum_of_even_powers') + assert classify_diop(x**2 + y**2 + z**2) == ( + [x, y, z], {x**2: 1, y**2: 1, z**2: 1}, 'homogeneous_ternary_quadratic_normal') + + +def test_linear(): + assert diop_solve(x) == (0,) + assert diop_solve(1*x) == (0,) + assert diop_solve(3*x) == (0,) + assert diop_solve(x + 1) == (-1,) + assert diop_solve(2*x + 1) == (None,) + assert diop_solve(2*x + 4) == (-2,) + assert diop_solve(y + x) == (t_0, -t_0) + assert diop_solve(y + x + 0) == (t_0, -t_0) + assert diop_solve(y + x - 0) == (t_0, -t_0) + assert diop_solve(0*x - y - 5) == (-5,) + assert diop_solve(3*y + 2*x - 5) == (3*t_0 - 5, -2*t_0 + 5) + assert diop_solve(2*x - 3*y - 5) == (3*t_0 - 5, 2*t_0 - 5) + assert diop_solve(-2*x - 3*y - 5) == (3*t_0 + 5, -2*t_0 - 5) + assert diop_solve(7*x + 5*y) == (5*t_0, -7*t_0) + assert diop_solve(2*x + 4*y) == (-2*t_0, t_0) + assert diop_solve(4*x + 6*y - 4) == (3*t_0 - 2, -2*t_0 + 2) + assert diop_solve(4*x + 6*y - 3) == (None, None) + assert diop_solve(0*x + 3*y - 4*z + 5) == (4*t_0 + 5, 3*t_0 + 5) + assert diop_solve(4*x + 3*y - 4*z + 5) == (t_0, 8*t_0 + 4*t_1 + 5, 7*t_0 + 3*t_1 + 5) + assert diop_solve(4*x + 3*y - 4*z + 5, None) == (0, 5, 5) + assert diop_solve(4*x + 2*y + 8*z - 5) == (None, None, None) + assert diop_solve(5*x + 7*y - 2*z - 6) == (t_0, -3*t_0 + 2*t_1 + 6, -8*t_0 + 7*t_1 + 18) + assert diop_solve(3*x - 6*y + 12*z - 9) == (2*t_0 + 3, t_0 + 2*t_1, t_1) + assert diop_solve(6*w + 9*x + 20*y - z) == (t_0, t_1, t_1 + t_2, 6*t_0 + 29*t_1 + 20*t_2) + + # to ignore constant factors, use diophantine + raises(TypeError, lambda: diop_solve(x/2)) + + +def test_quadratic_simple_hyperbolic_case(): + # Simple Hyperbolic case: A = C = 0 and B != 0 + assert diop_solve(3*x*y + 34*x - 12*y + 1) == \ + {(-133, -11), (5, -57)} + assert diop_solve(6*x*y + 2*x + 3*y + 1) == set() + assert diop_solve(-13*x*y + 2*x - 4*y - 54) == {(27, 0)} + assert diop_solve(-27*x*y - 30*x - 12*y - 54) == {(-14, -1)} + assert diop_solve(2*x*y + 5*x + 56*y + 7) == {(-161, -3), (-47, -6), (-35, -12), + (-29, -69), (-27, 64), (-21, 7), + (-9, 1), (105, -2)} + assert diop_solve(6*x*y + 9*x + 2*y + 3) == set() + assert diop_solve(x*y + x + y + 1) == {(-1, t), (t, -1)} + assert diophantine(48*x*y) + + +def test_quadratic_elliptical_case(): + # Elliptical case: B**2 - 4AC < 0 + + assert diop_solve(42*x**2 + 8*x*y + 15*y**2 + 23*x + 17*y - 4915) == {(-11, -1)} + assert diop_solve(4*x**2 + 3*y**2 + 5*x - 11*y + 12) == set() + assert diop_solve(x**2 + y**2 + 2*x + 2*y + 2) == {(-1, -1)} + assert diop_solve(15*x**2 - 9*x*y + 14*y**2 - 23*x - 14*y - 4950) == {(-15, 6)} + assert diop_solve(10*x**2 + 12*x*y + 12*y**2 - 34) == \ + {(-1, -1), (-1, 2), (1, -2), (1, 1)} + + +def test_quadratic_parabolic_case(): + # Parabolic case: B**2 - 4AC = 0 + assert check_solutions(8*x**2 - 24*x*y + 18*y**2 + 5*x + 7*y + 16) + assert check_solutions(8*x**2 - 24*x*y + 18*y**2 + 6*x + 12*y - 6) + assert check_solutions(8*x**2 + 24*x*y + 18*y**2 + 4*x + 6*y - 7) + assert check_solutions(-4*x**2 + 4*x*y - y**2 + 2*x - 3) + assert check_solutions(x**2 + 2*x*y + y**2 + 2*x + 2*y + 1) + assert check_solutions(x**2 - 2*x*y + y**2 + 2*x + 2*y + 1) + assert check_solutions(y**2 - 41*x + 40) + + +def test_quadratic_perfect_square(): + # B**2 - 4*A*C > 0 + # B**2 - 4*A*C is a perfect square + assert check_solutions(48*x*y) + assert check_solutions(4*x**2 - 5*x*y + y**2 + 2) + assert check_solutions(-2*x**2 - 3*x*y + 2*y**2 -2*x - 17*y + 25) + assert check_solutions(12*x**2 + 13*x*y + 3*y**2 - 2*x + 3*y - 12) + assert check_solutions(8*x**2 + 10*x*y + 2*y**2 - 32*x - 13*y - 23) + assert check_solutions(4*x**2 - 4*x*y - 3*y- 8*x - 3) + assert check_solutions(- 4*x*y - 4*y**2 - 3*y- 5*x - 10) + assert check_solutions(x**2 - y**2 - 2*x - 2*y) + assert check_solutions(x**2 - 9*y**2 - 2*x - 6*y) + assert check_solutions(4*x**2 - 9*y**2 - 4*x - 12*y - 3) + + +def test_quadratic_non_perfect_square(): + # B**2 - 4*A*C is not a perfect square + # Used check_solutions() since the solutions are complex expressions involving + # square roots and exponents + assert check_solutions(x**2 - 2*x - 5*y**2) + assert check_solutions(3*x**2 - 2*y**2 - 2*x - 2*y) + assert check_solutions(x**2 - x*y - y**2 - 3*y) + assert check_solutions(x**2 - 9*y**2 - 2*x - 6*y) + assert BinaryQuadratic(x**2 + y**2 + 2*x + 2*y + 2).solve() == {(-1, -1)} + + +def test_issue_9106(): + eq = -48 - 2*x*(3*x - 1) + y*(3*y - 1) + v = (x, y) + for sol in diophantine(eq): + assert not diop_simplify(eq.xreplace(dict(zip(v, sol)))) + + +def test_issue_18138(): + eq = x**2 - x - y**2 + v = (x, y) + for sol in diophantine(eq): + assert not diop_simplify(eq.xreplace(dict(zip(v, sol)))) + + +@slow +def test_quadratic_non_perfect_slow(): + assert check_solutions(8*x**2 + 10*x*y - 2*y**2 - 32*x - 13*y - 23) + # This leads to very large numbers. + # assert check_solutions(5*x**2 - 13*x*y + y**2 - 4*x - 4*y - 15) + assert check_solutions(-3*x**2 - 2*x*y + 7*y**2 - 5*x - 7) + assert check_solutions(-4 - x + 4*x**2 - y - 3*x*y - 4*y**2) + assert check_solutions(1 + 2*x + 2*x**2 + 2*y + x*y - 2*y**2) + + +def test_DN(): + # Most of the test cases were adapted from, + # Solving the generalized Pell equation x**2 - D*y**2 = N, John P. Robertson, July 31, 2004. + # https://web.archive.org/web/20160323033128/http://www.jpr2718.org/pell.pdf + # others are verified using Wolfram Alpha. + + # Covers cases where D <= 0 or D > 0 and D is a square or N = 0 + # Solutions are straightforward in these cases. + assert diop_DN(3, 0) == [(0, 0)] + assert diop_DN(-17, -5) == [] + assert diop_DN(-19, 23) == [(2, 1)] + assert diop_DN(-13, 17) == [(2, 1)] + assert diop_DN(-15, 13) == [] + assert diop_DN(0, 5) == [] + assert diop_DN(0, 9) == [(3, t)] + assert diop_DN(9, 0) == [(3*t, t)] + assert diop_DN(16, 24) == [] + assert diop_DN(9, 180) == [(18, 4)] + assert diop_DN(9, -180) == [(12, 6)] + assert diop_DN(7, 0) == [(0, 0)] + + # When equation is x**2 + y**2 = N + # Solutions are interchangeable + assert diop_DN(-1, 5) == [(2, 1), (1, 2)] + assert diop_DN(-1, 169) == [(12, 5), (5, 12), (13, 0), (0, 13)] + + # D > 0 and D is not a square + + # N = 1 + assert diop_DN(13, 1) == [(649, 180)] + assert diop_DN(980, 1) == [(51841, 1656)] + assert diop_DN(981, 1) == [(158070671986249, 5046808151700)] + assert diop_DN(986, 1) == [(49299, 1570)] + assert diop_DN(991, 1) == [(379516400906811930638014896080, 12055735790331359447442538767)] + assert diop_DN(17, 1) == [(33, 8)] + assert diop_DN(19, 1) == [(170, 39)] + + # N = -1 + assert diop_DN(13, -1) == [(18, 5)] + assert diop_DN(991, -1) == [] + assert diop_DN(41, -1) == [(32, 5)] + assert diop_DN(290, -1) == [(17, 1)] + assert diop_DN(21257, -1) == [(13913102721304, 95427381109)] + assert diop_DN(32, -1) == [] + + # |N| > 1 + # Some tests were created using calculator at + # http://www.numbertheory.org/php/patz.html + + assert diop_DN(13, -4) == [(3, 1), (393, 109), (36, 10)] + # Source I referred returned (3, 1), (393, 109) and (-3, 1) as fundamental solutions + # So (-3, 1) and (393, 109) should be in the same equivalent class + assert equivalent(-3, 1, 393, 109, 13, -4) == True + + assert diop_DN(13, 27) == [(220, 61), (40, 11), (768, 213), (12, 3)] + assert set(diop_DN(157, 12)) == {(13, 1), (10663, 851), (579160, 46222), + (483790960, 38610722), (26277068347, 2097138361), + (21950079635497, 1751807067011)} + assert diop_DN(13, 25) == [(3245, 900)] + assert diop_DN(192, 18) == [] + assert diop_DN(23, 13) == [(-6, 1), (6, 1)] + assert diop_DN(167, 2) == [(13, 1)] + assert diop_DN(167, -2) == [] + + assert diop_DN(123, -2) == [(11, 1)] + # One calculator returned [(11, 1), (-11, 1)] but both of these are in + # the same equivalence class + assert equivalent(11, 1, -11, 1, 123, -2) + + assert diop_DN(123, -23) == [(-10, 1), (10, 1)] + + assert diop_DN(0, 0, t) == [(0, t)] + assert diop_DN(0, -1, t) == [] + + +def test_bf_pell(): + assert diop_bf_DN(13, -4) == [(3, 1), (-3, 1), (36, 10)] + assert diop_bf_DN(13, 27) == [(12, 3), (-12, 3), (40, 11), (-40, 11)] + assert diop_bf_DN(167, -2) == [] + assert diop_bf_DN(1729, 1) == [(44611924489705, 1072885712316)] + assert diop_bf_DN(89, -8) == [(9, 1), (-9, 1)] + assert diop_bf_DN(21257, -1) == [(13913102721304, 95427381109)] + assert diop_bf_DN(340, -4) == [(756, 41)] + assert diop_bf_DN(-1, 0, t) == [(0, 0)] + assert diop_bf_DN(0, 0, t) == [(0, t)] + assert diop_bf_DN(4, 0, t) == [(2*t, t), (-2*t, t)] + assert diop_bf_DN(3, 0, t) == [(0, 0)] + assert diop_bf_DN(1, -2, t) == [] + + +def test_length(): + assert length(2, 1, 0) == 1 + assert length(-2, 4, 5) == 3 + assert length(-5, 4, 17) == 4 + assert length(0, 4, 13) == 6 + assert length(7, 13, 11) == 23 + assert length(1, 6, 4) == 2 + + +def is_pell_transformation_ok(eq): + """ + Test whether X*Y, X, or Y terms are present in the equation + after transforming the equation using the transformation returned + by transformation_to_pell(). If they are not present we are good. + Moreover, coefficient of X**2 should be a divisor of coefficient of + Y**2 and the constant term. + """ + A, B = transformation_to_DN(eq) + u = (A*Matrix([X, Y]) + B)[0] + v = (A*Matrix([X, Y]) + B)[1] + simplified = diop_simplify(eq.subs(zip((x, y), (u, v)))) + + coeff = dict([reversed(t.as_independent(*[X, Y])) for t in simplified.args]) + + for term in [X*Y, X, Y]: + if term in coeff.keys(): + return False + + for term in [X**2, Y**2, 1]: + if term not in coeff.keys(): + coeff[term] = 0 + + if coeff[X**2] != 0: + return divisible(coeff[Y**2], coeff[X**2]) and \ + divisible(coeff[1], coeff[X**2]) + + return True + + +def test_transformation_to_pell(): + assert is_pell_transformation_ok(-13*x**2 - 7*x*y + y**2 + 2*x - 2*y - 14) + assert is_pell_transformation_ok(-17*x**2 + 19*x*y - 7*y**2 - 5*x - 13*y - 23) + assert is_pell_transformation_ok(x**2 - y**2 + 17) + assert is_pell_transformation_ok(-x**2 + 7*y**2 - 23) + assert is_pell_transformation_ok(25*x**2 - 45*x*y + 5*y**2 - 5*x - 10*y + 5) + assert is_pell_transformation_ok(190*x**2 + 30*x*y + y**2 - 3*y - 170*x - 130) + assert is_pell_transformation_ok(x**2 - 2*x*y -190*y**2 - 7*y - 23*x - 89) + assert is_pell_transformation_ok(15*x**2 - 9*x*y + 14*y**2 - 23*x - 14*y - 4950) + + +def test_find_DN(): + assert find_DN(x**2 - 2*x - y**2) == (1, 1) + assert find_DN(x**2 - 3*y**2 - 5) == (3, 5) + assert find_DN(x**2 - 2*x*y - 4*y**2 - 7) == (5, 7) + assert find_DN(4*x**2 - 8*x*y - y**2 - 9) == (20, 36) + assert find_DN(7*x**2 - 2*x*y - y**2 - 12) == (8, 84) + assert find_DN(-3*x**2 + 4*x*y -y**2) == (1, 0) + assert find_DN(-13*x**2 - 7*x*y + y**2 + 2*x - 2*y -14) == (101, -7825480) + + +def test_ldescent(): + # Equations which have solutions + u = ([(13, 23), (3, -11), (41, -113), (4, -7), (-7, 4), (91, -3), (1, 1), (1, -1), + (4, 32), (17, 13), (123689, 1), (19, -570)]) + for a, b in u: + w, x, y = ldescent(a, b) + assert a*x**2 + b*y**2 == w**2 + assert ldescent(-1, -1) is None + assert ldescent(2, 6) is None + + +def test_diop_ternary_quadratic_normal(): + assert check_solutions(234*x**2 - 65601*y**2 - z**2) + assert check_solutions(23*x**2 + 616*y**2 - z**2) + assert check_solutions(5*x**2 + 4*y**2 - z**2) + assert check_solutions(3*x**2 + 6*y**2 - 3*z**2) + assert check_solutions(x**2 + 3*y**2 - z**2) + assert check_solutions(4*x**2 + 5*y**2 - z**2) + assert check_solutions(x**2 + y**2 - z**2) + assert check_solutions(16*x**2 + y**2 - 25*z**2) + assert check_solutions(6*x**2 - y**2 + 10*z**2) + assert check_solutions(213*x**2 + 12*y**2 - 9*z**2) + assert check_solutions(34*x**2 - 3*y**2 - 301*z**2) + assert check_solutions(124*x**2 - 30*y**2 - 7729*z**2) + + +def is_normal_transformation_ok(eq): + A = transformation_to_normal(eq) + X, Y, Z = A*Matrix([x, y, z]) + simplified = diop_simplify(eq.subs(zip((x, y, z), (X, Y, Z)))) + + coeff = dict([reversed(t.as_independent(*[X, Y, Z])) for t in simplified.args]) + for term in [X*Y, Y*Z, X*Z]: + if term in coeff.keys(): + return False + + return True + + +def test_transformation_to_normal(): + assert is_normal_transformation_ok(x**2 + 3*y**2 + z**2 - 13*x*y - 16*y*z + 12*x*z) + assert is_normal_transformation_ok(x**2 + 3*y**2 - 100*z**2) + assert is_normal_transformation_ok(x**2 + 23*y*z) + assert is_normal_transformation_ok(3*y**2 - 100*z**2 - 12*x*y) + assert is_normal_transformation_ok(x**2 + 23*x*y - 34*y*z + 12*x*z) + assert is_normal_transformation_ok(z**2 + 34*x*y - 23*y*z + x*z) + assert is_normal_transformation_ok(x**2 + y**2 + z**2 - x*y - y*z - x*z) + assert is_normal_transformation_ok(x**2 + 2*y*z + 3*z**2) + assert is_normal_transformation_ok(x*y + 2*x*z + 3*y*z) + assert is_normal_transformation_ok(2*x*z + 3*y*z) + + +def test_diop_ternary_quadratic(): + assert check_solutions(2*x**2 + z**2 + y**2 - 4*x*y) + assert check_solutions(x**2 - y**2 - z**2 - x*y - y*z) + assert check_solutions(3*x**2 - x*y - y*z - x*z) + assert check_solutions(x**2 - y*z - x*z) + assert check_solutions(5*x**2 - 3*x*y - x*z) + assert check_solutions(4*x**2 - 5*y**2 - x*z) + assert check_solutions(3*x**2 + 2*y**2 - z**2 - 2*x*y + 5*y*z - 7*y*z) + assert check_solutions(8*x**2 - 12*y*z) + assert check_solutions(45*x**2 - 7*y**2 - 8*x*y - z**2) + assert check_solutions(x**2 - 49*y**2 - z**2 + 13*z*y -8*x*y) + assert check_solutions(90*x**2 + 3*y**2 + 5*x*y + 2*z*y + 5*x*z) + assert check_solutions(x**2 + 3*y**2 + z**2 - x*y - 17*y*z) + assert check_solutions(x**2 + 3*y**2 + z**2 - x*y - 16*y*z + 12*x*z) + assert check_solutions(x**2 + 3*y**2 + z**2 - 13*x*y - 16*y*z + 12*x*z) + assert check_solutions(x*y - 7*y*z + 13*x*z) + + assert diop_ternary_quadratic_normal(x**2 + y**2 + z**2) == (None, None, None) + assert diop_ternary_quadratic_normal(x**2 + y**2) is None + raises(ValueError, lambda: + _diop_ternary_quadratic_normal((x, y, z), + {x*y: 1, x**2: 2, y**2: 3, z**2: 0})) + eq = -2*x*y - 6*x*z + 7*y**2 - 3*y*z + 4*z**2 + assert diop_ternary_quadratic(eq) == (7, 2, 0) + assert diop_ternary_quadratic_normal(4*x**2 + 5*y**2 - z**2) == \ + (1, 0, 2) + assert diop_ternary_quadratic(x*y + 2*y*z) == \ + (-2, 0, n1) + eq = -5*x*y - 8*x*z - 3*y*z + 8*z**2 + assert parametrize_ternary_quadratic(eq) == \ + (8*p**2 - 3*p*q, -8*p*q + 8*q**2, 5*p*q) + # this cannot be tested with diophantine because it will + # factor into a product + assert diop_solve(x*y + 2*y*z) == (-2*p*q, -n1*p**2 + p**2, p*q) + + +def test_square_factor(): + assert square_factor(1) == square_factor(-1) == 1 + assert square_factor(0) == 1 + assert square_factor(5) == square_factor(-5) == 1 + assert square_factor(4) == square_factor(-4) == 2 + assert square_factor(12) == square_factor(-12) == 2 + assert square_factor(6) == 1 + assert square_factor(18) == 3 + assert square_factor(52) == 2 + assert square_factor(49) == 7 + assert square_factor(392) == 14 + assert square_factor(factorint(-12)) == 2 + + +def test_parametrize_ternary_quadratic(): + assert check_solutions(x**2 + y**2 - z**2) + assert check_solutions(x**2 + 2*x*y + z**2) + assert check_solutions(234*x**2 - 65601*y**2 - z**2) + assert check_solutions(3*x**2 + 2*y**2 - z**2 - 2*x*y + 5*y*z - 7*y*z) + assert check_solutions(x**2 - y**2 - z**2) + assert check_solutions(x**2 - 49*y**2 - z**2 + 13*z*y - 8*x*y) + assert check_solutions(8*x*y + z**2) + assert check_solutions(124*x**2 - 30*y**2 - 7729*z**2) + assert check_solutions(236*x**2 - 225*y**2 - 11*x*y - 13*y*z - 17*x*z) + assert check_solutions(90*x**2 + 3*y**2 + 5*x*y + 2*z*y + 5*x*z) + assert check_solutions(124*x**2 - 30*y**2 - 7729*z**2) + + +def test_no_square_ternary_quadratic(): + assert check_solutions(2*x*y + y*z - 3*x*z) + assert check_solutions(189*x*y - 345*y*z - 12*x*z) + assert check_solutions(23*x*y + 34*y*z) + assert check_solutions(x*y + y*z + z*x) + assert check_solutions(23*x*y + 23*y*z + 23*x*z) + + +def test_descent(): + + u = ([(13, 23), (3, -11), (41, -113), (91, -3), (1, 1), (1, -1), (17, 13), (123689, 1), (19, -570)]) + for a, b in u: + w, x, y = descent(a, b) + assert a*x**2 + b*y**2 == w**2 + # the docstring warns against bad input, so these are expected results + # - can't both be negative + raises(TypeError, lambda: descent(-1, -3)) + # A can't be zero unless B != 1 + raises(ZeroDivisionError, lambda: descent(0, 3)) + # supposed to be square-free + raises(TypeError, lambda: descent(4, 3)) + + +def test_diophantine(): + assert check_solutions((x - y)*(y - z)*(z - x)) + assert check_solutions((x - y)*(x**2 + y**2 - z**2)) + assert check_solutions((x - 3*y + 7*z)*(x**2 + y**2 - z**2)) + assert check_solutions(x**2 - 3*y**2 - 1) + assert check_solutions(y**2 + 7*x*y) + assert check_solutions(x**2 - 3*x*y + y**2) + assert check_solutions(z*(x**2 - y**2 - 15)) + assert check_solutions(x*(2*y - 2*z + 5)) + assert check_solutions((x**2 - 3*y**2 - 1)*(x**2 - y**2 - 15)) + assert check_solutions((x**2 - 3*y**2 - 1)*(y - 7*z)) + assert check_solutions((x**2 + y**2 - z**2)*(x - 7*y - 3*z + 4*w)) + # Following test case caused problems in parametric representation + # But this can be solved by factoring out y. + # No need to use methods for ternary quadratic equations. + assert check_solutions(y**2 - 7*x*y + 4*y*z) + assert check_solutions(x**2 - 2*x + 1) + + assert diophantine(x - y) == diophantine(Eq(x, y)) + # 18196 + eq = x**4 + y**4 - 97 + assert diophantine(eq, permute=True) == diophantine(-eq, permute=True) + assert diophantine(3*x*pi - 2*y*pi) == {(2*t_0, 3*t_0)} + eq = x**2 + y**2 + z**2 - 14 + base_sol = {(1, 2, 3)} + assert diophantine(eq) == base_sol + complete_soln = set(signed_permutations(base_sol.pop())) + assert diophantine(eq, permute=True) == complete_soln + + assert diophantine(x**2 + x*Rational(15, 14) - 3) == set() + # test issue 11049 + eq = 92*x**2 - 99*y**2 - z**2 + coeff = eq.as_coefficients_dict() + assert _diop_ternary_quadratic_normal((x, y, z), coeff) == \ + {(9, 7, 51)} + assert diophantine(eq) == {( + 891*p**2 + 9*q**2, -693*p**2 - 102*p*q + 7*q**2, + 5049*p**2 - 1386*p*q - 51*q**2)} + eq = 2*x**2 + 2*y**2 - z**2 + coeff = eq.as_coefficients_dict() + assert _diop_ternary_quadratic_normal((x, y, z), coeff) == \ + {(1, 1, 2)} + assert diophantine(eq) == {( + 2*p**2 - q**2, -2*p**2 + 4*p*q - q**2, + 4*p**2 - 4*p*q + 2*q**2)} + eq = 411*x**2+57*y**2-221*z**2 + coeff = eq.as_coefficients_dict() + assert _diop_ternary_quadratic_normal((x, y, z), coeff) == \ + {(2021, 2645, 3066)} + assert diophantine(eq) == \ + {(115197*p**2 - 446641*q**2, -150765*p**2 + 1355172*p*q - + 584545*q**2, 174762*p**2 - 301530*p*q + 677586*q**2)} + eq = 573*x**2+267*y**2-984*z**2 + coeff = eq.as_coefficients_dict() + assert _diop_ternary_quadratic_normal((x, y, z), coeff) == \ + {(49, 233, 127)} + assert diophantine(eq) == \ + {(4361*p**2 - 16072*q**2, -20737*p**2 + 83312*p*q - 76424*q**2, + 11303*p**2 - 41474*p*q + 41656*q**2)} + # this produces factors during reconstruction + eq = x**2 + 3*y**2 - 12*z**2 + coeff = eq.as_coefficients_dict() + assert _diop_ternary_quadratic_normal((x, y, z), coeff) == \ + {(0, 2, 1)} + assert diophantine(eq) == \ + {(24*p*q, 2*p**2 - 24*q**2, p**2 + 12*q**2)} + # solvers have not been written for every type + raises(NotImplementedError, lambda: diophantine(x*y**2 + 1)) + + # rational expressions + assert diophantine(1/x) == set() + assert diophantine(1/x + 1/y - S.Half) == {(6, 3), (-2, 1), (4, 4), (1, -2), (3, 6)} + assert diophantine(x**2 + y**2 +3*x- 5, permute=True) == \ + {(-1, 1), (-4, -1), (1, -1), (1, 1), (-4, 1), (-1, -1), (4, 1), (4, -1)} + + + #test issue 18186 + assert diophantine(y**4 + x**4 - 2**4 - 3**4, syms=(x, y), permute=True) == \ + {(-3, -2), (-3, 2), (-2, -3), (-2, 3), (2, -3), (2, 3), (3, -2), (3, 2)} + assert diophantine(y**4 + x**4 - 2**4 - 3**4, syms=(y, x), permute=True) == \ + {(-3, -2), (-3, 2), (-2, -3), (-2, 3), (2, -3), (2, 3), (3, -2), (3, 2)} + + # issue 18122 + assert check_solutions(x**2 - y) + assert check_solutions(y**2 - x) + assert diophantine((x**2 - y), t) == {(t, t**2)} + assert diophantine((y**2 - x), t) == {(t**2, t)} + + +def test_general_pythagorean(): + from sympy.abc import a, b, c, d, e + + assert check_solutions(a**2 + b**2 + c**2 - d**2) + assert check_solutions(a**2 + 4*b**2 + 4*c**2 - d**2) + assert check_solutions(9*a**2 + 4*b**2 + 4*c**2 - d**2) + assert check_solutions(9*a**2 + 4*b**2 - 25*d**2 + 4*c**2 ) + assert check_solutions(9*a**2 - 16*d**2 + 4*b**2 + 4*c**2) + assert check_solutions(-e**2 + 9*a**2 + 4*b**2 + 4*c**2 + 25*d**2) + assert check_solutions(16*a**2 - b**2 + 9*c**2 + d**2 + 25*e**2) + + assert GeneralPythagorean(a**2 + b**2 + c**2 - d**2).solve(parameters=[x, y, z]) == \ + {(x**2 + y**2 - z**2, 2*x*z, 2*y*z, x**2 + y**2 + z**2)} + + +def test_diop_general_sum_of_squares_quick(): + for i in range(3, 10): + assert check_solutions(sum(i**2 for i in symbols(':%i' % i)) - i) + + assert diop_general_sum_of_squares(x**2 + y**2 - 2) is None + assert diop_general_sum_of_squares(x**2 + y**2 + z**2 + 2) == set() + eq = x**2 + y**2 + z**2 - (1 + 4 + 9) + assert diop_general_sum_of_squares(eq) == \ + {(1, 2, 3)} + eq = u**2 + v**2 + x**2 + y**2 + z**2 - 1313 + assert len(diop_general_sum_of_squares(eq, 3)) == 3 + # issue 11016 + var = symbols(':5') + (symbols('6', negative=True),) + eq = Add(*[i**2 for i in var]) - 112 + + base_soln = {(0, 1, 1, 5, 6, -7), (1, 1, 1, 3, 6, -8), (2, 3, 3, 4, 5, -7), (0, 1, 1, 1, 3, -10), + (0, 0, 4, 4, 4, -8), (1, 2, 3, 3, 5, -8), (0, 1, 2, 3, 7, -7), (2, 2, 4, 4, 6, -6), + (1, 1, 3, 4, 6, -7), (0, 2, 3, 3, 3, -9), (0, 0, 2, 2, 2, -10), (1, 1, 2, 3, 4, -9), + (0, 1, 1, 2, 5, -9), (0, 0, 2, 6, 6, -6), (1, 3, 4, 5, 5, -6), (0, 2, 2, 2, 6, -8), + (0, 3, 3, 3, 6, -7), (0, 2, 3, 5, 5, -7), (0, 1, 5, 5, 5, -6)} + assert diophantine(eq) == base_soln + assert len(diophantine(eq, permute=True)) == 196800 + + # handle negated squares with signsimp + assert diophantine(12 - x**2 - y**2 - z**2) == {(2, 2, 2)} + # diophantine handles simplification, so classify_diop should + # not have to look for additional patterns that are removed + # by diophantine + eq = a**2 + b**2 + c**2 + d**2 - 4 + raises(NotImplementedError, lambda: classify_diop(-eq)) + + +def test_issue_23807(): + # fixes recursion error + eq = x**2 + y**2 + z**2 - 1000000 + base_soln = {(0, 0, 1000), (0, 352, 936), (480, 600, 640), (24, 640, 768), (192, 640, 744), + (192, 480, 856), (168, 224, 960), (0, 600, 800), (280, 576, 768), (152, 480, 864), + (0, 280, 960), (352, 360, 864), (424, 480, 768), (360, 480, 800), (224, 600, 768), + (96, 360, 928), (168, 576, 800), (96, 480, 872)} + + assert diophantine(eq) == base_soln + + +def test_diop_partition(): + for n in [8, 10]: + for k in range(1, 8): + for p in partition(n, k): + assert len(p) == k + assert list(partition(3, 5)) == [] + assert [list(p) for p in partition(3, 5, 1)] == [ + [0, 0, 0, 0, 3], [0, 0, 0, 1, 2], [0, 0, 1, 1, 1]] + assert list(partition(0)) == [()] + assert list(partition(1, 0)) == [()] + assert [list(i) for i in partition(3)] == [[1, 1, 1], [1, 2], [3]] + + +def test_prime_as_sum_of_two_squares(): + for i in [5, 13, 17, 29, 37, 41, 2341, 3557, 34841, 64601]: + a, b = prime_as_sum_of_two_squares(i) + assert a**2 + b**2 == i + assert prime_as_sum_of_two_squares(7) is None + ans = prime_as_sum_of_two_squares(800029) + assert ans == (450, 773) and type(ans[0]) is int + + +def test_sum_of_three_squares(): + for i in [0, 1, 2, 34, 123, 34304595905, 34304595905394941, 343045959052344, + 800, 801, 802, 803, 804, 805, 806]: + a, b, c = sum_of_three_squares(i) + assert a**2 + b**2 + c**2 == i + assert a >= 0 + + # error + raises(ValueError, lambda: sum_of_three_squares(-1)) + + assert sum_of_three_squares(7) is None + assert sum_of_three_squares((4**5)*15) is None + # if there are two zeros, there might be a solution + # with only one zero, e.g. 25 => (0, 3, 4) or + # with no zeros, e.g. 49 => (2, 3, 6) + assert sum_of_three_squares(25) == (0, 0, 5) + assert sum_of_three_squares(4) == (0, 0, 2) + + +def test_sum_of_four_squares(): + from sympy.core.random import randint + + # this should never fail + n = randint(1, 100000000000000) + assert sum(i**2 for i in sum_of_four_squares(n)) == n + + # error + raises(ValueError, lambda: sum_of_four_squares(-1)) + + for n in range(1000): + result = sum_of_four_squares(n) + assert len(result) == 4 + assert all(r >= 0 for r in result) + assert sum(r**2 for r in result) == n + assert list(result) == sorted(result) + + +def test_power_representation(): + tests = [(1729, 3, 2), (234, 2, 4), (2, 1, 2), (3, 1, 3), (5, 2, 2), (12352, 2, 4), + (32760, 2, 3)] + + for test in tests: + n, p, k = test + f = power_representation(n, p, k) + + while True: + try: + l = next(f) + assert len(l) == k + + chk_sum = 0 + for l_i in l: + chk_sum = chk_sum + l_i**p + assert chk_sum == n + + except StopIteration: + break + + assert list(power_representation(20, 2, 4, True)) == \ + [(1, 1, 3, 3), (0, 0, 2, 4)] + raises(ValueError, lambda: list(power_representation(1.2, 2, 2))) + raises(ValueError, lambda: list(power_representation(2, 0, 2))) + raises(ValueError, lambda: list(power_representation(2, 2, 0))) + assert list(power_representation(-1, 2, 2)) == [] + assert list(power_representation(1, 1, 1)) == [(1,)] + assert list(power_representation(3, 2, 1)) == [] + assert list(power_representation(4, 2, 1)) == [(2,)] + assert list(power_representation(3**4, 4, 6, zeros=True)) == \ + [(1, 2, 2, 2, 2, 2), (0, 0, 0, 0, 0, 3)] + assert list(power_representation(3**4, 4, 5, zeros=False)) == [] + assert list(power_representation(-2, 3, 2)) == [(-1, -1)] + assert list(power_representation(-2, 4, 2)) == [] + assert list(power_representation(0, 3, 2, True)) == [(0, 0)] + assert list(power_representation(0, 3, 2, False)) == [] + # when we are dealing with squares, do feasibility checks + assert len(list(power_representation(4**10*(8*10 + 7), 2, 3))) == 0 + # there will be a recursion error if these aren't recognized + big = 2**30 + for i in [13, 10, 7, 5, 4, 2, 1]: + assert list(sum_of_powers(big, 2, big - i)) == [] + + +def test_assumptions(): + """ + Test whether diophantine respects the assumptions. + """ + #Test case taken from the below so question regarding assumptions in diophantine module + #https://stackoverflow.com/questions/23301941/how-can-i-declare-natural-symbols-with-sympy + m, n = symbols('m n', integer=True, positive=True) + diof = diophantine(n**2 + m*n - 500) + assert diof == {(5, 20), (40, 10), (95, 5), (121, 4), (248, 2), (499, 1)} + + a, b = symbols('a b', integer=True, positive=False) + diof = diophantine(a*b + 2*a + 3*b - 6) + assert diof == {(-15, -3), (-9, -4), (-7, -5), (-6, -6), (-5, -8), (-4, -14)} + + +def check_solutions(eq): + """ + Determines whether solutions returned by diophantine() satisfy the original + equation. Hope to generalize this so we can remove functions like check_ternay_quadratic, + check_solutions_normal, check_solutions() + """ + s = diophantine(eq) + + factors = Mul.make_args(eq) + + var = list(eq.free_symbols) + var.sort(key=default_sort_key) + + while s: + solution = s.pop() + for f in factors: + if diop_simplify(f.subs(zip(var, solution))) == 0: + break + else: + return False + return True + + +def test_diopcoverage(): + eq = (2*x + y + 1)**2 + assert diop_solve(eq) == {(t_0, -2*t_0 - 1)} + eq = 2*x**2 + 6*x*y + 12*x + 4*y**2 + 18*y + 18 + assert diop_solve(eq) == {(t, -t - 3), (-2*t - 3, t)} + assert diop_quadratic(x + y**2 - 3) == {(-t**2 + 3, t)} + + assert diop_linear(x + y - 3) == (t_0, 3 - t_0) + + assert base_solution_linear(0, 1, 2, t=None) == (0, 0) + ans = (3*t - 1, -2*t + 1) + assert base_solution_linear(4, 8, 12, t) == ans + assert base_solution_linear(4, 8, 12, t=None) == tuple(_.subs(t, 0) for _ in ans) + + assert cornacchia(1, 1, 20) == set() + assert cornacchia(1, 1, 5) == {(2, 1)} + assert cornacchia(1, 2, 17) == {(3, 2)} + + raises(ValueError, lambda: reconstruct(4, 20, 1)) + + assert gaussian_reduce(4, 1, 3) == (1, 1) + eq = -w**2 - x**2 - y**2 + z**2 + + assert diop_general_pythagorean(eq) == \ + diop_general_pythagorean(-eq) == \ + (m1**2 + m2**2 - m3**2, 2*m1*m3, + 2*m2*m3, m1**2 + m2**2 + m3**2) + + assert len(check_param(S(3) + x/3, S(4) + x/2, S(2), [x])) == 0 + assert len(check_param(Rational(3, 2), S(4) + x, S(2), [x])) == 0 + assert len(check_param(S(4) + x, Rational(3, 2), S(2), [x])) == 0 + + assert _nint_or_floor(16, 10) == 2 + assert _odd(1) == (not _even(1)) == True + assert _odd(0) == (not _even(0)) == False + assert _remove_gcd(2, 4, 6) == (1, 2, 3) + raises(TypeError, lambda: _remove_gcd((2, 4, 6))) + assert sqf_normal(2*3**2*5, 2*5*11, 2*7**2*11) == \ + (11, 1, 5) + + # it's ok if these pass some day when the solvers are implemented + raises(NotImplementedError, lambda: diophantine(x**2 + y**2 + x*y + 2*y*z - 12)) + raises(NotImplementedError, lambda: diophantine(x**3 + y**2)) + assert diop_quadratic(x**2 + y**2 - 1**2 - 3**4) == \ + {(-9, -1), (-9, 1), (-1, -9), (-1, 9), (1, -9), (1, 9), (9, -1), (9, 1)} + + +def test_holzer(): + # if the input is good, don't let it diverge in holzer() + # (but see test_fail_holzer below) + assert holzer(2, 7, 13, 4, 79, 23) == (2, 7, 13) + + # None in uv condition met; solution is not Holzer reduced + # so this will hopefully change but is here for coverage + assert holzer(2, 6, 2, 1, 1, 10) == (2, 6, 2) + + raises(ValueError, lambda: holzer(2, 7, 14, 4, 79, 23)) + + +@XFAIL +def test_fail_holzer(): + eq = lambda x, y, z: a*x**2 + b*y**2 - c*z**2 + a, b, c = 4, 79, 23 + x, y, z = xyz = 26, 1, 11 + X, Y, Z = ans = 2, 7, 13 + assert eq(*xyz) == 0 + assert eq(*ans) == 0 + assert max(a*x**2, b*y**2, c*z**2) <= a*b*c + assert max(a*X**2, b*Y**2, c*Z**2) <= a*b*c + h = holzer(x, y, z, a, b, c) + assert h == ans # it would be nice to get the smaller soln + + +def test_issue_9539(): + assert diophantine(6*w + 9*y + 20*x - z) == \ + {(t_0, t_1, t_1 + t_2, 6*t_0 + 29*t_1 + 9*t_2)} + + +def test_issue_8943(): + assert diophantine( + 3*(x**2 + y**2 + z**2) - 14*(x*y + y*z + z*x)) == \ + {(0, 0, 0)} + + +def test_diop_sum_of_even_powers(): + eq = x**4 + y**4 + z**4 - 2673 + assert diop_solve(eq) == {(3, 6, 6), (2, 4, 7)} + assert diop_general_sum_of_even_powers(eq, 2) == {(3, 6, 6), (2, 4, 7)} + raises(NotImplementedError, lambda: diop_general_sum_of_even_powers(-eq, 2)) + neg = symbols('neg', negative=True) + eq = x**4 + y**4 + neg**4 - 2673 + assert diop_general_sum_of_even_powers(eq) == {(-3, 6, 6)} + assert diophantine(x**4 + y**4 + 2) == set() + assert diop_general_sum_of_even_powers(x**4 + y**4 - 2, limit=0) == set() + + +def test_sum_of_squares_powers(): + tru = {(0, 0, 1, 1, 11), (0, 0, 5, 7, 7), (0, 1, 3, 7, 8), (0, 1, 4, 5, 9), (0, 3, 4, 7, 7), (0, 3, 5, 5, 8), + (1, 1, 2, 6, 9), (1, 1, 6, 6, 7), (1, 2, 3, 3, 10), (1, 3, 4, 4, 9), (1, 5, 5, 6, 6), (2, 2, 3, 5, 9), + (2, 3, 5, 6, 7), (3, 3, 4, 5, 8)} + eq = u**2 + v**2 + x**2 + y**2 + z**2 - 123 + ans = diop_general_sum_of_squares(eq, oo) # allow oo to be used + assert len(ans) == 14 + assert ans == tru + + raises(ValueError, lambda: list(sum_of_squares(10, -1))) + assert list(sum_of_squares(1, 1)) == [(1,)] + assert list(sum_of_squares(1, 2)) == [] + assert list(sum_of_squares(1, 2, True)) == [(0, 1)] + assert list(sum_of_squares(-10, 2)) == [] + assert list(sum_of_squares(2, 3)) == [] + assert list(sum_of_squares(0, 3, True)) == [(0, 0, 0)] + assert list(sum_of_squares(0, 3)) == [] + assert list(sum_of_squares(4, 1)) == [(2,)] + assert list(sum_of_squares(5, 1)) == [] + assert list(sum_of_squares(50, 2)) == [(5, 5), (1, 7)] + assert list(sum_of_squares(11, 5, True)) == [ + (1, 1, 1, 2, 2), (0, 0, 1, 1, 3)] + assert list(sum_of_squares(8, 8)) == [(1, 1, 1, 1, 1, 1, 1, 1)] + + assert [len(list(sum_of_squares(i, 5, True))) for i in range(30)] == [ + 1, 1, 1, 1, 2, + 2, 1, 1, 2, 2, + 2, 2, 2, 3, 2, + 1, 3, 3, 3, 3, + 4, 3, 3, 2, 2, + 4, 4, 4, 4, 5] + assert [len(list(sum_of_squares(i, 5))) for i in range(30)] == [ + 0, 0, 0, 0, 0, + 1, 0, 0, 1, 0, + 0, 1, 0, 1, 1, + 0, 1, 1, 0, 1, + 2, 1, 1, 1, 1, + 1, 1, 1, 1, 3] + for i in range(30): + s1 = set(sum_of_squares(i, 5, True)) + assert not s1 or all(sum(j**2 for j in t) == i for t in s1) + s2 = set(sum_of_squares(i, 5)) + assert all(sum(j**2 for j in t) == i for t in s2) + + raises(ValueError, lambda: list(sum_of_powers(2, -1, 1))) + raises(ValueError, lambda: list(sum_of_powers(2, 1, -1))) + assert list(sum_of_powers(-2, 3, 2)) == [(-1, -1)] + assert list(sum_of_powers(-2, 4, 2)) == [] + assert list(sum_of_powers(2, 1, 1)) == [(2,)] + assert list(sum_of_powers(2, 1, 3, True)) == [(0, 0, 2), (0, 1, 1)] + assert list(sum_of_powers(5, 1, 2, True)) == [(0, 5), (1, 4), (2, 3)] + assert list(sum_of_powers(6, 2, 2)) == [] + assert list(sum_of_powers(3**5, 3, 1)) == [] + assert list(sum_of_powers(3**6, 3, 1)) == [(9,)] and (9**3 == 3**6) + assert list(sum_of_powers(2**1000, 5, 2)) == [] + + +def test__can_do_sum_of_squares(): + assert _can_do_sum_of_squares(3, -1) is False + assert _can_do_sum_of_squares(-3, 1) is False + assert _can_do_sum_of_squares(0, 1) + assert _can_do_sum_of_squares(4, 1) + assert _can_do_sum_of_squares(1, 2) + assert _can_do_sum_of_squares(2, 2) + assert _can_do_sum_of_squares(3, 2) is False + + +def test_diophantine_permute_sign(): + from sympy.abc import a, b, c, d, e + eq = a**4 + b**4 - (2**4 + 3**4) + base_sol = {(2, 3)} + assert diophantine(eq) == base_sol + complete_soln = set(signed_permutations(base_sol.pop())) + assert diophantine(eq, permute=True) == complete_soln + + eq = a**2 + b**2 + c**2 + d**2 + e**2 - 234 + assert len(diophantine(eq)) == 35 + assert len(diophantine(eq, permute=True)) == 62000 + soln = {(-1, -1), (-1, 2), (1, -2), (1, 1)} + assert diophantine(10*x**2 + 12*x*y + 12*y**2 - 34, permute=True) == soln + + +@XFAIL +def test_not_implemented(): + eq = x**2 + y**4 - 1**2 - 3**4 + assert diophantine(eq, syms=[x, y]) == {(9, 1), (1, 3)} + + +def test_issue_9538(): + eq = x - 3*y + 2 + assert diophantine(eq, syms=[y,x]) == {(t_0, 3*t_0 - 2)} + raises(TypeError, lambda: diophantine(eq, syms={y, x})) + + +def test_ternary_quadratic(): + # solution with 3 parameters + s = diophantine(2*x**2 + y**2 - 2*z**2) + p, q, r = ordered(S(s).free_symbols) + assert s == {( + p**2 - 2*q**2, + -2*p**2 + 4*p*q - 4*p*r - 4*q**2, + p**2 - 4*p*q + 2*q**2 - 4*q*r)} + # solution with Mul in solution + s = diophantine(x**2 + 2*y**2 - 2*z**2) + assert s == {(4*p*q, p**2 - 2*q**2, p**2 + 2*q**2)} + # solution with no Mul in solution + s = diophantine(2*x**2 + 2*y**2 - z**2) + assert s == {(2*p**2 - q**2, -2*p**2 + 4*p*q - q**2, + 4*p**2 - 4*p*q + 2*q**2)} + # reduced form when parametrized + s = diophantine(3*x**2 + 72*y**2 - 27*z**2) + assert s == {(24*p**2 - 9*q**2, 6*p*q, 8*p**2 + 3*q**2)} + assert parametrize_ternary_quadratic( + 3*x**2 + 2*y**2 - z**2 - 2*x*y + 5*y*z - 7*y*z) == ( + 2*p**2 - 2*p*q - q**2, 2*p**2 + 2*p*q - q**2, 2*p**2 - + 2*p*q + 3*q**2) + assert parametrize_ternary_quadratic( + 124*x**2 - 30*y**2 - 7729*z**2) == ( + -1410*p**2 - 363263*q**2, 2700*p**2 + 30916*p*q - + 695610*q**2, -60*p**2 + 5400*p*q + 15458*q**2) + + +def test_diophantine_solution_set(): + s1 = DiophantineSolutionSet([], []) + assert set(s1) == set() + assert s1.symbols == () + assert s1.parameters == () + raises(ValueError, lambda: s1.add((x,))) + assert list(s1.dict_iterator()) == [] + + s2 = DiophantineSolutionSet([x, y], [t, u]) + assert s2.symbols == (x, y) + assert s2.parameters == (t, u) + raises(ValueError, lambda: s2.add((1,))) + s2.add((3, 4)) + assert set(s2) == {(3, 4)} + s2.update((3, 4), (-1, u)) + assert set(s2) == {(3, 4), (-1, u)} + raises(ValueError, lambda: s1.update(s2)) + assert list(s2.dict_iterator()) == [{x: -1, y: u}, {x: 3, y: 4}] + + s3 = DiophantineSolutionSet([x, y, z], [t, u]) + assert len(s3.parameters) == 2 + s3.add((t**2 + u, t - u, 1)) + assert set(s3) == {(t**2 + u, t - u, 1)} + assert s3.subs(t, 2) == {(u + 4, 2 - u, 1)} + assert s3(2) == {(u + 4, 2 - u, 1)} + assert s3.subs({t: 7, u: 8}) == {(57, -1, 1)} + assert s3(7, 8) == {(57, -1, 1)} + assert s3.subs({t: 5}) == {(u + 25, 5 - u, 1)} + assert s3(5) == {(u + 25, 5 - u, 1)} + assert s3.subs(u, -3) == {(t**2 - 3, t + 3, 1)} + assert s3(None, -3) == {(t**2 - 3, t + 3, 1)} + assert s3.subs({t: 2, u: 8}) == {(12, -6, 1)} + assert s3(2, 8) == {(12, -6, 1)} + assert s3.subs({t: 5, u: -3}) == {(22, 8, 1)} + assert s3(5, -3) == {(22, 8, 1)} + raises(ValueError, lambda: s3.subs(x=1)) + raises(ValueError, lambda: s3.subs(1, 2, 3)) + raises(ValueError, lambda: s3.add(())) + raises(ValueError, lambda: s3.add((1, 2, 3, 4))) + raises(ValueError, lambda: s3.add((1, 2))) + raises(ValueError, lambda: s3(1, 2, 3)) + raises(TypeError, lambda: s3(t=1)) + + s4 = DiophantineSolutionSet([x, y], [t, u]) + s4.add((t, 11*t)) + s4.add((-t, 22*t)) + assert s4(0, 0) == {(0, 0)} + + +def test_quadratic_parameter_passing(): + eq = -33*x*y + 3*y**2 + solution = BinaryQuadratic(eq).solve(parameters=[t, u]) + # test that parameters are passed all the way to the final solution + assert solution == {(t, 11*t), (t, -22*t)} + assert solution(0, 0) == {(0, 0)} diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/__init__.py b/lib/python3.12/site-packages/sympy/solvers/ode/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2b543425251dea6380a1860279cb6d636f3dd629 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/ode/__init__.py @@ -0,0 +1,16 @@ +from .ode import (allhints, checkinfsol, classify_ode, + constantsimp, dsolve, homogeneous_order) + +from .lie_group import infinitesimals + +from .subscheck import checkodesol + +from .systems import (canonical_odes, linear_ode_to_matrix, + linodesolve) + + +__all__ = [ + 'allhints', 'checkinfsol', 'checkodesol', 'classify_ode', 'constantsimp', + 'dsolve', 'homogeneous_order', 'infinitesimals', 'canonical_odes', 'linear_ode_to_matrix', + 'linodesolve' +] diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..66bde36405cd682981797b609f5be86a3e98d0d0 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/__init__.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/hypergeometric.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/hypergeometric.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a686f68a55aeedf7d4c0fbfefc4f9e205908abbb Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/hypergeometric.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/lie_group.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/lie_group.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9759364b56a14ed165e71175d8bb8c4c572c4ffe Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/lie_group.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/nonhomogeneous.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/nonhomogeneous.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e29d9407cd842bb09b552de01ca9c78226d09f38 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/nonhomogeneous.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/riccati.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/riccati.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9017b67c94f3c0ee1f69e345d713fd83f2d2f559 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/riccati.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/subscheck.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/subscheck.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a48803989f9d99ea06e0022422d4a3ffe6abc0b Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/subscheck.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/systems.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/systems.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..604fe9f5554e0431a1c6efa95ed147458e2d3ccb Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/ode/__pycache__/systems.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/hypergeometric.py b/lib/python3.12/site-packages/sympy/solvers/ode/hypergeometric.py new file mode 100644 index 0000000000000000000000000000000000000000..51a40b1cba32eabbdb120f9c4d5e3fd05dc644eb --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/ode/hypergeometric.py @@ -0,0 +1,272 @@ +r''' +This module contains the implementation of the 2nd_hypergeometric hint for +dsolve. This is an incomplete implementation of the algorithm described in [1]. +The algorithm solves 2nd order linear ODEs of the form + +.. math:: y'' + A(x) y' + B(x) y = 0\text{,} + +where `A` and `B` are rational functions. The algorithm should find any +solution of the form + +.. math:: y = P(x) _pF_q(..; ..;\frac{\alpha x^k + \beta}{\gamma x^k + \delta})\text{,} + +where pFq is any of 2F1, 1F1 or 0F1 and `P` is an "arbitrary function". +Currently only the 2F1 case is implemented in SymPy but the other cases are +described in the paper and could be implemented in future (contributions +welcome!). + +References +========== + +.. [1] L. Chan, E.S. Cheb-Terrab, Non-Liouvillian solutions for second order + linear ODEs, (2004). + https://arxiv.org/abs/math-ph/0402063 +''' + +from sympy.core import S, Pow +from sympy.core.function import expand +from sympy.core.relational import Eq +from sympy.core.symbol import Symbol, Wild +from sympy.functions import exp, sqrt, hyper +from sympy.integrals import Integral +from sympy.polys import roots, gcd +from sympy.polys.polytools import cancel, factor +from sympy.simplify import collect, simplify, logcombine # type: ignore +from sympy.simplify.powsimp import powdenest +from sympy.solvers.ode.ode import get_numbered_constants + + +def match_2nd_hypergeometric(eq, func): + x = func.args[0] + df = func.diff(x) + a3 = Wild('a3', exclude=[func, func.diff(x), func.diff(x, 2)]) + b3 = Wild('b3', exclude=[func, func.diff(x), func.diff(x, 2)]) + c3 = Wild('c3', exclude=[func, func.diff(x), func.diff(x, 2)]) + deq = a3*(func.diff(x, 2)) + b3*df + c3*func + r = collect(eq, + [func.diff(x, 2), func.diff(x), func]).match(deq) + if r: + if not all(val.is_polynomial() for val in r.values()): + n, d = eq.as_numer_denom() + eq = expand(n) + r = collect(eq, [func.diff(x, 2), func.diff(x), func]).match(deq) + + if r and r[a3]!=0: + A = cancel(r[b3]/r[a3]) + B = cancel(r[c3]/r[a3]) + return [A, B] + else: + return [] + + +def equivalence_hypergeometric(A, B, func): + # This method for finding the equivalence is only for 2F1 type. + # We can extend it for 1F1 and 0F1 type also. + x = func.args[0] + + # making given equation in normal form + I1 = factor(cancel(A.diff(x)/2 + A**2/4 - B)) + + # computing shifted invariant(J1) of the equation + J1 = factor(cancel(x**2*I1 + S(1)/4)) + num, dem = J1.as_numer_denom() + num = powdenest(expand(num)) + dem = powdenest(expand(dem)) + # this function will compute the different powers of variable(x) in J1. + # then it will help in finding value of k. k is power of x such that we can express + # J1 = x**k * J0(x**k) then all the powers in J0 become integers. + def _power_counting(num): + _pow = {0} + for val in num: + if val.has(x): + if isinstance(val, Pow) and val.as_base_exp()[0] == x: + _pow.add(val.as_base_exp()[1]) + elif val == x: + _pow.add(val.as_base_exp()[1]) + else: + _pow.update(_power_counting(val.args)) + return _pow + + pow_num = _power_counting((num, )) + pow_dem = _power_counting((dem, )) + pow_dem.update(pow_num) + + _pow = pow_dem + k = gcd(_pow) + + # computing I0 of the given equation + I0 = powdenest(simplify(factor(((J1/k**2) - S(1)/4)/((x**k)**2))), force=True) + I0 = factor(cancel(powdenest(I0.subs(x, x**(S(1)/k)), force=True))) + + # Before this point I0, J1 might be functions of e.g. sqrt(x) but replacing + # x with x**(1/k) should result in I0 being a rational function of x or + # otherwise the hypergeometric solver cannot be used. Note that k can be a + # non-integer rational such as 2/7. + if not I0.is_rational_function(x): + return None + + num, dem = I0.as_numer_denom() + + max_num_pow = max(_power_counting((num, ))) + dem_args = dem.args + sing_point = [] + dem_pow = [] + # calculating singular point of I0. + for arg in dem_args: + if arg.has(x): + if isinstance(arg, Pow): + # (x-a)**n + dem_pow.append(arg.as_base_exp()[1]) + sing_point.append(list(roots(arg.as_base_exp()[0], x).keys())[0]) + else: + # (x-a) type + dem_pow.append(arg.as_base_exp()[1]) + sing_point.append(list(roots(arg, x).keys())[0]) + + dem_pow.sort() + # checking if equivalence is exists or not. + + if equivalence(max_num_pow, dem_pow) == "2F1": + return {'I0':I0, 'k':k, 'sing_point':sing_point, 'type':"2F1"} + else: + return None + + +def match_2nd_2F1_hypergeometric(I, k, sing_point, func): + x = func.args[0] + a = Wild("a") + b = Wild("b") + c = Wild("c") + t = Wild("t") + s = Wild("s") + r = Wild("r") + alpha = Wild("alpha") + beta = Wild("beta") + gamma = Wild("gamma") + delta = Wild("delta") + # I0 of the standerd 2F1 equation. + I0 = ((a-b+1)*(a-b-1)*x**2 + 2*((1-a-b)*c + 2*a*b)*x + c*(c-2))/(4*x**2*(x-1)**2) + if sing_point != [0, 1]: + # If singular point is [0, 1] then we have standerd equation. + eqs = [] + sing_eqs = [-beta/alpha, -delta/gamma, (delta-beta)/(alpha-gamma)] + # making equations for the finding the mobius transformation + for i in range(3): + if i>> from sympy import Function, Eq, pprint + >>> from sympy.abc import x, y + >>> xi, eta, h = map(Function, ['xi', 'eta', 'h']) + >>> h = h(x, y) # dy/dx = h + >>> eta = eta(x, y) + >>> xi = xi(x, y) + >>> genform = Eq(eta.diff(x) + (eta.diff(y) - xi.diff(x))*h + ... - (xi.diff(y))*h**2 - xi*(h.diff(x)) - eta*(h.diff(y)), 0) + >>> pprint(genform) + /d d \ d 2 d d d + |--(eta(x, y)) - --(xi(x, y))|*h(x, y) - eta(x, y)*--(h(x, y)) - h (x, y)*--(xi(x, y)) - xi(x, y)*--(h(x, y)) + --(eta(x, y)) = 0 + \dy dx / dy dy dx dx + + Solving the above mentioned PDE is not trivial, and can be solved only by + making intelligent assumptions for `\xi` and `\eta` (heuristics). Once an + infinitesimal is found, the attempt to find more heuristics stops. This is done to + optimise the speed of solving the differential equation. If a list of all the + infinitesimals is needed, ``hint`` should be flagged as ``all``, which gives + the complete list of infinitesimals. If the infinitesimals for a particular + heuristic needs to be found, it can be passed as a flag to ``hint``. + + Examples + ======== + + >>> from sympy import Function + >>> from sympy.solvers.ode.lie_group import infinitesimals + >>> from sympy.abc import x + >>> f = Function('f') + >>> eq = f(x).diff(x) - x**2*f(x) + >>> infinitesimals(eq) + [{eta(x, f(x)): exp(x**3/3), xi(x, f(x)): 0}] + + References + ========== + + - Solving differential equations by Symmetry Groups, + John Starrett, pp. 1 - pp. 14 + + """ + + if isinstance(eq, Equality): + eq = eq.lhs - eq.rhs + if not func: + eq, func = _preprocess(eq) + variables = func.args + if len(variables) != 1: + raise ValueError("ODE's have only one independent variable") + else: + x = variables[0] + if not order: + order = ode_order(eq, func) + if order != 1: + raise NotImplementedError("Infinitesimals for only " + "first order ODE's have been implemented") + else: + df = func.diff(x) + # Matching differential equation of the form a*df + b + a = Wild('a', exclude = [df]) + b = Wild('b', exclude = [df]) + if match: # Used by lie_group hint + h = match['h'] + y = match['y'] + else: + match = collect(expand(eq), df).match(a*df + b) + if match: + h = -simplify(match[b]/match[a]) + else: + try: + sol = solve(eq, df) + except NotImplementedError: + raise NotImplementedError("Infinitesimals for the " + "first order ODE could not be found") + else: + h = sol[0] # Find infinitesimals for one solution + y = Dummy("y") + h = h.subs(func, y) + + u = Dummy("u") + hx = h.diff(x) + hy = h.diff(y) + hinv = ((1/h).subs([(x, u), (y, x)])).subs(u, y) # Inverse ODE + match = {'h': h, 'func': func, 'hx': hx, 'hy': hy, 'y': y, 'hinv': hinv} + if hint == 'all': + xieta = [] + for heuristic in lie_heuristics: + function = globals()['lie_heuristic_' + heuristic] + inflist = function(match, comp=True) + if inflist: + xieta.extend([inf for inf in inflist if inf not in xieta]) + if xieta: + return xieta + else: + raise NotImplementedError("Infinitesimals could not be found for " + "the given ODE") + + elif hint == 'default': + for heuristic in lie_heuristics: + function = globals()['lie_heuristic_' + heuristic] + xieta = function(match, comp=False) + if xieta: + return xieta + + raise NotImplementedError("Infinitesimals could not be found for" + " the given ODE") + + elif hint not in lie_heuristics: + raise ValueError("Heuristic not recognized: " + hint) + + else: + function = globals()['lie_heuristic_' + hint] + xieta = function(match, comp=True) + if xieta: + return xieta + else: + raise ValueError("Infinitesimals could not be found using the" + " given heuristic") + + +def lie_heuristic_abaco1_simple(match, comp=False): + r""" + The first heuristic uses the following four sets of + assumptions on `\xi` and `\eta` + + .. math:: \xi = 0, \eta = f(x) + + .. math:: \xi = 0, \eta = f(y) + + .. math:: \xi = f(x), \eta = 0 + + .. math:: \xi = f(y), \eta = 0 + + The success of this heuristic is determined by algebraic factorisation. + For the first assumption `\xi = 0` and `\eta` to be a function of `x`, the PDE + + .. math:: \frac{\partial \eta}{\partial x} + (\frac{\partial \eta}{\partial y} + - \frac{\partial \xi}{\partial x})*h + - \frac{\partial \xi}{\partial y}*h^{2} + - \xi*\frac{\partial h}{\partial x} - \eta*\frac{\partial h}{\partial y} = 0 + + reduces to `f'(x) - f\frac{\partial h}{\partial y} = 0` + If `\frac{\partial h}{\partial y}` is a function of `x`, then this can usually + be integrated easily. A similar idea is applied to the other 3 assumptions as well. + + + References + ========== + + - E.S Cheb-Terrab, L.G.S Duarte and L.A,C.P da Mota, Computer Algebra + Solving of First Order ODEs Using Symmetry Methods, pp. 8 + + + """ + + xieta = [] + y = match['y'] + h = match['h'] + func = match['func'] + x = func.args[0] + hx = match['hx'] + hy = match['hy'] + xi = Function('xi')(x, func) + eta = Function('eta')(x, func) + + hysym = hy.free_symbols + if y not in hysym: + try: + fx = exp(integrate(hy, x)) + except NotImplementedError: + pass + else: + inf = {xi: S.Zero, eta: fx} + if not comp: + return [inf] + if comp and inf not in xieta: + xieta.append(inf) + + factor = hy/h + facsym = factor.free_symbols + if x not in facsym: + try: + fy = exp(integrate(factor, y)) + except NotImplementedError: + pass + else: + inf = {xi: S.Zero, eta: fy.subs(y, func)} + if not comp: + return [inf] + if comp and inf not in xieta: + xieta.append(inf) + + factor = -hx/h + facsym = factor.free_symbols + if y not in facsym: + try: + fx = exp(integrate(factor, x)) + except NotImplementedError: + pass + else: + inf = {xi: fx, eta: S.Zero} + if not comp: + return [inf] + if comp and inf not in xieta: + xieta.append(inf) + + factor = -hx/(h**2) + facsym = factor.free_symbols + if x not in facsym: + try: + fy = exp(integrate(factor, y)) + except NotImplementedError: + pass + else: + inf = {xi: fy.subs(y, func), eta: S.Zero} + if not comp: + return [inf] + if comp and inf not in xieta: + xieta.append(inf) + + if xieta: + return xieta + +def lie_heuristic_abaco1_product(match, comp=False): + r""" + The second heuristic uses the following two assumptions on `\xi` and `\eta` + + .. math:: \eta = 0, \xi = f(x)*g(y) + + .. math:: \eta = f(x)*g(y), \xi = 0 + + The first assumption of this heuristic holds good if + `\frac{1}{h^{2}}\frac{\partial^2}{\partial x \partial y}\log(h)` is + separable in `x` and `y`, then the separated factors containing `x` + is `f(x)`, and `g(y)` is obtained by + + .. math:: e^{\int f\frac{\partial}{\partial x}\left(\frac{1}{f*h}\right)\,dy} + + provided `f\frac{\partial}{\partial x}\left(\frac{1}{f*h}\right)` is a function + of `y` only. + + The second assumption holds good if `\frac{dy}{dx} = h(x, y)` is rewritten as + `\frac{dy}{dx} = \frac{1}{h(y, x)}` and the same properties of the first assumption + satisfies. After obtaining `f(x)` and `g(y)`, the coordinates are again + interchanged, to get `\eta` as `f(x)*g(y)` + + + References + ========== + - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order + ODE Patterns, pp. 7 - pp. 8 + + """ + + xieta = [] + y = match['y'] + h = match['h'] + hinv = match['hinv'] + func = match['func'] + x = func.args[0] + xi = Function('xi')(x, func) + eta = Function('eta')(x, func) + + + inf = separatevars(((log(h).diff(y)).diff(x))/h**2, dict=True, symbols=[x, y]) + if inf and inf['coeff']: + fx = inf[x] + gy = simplify(fx*((1/(fx*h)).diff(x))) + gysyms = gy.free_symbols + if x not in gysyms: + gy = exp(integrate(gy, y)) + inf = {eta: S.Zero, xi: (fx*gy).subs(y, func)} + if not comp: + return [inf] + if comp and inf not in xieta: + xieta.append(inf) + + u1 = Dummy("u1") + inf = separatevars(((log(hinv).diff(y)).diff(x))/hinv**2, dict=True, symbols=[x, y]) + if inf and inf['coeff']: + fx = inf[x] + gy = simplify(fx*((1/(fx*hinv)).diff(x))) + gysyms = gy.free_symbols + if x not in gysyms: + gy = exp(integrate(gy, y)) + etaval = fx*gy + etaval = (etaval.subs([(x, u1), (y, x)])).subs(u1, y) + inf = {eta: etaval.subs(y, func), xi: S.Zero} + if not comp: + return [inf] + if comp and inf not in xieta: + xieta.append(inf) + + if xieta: + return xieta + +def lie_heuristic_bivariate(match, comp=False): + r""" + The third heuristic assumes the infinitesimals `\xi` and `\eta` + to be bi-variate polynomials in `x` and `y`. The assumption made here + for the logic below is that `h` is a rational function in `x` and `y` + though that may not be necessary for the infinitesimals to be + bivariate polynomials. The coefficients of the infinitesimals + are found out by substituting them in the PDE and grouping similar terms + that are polynomials and since they form a linear system, solve and check + for non trivial solutions. The degree of the assumed bivariates + are increased till a certain maximum value. + + References + ========== + - Lie Groups and Differential Equations + pp. 327 - pp. 329 + + """ + + h = match['h'] + hx = match['hx'] + hy = match['hy'] + func = match['func'] + x = func.args[0] + y = match['y'] + xi = Function('xi')(x, func) + eta = Function('eta')(x, func) + + if h.is_rational_function(): + # The maximum degree that the infinitesimals can take is + # calculated by this technique. + etax, etay, etad, xix, xiy, xid = symbols("etax etay etad xix xiy xid") + ipde = etax + (etay - xix)*h - xiy*h**2 - xid*hx - etad*hy + num, denom = cancel(ipde).as_numer_denom() + deg = Poly(num, x, y).total_degree() + deta = Function('deta')(x, y) + dxi = Function('dxi')(x, y) + ipde = (deta.diff(x) + (deta.diff(y) - dxi.diff(x))*h - (dxi.diff(y))*h**2 + - dxi*hx - deta*hy) + xieq = Symbol("xi0") + etaeq = Symbol("eta0") + + for i in range(deg + 1): + if i: + xieq += Add(*[ + Symbol("xi_" + str(power) + "_" + str(i - power))*x**power*y**(i - power) + for power in range(i + 1)]) + etaeq += Add(*[ + Symbol("eta_" + str(power) + "_" + str(i - power))*x**power*y**(i - power) + for power in range(i + 1)]) + pden, denom = (ipde.subs({dxi: xieq, deta: etaeq}).doit()).as_numer_denom() + pden = expand(pden) + + # If the individual terms are monomials, the coefficients + # are grouped + if pden.is_polynomial(x, y) and pden.is_Add: + polyy = Poly(pden, x, y).as_dict() + if polyy: + symset = xieq.free_symbols.union(etaeq.free_symbols) - {x, y} + soldict = solve(polyy.values(), *symset) + if isinstance(soldict, list): + soldict = soldict[0] + if any(soldict.values()): + xired = xieq.subs(soldict) + etared = etaeq.subs(soldict) + # Scaling is done by substituting one for the parameters + # This can be any number except zero. + dict_ = dict.fromkeys(symset, 1) + inf = {eta: etared.subs(dict_).subs(y, func), + xi: xired.subs(dict_).subs(y, func)} + return [inf] + +def lie_heuristic_chi(match, comp=False): + r""" + The aim of the fourth heuristic is to find the function `\chi(x, y)` + that satisfies the PDE `\frac{d\chi}{dx} + h\frac{d\chi}{dx} + - \frac{\partial h}{\partial y}\chi = 0`. + + This assumes `\chi` to be a bivariate polynomial in `x` and `y`. By intuition, + `h` should be a rational function in `x` and `y`. The method used here is + to substitute a general binomial for `\chi` up to a certain maximum degree + is reached. The coefficients of the polynomials, are calculated by by collecting + terms of the same order in `x` and `y`. + + After finding `\chi`, the next step is to use `\eta = \xi*h + \chi`, to + determine `\xi` and `\eta`. This can be done by dividing `\chi` by `h` + which would give `-\xi` as the quotient and `\eta` as the remainder. + + + References + ========== + - E.S Cheb-Terrab, L.G.S Duarte and L.A,C.P da Mota, Computer Algebra + Solving of First Order ODEs Using Symmetry Methods, pp. 8 + + """ + + h = match['h'] + hy = match['hy'] + func = match['func'] + x = func.args[0] + y = match['y'] + xi = Function('xi')(x, func) + eta = Function('eta')(x, func) + + if h.is_rational_function(): + schi, schix, schiy = symbols("schi, schix, schiy") + cpde = schix + h*schiy - hy*schi + num, denom = cancel(cpde).as_numer_denom() + deg = Poly(num, x, y).total_degree() + + chi = Function('chi')(x, y) + chix = chi.diff(x) + chiy = chi.diff(y) + cpde = chix + h*chiy - hy*chi + chieq = Symbol("chi") + for i in range(1, deg + 1): + chieq += Add(*[ + Symbol("chi_" + str(power) + "_" + str(i - power))*x**power*y**(i - power) + for power in range(i + 1)]) + cnum, cden = cancel(cpde.subs({chi : chieq}).doit()).as_numer_denom() + cnum = expand(cnum) + if cnum.is_polynomial(x, y) and cnum.is_Add: + cpoly = Poly(cnum, x, y).as_dict() + if cpoly: + solsyms = chieq.free_symbols - {x, y} + soldict = solve(cpoly.values(), *solsyms) + if isinstance(soldict, list): + soldict = soldict[0] + if any(soldict.values()): + chieq = chieq.subs(soldict) + dict_ = dict.fromkeys(solsyms, 1) + chieq = chieq.subs(dict_) + # After finding chi, the main aim is to find out + # eta, xi by the equation eta = xi*h + chi + # One method to set xi, would be rearranging it to + # (eta/h) - xi = (chi/h). This would mean dividing + # chi by h would give -xi as the quotient and eta + # as the remainder. Thanks to Sean Vig for suggesting + # this method. + xic, etac = div(chieq, h) + inf = {eta: etac.subs(y, func), xi: -xic.subs(y, func)} + return [inf] + +def lie_heuristic_function_sum(match, comp=False): + r""" + This heuristic uses the following two assumptions on `\xi` and `\eta` + + .. math:: \eta = 0, \xi = f(x) + g(y) + + .. math:: \eta = f(x) + g(y), \xi = 0 + + The first assumption of this heuristic holds good if + + .. math:: \frac{\partial}{\partial y}[(h\frac{\partial^{2}}{ + \partial x^{2}}(h^{-1}))^{-1}] + + is separable in `x` and `y`, + + 1. The separated factors containing `y` is `\frac{\partial g}{\partial y}`. + From this `g(y)` can be determined. + 2. The separated factors containing `x` is `f''(x)`. + 3. `h\frac{\partial^{2}}{\partial x^{2}}(h^{-1})` equals + `\frac{f''(x)}{f(x) + g(y)}`. From this `f(x)` can be determined. + + The second assumption holds good if `\frac{dy}{dx} = h(x, y)` is rewritten as + `\frac{dy}{dx} = \frac{1}{h(y, x)}` and the same properties of the first + assumption satisfies. After obtaining `f(x)` and `g(y)`, the coordinates + are again interchanged, to get `\eta` as `f(x) + g(y)`. + + For both assumptions, the constant factors are separated among `g(y)` + and `f''(x)`, such that `f''(x)` obtained from 3] is the same as that + obtained from 2]. If not possible, then this heuristic fails. + + + References + ========== + - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order + ODE Patterns, pp. 7 - pp. 8 + + """ + + xieta = [] + h = match['h'] + func = match['func'] + hinv = match['hinv'] + x = func.args[0] + y = match['y'] + xi = Function('xi')(x, func) + eta = Function('eta')(x, func) + + for odefac in [h, hinv]: + factor = odefac*((1/odefac).diff(x, 2)) + sep = separatevars((1/factor).diff(y), dict=True, symbols=[x, y]) + if sep and sep['coeff'] and sep[x].has(x) and sep[y].has(y): + k = Dummy("k") + try: + gy = k*integrate(sep[y], y) + except NotImplementedError: + pass + else: + fdd = 1/(k*sep[x]*sep['coeff']) + fx = simplify(fdd/factor - gy) + check = simplify(fx.diff(x, 2) - fdd) + if fx: + if not check: + fx = fx.subs(k, 1) + gy = (gy/k) + else: + sol = solve(check, k) + if sol: + sol = sol[0] + fx = fx.subs(k, sol) + gy = (gy/k)*sol + else: + continue + if odefac == hinv: # Inverse ODE + fx = fx.subs(x, y) + gy = gy.subs(y, x) + etaval = factor_terms(fx + gy) + if etaval.is_Mul: + etaval = Mul(*[arg for arg in etaval.args if arg.has(x, y)]) + if odefac == hinv: # Inverse ODE + inf = {eta: etaval.subs(y, func), xi : S.Zero} + else: + inf = {xi: etaval.subs(y, func), eta : S.Zero} + if not comp: + return [inf] + else: + xieta.append(inf) + + if xieta: + return xieta + +def lie_heuristic_abaco2_similar(match, comp=False): + r""" + This heuristic uses the following two assumptions on `\xi` and `\eta` + + .. math:: \eta = g(x), \xi = f(x) + + .. math:: \eta = f(y), \xi = g(y) + + For the first assumption, + + 1. First `\frac{\frac{\partial h}{\partial y}}{\frac{\partial^{2} h}{ + \partial yy}}` is calculated. Let us say this value is A + + 2. If this is constant, then `h` is matched to the form `A(x) + B(x)e^{ + \frac{y}{C}}` then, `\frac{e^{\int \frac{A(x)}{C} \,dx}}{B(x)}` gives `f(x)` + and `A(x)*f(x)` gives `g(x)` + + 3. Otherwise `\frac{\frac{\partial A}{\partial X}}{\frac{\partial A}{ + \partial Y}} = \gamma` is calculated. If + + a] `\gamma` is a function of `x` alone + + b] `\frac{\gamma\frac{\partial h}{\partial y} - \gamma'(x) - \frac{ + \partial h}{\partial x}}{h + \gamma} = G` is a function of `x` alone. + then, `e^{\int G \,dx}` gives `f(x)` and `-\gamma*f(x)` gives `g(x)` + + The second assumption holds good if `\frac{dy}{dx} = h(x, y)` is rewritten as + `\frac{dy}{dx} = \frac{1}{h(y, x)}` and the same properties of the first assumption + satisfies. After obtaining `f(x)` and `g(x)`, the coordinates are again + interchanged, to get `\xi` as `f(x^*)` and `\eta` as `g(y^*)` + + References + ========== + - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order + ODE Patterns, pp. 10 - pp. 12 + + """ + + h = match['h'] + hx = match['hx'] + hy = match['hy'] + func = match['func'] + hinv = match['hinv'] + x = func.args[0] + y = match['y'] + xi = Function('xi')(x, func) + eta = Function('eta')(x, func) + + factor = cancel(h.diff(y)/h.diff(y, 2)) + factorx = factor.diff(x) + factory = factor.diff(y) + if not factor.has(x) and not factor.has(y): + A = Wild('A', exclude=[y]) + B = Wild('B', exclude=[y]) + C = Wild('C', exclude=[x, y]) + match = h.match(A + B*exp(y/C)) + try: + tau = exp(-integrate(match[A]/match[C]), x)/match[B] + except NotImplementedError: + pass + else: + gx = match[A]*tau + return [{xi: tau, eta: gx}] + + else: + gamma = cancel(factorx/factory) + if not gamma.has(y): + tauint = cancel((gamma*hy - gamma.diff(x) - hx)/(h + gamma)) + if not tauint.has(y): + try: + tau = exp(integrate(tauint, x)) + except NotImplementedError: + pass + else: + gx = -tau*gamma + return [{xi: tau, eta: gx}] + + factor = cancel(hinv.diff(y)/hinv.diff(y, 2)) + factorx = factor.diff(x) + factory = factor.diff(y) + if not factor.has(x) and not factor.has(y): + A = Wild('A', exclude=[y]) + B = Wild('B', exclude=[y]) + C = Wild('C', exclude=[x, y]) + match = h.match(A + B*exp(y/C)) + try: + tau = exp(-integrate(match[A]/match[C]), x)/match[B] + except NotImplementedError: + pass + else: + gx = match[A]*tau + return [{eta: tau.subs(x, func), xi: gx.subs(x, func)}] + + else: + gamma = cancel(factorx/factory) + if not gamma.has(y): + tauint = cancel((gamma*hinv.diff(y) - gamma.diff(x) - hinv.diff(x))/( + hinv + gamma)) + if not tauint.has(y): + try: + tau = exp(integrate(tauint, x)) + except NotImplementedError: + pass + else: + gx = -tau*gamma + return [{eta: tau.subs(x, func), xi: gx.subs(x, func)}] + + +def lie_heuristic_abaco2_unique_unknown(match, comp=False): + r""" + This heuristic assumes the presence of unknown functions or known functions + with non-integer powers. + + 1. A list of all functions and non-integer powers containing x and y + 2. Loop over each element `f` in the list, find `\frac{\frac{\partial f}{\partial x}}{ + \frac{\partial f}{\partial x}} = R` + + If it is separable in `x` and `y`, let `X` be the factors containing `x`. Then + + a] Check if `\xi = X` and `\eta = -\frac{X}{R}` satisfy the PDE. If yes, then return + `\xi` and `\eta` + b] Check if `\xi = \frac{-R}{X}` and `\eta = -\frac{1}{X}` satisfy the PDE. + If yes, then return `\xi` and `\eta` + + If not, then check if + + a] :math:`\xi = -R,\eta = 1` + + b] :math:`\xi = 1, \eta = -\frac{1}{R}` + + are solutions. + + References + ========== + - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order + ODE Patterns, pp. 10 - pp. 12 + + """ + + h = match['h'] + hx = match['hx'] + hy = match['hy'] + func = match['func'] + x = func.args[0] + y = match['y'] + xi = Function('xi')(x, func) + eta = Function('eta')(x, func) + + funclist = [] + for atom in h.atoms(Pow): + base, exp = atom.as_base_exp() + if base.has(x) and base.has(y): + if not exp.is_Integer: + funclist.append(atom) + + for function in h.atoms(AppliedUndef): + syms = function.free_symbols + if x in syms and y in syms: + funclist.append(function) + + for f in funclist: + frac = cancel(f.diff(y)/f.diff(x)) + sep = separatevars(frac, dict=True, symbols=[x, y]) + if sep and sep['coeff']: + xitry1 = sep[x] + etatry1 = -1/(sep[y]*sep['coeff']) + pde1 = etatry1.diff(y)*h - xitry1.diff(x)*h - xitry1*hx - etatry1*hy + if not simplify(pde1): + return [{xi: xitry1, eta: etatry1.subs(y, func)}] + xitry2 = 1/etatry1 + etatry2 = 1/xitry1 + pde2 = etatry2.diff(x) - (xitry2.diff(y))*h**2 - xitry2*hx - etatry2*hy + if not simplify(expand(pde2)): + return [{xi: xitry2.subs(y, func), eta: etatry2}] + + else: + etatry = -1/frac + pde = etatry.diff(x) + etatry.diff(y)*h - hx - etatry*hy + if not simplify(pde): + return [{xi: S.One, eta: etatry.subs(y, func)}] + xitry = -frac + pde = -xitry.diff(x)*h -xitry.diff(y)*h**2 - xitry*hx -hy + if not simplify(expand(pde)): + return [{xi: xitry.subs(y, func), eta: S.One}] + + +def lie_heuristic_abaco2_unique_general(match, comp=False): + r""" + This heuristic finds if infinitesimals of the form `\eta = f(x)`, `\xi = g(y)` + without making any assumptions on `h`. + + The complete sequence of steps is given in the paper mentioned below. + + References + ========== + - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order + ODE Patterns, pp. 10 - pp. 12 + + """ + hx = match['hx'] + hy = match['hy'] + func = match['func'] + x = func.args[0] + y = match['y'] + xi = Function('xi')(x, func) + eta = Function('eta')(x, func) + + A = hx.diff(y) + B = hy.diff(y) + hy**2 + C = hx.diff(x) - hx**2 + + if not (A and B and C): + return + + Ax = A.diff(x) + Ay = A.diff(y) + Axy = Ax.diff(y) + Axx = Ax.diff(x) + Ayy = Ay.diff(y) + D = simplify(2*Axy + hx*Ay - Ax*hy + (hx*hy + 2*A)*A)*A - 3*Ax*Ay + if not D: + E1 = simplify(3*Ax**2 + ((hx**2 + 2*C)*A - 2*Axx)*A) + if E1: + E2 = simplify((2*Ayy + (2*B - hy**2)*A)*A - 3*Ay**2) + if not E2: + E3 = simplify( + E1*((28*Ax + 4*hx*A)*A**3 - E1*(hy*A + Ay)) - E1.diff(x)*8*A**4) + if not E3: + etaval = cancel((4*A**3*(Ax - hx*A) + E1*(hy*A - Ay))/(S(2)*A*E1)) + if x not in etaval: + try: + etaval = exp(integrate(etaval, y)) + except NotImplementedError: + pass + else: + xival = -4*A**3*etaval/E1 + if y not in xival: + return [{xi: xival, eta: etaval.subs(y, func)}] + + else: + E1 = simplify((2*Ayy + (2*B - hy**2)*A)*A - 3*Ay**2) + if E1: + E2 = simplify( + 4*A**3*D - D**2 + E1*((2*Axx - (hx**2 + 2*C)*A)*A - 3*Ax**2)) + if not E2: + E3 = simplify( + -(A*D)*E1.diff(y) + ((E1.diff(x) - hy*D)*A + 3*Ay*D + + (A*hx - 3*Ax)*E1)*E1) + if not E3: + etaval = cancel(((A*hx - Ax)*E1 - (Ay + A*hy)*D)/(S(2)*A*D)) + if x not in etaval: + try: + etaval = exp(integrate(etaval, y)) + except NotImplementedError: + pass + else: + xival = -E1*etaval/D + if y not in xival: + return [{xi: xival, eta: etaval.subs(y, func)}] + + +def lie_heuristic_linear(match, comp=False): + r""" + This heuristic assumes + + 1. `\xi = ax + by + c` and + 2. `\eta = fx + gy + h` + + After substituting the following assumptions in the determining PDE, it + reduces to + + .. math:: f + (g - a)h - bh^{2} - (ax + by + c)\frac{\partial h}{\partial x} + - (fx + gy + c)\frac{\partial h}{\partial y} + + Solving the reduced PDE obtained, using the method of characteristics, becomes + impractical. The method followed is grouping similar terms and solving the system + of linear equations obtained. The difference between the bivariate heuristic is that + `h` need not be a rational function in this case. + + References + ========== + - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order + ODE Patterns, pp. 10 - pp. 12 + + """ + h = match['h'] + hx = match['hx'] + hy = match['hy'] + func = match['func'] + x = func.args[0] + y = match['y'] + xi = Function('xi')(x, func) + eta = Function('eta')(x, func) + + coeffdict = {} + symbols = numbered_symbols("c", cls=Dummy) + symlist = [next(symbols) for _ in islice(symbols, 6)] + C0, C1, C2, C3, C4, C5 = symlist + pde = C3 + (C4 - C0)*h - (C0*x + C1*y + C2)*hx - (C3*x + C4*y + C5)*hy - C1*h**2 + pde, denom = pde.as_numer_denom() + pde = powsimp(expand(pde)) + if pde.is_Add: + terms = pde.args + for term in terms: + if term.is_Mul: + rem = Mul(*[m for m in term.args if not m.has(x, y)]) + xypart = term/rem + if xypart not in coeffdict: + coeffdict[xypart] = rem + else: + coeffdict[xypart] += rem + else: + if term not in coeffdict: + coeffdict[term] = S.One + else: + coeffdict[term] += S.One + + sollist = coeffdict.values() + soldict = solve(sollist, symlist) + if soldict: + if isinstance(soldict, list): + soldict = soldict[0] + subval = soldict.values() + if any(t for t in subval): + onedict = dict(zip(symlist, [1]*6)) + xival = C0*x + C1*func + C2 + etaval = C3*x + C4*func + C5 + xival = xival.subs(soldict) + etaval = etaval.subs(soldict) + xival = xival.subs(onedict) + etaval = etaval.subs(onedict) + return [{xi: xival, eta: etaval}] + + +def _lie_group_remove(coords): + r""" + This function is strictly meant for internal use by the Lie group ODE solving + method. It replaces arbitrary functions returned by pdsolve as follows: + + 1] If coords is an arbitrary function, then its argument is returned. + 2] An arbitrary function in an Add object is replaced by zero. + 3] An arbitrary function in a Mul object is replaced by one. + 4] If there is no arbitrary function coords is returned unchanged. + + Examples + ======== + + >>> from sympy.solvers.ode.lie_group import _lie_group_remove + >>> from sympy import Function + >>> from sympy.abc import x, y + >>> F = Function("F") + >>> eq = x**2*y + >>> _lie_group_remove(eq) + x**2*y + >>> eq = F(x**2*y) + >>> _lie_group_remove(eq) + x**2*y + >>> eq = x*y**2 + F(x**3) + >>> _lie_group_remove(eq) + x*y**2 + >>> eq = (F(x**3) + y)*x**4 + >>> _lie_group_remove(eq) + x**4*y + + """ + if isinstance(coords, AppliedUndef): + return coords.args[0] + elif coords.is_Add: + subfunc = coords.atoms(AppliedUndef) + if subfunc: + for func in subfunc: + coords = coords.subs(func, 0) + return coords + elif coords.is_Pow: + base, expr = coords.as_base_exp() + base = _lie_group_remove(base) + expr = _lie_group_remove(expr) + return base**expr + elif coords.is_Mul: + mulargs = [] + coordargs = coords.args + for arg in coordargs: + if not isinstance(coords, AppliedUndef): + mulargs.append(_lie_group_remove(arg)) + return Mul(*mulargs) + return coords diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/nonhomogeneous.py b/lib/python3.12/site-packages/sympy/solvers/ode/nonhomogeneous.py new file mode 100644 index 0000000000000000000000000000000000000000..87ff54074871f76304a60ec0e46aa3ff999df9ec --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/ode/nonhomogeneous.py @@ -0,0 +1,499 @@ +r""" +This File contains helper functions for nth_linear_constant_coeff_undetermined_coefficients, +nth_linear_euler_eq_nonhomogeneous_undetermined_coefficients, +nth_linear_constant_coeff_variation_of_parameters, +and nth_linear_euler_eq_nonhomogeneous_variation_of_parameters. + +All the functions in this file are used by more than one solvers so, instead of creating +instances in other classes for using them it is better to keep it here as separate helpers. + +""" +from collections import defaultdict +from sympy.core import Add, S +from sympy.core.function import diff, expand, _mexpand, expand_mul +from sympy.core.relational import Eq +from sympy.core.sorting import default_sort_key +from sympy.core.symbol import Dummy, Wild +from sympy.functions import exp, cos, cosh, im, log, re, sin, sinh, \ + atan2, conjugate +from sympy.integrals import Integral +from sympy.polys import (Poly, RootOf, rootof, roots) +from sympy.simplify import collect, simplify, separatevars, powsimp, trigsimp # type: ignore +from sympy.utilities import numbered_symbols +from sympy.solvers.solvers import solve +from sympy.matrices import wronskian +from .subscheck import sub_func_doit +from sympy.solvers.ode.ode import get_numbered_constants + + +def _test_term(coeff, func, order): + r""" + Linear Euler ODEs have the form K*x**order*diff(y(x), x, order) = F(x), + where K is independent of x and y(x), order>= 0. + So we need to check that for each term, coeff == K*x**order from + some K. We have a few cases, since coeff may have several + different types. + """ + x = func.args[0] + f = func.func + if order < 0: + raise ValueError("order should be greater than 0") + if coeff == 0: + return True + if order == 0: + if x in coeff.free_symbols: + return False + return True + if coeff.is_Mul: + if coeff.has(f(x)): + return False + return x**order in coeff.args + elif coeff.is_Pow: + return coeff.as_base_exp() == (x, order) + elif order == 1: + return x == coeff + return False + + +def _get_euler_characteristic_eq_sols(eq, func, match_obj): + r""" + Returns the solution of homogeneous part of the linear euler ODE and + the list of roots of characteristic equation. + + The parameter ``match_obj`` is a dict of order:coeff terms, where order is the order + of the derivative on each term, and coeff is the coefficient of that derivative. + + """ + x = func.args[0] + f = func.func + + # First, set up characteristic equation. + chareq, symbol = S.Zero, Dummy('x') + + for i in match_obj: + if i >= 0: + chareq += (match_obj[i]*diff(x**symbol, x, i)*x**-symbol).expand() + + chareq = Poly(chareq, symbol) + chareqroots = [rootof(chareq, k) for k in range(chareq.degree())] + collectterms = [] + + # A generator of constants + constants = list(get_numbered_constants(eq, num=chareq.degree()*2)) + constants.reverse() + + # Create a dict root: multiplicity or charroots + charroots = defaultdict(int) + for root in chareqroots: + charroots[root] += 1 + gsol = S.Zero + ln = log + for root, multiplicity in charroots.items(): + for i in range(multiplicity): + if isinstance(root, RootOf): + gsol += (x**root) * constants.pop() + if multiplicity != 1: + raise ValueError("Value should be 1") + collectterms = [(0, root, 0)] + collectterms + elif root.is_real: + gsol += ln(x)**i*(x**root) * constants.pop() + collectterms = [(i, root, 0)] + collectterms + else: + reroot = re(root) + imroot = im(root) + gsol += ln(x)**i * (x**reroot) * ( + constants.pop() * sin(abs(imroot)*ln(x)) + + constants.pop() * cos(imroot*ln(x))) + collectterms = [(i, reroot, imroot)] + collectterms + + gsol = Eq(f(x), gsol) + + gensols = [] + # Keep track of when to use sin or cos for nonzero imroot + for i, reroot, imroot in collectterms: + if imroot == 0: + gensols.append(ln(x)**i*x**reroot) + else: + sin_form = ln(x)**i*x**reroot*sin(abs(imroot)*ln(x)) + if sin_form in gensols: + cos_form = ln(x)**i*x**reroot*cos(imroot*ln(x)) + gensols.append(cos_form) + else: + gensols.append(sin_form) + return gsol, gensols + + +def _solve_variation_of_parameters(eq, func, roots, homogen_sol, order, match_obj, simplify_flag=True): + r""" + Helper function for the method of variation of parameters and nonhomogeneous euler eq. + + See the + :py:meth:`~sympy.solvers.ode.single.NthLinearConstantCoeffVariationOfParameters` + docstring for more information on this method. + + The parameter are ``match_obj`` should be a dictionary that has the following + keys: + + ``list`` + A list of solutions to the homogeneous equation. + + ``sol`` + The general solution. + + """ + f = func.func + x = func.args[0] + r = match_obj + psol = 0 + wr = wronskian(roots, x) + + if simplify_flag: + wr = simplify(wr) # We need much better simplification for + # some ODEs. See issue 4662, for example. + # To reduce commonly occurring sin(x)**2 + cos(x)**2 to 1 + wr = trigsimp(wr, deep=True, recursive=True) + if not wr: + # The wronskian will be 0 iff the solutions are not linearly + # independent. + raise NotImplementedError("Cannot find " + str(order) + + " solutions to the homogeneous equation necessary to apply " + + "variation of parameters to " + str(eq) + " (Wronskian == 0)") + if len(roots) != order: + raise NotImplementedError("Cannot find " + str(order) + + " solutions to the homogeneous equation necessary to apply " + + "variation of parameters to " + + str(eq) + " (number of terms != order)") + negoneterm = S.NegativeOne**(order) + for i in roots: + psol += negoneterm*Integral(wronskian([sol for sol in roots if sol != i], x)*r[-1]/wr, x)*i/r[order] + negoneterm *= -1 + + if simplify_flag: + psol = simplify(psol) + psol = trigsimp(psol, deep=True) + return Eq(f(x), homogen_sol.rhs + psol) + + +def _get_const_characteristic_eq_sols(r, func, order): + r""" + Returns the roots of characteristic equation of constant coefficient + linear ODE and list of collectterms which is later on used by simplification + to use collect on solution. + + The parameter `r` is a dict of order:coeff terms, where order is the order of the + derivative on each term, and coeff is the coefficient of that derivative. + + """ + x = func.args[0] + # First, set up characteristic equation. + chareq, symbol = S.Zero, Dummy('x') + + for i in r.keys(): + if isinstance(i, str) or i < 0: + pass + else: + chareq += r[i]*symbol**i + + chareq = Poly(chareq, symbol) + # Can't just call roots because it doesn't return rootof for unsolveable + # polynomials. + chareqroots = roots(chareq, multiple=True) + if len(chareqroots) != order: + chareqroots = [rootof(chareq, k) for k in range(chareq.degree())] + + chareq_is_complex = not all(i.is_real for i in chareq.all_coeffs()) + + # Create a dict root: multiplicity or charroots + charroots = defaultdict(int) + for root in chareqroots: + charroots[root] += 1 + # We need to keep track of terms so we can run collect() at the end. + # This is necessary for constantsimp to work properly. + collectterms = [] + gensols = [] + conjugate_roots = [] # used to prevent double-use of conjugate roots + # Loop over roots in theorder provided by roots/rootof... + for root in chareqroots: + # but don't repoeat multiple roots. + if root not in charroots: + continue + multiplicity = charroots.pop(root) + for i in range(multiplicity): + if chareq_is_complex: + gensols.append(x**i*exp(root*x)) + collectterms = [(i, root, 0)] + collectterms + continue + reroot = re(root) + imroot = im(root) + if imroot.has(atan2) and reroot.has(atan2): + # Remove this condition when re and im stop returning + # circular atan2 usages. + gensols.append(x**i*exp(root*x)) + collectterms = [(i, root, 0)] + collectterms + else: + if root in conjugate_roots: + collectterms = [(i, reroot, imroot)] + collectterms + continue + if imroot == 0: + gensols.append(x**i*exp(reroot*x)) + collectterms = [(i, reroot, 0)] + collectterms + continue + conjugate_roots.append(conjugate(root)) + gensols.append(x**i*exp(reroot*x) * sin(abs(imroot) * x)) + gensols.append(x**i*exp(reroot*x) * cos( imroot * x)) + + # This ordering is important + collectterms = [(i, reroot, imroot)] + collectterms + return gensols, collectterms + + +# Ideally these kind of simplification functions shouldn't be part of solvers. +# odesimp should be improved to handle these kind of specific simplifications. +def _get_simplified_sol(sol, func, collectterms): + r""" + Helper function which collects the solution on + collectterms. Ideally this should be handled by odesimp.It is used + only when the simplify is set to True in dsolve. + + The parameter ``collectterms`` is a list of tuple (i, reroot, imroot) where `i` is + the multiplicity of the root, reroot is real part and imroot being the imaginary part. + + """ + f = func.func + x = func.args[0] + collectterms.sort(key=default_sort_key) + collectterms.reverse() + assert len(sol) == 1 and sol[0].lhs == f(x) + sol = sol[0].rhs + sol = expand_mul(sol) + for i, reroot, imroot in collectterms: + sol = collect(sol, x**i*exp(reroot*x)*sin(abs(imroot)*x)) + sol = collect(sol, x**i*exp(reroot*x)*cos(imroot*x)) + for i, reroot, imroot in collectterms: + sol = collect(sol, x**i*exp(reroot*x)) + sol = powsimp(sol) + return Eq(f(x), sol) + + +def _undetermined_coefficients_match(expr, x, func=None, eq_homogeneous=S.Zero): + r""" + Returns a trial function match if undetermined coefficients can be applied + to ``expr``, and ``None`` otherwise. + + A trial expression can be found for an expression for use with the method + of undetermined coefficients if the expression is an + additive/multiplicative combination of constants, polynomials in `x` (the + independent variable of expr), `\sin(a x + b)`, `\cos(a x + b)`, and + `e^{a x}` terms (in other words, it has a finite number of linearly + independent derivatives). + + Note that you may still need to multiply each term returned here by + sufficient `x` to make it linearly independent with the solutions to the + homogeneous equation. + + This is intended for internal use by ``undetermined_coefficients`` hints. + + SymPy currently has no way to convert `\sin^n(x) \cos^m(y)` into a sum of + only `\sin(a x)` and `\cos(b x)` terms, so these are not implemented. So, + for example, you will need to manually convert `\sin^2(x)` into `[1 + + \cos(2 x)]/2` to properly apply the method of undetermined coefficients on + it. + + Examples + ======== + + >>> from sympy import log, exp + >>> from sympy.solvers.ode.nonhomogeneous import _undetermined_coefficients_match + >>> from sympy.abc import x + >>> _undetermined_coefficients_match(9*x*exp(x) + exp(-x), x) + {'test': True, 'trialset': {x*exp(x), exp(-x), exp(x)}} + >>> _undetermined_coefficients_match(log(x), x) + {'test': False} + + """ + a = Wild('a', exclude=[x]) + b = Wild('b', exclude=[x]) + expr = powsimp(expr, combine='exp') # exp(x)*exp(2*x + 1) => exp(3*x + 1) + retdict = {} + + def _test_term(expr, x): + r""" + Test if ``expr`` fits the proper form for undetermined coefficients. + """ + if not expr.has(x): + return True + elif expr.is_Add: + return all(_test_term(i, x) for i in expr.args) + elif expr.is_Mul: + if expr.has(sin, cos): + foundtrig = False + # Make sure that there is only one trig function in the args. + # See the docstring. + for i in expr.args: + if i.has(sin, cos): + if foundtrig: + return False + else: + foundtrig = True + return all(_test_term(i, x) for i in expr.args) + elif expr.is_Function: + if expr.func in (sin, cos, exp, sinh, cosh): + if expr.args[0].match(a*x + b): + return True + else: + return False + else: + return False + elif expr.is_Pow and expr.base.is_Symbol and expr.exp.is_Integer and \ + expr.exp >= 0: + return True + elif expr.is_Pow and expr.base.is_number: + if expr.exp.match(a*x + b): + return True + else: + return False + elif expr.is_Symbol or expr.is_number: + return True + else: + return False + + def _get_trial_set(expr, x, exprs=set()): + r""" + Returns a set of trial terms for undetermined coefficients. + + The idea behind undetermined coefficients is that the terms expression + repeat themselves after a finite number of derivatives, except for the + coefficients (they are linearly dependent). So if we collect these, + we should have the terms of our trial function. + """ + def _remove_coefficient(expr, x): + r""" + Returns the expression without a coefficient. + + Similar to expr.as_independent(x)[1], except it only works + multiplicatively. + """ + term = S.One + if expr.is_Mul: + for i in expr.args: + if i.has(x): + term *= i + elif expr.has(x): + term = expr + return term + + expr = expand_mul(expr) + if expr.is_Add: + for term in expr.args: + if _remove_coefficient(term, x) in exprs: + pass + else: + exprs.add(_remove_coefficient(term, x)) + exprs = exprs.union(_get_trial_set(term, x, exprs)) + else: + term = _remove_coefficient(expr, x) + tmpset = exprs.union({term}) + oldset = set() + while tmpset != oldset: + # If you get stuck in this loop, then _test_term is probably + # broken + oldset = tmpset.copy() + expr = expr.diff(x) + term = _remove_coefficient(expr, x) + if term.is_Add: + tmpset = tmpset.union(_get_trial_set(term, x, tmpset)) + else: + tmpset.add(term) + exprs = tmpset + return exprs + + def is_homogeneous_solution(term): + r""" This function checks whether the given trialset contains any root + of homogeneous equation""" + return expand(sub_func_doit(eq_homogeneous, func, term)).is_zero + + retdict['test'] = _test_term(expr, x) + if retdict['test']: + # Try to generate a list of trial solutions that will have the + # undetermined coefficients. Note that if any of these are not linearly + # independent with any of the solutions to the homogeneous equation, + # then they will need to be multiplied by sufficient x to make them so. + # This function DOES NOT do that (it doesn't even look at the + # homogeneous equation). + temp_set = set() + for i in Add.make_args(expr): + act = _get_trial_set(i, x) + if eq_homogeneous is not S.Zero: + while any(is_homogeneous_solution(ts) for ts in act): + act = {x*ts for ts in act} + temp_set = temp_set.union(act) + + retdict['trialset'] = temp_set + return retdict + + +def _solve_undetermined_coefficients(eq, func, order, match, trialset): + r""" + Helper function for the method of undetermined coefficients. + + See the + :py:meth:`~sympy.solvers.ode.single.NthLinearConstantCoeffUndeterminedCoefficients` + docstring for more information on this method. + + The parameter ``trialset`` is the set of trial functions as returned by + ``_undetermined_coefficients_match()['trialset']``. + + The parameter ``match`` should be a dictionary that has the following + keys: + + ``list`` + A list of solutions to the homogeneous equation. + + ``sol`` + The general solution. + + """ + r = match + coeffs = numbered_symbols('a', cls=Dummy) + coefflist = [] + gensols = r['list'] + gsol = r['sol'] + f = func.func + x = func.args[0] + + if len(gensols) != order: + raise NotImplementedError("Cannot find " + str(order) + + " solutions to the homogeneous equation necessary to apply" + + " undetermined coefficients to " + str(eq) + + " (number of terms != order)") + + trialfunc = 0 + for i in trialset: + c = next(coeffs) + coefflist.append(c) + trialfunc += c*i + + eqs = sub_func_doit(eq, f(x), trialfunc) + + coeffsdict = dict(list(zip(trialset, [0]*(len(trialset) + 1)))) + + eqs = _mexpand(eqs) + + for i in Add.make_args(eqs): + s = separatevars(i, dict=True, symbols=[x]) + if coeffsdict.get(s[x]): + coeffsdict[s[x]] += s['coeff'] + else: + coeffsdict[s[x]] = s['coeff'] + + coeffvals = solve(list(coeffsdict.values()), coefflist) + + if not coeffvals: + raise NotImplementedError( + "Could not solve `%s` using the " + "method of undetermined coefficients " + "(unable to solve for coefficients)." % eq) + + psol = trialfunc.subs(coeffvals) + + return Eq(f(x), gsol.rhs + psol) diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/ode.py b/lib/python3.12/site-packages/sympy/solvers/ode/ode.py new file mode 100644 index 0000000000000000000000000000000000000000..be82bb18a5c800ae3848605d636d9744a011b6bc --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/ode/ode.py @@ -0,0 +1,3575 @@ +r""" +This module contains :py:meth:`~sympy.solvers.ode.dsolve` and different helper +functions that it uses. + +:py:meth:`~sympy.solvers.ode.dsolve` solves ordinary differential equations. +See the docstring on the various functions for their uses. Note that partial +differential equations support is in ``pde.py``. Note that hint functions +have docstrings describing their various methods, but they are intended for +internal use. Use ``dsolve(ode, func, hint=hint)`` to solve an ODE using a +specific hint. See also the docstring on +:py:meth:`~sympy.solvers.ode.dsolve`. + +**Functions in this module** + + These are the user functions in this module: + + - :py:meth:`~sympy.solvers.ode.dsolve` - Solves ODEs. + - :py:meth:`~sympy.solvers.ode.classify_ode` - Classifies ODEs into + possible hints for :py:meth:`~sympy.solvers.ode.dsolve`. + - :py:meth:`~sympy.solvers.ode.checkodesol` - Checks if an equation is the + solution to an ODE. + - :py:meth:`~sympy.solvers.ode.homogeneous_order` - Returns the + homogeneous order of an expression. + - :py:meth:`~sympy.solvers.ode.infinitesimals` - Returns the infinitesimals + of the Lie group of point transformations of an ODE, such that it is + invariant. + - :py:meth:`~sympy.solvers.ode.checkinfsol` - Checks if the given infinitesimals + are the actual infinitesimals of a first order ODE. + + These are the non-solver helper functions that are for internal use. The + user should use the various options to + :py:meth:`~sympy.solvers.ode.dsolve` to obtain the functionality provided + by these functions: + + - :py:meth:`~sympy.solvers.ode.ode.odesimp` - Does all forms of ODE + simplification. + - :py:meth:`~sympy.solvers.ode.ode.ode_sol_simplicity` - A key function for + comparing solutions by simplicity. + - :py:meth:`~sympy.solvers.ode.constantsimp` - Simplifies arbitrary + constants. + - :py:meth:`~sympy.solvers.ode.ode.constant_renumber` - Renumber arbitrary + constants. + - :py:meth:`~sympy.solvers.ode.ode._handle_Integral` - Evaluate unevaluated + Integrals. + + See also the docstrings of these functions. + +**Currently implemented solver methods** + +The following methods are implemented for solving ordinary differential +equations. See the docstrings of the various hint functions for more +information on each (run ``help(ode)``): + + - 1st order separable differential equations. + - 1st order differential equations whose coefficients or `dx` and `dy` are + functions homogeneous of the same order. + - 1st order exact differential equations. + - 1st order linear differential equations. + - 1st order Bernoulli differential equations. + - Power series solutions for first order differential equations. + - Lie Group method of solving first order differential equations. + - 2nd order Liouville differential equations. + - Power series solutions for second order differential equations + at ordinary and regular singular points. + - `n`\th order differential equation that can be solved with algebraic + rearrangement and integration. + - `n`\th order linear homogeneous differential equation with constant + coefficients. + - `n`\th order linear inhomogeneous differential equation with constant + coefficients using the method of undetermined coefficients. + - `n`\th order linear inhomogeneous differential equation with constant + coefficients using the method of variation of parameters. + +**Philosophy behind this module** + +This module is designed to make it easy to add new ODE solving methods without +having to mess with the solving code for other methods. The idea is that +there is a :py:meth:`~sympy.solvers.ode.classify_ode` function, which takes in +an ODE and tells you what hints, if any, will solve the ODE. It does this +without attempting to solve the ODE, so it is fast. Each solving method is a +hint, and it has its own function, named ``ode_``. That function takes +in the ODE and any match expression gathered by +:py:meth:`~sympy.solvers.ode.classify_ode` and returns a solved result. If +this result has any integrals in it, the hint function will return an +unevaluated :py:class:`~sympy.integrals.integrals.Integral` class. +:py:meth:`~sympy.solvers.ode.dsolve`, which is the user wrapper function +around all of this, will then call :py:meth:`~sympy.solvers.ode.ode.odesimp` on +the result, which, among other things, will attempt to solve the equation for +the dependent variable (the function we are solving for), simplify the +arbitrary constants in the expression, and evaluate any integrals, if the hint +allows it. + +**How to add new solution methods** + +If you have an ODE that you want :py:meth:`~sympy.solvers.ode.dsolve` to be +able to solve, try to avoid adding special case code here. Instead, try +finding a general method that will solve your ODE, as well as others. This +way, the :py:mod:`~sympy.solvers.ode` module will become more robust, and +unhindered by special case hacks. WolphramAlpha and Maple's +DETools[odeadvisor] function are two resources you can use to classify a +specific ODE. It is also better for a method to work with an `n`\th order ODE +instead of only with specific orders, if possible. + +To add a new method, there are a few things that you need to do. First, you +need a hint name for your method. Try to name your hint so that it is +unambiguous with all other methods, including ones that may not be implemented +yet. If your method uses integrals, also include a ``hint_Integral`` hint. +If there is more than one way to solve ODEs with your method, include a hint +for each one, as well as a ``_best`` hint. Your ``ode__best()`` +function should choose the best using min with ``ode_sol_simplicity`` as the +key argument. See +:obj:`~sympy.solvers.ode.single.HomogeneousCoeffBest`, for example. +The function that uses your method will be called ``ode_()``, so the +hint must only use characters that are allowed in a Python function name +(alphanumeric characters and the underscore '``_``' character). Include a +function for every hint, except for ``_Integral`` hints +(:py:meth:`~sympy.solvers.ode.dsolve` takes care of those automatically). +Hint names should be all lowercase, unless a word is commonly capitalized +(such as Integral or Bernoulli). If you have a hint that you do not want to +run with ``all_Integral`` that does not have an ``_Integral`` counterpart (such +as a best hint that would defeat the purpose of ``all_Integral``), you will +need to remove it manually in the :py:meth:`~sympy.solvers.ode.dsolve` code. +See also the :py:meth:`~sympy.solvers.ode.classify_ode` docstring for +guidelines on writing a hint name. + +Determine *in general* how the solutions returned by your method compare with +other methods that can potentially solve the same ODEs. Then, put your hints +in the :py:data:`~sympy.solvers.ode.allhints` tuple in the order that they +should be called. The ordering of this tuple determines which hints are +default. Note that exceptions are ok, because it is easy for the user to +choose individual hints with :py:meth:`~sympy.solvers.ode.dsolve`. In +general, ``_Integral`` variants should go at the end of the list, and +``_best`` variants should go before the various hints they apply to. For +example, the ``undetermined_coefficients`` hint comes before the +``variation_of_parameters`` hint because, even though variation of parameters +is more general than undetermined coefficients, undetermined coefficients +generally returns cleaner results for the ODEs that it can solve than +variation of parameters does, and it does not require integration, so it is +much faster. + +Next, you need to have a match expression or a function that matches the type +of the ODE, which you should put in :py:meth:`~sympy.solvers.ode.classify_ode` +(if the match function is more than just a few lines. It should match the +ODE without solving for it as much as possible, so that +:py:meth:`~sympy.solvers.ode.classify_ode` remains fast and is not hindered by +bugs in solving code. Be sure to consider corner cases. For example, if your +solution method involves dividing by something, make sure you exclude the case +where that division will be 0. + +In most cases, the matching of the ODE will also give you the various parts +that you need to solve it. You should put that in a dictionary (``.match()`` +will do this for you), and add that as ``matching_hints['hint'] = matchdict`` +in the relevant part of :py:meth:`~sympy.solvers.ode.classify_ode`. +:py:meth:`~sympy.solvers.ode.classify_ode` will then send this to +:py:meth:`~sympy.solvers.ode.dsolve`, which will send it to your function as +the ``match`` argument. Your function should be named ``ode_(eq, func, +order, match)`. If you need to send more information, put it in the ``match`` +dictionary. For example, if you had to substitute in a dummy variable in +:py:meth:`~sympy.solvers.ode.classify_ode` to match the ODE, you will need to +pass it to your function using the `match` dict to access it. You can access +the independent variable using ``func.args[0]``, and the dependent variable +(the function you are trying to solve for) as ``func.func``. If, while trying +to solve the ODE, you find that you cannot, raise ``NotImplementedError``. +:py:meth:`~sympy.solvers.ode.dsolve` will catch this error with the ``all`` +meta-hint, rather than causing the whole routine to fail. + +Add a docstring to your function that describes the method employed. Like +with anything else in SymPy, you will need to add a doctest to the docstring, +in addition to real tests in ``test_ode.py``. Try to maintain consistency +with the other hint functions' docstrings. Add your method to the list at the +top of this docstring. Also, add your method to ``ode.rst`` in the +``docs/src`` directory, so that the Sphinx docs will pull its docstring into +the main SymPy documentation. Be sure to make the Sphinx documentation by +running ``make html`` from within the doc directory to verify that the +docstring formats correctly. + +If your solution method involves integrating, use :py:obj:`~.Integral` instead of +:py:meth:`~sympy.core.expr.Expr.integrate`. This allows the user to bypass +hard/slow integration by using the ``_Integral`` variant of your hint. In +most cases, calling :py:meth:`sympy.core.basic.Basic.doit` will integrate your +solution. If this is not the case, you will need to write special code in +:py:meth:`~sympy.solvers.ode.ode._handle_Integral`. Arbitrary constants should be +symbols named ``C1``, ``C2``, and so on. All solution methods should return +an equality instance. If you need an arbitrary number of arbitrary constants, +you can use ``constants = numbered_symbols(prefix='C', cls=Symbol, start=1)``. +If it is possible to solve for the dependent function in a general way, do so. +Otherwise, do as best as you can, but do not call solve in your +``ode_()`` function. :py:meth:`~sympy.solvers.ode.ode.odesimp` will attempt +to solve the solution for you, so you do not need to do that. Lastly, if your +ODE has a common simplification that can be applied to your solutions, you can +add a special case in :py:meth:`~sympy.solvers.ode.ode.odesimp` for it. For +example, solutions returned from the ``1st_homogeneous_coeff`` hints often +have many :obj:`~sympy.functions.elementary.exponential.log` terms, so +:py:meth:`~sympy.solvers.ode.ode.odesimp` calls +:py:meth:`~sympy.simplify.simplify.logcombine` on them (it also helps to write +the arbitrary constant as ``log(C1)`` instead of ``C1`` in this case). Also +consider common ways that you can rearrange your solution to have +:py:meth:`~sympy.solvers.ode.constantsimp` take better advantage of it. It is +better to put simplification in :py:meth:`~sympy.solvers.ode.ode.odesimp` than in +your method, because it can then be turned off with the simplify flag in +:py:meth:`~sympy.solvers.ode.dsolve`. If you have any extraneous +simplification in your function, be sure to only run it using ``if +match.get('simplify', True):``, especially if it can be slow or if it can +reduce the domain of the solution. + +Finally, as with every contribution to SymPy, your method will need to be +tested. Add a test for each method in ``test_ode.py``. Follow the +conventions there, i.e., test the solver using ``dsolve(eq, f(x), +hint=your_hint)``, and also test the solution using +:py:meth:`~sympy.solvers.ode.checkodesol` (you can put these in a separate +tests and skip/XFAIL if it runs too slow/does not work). Be sure to call your +hint specifically in :py:meth:`~sympy.solvers.ode.dsolve`, that way the test +will not be broken simply by the introduction of another matching hint. If your +method works for higher order (>1) ODEs, you will need to run ``sol = +constant_renumber(sol, 'C', 1, order)`` for each solution, where ``order`` is +the order of the ODE. This is because ``constant_renumber`` renumbers the +arbitrary constants by printing order, which is platform dependent. Try to +test every corner case of your solver, including a range of orders if it is a +`n`\th order solver, but if your solver is slow, such as if it involves hard +integration, try to keep the test run time down. + +Feel free to refactor existing hints to avoid duplicating code or creating +inconsistencies. If you can show that your method exactly duplicates an +existing method, including in the simplicity and speed of obtaining the +solutions, then you can remove the old, less general method. The existing +code is tested extensively in ``test_ode.py``, so if anything is broken, one +of those tests will surely fail. + +""" + +from sympy.core import Add, S, Mul, Pow, oo +from sympy.core.containers import Tuple +from sympy.core.expr import AtomicExpr, Expr +from sympy.core.function import (Function, Derivative, AppliedUndef, diff, + expand, expand_mul, Subs) +from sympy.core.multidimensional import vectorize +from sympy.core.numbers import nan, zoo, Number +from sympy.core.relational import Equality, Eq +from sympy.core.sorting import default_sort_key, ordered +from sympy.core.symbol import Symbol, Wild, Dummy, symbols +from sympy.core.sympify import sympify +from sympy.core.traversal import preorder_traversal + +from sympy.logic.boolalg import (BooleanAtom, BooleanTrue, + BooleanFalse) +from sympy.functions import exp, log, sqrt +from sympy.functions.combinatorial.factorials import factorial +from sympy.integrals.integrals import Integral +from sympy.polys import (Poly, terms_gcd, PolynomialError, lcm) +from sympy.polys.polytools import cancel +from sympy.series import Order +from sympy.series.series import series +from sympy.simplify import (collect, logcombine, powsimp, # type: ignore + separatevars, simplify, cse) +from sympy.simplify.radsimp import collect_const +from sympy.solvers import checksol, solve + +from sympy.utilities import numbered_symbols +from sympy.utilities.iterables import uniq, sift, iterable +from sympy.solvers.deutils import _preprocess, ode_order, _desolve + + +#: This is a list of hints in the order that they should be preferred by +#: :py:meth:`~sympy.solvers.ode.classify_ode`. In general, hints earlier in the +#: list should produce simpler solutions than those later in the list (for +#: ODEs that fit both). For now, the order of this list is based on empirical +#: observations by the developers of SymPy. +#: +#: The hint used by :py:meth:`~sympy.solvers.ode.dsolve` for a specific ODE +#: can be overridden (see the docstring). +#: +#: In general, ``_Integral`` hints are grouped at the end of the list, unless +#: there is a method that returns an unevaluable integral most of the time +#: (which go near the end of the list anyway). ``default``, ``all``, +#: ``best``, and ``all_Integral`` meta-hints should not be included in this +#: list, but ``_best`` and ``_Integral`` hints should be included. +allhints = ( + "factorable", + "nth_algebraic", + "separable", + "1st_exact", + "1st_linear", + "Bernoulli", + "1st_rational_riccati", + "Riccati_special_minus2", + "1st_homogeneous_coeff_best", + "1st_homogeneous_coeff_subs_indep_div_dep", + "1st_homogeneous_coeff_subs_dep_div_indep", + "almost_linear", + "linear_coefficients", + "separable_reduced", + "1st_power_series", + "lie_group", + "nth_linear_constant_coeff_homogeneous", + "nth_linear_euler_eq_homogeneous", + "nth_linear_constant_coeff_undetermined_coefficients", + "nth_linear_euler_eq_nonhomogeneous_undetermined_coefficients", + "nth_linear_constant_coeff_variation_of_parameters", + "nth_linear_euler_eq_nonhomogeneous_variation_of_parameters", + "Liouville", + "2nd_linear_airy", + "2nd_linear_bessel", + "2nd_hypergeometric", + "2nd_hypergeometric_Integral", + "nth_order_reducible", + "2nd_power_series_ordinary", + "2nd_power_series_regular", + "nth_algebraic_Integral", + "separable_Integral", + "1st_exact_Integral", + "1st_linear_Integral", + "Bernoulli_Integral", + "1st_homogeneous_coeff_subs_indep_div_dep_Integral", + "1st_homogeneous_coeff_subs_dep_div_indep_Integral", + "almost_linear_Integral", + "linear_coefficients_Integral", + "separable_reduced_Integral", + "nth_linear_constant_coeff_variation_of_parameters_Integral", + "nth_linear_euler_eq_nonhomogeneous_variation_of_parameters_Integral", + "Liouville_Integral", + "2nd_nonlinear_autonomous_conserved", + "2nd_nonlinear_autonomous_conserved_Integral", + ) + + + +def get_numbered_constants(eq, num=1, start=1, prefix='C'): + """ + Returns a list of constants that do not occur + in eq already. + """ + + ncs = iter_numbered_constants(eq, start, prefix) + Cs = [next(ncs) for i in range(num)] + return (Cs[0] if num == 1 else tuple(Cs)) + + +def iter_numbered_constants(eq, start=1, prefix='C'): + """ + Returns an iterator of constants that do not occur + in eq already. + """ + + if isinstance(eq, (Expr, Eq)): + eq = [eq] + elif not iterable(eq): + raise ValueError("Expected Expr or iterable but got %s" % eq) + + atom_set = set().union(*[i.free_symbols for i in eq]) + func_set = set().union(*[i.atoms(Function) for i in eq]) + if func_set: + atom_set |= {Symbol(str(f.func)) for f in func_set} + return numbered_symbols(start=start, prefix=prefix, exclude=atom_set) + + +def dsolve(eq, func=None, hint="default", simplify=True, + ics= None, xi=None, eta=None, x0=0, n=6, **kwargs): + r""" + Solves any (supported) kind of ordinary differential equation and + system of ordinary differential equations. + + For single ordinary differential equation + ========================================= + + It is classified under this when number of equation in ``eq`` is one. + **Usage** + + ``dsolve(eq, f(x), hint)`` -> Solve ordinary differential equation + ``eq`` for function ``f(x)``, using method ``hint``. + + **Details** + + ``eq`` can be any supported ordinary differential equation (see the + :py:mod:`~sympy.solvers.ode` docstring for supported methods). + This can either be an :py:class:`~sympy.core.relational.Equality`, + or an expression, which is assumed to be equal to ``0``. + + ``f(x)`` is a function of one variable whose derivatives in that + variable make up the ordinary differential equation ``eq``. In + many cases it is not necessary to provide this; it will be + autodetected (and an error raised if it could not be detected). + + ``hint`` is the solving method that you want dsolve to use. Use + ``classify_ode(eq, f(x))`` to get all of the possible hints for an + ODE. The default hint, ``default``, will use whatever hint is + returned first by :py:meth:`~sympy.solvers.ode.classify_ode`. See + Hints below for more options that you can use for hint. + + ``simplify`` enables simplification by + :py:meth:`~sympy.solvers.ode.ode.odesimp`. See its docstring for more + information. Turn this off, for example, to disable solving of + solutions for ``func`` or simplification of arbitrary constants. + It will still integrate with this hint. Note that the solution may + contain more arbitrary constants than the order of the ODE with + this option enabled. + + ``xi`` and ``eta`` are the infinitesimal functions of an ordinary + differential equation. They are the infinitesimals of the Lie group + of point transformations for which the differential equation is + invariant. The user can specify values for the infinitesimals. If + nothing is specified, ``xi`` and ``eta`` are calculated using + :py:meth:`~sympy.solvers.ode.infinitesimals` with the help of various + heuristics. + + ``ics`` is the set of initial/boundary conditions for the differential equation. + It should be given in the form of ``{f(x0): x1, f(x).diff(x).subs(x, x2): + x3}`` and so on. For power series solutions, if no initial + conditions are specified ``f(0)`` is assumed to be ``C0`` and the power + series solution is calculated about 0. + + ``x0`` is the point about which the power series solution of a differential + equation is to be evaluated. + + ``n`` gives the exponent of the dependent variable up to which the power series + solution of a differential equation is to be evaluated. + + **Hints** + + Aside from the various solving methods, there are also some meta-hints + that you can pass to :py:meth:`~sympy.solvers.ode.dsolve`: + + ``default``: + This uses whatever hint is returned first by + :py:meth:`~sympy.solvers.ode.classify_ode`. This is the + default argument to :py:meth:`~sympy.solvers.ode.dsolve`. + + ``all``: + To make :py:meth:`~sympy.solvers.ode.dsolve` apply all + relevant classification hints, use ``dsolve(ODE, func, + hint="all")``. This will return a dictionary of + ``hint:solution`` terms. If a hint causes dsolve to raise the + ``NotImplementedError``, value of that hint's key will be the + exception object raised. The dictionary will also include + some special keys: + + - ``order``: The order of the ODE. See also + :py:meth:`~sympy.solvers.deutils.ode_order` in + ``deutils.py``. + - ``best``: The simplest hint; what would be returned by + ``best`` below. + - ``best_hint``: The hint that would produce the solution + given by ``best``. If more than one hint produces the best + solution, the first one in the tuple returned by + :py:meth:`~sympy.solvers.ode.classify_ode` is chosen. + - ``default``: The solution that would be returned by default. + This is the one produced by the hint that appears first in + the tuple returned by + :py:meth:`~sympy.solvers.ode.classify_ode`. + + ``all_Integral``: + This is the same as ``all``, except if a hint also has a + corresponding ``_Integral`` hint, it only returns the + ``_Integral`` hint. This is useful if ``all`` causes + :py:meth:`~sympy.solvers.ode.dsolve` to hang because of a + difficult or impossible integral. This meta-hint will also be + much faster than ``all``, because + :py:meth:`~sympy.core.expr.Expr.integrate` is an expensive + routine. + + ``best``: + To have :py:meth:`~sympy.solvers.ode.dsolve` try all methods + and return the simplest one. This takes into account whether + the solution is solvable in the function, whether it contains + any Integral classes (i.e. unevaluatable integrals), and + which one is the shortest in size. + + See also the :py:meth:`~sympy.solvers.ode.classify_ode` docstring for + more info on hints, and the :py:mod:`~sympy.solvers.ode` docstring for + a list of all supported hints. + + **Tips** + + - You can declare the derivative of an unknown function this way: + + >>> from sympy import Function, Derivative + >>> from sympy.abc import x # x is the independent variable + >>> f = Function("f")(x) # f is a function of x + >>> # f_ will be the derivative of f with respect to x + >>> f_ = Derivative(f, x) + + - See ``test_ode.py`` for many tests, which serves also as a set of + examples for how to use :py:meth:`~sympy.solvers.ode.dsolve`. + - :py:meth:`~sympy.solvers.ode.dsolve` always returns an + :py:class:`~sympy.core.relational.Equality` class (except for the + case when the hint is ``all`` or ``all_Integral``). If possible, it + solves the solution explicitly for the function being solved for. + Otherwise, it returns an implicit solution. + - Arbitrary constants are symbols named ``C1``, ``C2``, and so on. + - Because all solutions should be mathematically equivalent, some + hints may return the exact same result for an ODE. Often, though, + two different hints will return the same solution formatted + differently. The two should be equivalent. Also note that sometimes + the values of the arbitrary constants in two different solutions may + not be the same, because one constant may have "absorbed" other + constants into it. + - Do ``help(ode.ode_)`` to get help more information on a + specific hint, where ```` is the name of a hint without + ``_Integral``. + + For system of ordinary differential equations + ============================================= + + **Usage** + ``dsolve(eq, func)`` -> Solve a system of ordinary differential + equations ``eq`` for ``func`` being list of functions including + `x(t)`, `y(t)`, `z(t)` where number of functions in the list depends + upon the number of equations provided in ``eq``. + + **Details** + + ``eq`` can be any supported system of ordinary differential equations + This can either be an :py:class:`~sympy.core.relational.Equality`, + or an expression, which is assumed to be equal to ``0``. + + ``func`` holds ``x(t)`` and ``y(t)`` being functions of one variable which + together with some of their derivatives make up the system of ordinary + differential equation ``eq``. It is not necessary to provide this; it + will be autodetected (and an error raised if it could not be detected). + + **Hints** + + The hints are formed by parameters returned by classify_sysode, combining + them give hints name used later for forming method name. + + Examples + ======== + + >>> from sympy import Function, dsolve, Eq, Derivative, sin, cos, symbols + >>> from sympy.abc import x + >>> f = Function('f') + >>> dsolve(Derivative(f(x), x, x) + 9*f(x), f(x)) + Eq(f(x), C1*sin(3*x) + C2*cos(3*x)) + + >>> eq = sin(x)*cos(f(x)) + cos(x)*sin(f(x))*f(x).diff(x) + >>> dsolve(eq, hint='1st_exact') + [Eq(f(x), -acos(C1/cos(x)) + 2*pi), Eq(f(x), acos(C1/cos(x)))] + >>> dsolve(eq, hint='almost_linear') + [Eq(f(x), -acos(C1/cos(x)) + 2*pi), Eq(f(x), acos(C1/cos(x)))] + >>> t = symbols('t') + >>> x, y = symbols('x, y', cls=Function) + >>> eq = (Eq(Derivative(x(t),t), 12*t*x(t) + 8*y(t)), Eq(Derivative(y(t),t), 21*x(t) + 7*t*y(t))) + >>> dsolve(eq) + [Eq(x(t), C1*x0(t) + C2*x0(t)*Integral(8*exp(Integral(7*t, t))*exp(Integral(12*t, t))/x0(t)**2, t)), + Eq(y(t), C1*y0(t) + C2*(y0(t)*Integral(8*exp(Integral(7*t, t))*exp(Integral(12*t, t))/x0(t)**2, t) + + exp(Integral(7*t, t))*exp(Integral(12*t, t))/x0(t)))] + >>> eq = (Eq(Derivative(x(t),t),x(t)*y(t)*sin(t)), Eq(Derivative(y(t),t),y(t)**2*sin(t))) + >>> dsolve(eq) + {Eq(x(t), -exp(C1)/(C2*exp(C1) - cos(t))), Eq(y(t), -1/(C1 - cos(t)))} + """ + if iterable(eq): + from sympy.solvers.ode.systems import dsolve_system + + # This may have to be changed in future + # when we have weakly and strongly + # connected components. This have to + # changed to show the systems that haven't + # been solved. + try: + sol = dsolve_system(eq, funcs=func, ics=ics, doit=True) + return sol[0] if len(sol) == 1 else sol + except NotImplementedError: + pass + + match = classify_sysode(eq, func) + + eq = match['eq'] + order = match['order'] + func = match['func'] + t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] + + # keep highest order term coefficient positive + for i in range(len(eq)): + for func_ in func: + if isinstance(func_, list): + pass + else: + if eq[i].coeff(diff(func[i],t,ode_order(eq[i], func[i]))).is_negative: + eq[i] = -eq[i] + match['eq'] = eq + if len(set(order.values()))!=1: + raise ValueError("It solves only those systems of equations whose orders are equal") + match['order'] = list(order.values())[0] + def recur_len(l): + return sum(recur_len(item) if isinstance(item,list) else 1 for item in l) + if recur_len(func) != len(eq): + raise ValueError("dsolve() and classify_sysode() work with " + "number of functions being equal to number of equations") + if match['type_of_equation'] is None: + raise NotImplementedError + else: + if match['is_linear'] == True: + solvefunc = globals()['sysode_linear_%(no_of_equation)seq_order%(order)s' % match] + else: + solvefunc = globals()['sysode_nonlinear_%(no_of_equation)seq_order%(order)s' % match] + sols = solvefunc(match) + if ics: + constants = Tuple(*sols).free_symbols - Tuple(*eq).free_symbols + solved_constants = solve_ics(sols, func, constants, ics) + return [sol.subs(solved_constants) for sol in sols] + return sols + else: + given_hint = hint # hint given by the user + + # See the docstring of _desolve for more details. + hints = _desolve(eq, func=func, + hint=hint, simplify=True, xi=xi, eta=eta, type='ode', ics=ics, + x0=x0, n=n, **kwargs) + eq = hints.pop('eq', eq) + all_ = hints.pop('all', False) + if all_: + retdict = {} + failed_hints = {} + gethints = classify_ode(eq, dict=True, hint='all') + orderedhints = gethints['ordered_hints'] + for hint in hints: + try: + rv = _helper_simplify(eq, hint, hints[hint], simplify) + except NotImplementedError as detail: + failed_hints[hint] = detail + else: + retdict[hint] = rv + func = hints[hint]['func'] + + retdict['best'] = min(list(retdict.values()), key=lambda x: + ode_sol_simplicity(x, func, trysolving=not simplify)) + if given_hint == 'best': + return retdict['best'] + for i in orderedhints: + if retdict['best'] == retdict.get(i, None): + retdict['best_hint'] = i + break + retdict['default'] = gethints['default'] + retdict['order'] = gethints['order'] + retdict.update(failed_hints) + return retdict + + else: + # The key 'hint' stores the hint needed to be solved for. + hint = hints['hint'] + return _helper_simplify(eq, hint, hints, simplify, ics=ics) + + +def _helper_simplify(eq, hint, match, simplify=True, ics=None, **kwargs): + r""" + Helper function of dsolve that calls the respective + :py:mod:`~sympy.solvers.ode` functions to solve for the ordinary + differential equations. This minimizes the computation in calling + :py:meth:`~sympy.solvers.deutils._desolve` multiple times. + """ + r = match + func = r['func'] + order = r['order'] + match = r[hint] + + if isinstance(match, SingleODESolver): + solvefunc = match + elif hint.endswith('_Integral'): + solvefunc = globals()['ode_' + hint[:-len('_Integral')]] + else: + solvefunc = globals()['ode_' + hint] + + free = eq.free_symbols + cons = lambda s: s.free_symbols.difference(free) + + if simplify: + # odesimp() will attempt to integrate, if necessary, apply constantsimp(), + # attempt to solve for func, and apply any other hint specific + # simplifications + if isinstance(solvefunc, SingleODESolver): + sols = solvefunc.get_general_solution() + else: + sols = solvefunc(eq, func, order, match) + if iterable(sols): + rv = [] + for s in sols: + simp = odesimp(eq, s, func, hint) + if iterable(simp): + rv.extend(simp) + else: + rv.append(simp) + else: + rv = odesimp(eq, sols, func, hint) + else: + # We still want to integrate (you can disable it separately with the hint) + if isinstance(solvefunc, SingleODESolver): + exprs = solvefunc.get_general_solution(simplify=False) + else: + match['simplify'] = False # Some hints can take advantage of this option + exprs = solvefunc(eq, func, order, match) + if isinstance(exprs, list): + rv = [_handle_Integral(expr, func, hint) for expr in exprs] + else: + rv = _handle_Integral(exprs, func, hint) + + if isinstance(rv, list): + assert all(isinstance(i, Eq) for i in rv), rv # if not => internal error + if simplify: + rv = _remove_redundant_solutions(eq, rv, order, func.args[0]) + if len(rv) == 1: + rv = rv[0] + if ics and 'power_series' not in hint: + if isinstance(rv, (Expr, Eq)): + solved_constants = solve_ics([rv], [r['func']], cons(rv), ics) + rv = rv.subs(solved_constants) + else: + rv1 = [] + for s in rv: + try: + solved_constants = solve_ics([s], [r['func']], cons(s), ics) + except ValueError: + continue + rv1.append(s.subs(solved_constants)) + if len(rv1) == 1: + return rv1[0] + rv = rv1 + return rv + + +def solve_ics(sols, funcs, constants, ics): + """ + Solve for the constants given initial conditions + + ``sols`` is a list of solutions. + + ``funcs`` is a list of functions. + + ``constants`` is a list of constants. + + ``ics`` is the set of initial/boundary conditions for the differential + equation. It should be given in the form of ``{f(x0): x1, + f(x).diff(x).subs(x, x2): x3}`` and so on. + + Returns a dictionary mapping constants to values. + ``solution.subs(constants)`` will replace the constants in ``solution``. + + Example + ======= + >>> # From dsolve(f(x).diff(x) - f(x), f(x)) + >>> from sympy import symbols, Eq, exp, Function + >>> from sympy.solvers.ode.ode import solve_ics + >>> f = Function('f') + >>> x, C1 = symbols('x C1') + >>> sols = [Eq(f(x), C1*exp(x))] + >>> funcs = [f(x)] + >>> constants = [C1] + >>> ics = {f(0): 2} + >>> solved_constants = solve_ics(sols, funcs, constants, ics) + >>> solved_constants + {C1: 2} + >>> sols[0].subs(solved_constants) + Eq(f(x), 2*exp(x)) + + """ + # Assume ics are of the form f(x0): value or Subs(diff(f(x), x, n), (x, + # x0)): value (currently checked by classify_ode). To solve, replace x + # with x0, f(x0) with value, then solve for constants. For f^(n)(x0), + # differentiate the solution n times, so that f^(n)(x) appears. + x = funcs[0].args[0] + diff_sols = [] + subs_sols = [] + diff_variables = set() + for funcarg, value in ics.items(): + if isinstance(funcarg, AppliedUndef): + x0 = funcarg.args[0] + matching_func = [f for f in funcs if f.func == funcarg.func][0] + S = sols + elif isinstance(funcarg, (Subs, Derivative)): + if isinstance(funcarg, Subs): + # Make sure it stays a subs. Otherwise subs below will produce + # a different looking term. + funcarg = funcarg.doit() + if isinstance(funcarg, Subs): + deriv = funcarg.expr + x0 = funcarg.point[0] + variables = funcarg.expr.variables + matching_func = deriv + elif isinstance(funcarg, Derivative): + deriv = funcarg + x0 = funcarg.variables[0] + variables = (x,)*len(funcarg.variables) + matching_func = deriv.subs(x0, x) + for sol in sols: + if sol.has(deriv.expr.func): + diff_sols.append(Eq(sol.lhs.diff(*variables), sol.rhs.diff(*variables))) + diff_variables.add(variables) + S = diff_sols + else: + raise NotImplementedError("Unrecognized initial condition") + + for sol in S: + if sol.has(matching_func): + sol2 = sol + sol2 = sol2.subs(x, x0) + sol2 = sol2.subs(funcarg, value) + # This check is necessary because of issue #15724 + if not isinstance(sol2, BooleanAtom) or not subs_sols: + subs_sols = [s for s in subs_sols if not isinstance(s, BooleanAtom)] + subs_sols.append(sol2) + + # TODO: Use solveset here + try: + solved_constants = solve(subs_sols, constants, dict=True) + except NotImplementedError: + solved_constants = [] + + # XXX: We can't differentiate between the solution not existing because of + # invalid initial conditions, and not existing because solve is not smart + # enough. If we could use solveset, this might be improvable, but for now, + # we use NotImplementedError in this case. + if not solved_constants: + raise ValueError("Couldn't solve for initial conditions") + + if solved_constants == True: + raise ValueError("Initial conditions did not produce any solutions for constants. Perhaps they are degenerate.") + + if len(solved_constants) > 1: + raise NotImplementedError("Initial conditions produced too many solutions for constants") + + return solved_constants[0] + +def classify_ode(eq, func=None, dict=False, ics=None, *, prep=True, xi=None, eta=None, n=None, **kwargs): + r""" + Returns a tuple of possible :py:meth:`~sympy.solvers.ode.dsolve` + classifications for an ODE. + + The tuple is ordered so that first item is the classification that + :py:meth:`~sympy.solvers.ode.dsolve` uses to solve the ODE by default. In + general, classifications at the near the beginning of the list will + produce better solutions faster than those near the end, thought there are + always exceptions. To make :py:meth:`~sympy.solvers.ode.dsolve` use a + different classification, use ``dsolve(ODE, func, + hint=)``. See also the + :py:meth:`~sympy.solvers.ode.dsolve` docstring for different meta-hints + you can use. + + If ``dict`` is true, :py:meth:`~sympy.solvers.ode.classify_ode` will + return a dictionary of ``hint:match`` expression terms. This is intended + for internal use by :py:meth:`~sympy.solvers.ode.dsolve`. Note that + because dictionaries are ordered arbitrarily, this will most likely not be + in the same order as the tuple. + + You can get help on different hints by executing + ``help(ode.ode_hintname)``, where ``hintname`` is the name of the hint + without ``_Integral``. + + See :py:data:`~sympy.solvers.ode.allhints` or the + :py:mod:`~sympy.solvers.ode` docstring for a list of all supported hints + that can be returned from :py:meth:`~sympy.solvers.ode.classify_ode`. + + Notes + ===== + + These are remarks on hint names. + + ``_Integral`` + + If a classification has ``_Integral`` at the end, it will return the + expression with an unevaluated :py:class:`~.Integral` + class in it. Note that a hint may do this anyway if + :py:meth:`~sympy.core.expr.Expr.integrate` cannot do the integral, + though just using an ``_Integral`` will do so much faster. Indeed, an + ``_Integral`` hint will always be faster than its corresponding hint + without ``_Integral`` because + :py:meth:`~sympy.core.expr.Expr.integrate` is an expensive routine. + If :py:meth:`~sympy.solvers.ode.dsolve` hangs, it is probably because + :py:meth:`~sympy.core.expr.Expr.integrate` is hanging on a tough or + impossible integral. Try using an ``_Integral`` hint or + ``all_Integral`` to get it return something. + + Note that some hints do not have ``_Integral`` counterparts. This is + because :py:func:`~sympy.integrals.integrals.integrate` is not used in + solving the ODE for those method. For example, `n`\th order linear + homogeneous ODEs with constant coefficients do not require integration + to solve, so there is no + ``nth_linear_homogeneous_constant_coeff_Integrate`` hint. You can + easily evaluate any unevaluated + :py:class:`~sympy.integrals.integrals.Integral`\s in an expression by + doing ``expr.doit()``. + + Ordinals + + Some hints contain an ordinal such as ``1st_linear``. This is to help + differentiate them from other hints, as well as from other methods + that may not be implemented yet. If a hint has ``nth`` in it, such as + the ``nth_linear`` hints, this means that the method used to applies + to ODEs of any order. + + ``indep`` and ``dep`` + + Some hints contain the words ``indep`` or ``dep``. These reference + the independent variable and the dependent function, respectively. For + example, if an ODE is in terms of `f(x)`, then ``indep`` will refer to + `x` and ``dep`` will refer to `f`. + + ``subs`` + + If a hints has the word ``subs`` in it, it means that the ODE is solved + by substituting the expression given after the word ``subs`` for a + single dummy variable. This is usually in terms of ``indep`` and + ``dep`` as above. The substituted expression will be written only in + characters allowed for names of Python objects, meaning operators will + be spelled out. For example, ``indep``/``dep`` will be written as + ``indep_div_dep``. + + ``coeff`` + + The word ``coeff`` in a hint refers to the coefficients of something + in the ODE, usually of the derivative terms. See the docstring for + the individual methods for more info (``help(ode)``). This is + contrast to ``coefficients``, as in ``undetermined_coefficients``, + which refers to the common name of a method. + + ``_best`` + + Methods that have more than one fundamental way to solve will have a + hint for each sub-method and a ``_best`` meta-classification. This + will evaluate all hints and return the best, using the same + considerations as the normal ``best`` meta-hint. + + + Examples + ======== + + >>> from sympy import Function, classify_ode, Eq + >>> from sympy.abc import x + >>> f = Function('f') + >>> classify_ode(Eq(f(x).diff(x), 0), f(x)) + ('nth_algebraic', + 'separable', + '1st_exact', + '1st_linear', + 'Bernoulli', + '1st_homogeneous_coeff_best', + '1st_homogeneous_coeff_subs_indep_div_dep', + '1st_homogeneous_coeff_subs_dep_div_indep', + '1st_power_series', 'lie_group', 'nth_linear_constant_coeff_homogeneous', + 'nth_linear_euler_eq_homogeneous', + 'nth_algebraic_Integral', 'separable_Integral', '1st_exact_Integral', + '1st_linear_Integral', 'Bernoulli_Integral', + '1st_homogeneous_coeff_subs_indep_div_dep_Integral', + '1st_homogeneous_coeff_subs_dep_div_indep_Integral') + >>> classify_ode(f(x).diff(x, 2) + 3*f(x).diff(x) + 2*f(x) - 4) + ('factorable', 'nth_linear_constant_coeff_undetermined_coefficients', + 'nth_linear_constant_coeff_variation_of_parameters', + 'nth_linear_constant_coeff_variation_of_parameters_Integral') + + """ + ics = sympify(ics) + + if func and len(func.args) != 1: + raise ValueError("dsolve() and classify_ode() only " + "work with functions of one variable, not %s" % func) + + if isinstance(eq, Equality): + eq = eq.lhs - eq.rhs + + # Some methods want the unprocessed equation + eq_orig = eq + + if prep or func is None: + eq, func_ = _preprocess(eq, func) + if func is None: + func = func_ + x = func.args[0] + f = func.func + y = Dummy('y') + terms = 5 if n is None else n + + order = ode_order(eq, f(x)) + # hint:matchdict or hint:(tuple of matchdicts) + # Also will contain "default": and "order":order items. + matching_hints = {"order": order} + + df = f(x).diff(x) + a = Wild('a', exclude=[f(x)]) + d = Wild('d', exclude=[df, f(x).diff(x, 2)]) + e = Wild('e', exclude=[df]) + n = Wild('n', exclude=[x, f(x), df]) + c1 = Wild('c1', exclude=[x]) + a3 = Wild('a3', exclude=[f(x), df, f(x).diff(x, 2)]) + b3 = Wild('b3', exclude=[f(x), df, f(x).diff(x, 2)]) + c3 = Wild('c3', exclude=[f(x), df, f(x).diff(x, 2)]) + boundary = {} # Used to extract initial conditions + C1 = Symbol("C1") + + # Preprocessing to get the initial conditions out + if ics is not None: + for funcarg in ics: + # Separating derivatives + if isinstance(funcarg, (Subs, Derivative)): + # f(x).diff(x).subs(x, 0) is a Subs, but f(x).diff(x).subs(x, + # y) is a Derivative + if isinstance(funcarg, Subs): + deriv = funcarg.expr + old = funcarg.variables[0] + new = funcarg.point[0] + elif isinstance(funcarg, Derivative): + deriv = funcarg + # No information on this. Just assume it was x + old = x + new = funcarg.variables[0] + + if (isinstance(deriv, Derivative) and isinstance(deriv.args[0], + AppliedUndef) and deriv.args[0].func == f and + len(deriv.args[0].args) == 1 and old == x and not + new.has(x) and all(i == deriv.variables[0] for i in + deriv.variables) and x not in ics[funcarg].free_symbols): + + dorder = ode_order(deriv, x) + temp = 'f' + str(dorder) + boundary.update({temp: new, temp + 'val': ics[funcarg]}) + else: + raise ValueError("Invalid boundary conditions for Derivatives") + + + # Separating functions + elif isinstance(funcarg, AppliedUndef): + if (funcarg.func == f and len(funcarg.args) == 1 and + not funcarg.args[0].has(x) and x not in ics[funcarg].free_symbols): + boundary.update({'f0': funcarg.args[0], 'f0val': ics[funcarg]}) + else: + raise ValueError("Invalid boundary conditions for Function") + + else: + raise ValueError("Enter boundary conditions of the form ics={f(point): value, f(x).diff(x, order).subs(x, point): value}") + + ode = SingleODEProblem(eq_orig, func, x, prep=prep, xi=xi, eta=eta) + user_hint = kwargs.get('hint', 'default') + # Used when dsolve is called without an explicit hint. + # We exit early to return the first valid match + early_exit = (user_hint=='default') + if user_hint.endswith('_Integral'): + user_hint = user_hint[:-len('_Integral')] + user_map = solver_map + # An explicit hint has been given to dsolve + # Skip matching code for other hints + if user_hint not in ['default', 'all', 'all_Integral', 'best'] and user_hint in solver_map: + user_map = {user_hint: solver_map[user_hint]} + + for hint in user_map: + solver = user_map[hint](ode) + if solver.matches(): + matching_hints[hint] = solver + if user_map[hint].has_integral: + matching_hints[hint + "_Integral"] = solver + if dict and early_exit: + matching_hints["default"] = hint + return matching_hints + + eq = expand(eq) + # Precondition to try remove f(x) from highest order derivative + reduced_eq = None + if eq.is_Add: + deriv_coef = eq.coeff(f(x).diff(x, order)) + if deriv_coef not in (1, 0): + r = deriv_coef.match(a*f(x)**c1) + if r and r[c1]: + den = f(x)**r[c1] + reduced_eq = Add(*[arg/den for arg in eq.args]) + if not reduced_eq: + reduced_eq = eq + + if order == 1: + + # NON-REDUCED FORM OF EQUATION matches + r = collect(eq, df, exact=True).match(d + e * df) + if r: + r['d'] = d + r['e'] = e + r['y'] = y + r[d] = r[d].subs(f(x), y) + r[e] = r[e].subs(f(x), y) + + # FIRST ORDER POWER SERIES WHICH NEEDS INITIAL CONDITIONS + # TODO: Hint first order series should match only if d/e is analytic. + # For now, only d/e and (d/e).diff(arg) is checked for existence at + # at a given point. + # This is currently done internally in ode_1st_power_series. + point = boundary.get('f0', 0) + value = boundary.get('f0val', C1) + check = cancel(r[d]/r[e]) + check1 = check.subs({x: point, y: value}) + if not check1.has(oo) and not check1.has(zoo) and \ + not check1.has(nan) and not check1.has(-oo): + check2 = (check1.diff(x)).subs({x: point, y: value}) + if not check2.has(oo) and not check2.has(zoo) and \ + not check2.has(nan) and not check2.has(-oo): + rseries = r.copy() + rseries.update({'terms': terms, 'f0': point, 'f0val': value}) + matching_hints["1st_power_series"] = rseries + + elif order == 2: + # Homogeneous second order differential equation of the form + # a3*f(x).diff(x, 2) + b3*f(x).diff(x) + c3 + # It has a definite power series solution at point x0 if, b3/a3 and c3/a3 + # are analytic at x0. + deq = a3*(f(x).diff(x, 2)) + b3*df + c3*f(x) + r = collect(reduced_eq, + [f(x).diff(x, 2), f(x).diff(x), f(x)]).match(deq) + ordinary = False + if r: + if not all(r[key].is_polynomial() for key in r): + n, d = reduced_eq.as_numer_denom() + reduced_eq = expand(n) + r = collect(reduced_eq, + [f(x).diff(x, 2), f(x).diff(x), f(x)]).match(deq) + if r and r[a3] != 0: + p = cancel(r[b3]/r[a3]) # Used below + q = cancel(r[c3]/r[a3]) # Used below + point = kwargs.get('x0', 0) + check = p.subs(x, point) + if not check.has(oo, nan, zoo, -oo): + check = q.subs(x, point) + if not check.has(oo, nan, zoo, -oo): + ordinary = True + r.update({'a3': a3, 'b3': b3, 'c3': c3, 'x0': point, 'terms': terms}) + matching_hints["2nd_power_series_ordinary"] = r + + # Checking if the differential equation has a regular singular point + # at x0. It has a regular singular point at x0, if (b3/a3)*(x - x0) + # and (c3/a3)*((x - x0)**2) are analytic at x0. + if not ordinary: + p = cancel((x - point)*p) + check = p.subs(x, point) + if not check.has(oo, nan, zoo, -oo): + q = cancel(((x - point)**2)*q) + check = q.subs(x, point) + if not check.has(oo, nan, zoo, -oo): + coeff_dict = {'p': p, 'q': q, 'x0': point, 'terms': terms} + matching_hints["2nd_power_series_regular"] = coeff_dict + + + # Order keys based on allhints. + retlist = [i for i in allhints if i in matching_hints] + if dict: + # Dictionaries are ordered arbitrarily, so make note of which + # hint would come first for dsolve(). Use an ordered dict in Py 3. + matching_hints["default"] = retlist[0] if retlist else None + matching_hints["ordered_hints"] = tuple(retlist) + return matching_hints + else: + return tuple(retlist) + + +def classify_sysode(eq, funcs=None, **kwargs): + r""" + Returns a dictionary of parameter names and values that define the system + of ordinary differential equations in ``eq``. + The parameters are further used in + :py:meth:`~sympy.solvers.ode.dsolve` for solving that system. + + Some parameter names and values are: + + 'is_linear' (boolean), which tells whether the given system is linear. + Note that "linear" here refers to the operator: terms such as ``x*diff(x,t)`` are + nonlinear, whereas terms like ``sin(t)*diff(x,t)`` are still linear operators. + + 'func' (list) contains the :py:class:`~sympy.core.function.Function`s that + appear with a derivative in the ODE, i.e. those that we are trying to solve + the ODE for. + + 'order' (dict) with the maximum derivative for each element of the 'func' + parameter. + + 'func_coeff' (dict or Matrix) with the coefficient for each triple ``(equation number, + function, order)```. The coefficients are those subexpressions that do not + appear in 'func', and hence can be considered constant for purposes of ODE + solving. The value of this parameter can also be a Matrix if the system of ODEs are + linear first order of the form X' = AX where X is the vector of dependent variables. + Here, this function returns the coefficient matrix A. + + 'eq' (list) with the equations from ``eq``, sympified and transformed into + expressions (we are solving for these expressions to be zero). + + 'no_of_equations' (int) is the number of equations (same as ``len(eq)``). + + 'type_of_equation' (string) is an internal classification of the type of + ODE. + + 'is_constant' (boolean), which tells if the system of ODEs is constant coefficient + or not. This key is temporary addition for now and is in the match dict only when + the system of ODEs is linear first order constant coefficient homogeneous. So, this + key's value is True for now if it is available else it does not exist. + + 'is_homogeneous' (boolean), which tells if the system of ODEs is homogeneous. Like the + key 'is_constant', this key is a temporary addition and it is True since this key value + is available only when the system is linear first order constant coefficient homogeneous. + + References + ========== + -https://eqworld.ipmnet.ru/en/solutions/sysode/sode-toc1.htm + -A. D. Polyanin and A. V. Manzhirov, Handbook of Mathematics for Engineers and Scientists + + Examples + ======== + + >>> from sympy import Function, Eq, symbols, diff + >>> from sympy.solvers.ode.ode import classify_sysode + >>> from sympy.abc import t + >>> f, x, y = symbols('f, x, y', cls=Function) + >>> k, l, m, n = symbols('k, l, m, n', Integer=True) + >>> x1 = diff(x(t), t) ; y1 = diff(y(t), t) + >>> x2 = diff(x(t), t, t) ; y2 = diff(y(t), t, t) + >>> eq = (Eq(x1, 12*x(t) - 6*y(t)), Eq(y1, 11*x(t) + 3*y(t))) + >>> classify_sysode(eq) + {'eq': [-12*x(t) + 6*y(t) + Derivative(x(t), t), -11*x(t) - 3*y(t) + Derivative(y(t), t)], 'func': [x(t), y(t)], + 'func_coeff': {(0, x(t), 0): -12, (0, x(t), 1): 1, (0, y(t), 0): 6, (0, y(t), 1): 0, (1, x(t), 0): -11, (1, x(t), 1): 0, (1, y(t), 0): -3, (1, y(t), 1): 1}, 'is_linear': True, 'no_of_equation': 2, 'order': {x(t): 1, y(t): 1}, 'type_of_equation': None} + >>> eq = (Eq(diff(x(t),t), 5*t*x(t) + t**2*y(t) + 2), Eq(diff(y(t),t), -t**2*x(t) + 5*t*y(t))) + >>> classify_sysode(eq) + {'eq': [-t**2*y(t) - 5*t*x(t) + Derivative(x(t), t) - 2, t**2*x(t) - 5*t*y(t) + Derivative(y(t), t)], + 'func': [x(t), y(t)], 'func_coeff': {(0, x(t), 0): -5*t, (0, x(t), 1): 1, (0, y(t), 0): -t**2, (0, y(t), 1): 0, + (1, x(t), 0): t**2, (1, x(t), 1): 0, (1, y(t), 0): -5*t, (1, y(t), 1): 1}, 'is_linear': True, 'no_of_equation': 2, + 'order': {x(t): 1, y(t): 1}, 'type_of_equation': None} + + """ + + # Sympify equations and convert iterables of equations into + # a list of equations + def _sympify(eq): + return list(map(sympify, eq if iterable(eq) else [eq])) + + eq, funcs = (_sympify(w) for w in [eq, funcs]) + for i, fi in enumerate(eq): + if isinstance(fi, Equality): + eq[i] = fi.lhs - fi.rhs + + t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] + matching_hints = {"no_of_equation":i+1} + matching_hints['eq'] = eq + if i==0: + raise ValueError("classify_sysode() works for systems of ODEs. " + "For scalar ODEs, classify_ode should be used") + + # find all the functions if not given + order = {} + if funcs==[None]: + funcs = _extract_funcs(eq) + + funcs = list(set(funcs)) + if len(funcs) != len(eq): + raise ValueError("Number of functions given is not equal to the number of equations %s" % funcs) + + # This logic of list of lists in funcs to + # be replaced later. + func_dict = {} + for func in funcs: + if not order.get(func, False): + max_order = 0 + for i, eqs_ in enumerate(eq): + order_ = ode_order(eqs_,func) + if max_order < order_: + max_order = order_ + eq_no = i + if eq_no in func_dict: + func_dict[eq_no] = [func_dict[eq_no], func] + else: + func_dict[eq_no] = func + order[func] = max_order + + funcs = [func_dict[i] for i in range(len(func_dict))] + matching_hints['func'] = funcs + for func in funcs: + if isinstance(func, list): + for func_elem in func: + if len(func_elem.args) != 1: + raise ValueError("dsolve() and classify_sysode() work with " + "functions of one variable only, not %s" % func) + else: + if func and len(func.args) != 1: + raise ValueError("dsolve() and classify_sysode() work with " + "functions of one variable only, not %s" % func) + + # find the order of all equation in system of odes + matching_hints["order"] = order + + # find coefficients of terms f(t), diff(f(t),t) and higher derivatives + # and similarly for other functions g(t), diff(g(t),t) in all equations. + # Here j denotes the equation number, funcs[l] denotes the function about + # which we are talking about and k denotes the order of function funcs[l] + # whose coefficient we are calculating. + def linearity_check(eqs, j, func, is_linear_): + for k in range(order[func] + 1): + func_coef[j, func, k] = collect(eqs.expand(), [diff(func, t, k)]).coeff(diff(func, t, k)) + if is_linear_ == True: + if func_coef[j, func, k] == 0: + if k == 0: + coef = eqs.as_independent(func, as_Add=True)[1] + for xr in range(1, ode_order(eqs,func) + 1): + coef -= eqs.as_independent(diff(func, t, xr), as_Add=True)[1] + if coef != 0: + is_linear_ = False + else: + if eqs.as_independent(diff(func, t, k), as_Add=True)[1]: + is_linear_ = False + else: + for func_ in funcs: + if isinstance(func_, list): + for elem_func_ in func_: + dep = func_coef[j, func, k].as_independent(elem_func_, as_Add=True)[1] + if dep != 0: + is_linear_ = False + else: + dep = func_coef[j, func, k].as_independent(func_, as_Add=True)[1] + if dep != 0: + is_linear_ = False + return is_linear_ + + func_coef = {} + is_linear = True + for j, eqs in enumerate(eq): + for func in funcs: + if isinstance(func, list): + for func_elem in func: + is_linear = linearity_check(eqs, j, func_elem, is_linear) + else: + is_linear = linearity_check(eqs, j, func, is_linear) + matching_hints['func_coeff'] = func_coef + matching_hints['is_linear'] = is_linear + + + if len(set(order.values())) == 1: + order_eq = list(matching_hints['order'].values())[0] + if matching_hints['is_linear'] == True: + if matching_hints['no_of_equation'] == 2: + if order_eq == 1: + type_of_equation = check_linear_2eq_order1(eq, funcs, func_coef) + else: + type_of_equation = None + # If the equation does not match up with any of the + # general case solvers in systems.py and the number + # of equations is greater than 2, then NotImplementedError + # should be raised. + else: + type_of_equation = None + + else: + if matching_hints['no_of_equation'] == 2: + if order_eq == 1: + type_of_equation = check_nonlinear_2eq_order1(eq, funcs, func_coef) + else: + type_of_equation = None + elif matching_hints['no_of_equation'] == 3: + if order_eq == 1: + type_of_equation = check_nonlinear_3eq_order1(eq, funcs, func_coef) + else: + type_of_equation = None + else: + type_of_equation = None + else: + type_of_equation = None + + matching_hints['type_of_equation'] = type_of_equation + + return matching_hints + + +def check_linear_2eq_order1(eq, func, func_coef): + x = func[0].func + y = func[1].func + fc = func_coef + t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] + r = {} + # for equations Eq(a1*diff(x(t),t), b1*x(t) + c1*y(t) + d1) + # and Eq(a2*diff(y(t),t), b2*x(t) + c2*y(t) + d2) + r['a1'] = fc[0,x(t),1] ; r['a2'] = fc[1,y(t),1] + r['b1'] = -fc[0,x(t),0]/fc[0,x(t),1] ; r['b2'] = -fc[1,x(t),0]/fc[1,y(t),1] + r['c1'] = -fc[0,y(t),0]/fc[0,x(t),1] ; r['c2'] = -fc[1,y(t),0]/fc[1,y(t),1] + forcing = [S.Zero,S.Zero] + for i in range(2): + for j in Add.make_args(eq[i]): + if not j.has(x(t), y(t)): + forcing[i] += j + if not (forcing[0].has(t) or forcing[1].has(t)): + # We can handle homogeneous case and simple constant forcings + r['d1'] = forcing[0] + r['d2'] = forcing[1] + else: + # Issue #9244: nonhomogeneous linear systems are not supported + return None + + # Conditions to check for type 6 whose equations are Eq(diff(x(t),t), f(t)*x(t) + g(t)*y(t)) and + # Eq(diff(y(t),t), a*[f(t) + a*h(t)]x(t) + a*[g(t) - h(t)]*y(t)) + p = 0 + q = 0 + p1 = cancel(r['b2']/(cancel(r['b2']/r['c2']).as_numer_denom()[0])) + p2 = cancel(r['b1']/(cancel(r['b1']/r['c1']).as_numer_denom()[0])) + for n, i in enumerate([p1, p2]): + for j in Mul.make_args(collect_const(i)): + if not j.has(t): + q = j + if q and n==0: + if ((r['b2']/j - r['b1'])/(r['c1'] - r['c2']/j)) == j: + p = 1 + elif q and n==1: + if ((r['b1']/j - r['b2'])/(r['c2'] - r['c1']/j)) == j: + p = 2 + # End of condition for type 6 + + if r['d1']!=0 or r['d2']!=0: + return None + else: + if not any(r[k].has(t) for k in 'a1 a2 b1 b2 c1 c2'.split()): + return None + else: + r['b1'] = r['b1']/r['a1'] ; r['b2'] = r['b2']/r['a2'] + r['c1'] = r['c1']/r['a1'] ; r['c2'] = r['c2']/r['a2'] + if p: + return "type6" + else: + # Equations for type 7 are Eq(diff(x(t),t), f(t)*x(t) + g(t)*y(t)) and Eq(diff(y(t),t), h(t)*x(t) + p(t)*y(t)) + return "type7" +def check_nonlinear_2eq_order1(eq, func, func_coef): + t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] + f = Wild('f') + g = Wild('g') + u, v = symbols('u, v', cls=Dummy) + def check_type(x, y): + r1 = eq[0].match(t*diff(x(t),t) - x(t) + f) + r2 = eq[1].match(t*diff(y(t),t) - y(t) + g) + if not (r1 and r2): + r1 = eq[0].match(diff(x(t),t) - x(t)/t + f/t) + r2 = eq[1].match(diff(y(t),t) - y(t)/t + g/t) + if not (r1 and r2): + r1 = (-eq[0]).match(t*diff(x(t),t) - x(t) + f) + r2 = (-eq[1]).match(t*diff(y(t),t) - y(t) + g) + if not (r1 and r2): + r1 = (-eq[0]).match(diff(x(t),t) - x(t)/t + f/t) + r2 = (-eq[1]).match(diff(y(t),t) - y(t)/t + g/t) + if r1 and r2 and not (r1[f].subs(diff(x(t),t),u).subs(diff(y(t),t),v).has(t) \ + or r2[g].subs(diff(x(t),t),u).subs(diff(y(t),t),v).has(t)): + return 'type5' + else: + return None + for func_ in func: + if isinstance(func_, list): + x = func[0][0].func + y = func[0][1].func + eq_type = check_type(x, y) + if not eq_type: + eq_type = check_type(y, x) + return eq_type + x = func[0].func + y = func[1].func + fc = func_coef + n = Wild('n', exclude=[x(t),y(t)]) + f1 = Wild('f1', exclude=[v,t]) + f2 = Wild('f2', exclude=[v,t]) + g1 = Wild('g1', exclude=[u,t]) + g2 = Wild('g2', exclude=[u,t]) + for i in range(2): + eqs = 0 + for terms in Add.make_args(eq[i]): + eqs += terms/fc[i,func[i],1] + eq[i] = eqs + r = eq[0].match(diff(x(t),t) - x(t)**n*f) + if r: + g = (diff(y(t),t) - eq[1])/r[f] + if r and not (g.has(x(t)) or g.subs(y(t),v).has(t) or r[f].subs(x(t),u).subs(y(t),v).has(t)): + return 'type1' + r = eq[0].match(diff(x(t),t) - exp(n*x(t))*f) + if r: + g = (diff(y(t),t) - eq[1])/r[f] + if r and not (g.has(x(t)) or g.subs(y(t),v).has(t) or r[f].subs(x(t),u).subs(y(t),v).has(t)): + return 'type2' + g = Wild('g') + r1 = eq[0].match(diff(x(t),t) - f) + r2 = eq[1].match(diff(y(t),t) - g) + if r1 and r2 and not (r1[f].subs(x(t),u).subs(y(t),v).has(t) or \ + r2[g].subs(x(t),u).subs(y(t),v).has(t)): + return 'type3' + r1 = eq[0].match(diff(x(t),t) - f) + r2 = eq[1].match(diff(y(t),t) - g) + num, den = ( + (r1[f].subs(x(t),u).subs(y(t),v))/ + (r2[g].subs(x(t),u).subs(y(t),v))).as_numer_denom() + R1 = num.match(f1*g1) + R2 = den.match(f2*g2) + # phi = (r1[f].subs(x(t),u).subs(y(t),v))/num + if R1 and R2: + return 'type4' + return None + + +def check_nonlinear_2eq_order2(eq, func, func_coef): + return None + +def check_nonlinear_3eq_order1(eq, func, func_coef): + x = func[0].func + y = func[1].func + z = func[2].func + fc = func_coef + t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] + u, v, w = symbols('u, v, w', cls=Dummy) + a = Wild('a', exclude=[x(t), y(t), z(t), t]) + b = Wild('b', exclude=[x(t), y(t), z(t), t]) + c = Wild('c', exclude=[x(t), y(t), z(t), t]) + f = Wild('f') + F1 = Wild('F1') + F2 = Wild('F2') + F3 = Wild('F3') + for i in range(3): + eqs = 0 + for terms in Add.make_args(eq[i]): + eqs += terms/fc[i,func[i],1] + eq[i] = eqs + r1 = eq[0].match(diff(x(t),t) - a*y(t)*z(t)) + r2 = eq[1].match(diff(y(t),t) - b*z(t)*x(t)) + r3 = eq[2].match(diff(z(t),t) - c*x(t)*y(t)) + if r1 and r2 and r3: + num1, den1 = r1[a].as_numer_denom() + num2, den2 = r2[b].as_numer_denom() + num3, den3 = r3[c].as_numer_denom() + if solve([num1*u-den1*(v-w), num2*v-den2*(w-u), num3*w-den3*(u-v)],[u, v]): + return 'type1' + r = eq[0].match(diff(x(t),t) - y(t)*z(t)*f) + if r: + r1 = collect_const(r[f]).match(a*f) + r2 = ((diff(y(t),t) - eq[1])/r1[f]).match(b*z(t)*x(t)) + r3 = ((diff(z(t),t) - eq[2])/r1[f]).match(c*x(t)*y(t)) + if r1 and r2 and r3: + num1, den1 = r1[a].as_numer_denom() + num2, den2 = r2[b].as_numer_denom() + num3, den3 = r3[c].as_numer_denom() + if solve([num1*u-den1*(v-w), num2*v-den2*(w-u), num3*w-den3*(u-v)],[u, v]): + return 'type2' + r = eq[0].match(diff(x(t),t) - (F2-F3)) + if r: + r1 = collect_const(r[F2]).match(c*F2) + r1.update(collect_const(r[F3]).match(b*F3)) + if r1: + if eq[1].has(r1[F2]) and not eq[1].has(r1[F3]): + r1[F2], r1[F3] = r1[F3], r1[F2] + r1[c], r1[b] = -r1[b], -r1[c] + r2 = eq[1].match(diff(y(t),t) - a*r1[F3] + r1[c]*F1) + if r2: + r3 = (eq[2] == diff(z(t),t) - r1[b]*r2[F1] + r2[a]*r1[F2]) + if r1 and r2 and r3: + return 'type3' + r = eq[0].match(diff(x(t),t) - z(t)*F2 + y(t)*F3) + if r: + r1 = collect_const(r[F2]).match(c*F2) + r1.update(collect_const(r[F3]).match(b*F3)) + if r1: + if eq[1].has(r1[F2]) and not eq[1].has(r1[F3]): + r1[F2], r1[F3] = r1[F3], r1[F2] + r1[c], r1[b] = -r1[b], -r1[c] + r2 = (diff(y(t),t) - eq[1]).match(a*x(t)*r1[F3] - r1[c]*z(t)*F1) + if r2: + r3 = (diff(z(t),t) - eq[2] == r1[b]*y(t)*r2[F1] - r2[a]*x(t)*r1[F2]) + if r1 and r2 and r3: + return 'type4' + r = (diff(x(t),t) - eq[0]).match(x(t)*(F2 - F3)) + if r: + r1 = collect_const(r[F2]).match(c*F2) + r1.update(collect_const(r[F3]).match(b*F3)) + if r1: + if eq[1].has(r1[F2]) and not eq[1].has(r1[F3]): + r1[F2], r1[F3] = r1[F3], r1[F2] + r1[c], r1[b] = -r1[b], -r1[c] + r2 = (diff(y(t),t) - eq[1]).match(y(t)*(a*r1[F3] - r1[c]*F1)) + if r2: + r3 = (diff(z(t),t) - eq[2] == z(t)*(r1[b]*r2[F1] - r2[a]*r1[F2])) + if r1 and r2 and r3: + return 'type5' + return None + + +def check_nonlinear_3eq_order2(eq, func, func_coef): + return None + + +@vectorize(0) +def odesimp(ode, eq, func, hint): + r""" + Simplifies solutions of ODEs, including trying to solve for ``func`` and + running :py:meth:`~sympy.solvers.ode.constantsimp`. + + It may use knowledge of the type of solution that the hint returns to + apply additional simplifications. + + It also attempts to integrate any :py:class:`~sympy.integrals.integrals.Integral`\s + in the expression, if the hint is not an ``_Integral`` hint. + + This function should have no effect on expressions returned by + :py:meth:`~sympy.solvers.ode.dsolve`, as + :py:meth:`~sympy.solvers.ode.dsolve` already calls + :py:meth:`~sympy.solvers.ode.ode.odesimp`, but the individual hint functions + do not call :py:meth:`~sympy.solvers.ode.ode.odesimp` (because the + :py:meth:`~sympy.solvers.ode.dsolve` wrapper does). Therefore, this + function is designed for mainly internal use. + + Examples + ======== + + >>> from sympy import sin, symbols, dsolve, pprint, Function + >>> from sympy.solvers.ode.ode import odesimp + >>> x, u2, C1= symbols('x,u2,C1') + >>> f = Function('f') + + >>> eq = dsolve(x*f(x).diff(x) - f(x) - x*sin(f(x)/x), f(x), + ... hint='1st_homogeneous_coeff_subs_indep_div_dep_Integral', + ... simplify=False) + >>> pprint(eq, wrap_line=False) + x + ---- + f(x) + / + | + | / 1 \ + | -|u1 + -------| + | | /1 \| + | | sin|--|| + | \ \u1// + log(f(x)) = log(C1) + | ---------------- d(u1) + | 2 + | u1 + | + / + + >>> pprint(odesimp(eq, f(x), 1, {C1}, + ... hint='1st_homogeneous_coeff_subs_indep_div_dep' + ... )) #doctest: +SKIP + x + --------- = C1 + /f(x)\ + tan|----| + \2*x / + + """ + x = func.args[0] + f = func.func + C1 = get_numbered_constants(eq, num=1) + constants = eq.free_symbols - ode.free_symbols + + # First, integrate if the hint allows it. + eq = _handle_Integral(eq, func, hint) + if hint.startswith("nth_linear_euler_eq_nonhomogeneous"): + eq = simplify(eq) + if not isinstance(eq, Equality): + raise TypeError("eq should be an instance of Equality") + + # allow simplifications under assumption that symbols are nonzero + eq = eq.xreplace((_:={i: Dummy(nonzero=True) for i in constants})).xreplace({_[i]: i for i in _}) + + # Second, clean up the arbitrary constants. + # Right now, nth linear hints can put as many as 2*order constants in an + # expression. If that number grows with another hint, the third argument + # here should be raised accordingly, or constantsimp() rewritten to handle + # an arbitrary number of constants. + eq = constantsimp(eq, constants) + + # Lastly, now that we have cleaned up the expression, try solving for func. + # When CRootOf is implemented in solve(), we will want to return a CRootOf + # every time instead of an Equality. + + # Get the f(x) on the left if possible. + if eq.rhs == func and not eq.lhs.has(func): + eq = [Eq(eq.rhs, eq.lhs)] + + # make sure we are working with lists of solutions in simplified form. + if eq.lhs == func and not eq.rhs.has(func): + # The solution is already solved + eq = [eq] + + else: + # The solution is not solved, so try to solve it + try: + floats = any(i.is_Float for i in eq.atoms(Number)) + eqsol = solve(eq, func, force=True, rational=False if floats else None) + if not eqsol: + raise NotImplementedError + except (NotImplementedError, PolynomialError): + eq = [eq] + else: + def _expand(expr): + numer, denom = expr.as_numer_denom() + + if denom.is_Add: + return expr + else: + return powsimp(expr.expand(), combine='exp', deep=True) + + # XXX: the rest of odesimp() expects each ``t`` to be in a + # specific normal form: rational expression with numerator + # expanded, but with combined exponential functions (at + # least in this setup all tests pass). + eq = [Eq(f(x), _expand(t)) for t in eqsol] + + # special simplification of the lhs. + if hint.startswith("1st_homogeneous_coeff"): + for j, eqi in enumerate(eq): + newi = logcombine(eqi, force=True) + if isinstance(newi.lhs, log) and newi.rhs == 0: + newi = Eq(newi.lhs.args[0]/C1, C1) + eq[j] = newi + + # We cleaned up the constants before solving to help the solve engine with + # a simpler expression, but the solved expression could have introduced + # things like -C1, so rerun constantsimp() one last time before returning. + for i, eqi in enumerate(eq): + eq[i] = constantsimp(eqi, constants) + eq[i] = constant_renumber(eq[i], ode.free_symbols) + + # If there is only 1 solution, return it; + # otherwise return the list of solutions. + if len(eq) == 1: + eq = eq[0] + return eq + + +def ode_sol_simplicity(sol, func, trysolving=True): + r""" + Returns an extended integer representing how simple a solution to an ODE + is. + + The following things are considered, in order from most simple to least: + + - ``sol`` is solved for ``func``. + - ``sol`` is not solved for ``func``, but can be if passed to solve (e.g., + a solution returned by ``dsolve(ode, func, simplify=False``). + - If ``sol`` is not solved for ``func``, then base the result on the + length of ``sol``, as computed by ``len(str(sol))``. + - If ``sol`` has any unevaluated :py:class:`~sympy.integrals.integrals.Integral`\s, + this will automatically be considered less simple than any of the above. + + This function returns an integer such that if solution A is simpler than + solution B by above metric, then ``ode_sol_simplicity(sola, func) < + ode_sol_simplicity(solb, func)``. + + Currently, the following are the numbers returned, but if the heuristic is + ever improved, this may change. Only the ordering is guaranteed. + + +----------------------------------------------+-------------------+ + | Simplicity | Return | + +==============================================+===================+ + | ``sol`` solved for ``func`` | ``-2`` | + +----------------------------------------------+-------------------+ + | ``sol`` not solved for ``func`` but can be | ``-1`` | + +----------------------------------------------+-------------------+ + | ``sol`` is not solved nor solvable for | ``len(str(sol))`` | + | ``func`` | | + +----------------------------------------------+-------------------+ + | ``sol`` contains an | ``oo`` | + | :obj:`~sympy.integrals.integrals.Integral` | | + +----------------------------------------------+-------------------+ + + ``oo`` here means the SymPy infinity, which should compare greater than + any integer. + + If you already know :py:meth:`~sympy.solvers.solvers.solve` cannot solve + ``sol``, you can use ``trysolving=False`` to skip that step, which is the + only potentially slow step. For example, + :py:meth:`~sympy.solvers.ode.dsolve` with the ``simplify=False`` flag + should do this. + + If ``sol`` is a list of solutions, if the worst solution in the list + returns ``oo`` it returns that, otherwise it returns ``len(str(sol))``, + that is, the length of the string representation of the whole list. + + Examples + ======== + + This function is designed to be passed to ``min`` as the key argument, + such as ``min(listofsolutions, key=lambda i: ode_sol_simplicity(i, + f(x)))``. + + >>> from sympy import symbols, Function, Eq, tan, Integral + >>> from sympy.solvers.ode.ode import ode_sol_simplicity + >>> x, C1, C2 = symbols('x, C1, C2') + >>> f = Function('f') + + >>> ode_sol_simplicity(Eq(f(x), C1*x**2), f(x)) + -2 + >>> ode_sol_simplicity(Eq(x**2 + f(x), C1), f(x)) + -1 + >>> ode_sol_simplicity(Eq(f(x), C1*Integral(2*x, x)), f(x)) + oo + >>> eq1 = Eq(f(x)/tan(f(x)/(2*x)), C1) + >>> eq2 = Eq(f(x)/tan(f(x)/(2*x) + f(x)), C2) + >>> [ode_sol_simplicity(eq, f(x)) for eq in [eq1, eq2]] + [28, 35] + >>> min([eq1, eq2], key=lambda i: ode_sol_simplicity(i, f(x))) + Eq(f(x)/tan(f(x)/(2*x)), C1) + + """ + # TODO: if two solutions are solved for f(x), we still want to be + # able to get the simpler of the two + + # See the docstring for the coercion rules. We check easier (faster) + # things here first, to save time. + + if iterable(sol): + # See if there are Integrals + for i in sol: + if ode_sol_simplicity(i, func, trysolving=trysolving) == oo: + return oo + + return len(str(sol)) + + if sol.has(Integral): + return oo + + # Next, try to solve for func. This code will change slightly when CRootOf + # is implemented in solve(). Probably a CRootOf solution should fall + # somewhere between a normal solution and an unsolvable expression. + + # First, see if they are already solved + if sol.lhs == func and not sol.rhs.has(func) or \ + sol.rhs == func and not sol.lhs.has(func): + return -2 + # We are not so lucky, try solving manually + if trysolving: + try: + sols = solve(sol, func) + if not sols: + raise NotImplementedError + except NotImplementedError: + pass + else: + return -1 + + # Finally, a naive computation based on the length of the string version + # of the expression. This may favor combined fractions because they + # will not have duplicate denominators, and may slightly favor expressions + # with fewer additions and subtractions, as those are separated by spaces + # by the printer. + + # Additional ideas for simplicity heuristics are welcome, like maybe + # checking if a equation has a larger domain, or if constantsimp has + # introduced arbitrary constants numbered higher than the order of a + # given ODE that sol is a solution of. + return len(str(sol)) + + +def _extract_funcs(eqs): + funcs = [] + for eq in eqs: + derivs = [node for node in preorder_traversal(eq) if isinstance(node, Derivative)] + func = [] + for d in derivs: + func += list(d.atoms(AppliedUndef)) + for func_ in func: + funcs.append(func_) + funcs = list(uniq(funcs)) + + return funcs + + +def _get_constant_subexpressions(expr, Cs): + Cs = set(Cs) + Ces = [] + def _recursive_walk(expr): + expr_syms = expr.free_symbols + if expr_syms and expr_syms.issubset(Cs): + Ces.append(expr) + else: + if expr.func == exp: + expr = expr.expand(mul=True) + if expr.func in (Add, Mul): + d = sift(expr.args, lambda i : i.free_symbols.issubset(Cs)) + if len(d[True]) > 1: + x = expr.func(*d[True]) + if not x.is_number: + Ces.append(x) + elif isinstance(expr, Integral): + if expr.free_symbols.issubset(Cs) and \ + all(len(x) == 3 for x in expr.limits): + Ces.append(expr) + for i in expr.args: + _recursive_walk(i) + return + _recursive_walk(expr) + return Ces + +def __remove_linear_redundancies(expr, Cs): + cnts = {i: expr.count(i) for i in Cs} + Cs = [i for i in Cs if cnts[i] > 0] + + def _linear(expr): + if isinstance(expr, Add): + xs = [i for i in Cs if expr.count(i)==cnts[i] \ + and 0 == expr.diff(i, 2)] + d = {} + for x in xs: + y = expr.diff(x) + if y not in d: + d[y]=[] + d[y].append(x) + for y in d: + if len(d[y]) > 1: + d[y].sort(key=str) + for x in d[y][1:]: + expr = expr.subs(x, 0) + return expr + + def _recursive_walk(expr): + if len(expr.args) != 0: + expr = expr.func(*[_recursive_walk(i) for i in expr.args]) + expr = _linear(expr) + return expr + + if isinstance(expr, Equality): + lhs, rhs = [_recursive_walk(i) for i in expr.args] + f = lambda i: isinstance(i, Number) or i in Cs + if isinstance(lhs, Symbol) and lhs in Cs: + rhs, lhs = lhs, rhs + if lhs.func in (Add, Symbol) and rhs.func in (Add, Symbol): + dlhs = sift([lhs] if isinstance(lhs, AtomicExpr) else lhs.args, f) + drhs = sift([rhs] if isinstance(rhs, AtomicExpr) else rhs.args, f) + for i in [True, False]: + for hs in [dlhs, drhs]: + if i not in hs: + hs[i] = [0] + # this calculation can be simplified + lhs = Add(*dlhs[False]) - Add(*drhs[False]) + rhs = Add(*drhs[True]) - Add(*dlhs[True]) + elif lhs.func in (Mul, Symbol) and rhs.func in (Mul, Symbol): + dlhs = sift([lhs] if isinstance(lhs, AtomicExpr) else lhs.args, f) + if True in dlhs: + if False not in dlhs: + dlhs[False] = [1] + lhs = Mul(*dlhs[False]) + rhs = rhs/Mul(*dlhs[True]) + return Eq(lhs, rhs) + else: + return _recursive_walk(expr) + +@vectorize(0) +def constantsimp(expr, constants): + r""" + Simplifies an expression with arbitrary constants in it. + + This function is written specifically to work with + :py:meth:`~sympy.solvers.ode.dsolve`, and is not intended for general use. + + Simplification is done by "absorbing" the arbitrary constants into other + arbitrary constants, numbers, and symbols that they are not independent + of. + + The symbols must all have the same name with numbers after it, for + example, ``C1``, ``C2``, ``C3``. The ``symbolname`` here would be + '``C``', the ``startnumber`` would be 1, and the ``endnumber`` would be 3. + If the arbitrary constants are independent of the variable ``x``, then the + independent symbol would be ``x``. There is no need to specify the + dependent function, such as ``f(x)``, because it already has the + independent symbol, ``x``, in it. + + Because terms are "absorbed" into arbitrary constants and because + constants are renumbered after simplifying, the arbitrary constants in + expr are not necessarily equal to the ones of the same name in the + returned result. + + If two or more arbitrary constants are added, multiplied, or raised to the + power of each other, they are first absorbed together into a single + arbitrary constant. Then the new constant is combined into other terms if + necessary. + + Absorption of constants is done with limited assistance: + + 1. terms of :py:class:`~sympy.core.add.Add`\s are collected to try join + constants so `e^x (C_1 \cos(x) + C_2 \cos(x))` will simplify to `e^x + C_1 \cos(x)`; + + 2. powers with exponents that are :py:class:`~sympy.core.add.Add`\s are + expanded so `e^{C_1 + x}` will be simplified to `C_1 e^x`. + + Use :py:meth:`~sympy.solvers.ode.ode.constant_renumber` to renumber constants + after simplification or else arbitrary numbers on constants may appear, + e.g. `C_1 + C_3 x`. + + In rare cases, a single constant can be "simplified" into two constants. + Every differential equation solution should have as many arbitrary + constants as the order of the differential equation. The result here will + be technically correct, but it may, for example, have `C_1` and `C_2` in + an expression, when `C_1` is actually equal to `C_2`. Use your discretion + in such situations, and also take advantage of the ability to use hints in + :py:meth:`~sympy.solvers.ode.dsolve`. + + Examples + ======== + + >>> from sympy import symbols + >>> from sympy.solvers.ode.ode import constantsimp + >>> C1, C2, C3, x, y = symbols('C1, C2, C3, x, y') + >>> constantsimp(2*C1*x, {C1, C2, C3}) + C1*x + >>> constantsimp(C1 + 2 + x, {C1, C2, C3}) + C1 + x + >>> constantsimp(C1*C2 + 2 + C2 + C3*x, {C1, C2, C3}) + C1 + C3*x + + """ + # This function works recursively. The idea is that, for Mul, + # Add, Pow, and Function, if the class has a constant in it, then + # we can simplify it, which we do by recursing down and + # simplifying up. Otherwise, we can skip that part of the + # expression. + + Cs = constants + + orig_expr = expr + + constant_subexprs = _get_constant_subexpressions(expr, Cs) + for xe in constant_subexprs: + xes = list(xe.free_symbols) + if not xes: + continue + if all(expr.count(c) == xe.count(c) for c in xes): + xes.sort(key=str) + expr = expr.subs(xe, xes[0]) + + # try to perform common sub-expression elimination of constant terms + try: + commons, rexpr = cse(expr) + commons.reverse() + rexpr = rexpr[0] + for s in commons: + cs = list(s[1].atoms(Symbol)) + if len(cs) == 1 and cs[0] in Cs and \ + cs[0] not in rexpr.atoms(Symbol) and \ + not any(cs[0] in ex for ex in commons if ex != s): + rexpr = rexpr.subs(s[0], cs[0]) + else: + rexpr = rexpr.subs(*s) + expr = rexpr + except IndexError: + pass + expr = __remove_linear_redundancies(expr, Cs) + + def _conditional_term_factoring(expr): + new_expr = terms_gcd(expr, clear=False, deep=True, expand=False) + + # we do not want to factor exponentials, so handle this separately + if new_expr.is_Mul: + infac = False + asfac = False + for m in new_expr.args: + if isinstance(m, exp): + asfac = True + elif m.is_Add: + infac = any(isinstance(fi, exp) for t in m.args + for fi in Mul.make_args(t)) + if asfac and infac: + new_expr = expr + break + return new_expr + + expr = _conditional_term_factoring(expr) + + # call recursively if more simplification is possible + if orig_expr != expr: + return constantsimp(expr, Cs) + return expr + + +def constant_renumber(expr, variables=None, newconstants=None): + r""" + Renumber arbitrary constants in ``expr`` to use the symbol names as given + in ``newconstants``. In the process, this reorders expression terms in a + standard way. + + If ``newconstants`` is not provided then the new constant names will be + ``C1``, ``C2`` etc. Otherwise ``newconstants`` should be an iterable + giving the new symbols to use for the constants in order. + + The ``variables`` argument is a list of non-constant symbols. All other + free symbols found in ``expr`` are assumed to be constants and will be + renumbered. If ``variables`` is not given then any numbered symbol + beginning with ``C`` (e.g. ``C1``) is assumed to be a constant. + + Symbols are renumbered based on ``.sort_key()``, so they should be + numbered roughly in the order that they appear in the final, printed + expression. Note that this ordering is based in part on hashes, so it can + produce different results on different machines. + + The structure of this function is very similar to that of + :py:meth:`~sympy.solvers.ode.constantsimp`. + + Examples + ======== + + >>> from sympy import symbols + >>> from sympy.solvers.ode.ode import constant_renumber + >>> x, C1, C2, C3 = symbols('x,C1:4') + >>> expr = C3 + C2*x + C1*x**2 + >>> expr + C1*x**2 + C2*x + C3 + >>> constant_renumber(expr) + C1 + C2*x + C3*x**2 + + The ``variables`` argument specifies which are constants so that the + other symbols will not be renumbered: + + >>> constant_renumber(expr, [C1, x]) + C1*x**2 + C2 + C3*x + + The ``newconstants`` argument is used to specify what symbols to use when + replacing the constants: + + >>> constant_renumber(expr, [x], newconstants=symbols('E1:4')) + E1 + E2*x + E3*x**2 + + """ + + # System of expressions + if isinstance(expr, (set, list, tuple)): + return type(expr)(constant_renumber(Tuple(*expr), + variables=variables, newconstants=newconstants)) + + # Symbols in solution but not ODE are constants + if variables is not None: + variables = set(variables) + free_symbols = expr.free_symbols + constantsymbols = list(free_symbols - variables) + # Any Cn is a constant... + else: + variables = set() + isconstant = lambda s: s.startswith('C') and s[1:].isdigit() + constantsymbols = [sym for sym in expr.free_symbols if isconstant(sym.name)] + + # Find new constants checking that they aren't already in the ODE + if newconstants is None: + iter_constants = numbered_symbols(start=1, prefix='C', exclude=variables) + else: + iter_constants = (sym for sym in newconstants if sym not in variables) + + constants_found = [] + + # make a mapping to send all constantsymbols to S.One and use + # that to make sure that term ordering is not dependent on + # the indexed value of C + C_1 = [(ci, S.One) for ci in constantsymbols] + sort_key=lambda arg: default_sort_key(arg.subs(C_1)) + + def _constant_renumber(expr): + r""" + We need to have an internal recursive function + """ + + # For system of expressions + if isinstance(expr, Tuple): + renumbered = [_constant_renumber(e) for e in expr] + return Tuple(*renumbered) + + if isinstance(expr, Equality): + return Eq( + _constant_renumber(expr.lhs), + _constant_renumber(expr.rhs)) + + if type(expr) not in (Mul, Add, Pow) and not expr.is_Function and \ + not expr.has(*constantsymbols): + # Base case, as above. Hope there aren't constants inside + # of some other class, because they won't be renumbered. + return expr + elif expr.is_Piecewise: + return expr + elif expr in constantsymbols: + if expr not in constants_found: + constants_found.append(expr) + return expr + elif expr.is_Function or expr.is_Pow: + return expr.func( + *[_constant_renumber(x) for x in expr.args]) + else: + sortedargs = list(expr.args) + sortedargs.sort(key=sort_key) + return expr.func(*[_constant_renumber(x) for x in sortedargs]) + expr = _constant_renumber(expr) + + # Don't renumber symbols present in the ODE. + constants_found = [c for c in constants_found if c not in variables] + + # Renumbering happens here + subs_dict = dict(zip(constants_found, iter_constants)) + expr = expr.subs(subs_dict, simultaneous=True) + + return expr + + +def _handle_Integral(expr, func, hint): + r""" + Converts a solution with Integrals in it into an actual solution. + + For most hints, this simply runs ``expr.doit()``. + + """ + if hint == "nth_linear_constant_coeff_homogeneous": + sol = expr + elif not hint.endswith("_Integral"): + sol = expr.doit() + else: + sol = expr + return sol + + +# XXX: Should this function maybe go somewhere else? + + +def homogeneous_order(eq, *symbols): + r""" + Returns the order `n` if `g` is homogeneous and ``None`` if it is not + homogeneous. + + Determines if a function is homogeneous and if so of what order. A + function `f(x, y, \cdots)` is homogeneous of order `n` if `f(t x, t y, + \cdots) = t^n f(x, y, \cdots)`. + + If the function is of two variables, `F(x, y)`, then `f` being homogeneous + of any order is equivalent to being able to rewrite `F(x, y)` as `G(x/y)` + or `H(y/x)`. This fact is used to solve 1st order ordinary differential + equations whose coefficients are homogeneous of the same order (see the + docstrings of + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` and + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep`). + + Symbols can be functions, but every argument of the function must be a + symbol, and the arguments of the function that appear in the expression + must match those given in the list of symbols. If a declared function + appears with different arguments than given in the list of symbols, + ``None`` is returned. + + Examples + ======== + + >>> from sympy import Function, homogeneous_order, sqrt + >>> from sympy.abc import x, y + >>> f = Function('f') + >>> homogeneous_order(f(x), f(x)) is None + True + >>> homogeneous_order(f(x,y), f(y, x), x, y) is None + True + >>> homogeneous_order(f(x), f(x), x) + 1 + >>> homogeneous_order(x**2*f(x)/sqrt(x**2+f(x)**2), x, f(x)) + 2 + >>> homogeneous_order(x**2+f(x), x, f(x)) is None + True + + """ + + if not symbols: + raise ValueError("homogeneous_order: no symbols were given.") + symset = set(symbols) + eq = sympify(eq) + + # The following are not supported + if eq.has(Order, Derivative): + return None + + # These are all constants + if (eq.is_Number or + eq.is_NumberSymbol or + eq.is_number + ): + return S.Zero + + # Replace all functions with dummy variables + dum = numbered_symbols(prefix='d', cls=Dummy) + newsyms = set() + for i in [j for j in symset if getattr(j, 'is_Function')]: + iargs = set(i.args) + if iargs.difference(symset): + return None + else: + dummyvar = next(dum) + eq = eq.subs(i, dummyvar) + symset.remove(i) + newsyms.add(dummyvar) + symset.update(newsyms) + + if not eq.free_symbols & symset: + return None + + # assuming order of a nested function can only be equal to zero + if isinstance(eq, Function): + return None if homogeneous_order( + eq.args[0], *tuple(symset)) != 0 else S.Zero + + # make the replacement of x with x*t and see if t can be factored out + t = Dummy('t', positive=True) # It is sufficient that t > 0 + eqs = separatevars(eq.subs([(i, t*i) for i in symset]), [t], dict=True)[t] + if eqs is S.One: + return S.Zero # there was no term with only t + i, d = eqs.as_independent(t, as_Add=False) + b, e = d.as_base_exp() + if b == t: + return e + + +def ode_2nd_power_series_ordinary(eq, func, order, match): + r""" + Gives a power series solution to a second order homogeneous differential + equation with polynomial coefficients at an ordinary point. A homogeneous + differential equation is of the form + + .. math :: P(x)\frac{d^2y}{dx^2} + Q(x)\frac{dy}{dx} + R(x) y(x) = 0 + + For simplicity it is assumed that `P(x)`, `Q(x)` and `R(x)` are polynomials, + it is sufficient that `\frac{Q(x)}{P(x)}` and `\frac{R(x)}{P(x)}` exists at + `x_{0}`. A recurrence relation is obtained by substituting `y` as `\sum_{n=0}^\infty a_{n}x^{n}`, + in the differential equation, and equating the nth term. Using this relation + various terms can be generated. + + + Examples + ======== + + >>> from sympy import dsolve, Function, pprint + >>> from sympy.abc import x + >>> f = Function("f") + >>> eq = f(x).diff(x, 2) + f(x) + >>> pprint(dsolve(eq, hint='2nd_power_series_ordinary')) + / 4 2 \ / 2\ + |x x | | x | / 6\ + f(x) = C2*|-- - -- + 1| + C1*x*|1 - --| + O\x / + \24 2 / \ 6 / + + + References + ========== + - https://tutorial.math.lamar.edu/Classes/DE/SeriesSolutions.aspx + - George E. Simmons, "Differential Equations with Applications and + Historical Notes", p.p 176 - 184 + + """ + x = func.args[0] + f = func.func + C0, C1 = get_numbered_constants(eq, num=2) + n = Dummy("n", integer=True) + s = Wild("s") + k = Wild("k", exclude=[x]) + x0 = match['x0'] + terms = match['terms'] + p = match[match['a3']] + q = match[match['b3']] + r = match[match['c3']] + seriesdict = {} + recurr = Function("r") + + # Generating the recurrence relation which works this way: + # for the second order term the summation begins at n = 2. The coefficients + # p is multiplied with an*(n - 1)*(n - 2)*x**n-2 and a substitution is made such that + # the exponent of x becomes n. + # For example, if p is x, then the second degree recurrence term is + # an*(n - 1)*(n - 2)*x**n-1, substituting (n - 1) as n, it transforms to + # an+1*n*(n - 1)*x**n. + # A similar process is done with the first order and zeroth order term. + + coefflist = [(recurr(n), r), (n*recurr(n), q), (n*(n - 1)*recurr(n), p)] + for index, coeff in enumerate(coefflist): + if coeff[1]: + f2 = powsimp(expand((coeff[1]*(x - x0)**(n - index)).subs(x, x + x0))) + if f2.is_Add: + addargs = f2.args + else: + addargs = [f2] + for arg in addargs: + powm = arg.match(s*x**k) + term = coeff[0]*powm[s] + if not powm[k].is_Symbol: + term = term.subs(n, n - powm[k].as_independent(n)[0]) + startind = powm[k].subs(n, index) + # Seeing if the startterm can be reduced further. + # If it vanishes for n lesser than startind, it is + # equal to summation from n. + if startind: + for i in reversed(range(startind)): + if not term.subs(n, i): + seriesdict[term] = i + else: + seriesdict[term] = i + 1 + break + else: + seriesdict[term] = S.Zero + + # Stripping of terms so that the sum starts with the same number. + teq = S.Zero + suminit = seriesdict.values() + rkeys = seriesdict.keys() + req = Add(*rkeys) + if any(suminit): + maxval = max(suminit) + for term in seriesdict: + val = seriesdict[term] + if val != maxval: + for i in range(val, maxval): + teq += term.subs(n, val) + + finaldict = {} + if teq: + fargs = teq.atoms(AppliedUndef) + if len(fargs) == 1: + finaldict[fargs.pop()] = 0 + else: + maxf = max(fargs, key = lambda x: x.args[0]) + sol = solve(teq, maxf) + if isinstance(sol, list): + sol = sol[0] + finaldict[maxf] = sol + + # Finding the recurrence relation in terms of the largest term. + fargs = req.atoms(AppliedUndef) + maxf = max(fargs, key = lambda x: x.args[0]) + minf = min(fargs, key = lambda x: x.args[0]) + if minf.args[0].is_Symbol: + startiter = 0 + else: + startiter = -minf.args[0].as_independent(n)[0] + lhs = maxf + rhs = solve(req, maxf) + if isinstance(rhs, list): + rhs = rhs[0] + + # Checking how many values are already present + tcounter = len([t for t in finaldict.values() if t]) + + for _ in range(tcounter, terms - 3): # Assuming c0 and c1 to be arbitrary + check = rhs.subs(n, startiter) + nlhs = lhs.subs(n, startiter) + nrhs = check.subs(finaldict) + finaldict[nlhs] = nrhs + startiter += 1 + + # Post processing + series = C0 + C1*(x - x0) + for term in finaldict: + if finaldict[term]: + fact = term.args[0] + series += (finaldict[term].subs([(recurr(0), C0), (recurr(1), C1)])*( + x - x0)**fact) + series = collect(expand_mul(series), [C0, C1]) + Order(x**terms) + return Eq(f(x), series) + + +def ode_2nd_power_series_regular(eq, func, order, match): + r""" + Gives a power series solution to a second order homogeneous differential + equation with polynomial coefficients at a regular point. A second order + homogeneous differential equation is of the form + + .. math :: P(x)\frac{d^2y}{dx^2} + Q(x)\frac{dy}{dx} + R(x) y(x) = 0 + + A point is said to regular singular at `x0` if `x - x0\frac{Q(x)}{P(x)}` + and `(x - x0)^{2}\frac{R(x)}{P(x)}` are analytic at `x0`. For simplicity + `P(x)`, `Q(x)` and `R(x)` are assumed to be polynomials. The algorithm for + finding the power series solutions is: + + 1. Try expressing `(x - x0)P(x)` and `((x - x0)^{2})Q(x)` as power series + solutions about x0. Find `p0` and `q0` which are the constants of the + power series expansions. + 2. Solve the indicial equation `f(m) = m(m - 1) + m*p0 + q0`, to obtain the + roots `m1` and `m2` of the indicial equation. + 3. If `m1 - m2` is a non integer there exists two series solutions. If + `m1 = m2`, there exists only one solution. If `m1 - m2` is an integer, + then the existence of one solution is confirmed. The other solution may + or may not exist. + + The power series solution is of the form `x^{m}\sum_{n=0}^\infty a_{n}x^{n}`. The + coefficients are determined by the following recurrence relation. + `a_{n} = -\frac{\sum_{k=0}^{n-1} q_{n-k} + (m + k)p_{n-k}}{f(m + n)}`. For the case + in which `m1 - m2` is an integer, it can be seen from the recurrence relation + that for the lower root `m`, when `n` equals the difference of both the + roots, the denominator becomes zero. So if the numerator is not equal to zero, + a second series solution exists. + + + Examples + ======== + + >>> from sympy import dsolve, Function, pprint + >>> from sympy.abc import x + >>> f = Function("f") + >>> eq = x*(f(x).diff(x, 2)) + 2*(f(x).diff(x)) + x*f(x) + >>> pprint(dsolve(eq, hint='2nd_power_series_regular')) + / 6 4 2 \ + | x x x | + / 4 2 \ C1*|- --- + -- - -- + 1| + |x x | \ 720 24 2 / / 6\ + f(x) = C2*|--- - -- + 1| + ------------------------ + O\x / + \120 6 / x + + + References + ========== + - George E. Simmons, "Differential Equations with Applications and + Historical Notes", p.p 176 - 184 + + """ + x = func.args[0] + f = func.func + C0, C1 = get_numbered_constants(eq, num=2) + m = Dummy("m") # for solving the indicial equation + x0 = match['x0'] + terms = match['terms'] + p = match['p'] + q = match['q'] + + # Generating the indicial equation + indicial = [] + for term in [p, q]: + if not term.has(x): + indicial.append(term) + else: + term = series(term, x=x, n=1, x0=x0) + if isinstance(term, Order): + indicial.append(S.Zero) + else: + for arg in term.args: + if not arg.has(x): + indicial.append(arg) + break + + p0, q0 = indicial + sollist = solve(m*(m - 1) + m*p0 + q0, m) + if sollist and isinstance(sollist, list) and all( + sol.is_real for sol in sollist): + serdict1 = {} + serdict2 = {} + if len(sollist) == 1: + # Only one series solution exists in this case. + m1 = m2 = sollist.pop() + if terms-m1-1 <= 0: + return Eq(f(x), Order(terms)) + serdict1 = _frobenius(terms-m1-1, m1, p0, q0, p, q, x0, x, C0) + + else: + m1 = sollist[0] + m2 = sollist[1] + if m1 < m2: + m1, m2 = m2, m1 + # Irrespective of whether m1 - m2 is an integer or not, one + # Frobenius series solution exists. + serdict1 = _frobenius(terms-m1-1, m1, p0, q0, p, q, x0, x, C0) + if not (m1 - m2).is_integer: + # Second frobenius series solution exists. + serdict2 = _frobenius(terms-m2-1, m2, p0, q0, p, q, x0, x, C1) + else: + # Check if second frobenius series solution exists. + serdict2 = _frobenius(terms-m2-1, m2, p0, q0, p, q, x0, x, C1, check=m1) + + if serdict1: + finalseries1 = C0 + for key in serdict1: + power = int(key.name[1:]) + finalseries1 += serdict1[key]*(x - x0)**power + finalseries1 = (x - x0)**m1*finalseries1 + finalseries2 = S.Zero + if serdict2: + for key in serdict2: + power = int(key.name[1:]) + finalseries2 += serdict2[key]*(x - x0)**power + finalseries2 += C1 + finalseries2 = (x - x0)**m2*finalseries2 + return Eq(f(x), collect(finalseries1 + finalseries2, + [C0, C1]) + Order(x**terms)) + + +def _frobenius(n, m, p0, q0, p, q, x0, x, c, check=None): + r""" + Returns a dict with keys as coefficients and values as their values in terms of C0 + """ + n = int(n) + # In cases where m1 - m2 is not an integer + m2 = check + + d = Dummy("d") + numsyms = numbered_symbols("C", start=0) + numsyms = [next(numsyms) for i in range(n + 1)] + serlist = [] + for ser in [p, q]: + # Order term not present + if ser.is_polynomial(x) and Poly(ser, x).degree() <= n: + if x0: + ser = ser.subs(x, x + x0) + dict_ = Poly(ser, x).as_dict() + # Order term present + else: + tseries = series(ser, x=x0, n=n+1) + # Removing order + dict_ = Poly(list(ordered(tseries.args))[: -1], x).as_dict() + # Fill in with zeros, if coefficients are zero. + for i in range(n + 1): + if (i,) not in dict_: + dict_[(i,)] = S.Zero + serlist.append(dict_) + + pseries = serlist[0] + qseries = serlist[1] + indicial = d*(d - 1) + d*p0 + q0 + frobdict = {} + for i in range(1, n + 1): + num = c*(m*pseries[(i,)] + qseries[(i,)]) + for j in range(1, i): + sym = Symbol("C" + str(j)) + num += frobdict[sym]*((m + j)*pseries[(i - j,)] + qseries[(i - j,)]) + + # Checking for cases when m1 - m2 is an integer. If num equals zero + # then a second Frobenius series solution cannot be found. If num is not zero + # then set constant as zero and proceed. + if m2 is not None and i == m2 - m: + if num: + return False + else: + frobdict[numsyms[i]] = S.Zero + else: + frobdict[numsyms[i]] = -num/(indicial.subs(d, m+i)) + + return frobdict + +def _remove_redundant_solutions(eq, solns, order, var): + r""" + Remove redundant solutions from the set of solutions. + + This function is needed because otherwise dsolve can return + redundant solutions. As an example consider: + + eq = Eq((f(x).diff(x, 2))*f(x).diff(x), 0) + + There are two ways to find solutions to eq. The first is to solve f(x).diff(x, 2) = 0 + leading to solution f(x)=C1 + C2*x. The second is to solve the equation f(x).diff(x) = 0 + leading to the solution f(x) = C1. In this particular case we then see + that the second solution is a special case of the first and we do not + want to return it. + + This does not always happen. If we have + + eq = Eq((f(x)**2-4)*(f(x).diff(x)-4), 0) + + then we get the algebraic solution f(x) = [-2, 2] and the integral solution + f(x) = x + C1 and in this case the two solutions are not equivalent wrt + initial conditions so both should be returned. + """ + def is_special_case_of(soln1, soln2): + return _is_special_case_of(soln1, soln2, eq, order, var) + + unique_solns = [] + for soln1 in solns: + for soln2 in unique_solns[:]: + if is_special_case_of(soln1, soln2): + break + elif is_special_case_of(soln2, soln1): + unique_solns.remove(soln2) + else: + unique_solns.append(soln1) + + return unique_solns + +def _is_special_case_of(soln1, soln2, eq, order, var): + r""" + True if soln1 is found to be a special case of soln2 wrt some value of the + constants that appear in soln2. False otherwise. + """ + # The solutions returned by dsolve may be given explicitly or implicitly. + # We will equate the sol1=(soln1.rhs - soln1.lhs), sol2=(soln2.rhs - soln2.lhs) + # of the two solutions. + # + # Since this is supposed to hold for all x it also holds for derivatives. + # For an order n ode we should be able to differentiate + # each solution n times to get n+1 equations. + # + # We then try to solve those n+1 equations for the integrations constants + # in sol2. If we can find a solution that does not depend on x then it + # means that some value of the constants in sol1 is a special case of + # sol2 corresponding to a particular choice of the integration constants. + + # In case the solution is in implicit form we subtract the sides + soln1 = soln1.rhs - soln1.lhs + soln2 = soln2.rhs - soln2.lhs + + # Work for the series solution + if soln1.has(Order) and soln2.has(Order): + if soln1.getO() == soln2.getO(): + soln1 = soln1.removeO() + soln2 = soln2.removeO() + else: + return False + elif soln1.has(Order) or soln2.has(Order): + return False + + constants1 = soln1.free_symbols.difference(eq.free_symbols) + constants2 = soln2.free_symbols.difference(eq.free_symbols) + + constants1_new = get_numbered_constants(Tuple(soln1, soln2), len(constants1)) + if len(constants1) == 1: + constants1_new = {constants1_new} + for c_old, c_new in zip(constants1, constants1_new): + soln1 = soln1.subs(c_old, c_new) + + # n equations for sol1 = sol2, sol1'=sol2', ... + lhs = soln1 + rhs = soln2 + eqns = [Eq(lhs, rhs)] + for n in range(1, order): + lhs = lhs.diff(var) + rhs = rhs.diff(var) + eq = Eq(lhs, rhs) + eqns.append(eq) + + # BooleanTrue/False awkwardly show up for trivial equations + if any(isinstance(eq, BooleanFalse) for eq in eqns): + return False + eqns = [eq for eq in eqns if not isinstance(eq, BooleanTrue)] + + try: + constant_solns = solve(eqns, constants2) + except NotImplementedError: + return False + + # Sometimes returns a dict and sometimes a list of dicts + if isinstance(constant_solns, dict): + constant_solns = [constant_solns] + + # after solving the issue 17418, maybe we don't need the following checksol code. + for constant_soln in constant_solns: + for eq in eqns: + eq=eq.rhs-eq.lhs + if checksol(eq, constant_soln) is not True: + return False + + # If any solution gives all constants as expressions that don't depend on + # x then there exists constants for soln2 that give soln1 + for constant_soln in constant_solns: + if not any(c.has(var) for c in constant_soln.values()): + return True + + return False + + +def ode_1st_power_series(eq, func, order, match): + r""" + The power series solution is a method which gives the Taylor series expansion + to the solution of a differential equation. + + For a first order differential equation `\frac{dy}{dx} = h(x, y)`, a power + series solution exists at a point `x = x_{0}` if `h(x, y)` is analytic at `x_{0}`. + The solution is given by + + .. math:: y(x) = y(x_{0}) + \sum_{n = 1}^{\infty} \frac{F_{n}(x_{0},b)(x - x_{0})^n}{n!}, + + where `y(x_{0}) = b` is the value of y at the initial value of `x_{0}`. + To compute the values of the `F_{n}(x_{0},b)` the following algorithm is + followed, until the required number of terms are generated. + + 1. `F_1 = h(x_{0}, b)` + 2. `F_{n+1} = \frac{\partial F_{n}}{\partial x} + \frac{\partial F_{n}}{\partial y}F_{1}` + + Examples + ======== + + >>> from sympy import Function, pprint, exp, dsolve + >>> from sympy.abc import x + >>> f = Function('f') + >>> eq = exp(x)*(f(x).diff(x)) - f(x) + >>> pprint(dsolve(eq, hint='1st_power_series')) + 3 4 5 + C1*x C1*x C1*x / 6\ + f(x) = C1 + C1*x - ----- + ----- + ----- + O\x / + 6 24 60 + + + References + ========== + + - Travis W. Walker, Analytic power series technique for solving first-order + differential equations, p.p 17, 18 + + """ + x = func.args[0] + y = match['y'] + f = func.func + h = -match[match['d']]/match[match['e']] + point = match['f0'] + value = match['f0val'] + terms = match['terms'] + + # First term + F = h + if not h: + return Eq(f(x), value) + + # Initialization + series = value + if terms > 1: + hc = h.subs({x: point, y: value}) + if hc.has(oo) or hc.has(nan) or hc.has(zoo): + # Derivative does not exist, not analytic + return Eq(f(x), oo) + elif hc: + series += hc*(x - point) + + for factcount in range(2, terms): + Fnew = F.diff(x) + F.diff(y)*h + Fnewc = Fnew.subs({x: point, y: value}) + # Same logic as above + if Fnewc.has(oo) or Fnewc.has(nan) or Fnewc.has(-oo) or Fnewc.has(zoo): + return Eq(f(x), oo) + series += Fnewc*((x - point)**factcount)/factorial(factcount) + F = Fnew + series += Order(x**terms) + return Eq(f(x), series) + + +def checkinfsol(eq, infinitesimals, func=None, order=None): + r""" + This function is used to check if the given infinitesimals are the + actual infinitesimals of the given first order differential equation. + This method is specific to the Lie Group Solver of ODEs. + + As of now, it simply checks, by substituting the infinitesimals in the + partial differential equation. + + + .. math:: \frac{\partial \eta}{\partial x} + \left(\frac{\partial \eta}{\partial y} + - \frac{\partial \xi}{\partial x}\right)*h + - \frac{\partial \xi}{\partial y}*h^{2} + - \xi\frac{\partial h}{\partial x} - \eta\frac{\partial h}{\partial y} = 0 + + + where `\eta`, and `\xi` are the infinitesimals and `h(x,y) = \frac{dy}{dx}` + + The infinitesimals should be given in the form of a list of dicts + ``[{xi(x, y): inf, eta(x, y): inf}]``, corresponding to the + output of the function infinitesimals. It returns a list + of values of the form ``[(True/False, sol)]`` where ``sol`` is the value + obtained after substituting the infinitesimals in the PDE. If it + is ``True``, then ``sol`` would be 0. + + """ + if isinstance(eq, Equality): + eq = eq.lhs - eq.rhs + if not func: + eq, func = _preprocess(eq) + variables = func.args + if len(variables) != 1: + raise ValueError("ODE's have only one independent variable") + else: + x = variables[0] + if not order: + order = ode_order(eq, func) + if order != 1: + raise NotImplementedError("Lie groups solver has been implemented " + "only for first order differential equations") + else: + df = func.diff(x) + a = Wild('a', exclude = [df]) + b = Wild('b', exclude = [df]) + match = collect(expand(eq), df).match(a*df + b) + + if match: + h = -simplify(match[b]/match[a]) + else: + try: + sol = solve(eq, df) + except NotImplementedError: + raise NotImplementedError("Infinitesimals for the " + "first order ODE could not be found") + else: + h = sol[0] # Find infinitesimals for one solution + + y = Dummy('y') + h = h.subs(func, y) + xi = Function('xi')(x, y) + eta = Function('eta')(x, y) + dxi = Function('xi')(x, func) + deta = Function('eta')(x, func) + pde = (eta.diff(x) + (eta.diff(y) - xi.diff(x))*h - + (xi.diff(y))*h**2 - xi*(h.diff(x)) - eta*(h.diff(y))) + soltup = [] + for sol in infinitesimals: + tsol = {xi: S(sol[dxi]).subs(func, y), + eta: S(sol[deta]).subs(func, y)} + sol = simplify(pde.subs(tsol).doit()) + if sol: + soltup.append((False, sol.subs(y, func))) + else: + soltup.append((True, 0)) + return soltup + + +def sysode_linear_2eq_order1(match_): + x = match_['func'][0].func + y = match_['func'][1].func + func = match_['func'] + fc = match_['func_coeff'] + eq = match_['eq'] + r = {} + t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] + for i in range(2): + eq[i] = Add(*[terms/fc[i,func[i],1] for terms in Add.make_args(eq[i])]) + + # for equations Eq(a1*diff(x(t),t), a*x(t) + b*y(t) + k1) + # and Eq(a2*diff(x(t),t), c*x(t) + d*y(t) + k2) + r['a'] = -fc[0,x(t),0]/fc[0,x(t),1] + r['c'] = -fc[1,x(t),0]/fc[1,y(t),1] + r['b'] = -fc[0,y(t),0]/fc[0,x(t),1] + r['d'] = -fc[1,y(t),0]/fc[1,y(t),1] + forcing = [S.Zero,S.Zero] + for i in range(2): + for j in Add.make_args(eq[i]): + if not j.has(x(t), y(t)): + forcing[i] += j + if not (forcing[0].has(t) or forcing[1].has(t)): + r['k1'] = forcing[0] + r['k2'] = forcing[1] + else: + raise NotImplementedError("Only homogeneous problems are supported" + + " (and constant inhomogeneity)") + + if match_['type_of_equation'] == 'type6': + sol = _linear_2eq_order1_type6(x, y, t, r, eq) + if match_['type_of_equation'] == 'type7': + sol = _linear_2eq_order1_type7(x, y, t, r, eq) + return sol + +def _linear_2eq_order1_type6(x, y, t, r, eq): + r""" + The equations of this type of ode are . + + .. math:: x' = f(t) x + g(t) y + + .. math:: y' = a [f(t) + a h(t)] x + a [g(t) - h(t)] y + + This is solved by first multiplying the first equation by `-a` and adding + it to the second equation to obtain + + .. math:: y' - a x' = -a h(t) (y - a x) + + Setting `U = y - ax` and integrating the equation we arrive at + + .. math:: y - ax = C_1 e^{-a \int h(t) \,dt} + + and on substituting the value of y in first equation give rise to first order ODEs. After solving for + `x`, we can obtain `y` by substituting the value of `x` in second equation. + + """ + C1, C2, C3, C4 = get_numbered_constants(eq, num=4) + p = 0 + q = 0 + p1 = cancel(r['c']/cancel(r['c']/r['d']).as_numer_denom()[0]) + p2 = cancel(r['a']/cancel(r['a']/r['b']).as_numer_denom()[0]) + for n, i in enumerate([p1, p2]): + for j in Mul.make_args(collect_const(i)): + if not j.has(t): + q = j + if q!=0 and n==0: + if ((r['c']/j - r['a'])/(r['b'] - r['d']/j)) == j: + p = 1 + s = j + break + if q!=0 and n==1: + if ((r['a']/j - r['c'])/(r['d'] - r['b']/j)) == j: + p = 2 + s = j + break + + if p == 1: + equ = diff(x(t),t) - r['a']*x(t) - r['b']*(s*x(t) + C1*exp(-s*Integral(r['b'] - r['d']/s, t))) + hint1 = classify_ode(equ)[1] + sol1 = dsolve(equ, hint=hint1+'_Integral').rhs + sol2 = s*sol1 + C1*exp(-s*Integral(r['b'] - r['d']/s, t)) + elif p ==2: + equ = diff(y(t),t) - r['c']*y(t) - r['d']*s*y(t) + C1*exp(-s*Integral(r['d'] - r['b']/s, t)) + hint1 = classify_ode(equ)[1] + sol2 = dsolve(equ, hint=hint1+'_Integral').rhs + sol1 = s*sol2 + C1*exp(-s*Integral(r['d'] - r['b']/s, t)) + return [Eq(x(t), sol1), Eq(y(t), sol2)] + +def _linear_2eq_order1_type7(x, y, t, r, eq): + r""" + The equations of this type of ode are . + + .. math:: x' = f(t) x + g(t) y + + .. math:: y' = h(t) x + p(t) y + + Differentiating the first equation and substituting the value of `y` + from second equation will give a second-order linear equation + + .. math:: g x'' - (fg + gp + g') x' + (fgp - g^{2} h + f g' - f' g) x = 0 + + This above equation can be easily integrated if following conditions are satisfied. + + 1. `fgp - g^{2} h + f g' - f' g = 0` + + 2. `fgp - g^{2} h + f g' - f' g = ag, fg + gp + g' = bg` + + If first condition is satisfied then it is solved by current dsolve solver and in second case it becomes + a constant coefficient differential equation which is also solved by current solver. + + Otherwise if the above condition fails then, + a particular solution is assumed as `x = x_0(t)` and `y = y_0(t)` + Then the general solution is expressed as + + .. math:: x = C_1 x_0(t) + C_2 x_0(t) \int \frac{g(t) F(t) P(t)}{x_0^{2}(t)} \,dt + + .. math:: y = C_1 y_0(t) + C_2 [\frac{F(t) P(t)}{x_0(t)} + y_0(t) \int \frac{g(t) F(t) P(t)}{x_0^{2}(t)} \,dt] + + where C1 and C2 are arbitrary constants and + + .. math:: F(t) = e^{\int f(t) \,dt}, P(t) = e^{\int p(t) \,dt} + + """ + C1, C2, C3, C4 = get_numbered_constants(eq, num=4) + e1 = r['a']*r['b']*r['c'] - r['b']**2*r['c'] + r['a']*diff(r['b'],t) - diff(r['a'],t)*r['b'] + e2 = r['a']*r['c']*r['d'] - r['b']*r['c']**2 + diff(r['c'],t)*r['d'] - r['c']*diff(r['d'],t) + m1 = r['a']*r['b'] + r['b']*r['d'] + diff(r['b'],t) + m2 = r['a']*r['c'] + r['c']*r['d'] + diff(r['c'],t) + if e1 == 0: + sol1 = dsolve(r['b']*diff(x(t),t,t) - m1*diff(x(t),t)).rhs + sol2 = dsolve(diff(y(t),t) - r['c']*sol1 - r['d']*y(t)).rhs + elif e2 == 0: + sol2 = dsolve(r['c']*diff(y(t),t,t) - m2*diff(y(t),t)).rhs + sol1 = dsolve(diff(x(t),t) - r['a']*x(t) - r['b']*sol2).rhs + elif not (e1/r['b']).has(t) and not (m1/r['b']).has(t): + sol1 = dsolve(diff(x(t),t,t) - (m1/r['b'])*diff(x(t),t) - (e1/r['b'])*x(t)).rhs + sol2 = dsolve(diff(y(t),t) - r['c']*sol1 - r['d']*y(t)).rhs + elif not (e2/r['c']).has(t) and not (m2/r['c']).has(t): + sol2 = dsolve(diff(y(t),t,t) - (m2/r['c'])*diff(y(t),t) - (e2/r['c'])*y(t)).rhs + sol1 = dsolve(diff(x(t),t) - r['a']*x(t) - r['b']*sol2).rhs + else: + x0 = Function('x0')(t) # x0 and y0 being particular solutions + y0 = Function('y0')(t) + F = exp(Integral(r['a'],t)) + P = exp(Integral(r['d'],t)) + sol1 = C1*x0 + C2*x0*Integral(r['b']*F*P/x0**2, t) + sol2 = C1*y0 + C2*(F*P/x0 + y0*Integral(r['b']*F*P/x0**2, t)) + return [Eq(x(t), sol1), Eq(y(t), sol2)] + + +def sysode_nonlinear_2eq_order1(match_): + func = match_['func'] + eq = match_['eq'] + fc = match_['func_coeff'] + t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] + if match_['type_of_equation'] == 'type5': + sol = _nonlinear_2eq_order1_type5(func, t, eq) + return sol + x = func[0].func + y = func[1].func + for i in range(2): + eqs = 0 + for terms in Add.make_args(eq[i]): + eqs += terms/fc[i,func[i],1] + eq[i] = eqs + if match_['type_of_equation'] == 'type1': + sol = _nonlinear_2eq_order1_type1(x, y, t, eq) + elif match_['type_of_equation'] == 'type2': + sol = _nonlinear_2eq_order1_type2(x, y, t, eq) + elif match_['type_of_equation'] == 'type3': + sol = _nonlinear_2eq_order1_type3(x, y, t, eq) + elif match_['type_of_equation'] == 'type4': + sol = _nonlinear_2eq_order1_type4(x, y, t, eq) + return sol + + +def _nonlinear_2eq_order1_type1(x, y, t, eq): + r""" + Equations: + + .. math:: x' = x^n F(x,y) + + .. math:: y' = g(y) F(x,y) + + Solution: + + .. math:: x = \varphi(y), \int \frac{1}{g(y) F(\varphi(y),y)} \,dy = t + C_2 + + where + + if `n \neq 1` + + .. math:: \varphi = [C_1 + (1-n) \int \frac{1}{g(y)} \,dy]^{\frac{1}{1-n}} + + if `n = 1` + + .. math:: \varphi = C_1 e^{\int \frac{1}{g(y)} \,dy} + + where `C_1` and `C_2` are arbitrary constants. + + """ + C1, C2 = get_numbered_constants(eq, num=2) + n = Wild('n', exclude=[x(t),y(t)]) + f = Wild('f') + u, v = symbols('u, v') + r = eq[0].match(diff(x(t),t) - x(t)**n*f) + g = ((diff(y(t),t) - eq[1])/r[f]).subs(y(t),v) + F = r[f].subs(x(t),u).subs(y(t),v) + n = r[n] + if n!=1: + phi = (C1 + (1-n)*Integral(1/g, v))**(1/(1-n)) + else: + phi = C1*exp(Integral(1/g, v)) + phi = phi.doit() + sol2 = solve(Integral(1/(g*F.subs(u,phi)), v).doit() - t - C2, v) + sol = [] + for sols in sol2: + sol.append(Eq(x(t),phi.subs(v, sols))) + sol.append(Eq(y(t), sols)) + return sol + +def _nonlinear_2eq_order1_type2(x, y, t, eq): + r""" + Equations: + + .. math:: x' = e^{\lambda x} F(x,y) + + .. math:: y' = g(y) F(x,y) + + Solution: + + .. math:: x = \varphi(y), \int \frac{1}{g(y) F(\varphi(y),y)} \,dy = t + C_2 + + where + + if `\lambda \neq 0` + + .. math:: \varphi = -\frac{1}{\lambda} log(C_1 - \lambda \int \frac{1}{g(y)} \,dy) + + if `\lambda = 0` + + .. math:: \varphi = C_1 + \int \frac{1}{g(y)} \,dy + + where `C_1` and `C_2` are arbitrary constants. + + """ + C1, C2 = get_numbered_constants(eq, num=2) + n = Wild('n', exclude=[x(t),y(t)]) + f = Wild('f') + u, v = symbols('u, v') + r = eq[0].match(diff(x(t),t) - exp(n*x(t))*f) + g = ((diff(y(t),t) - eq[1])/r[f]).subs(y(t),v) + F = r[f].subs(x(t),u).subs(y(t),v) + n = r[n] + if n: + phi = -1/n*log(C1 - n*Integral(1/g, v)) + else: + phi = C1 + Integral(1/g, v) + phi = phi.doit() + sol2 = solve(Integral(1/(g*F.subs(u,phi)), v).doit() - t - C2, v) + sol = [] + for sols in sol2: + sol.append(Eq(x(t),phi.subs(v, sols))) + sol.append(Eq(y(t), sols)) + return sol + +def _nonlinear_2eq_order1_type3(x, y, t, eq): + r""" + Autonomous system of general form + + .. math:: x' = F(x,y) + + .. math:: y' = G(x,y) + + Assuming `y = y(x, C_1)` where `C_1` is an arbitrary constant is the general + solution of the first-order equation + + .. math:: F(x,y) y'_x = G(x,y) + + Then the general solution of the original system of equations has the form + + .. math:: \int \frac{1}{F(x,y(x,C_1))} \,dx = t + C_1 + + """ + C1, C2, C3, C4 = get_numbered_constants(eq, num=4) + v = Function('v') + u = Symbol('u') + f = Wild('f') + g = Wild('g') + r1 = eq[0].match(diff(x(t),t) - f) + r2 = eq[1].match(diff(y(t),t) - g) + F = r1[f].subs(x(t), u).subs(y(t), v(u)) + G = r2[g].subs(x(t), u).subs(y(t), v(u)) + sol2r = dsolve(Eq(diff(v(u), u), G/F)) + if isinstance(sol2r, Equality): + sol2r = [sol2r] + for sol2s in sol2r: + sol1 = solve(Integral(1/F.subs(v(u), sol2s.rhs), u).doit() - t - C2, u) + sol = [] + for sols in sol1: + sol.append(Eq(x(t), sols)) + sol.append(Eq(y(t), (sol2s.rhs).subs(u, sols))) + return sol + +def _nonlinear_2eq_order1_type4(x, y, t, eq): + r""" + Equation: + + .. math:: x' = f_1(x) g_1(y) \phi(x,y,t) + + .. math:: y' = f_2(x) g_2(y) \phi(x,y,t) + + First integral: + + .. math:: \int \frac{f_2(x)}{f_1(x)} \,dx - \int \frac{g_1(y)}{g_2(y)} \,dy = C + + where `C` is an arbitrary constant. + + On solving the first integral for `x` (resp., `y` ) and on substituting the + resulting expression into either equation of the original solution, one + arrives at a first-order equation for determining `y` (resp., `x` ). + + """ + C1, C2 = get_numbered_constants(eq, num=2) + u, v = symbols('u, v') + U, V = symbols('U, V', cls=Function) + f = Wild('f') + g = Wild('g') + f1 = Wild('f1', exclude=[v,t]) + f2 = Wild('f2', exclude=[v,t]) + g1 = Wild('g1', exclude=[u,t]) + g2 = Wild('g2', exclude=[u,t]) + r1 = eq[0].match(diff(x(t),t) - f) + r2 = eq[1].match(diff(y(t),t) - g) + num, den = ( + (r1[f].subs(x(t),u).subs(y(t),v))/ + (r2[g].subs(x(t),u).subs(y(t),v))).as_numer_denom() + R1 = num.match(f1*g1) + R2 = den.match(f2*g2) + phi = (r1[f].subs(x(t),u).subs(y(t),v))/num + F1 = R1[f1]; F2 = R2[f2] + G1 = R1[g1]; G2 = R2[g2] + sol1r = solve(Integral(F2/F1, u).doit() - Integral(G1/G2,v).doit() - C1, u) + sol2r = solve(Integral(F2/F1, u).doit() - Integral(G1/G2,v).doit() - C1, v) + sol = [] + for sols in sol1r: + sol.append(Eq(y(t), dsolve(diff(V(t),t) - F2.subs(u,sols).subs(v,V(t))*G2.subs(v,V(t))*phi.subs(u,sols).subs(v,V(t))).rhs)) + for sols in sol2r: + sol.append(Eq(x(t), dsolve(diff(U(t),t) - F1.subs(u,U(t))*G1.subs(v,sols).subs(u,U(t))*phi.subs(v,sols).subs(u,U(t))).rhs)) + return set(sol) + +def _nonlinear_2eq_order1_type5(func, t, eq): + r""" + Clairaut system of ODEs + + .. math:: x = t x' + F(x',y') + + .. math:: y = t y' + G(x',y') + + The following are solutions of the system + + `(i)` straight lines: + + .. math:: x = C_1 t + F(C_1, C_2), y = C_2 t + G(C_1, C_2) + + where `C_1` and `C_2` are arbitrary constants; + + `(ii)` envelopes of the above lines; + + `(iii)` continuously differentiable lines made up from segments of the lines + `(i)` and `(ii)`. + + """ + C1, C2 = get_numbered_constants(eq, num=2) + f = Wild('f') + g = Wild('g') + def check_type(x, y): + r1 = eq[0].match(t*diff(x(t),t) - x(t) + f) + r2 = eq[1].match(t*diff(y(t),t) - y(t) + g) + if not (r1 and r2): + r1 = eq[0].match(diff(x(t),t) - x(t)/t + f/t) + r2 = eq[1].match(diff(y(t),t) - y(t)/t + g/t) + if not (r1 and r2): + r1 = (-eq[0]).match(t*diff(x(t),t) - x(t) + f) + r2 = (-eq[1]).match(t*diff(y(t),t) - y(t) + g) + if not (r1 and r2): + r1 = (-eq[0]).match(diff(x(t),t) - x(t)/t + f/t) + r2 = (-eq[1]).match(diff(y(t),t) - y(t)/t + g/t) + return [r1, r2] + for func_ in func: + if isinstance(func_, list): + x = func[0][0].func + y = func[0][1].func + [r1, r2] = check_type(x, y) + if not (r1 and r2): + [r1, r2] = check_type(y, x) + x, y = y, x + x1 = diff(x(t),t); y1 = diff(y(t),t) + return {Eq(x(t), C1*t + r1[f].subs(x1,C1).subs(y1,C2)), Eq(y(t), C2*t + r2[g].subs(x1,C1).subs(y1,C2))} + +def sysode_nonlinear_3eq_order1(match_): + x = match_['func'][0].func + y = match_['func'][1].func + z = match_['func'][2].func + eq = match_['eq'] + t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] + if match_['type_of_equation'] == 'type1': + sol = _nonlinear_3eq_order1_type1(x, y, z, t, eq) + if match_['type_of_equation'] == 'type2': + sol = _nonlinear_3eq_order1_type2(x, y, z, t, eq) + if match_['type_of_equation'] == 'type3': + sol = _nonlinear_3eq_order1_type3(x, y, z, t, eq) + if match_['type_of_equation'] == 'type4': + sol = _nonlinear_3eq_order1_type4(x, y, z, t, eq) + if match_['type_of_equation'] == 'type5': + sol = _nonlinear_3eq_order1_type5(x, y, z, t, eq) + return sol + +def _nonlinear_3eq_order1_type1(x, y, z, t, eq): + r""" + Equations: + + .. math:: a x' = (b - c) y z, \enspace b y' = (c - a) z x, \enspace c z' = (a - b) x y + + First Integrals: + + .. math:: a x^{2} + b y^{2} + c z^{2} = C_1 + + .. math:: a^{2} x^{2} + b^{2} y^{2} + c^{2} z^{2} = C_2 + + where `C_1` and `C_2` are arbitrary constants. On solving the integrals for `y` and + `z` and on substituting the resulting expressions into the first equation of the + system, we arrives at a separable first-order equation on `x`. Similarly doing that + for other two equations, we will arrive at first order equation on `y` and `z` too. + + References + ========== + -https://eqworld.ipmnet.ru/en/solutions/sysode/sode0401.pdf + + """ + C1, C2 = get_numbered_constants(eq, num=2) + u, v, w = symbols('u, v, w') + p = Wild('p', exclude=[x(t), y(t), z(t), t]) + q = Wild('q', exclude=[x(t), y(t), z(t), t]) + s = Wild('s', exclude=[x(t), y(t), z(t), t]) + r = (diff(x(t),t) - eq[0]).match(p*y(t)*z(t)) + r.update((diff(y(t),t) - eq[1]).match(q*z(t)*x(t))) + r.update((diff(z(t),t) - eq[2]).match(s*x(t)*y(t))) + n1, d1 = r[p].as_numer_denom() + n2, d2 = r[q].as_numer_denom() + n3, d3 = r[s].as_numer_denom() + val = solve([n1*u-d1*v+d1*w, d2*u+n2*v-d2*w, d3*u-d3*v-n3*w],[u,v]) + vals = [val[v], val[u]] + c = lcm(vals[0].as_numer_denom()[1], vals[1].as_numer_denom()[1]) + b = vals[0].subs(w, c) + a = vals[1].subs(w, c) + y_x = sqrt(((c*C1-C2) - a*(c-a)*x(t)**2)/(b*(c-b))) + z_x = sqrt(((b*C1-C2) - a*(b-a)*x(t)**2)/(c*(b-c))) + z_y = sqrt(((a*C1-C2) - b*(a-b)*y(t)**2)/(c*(a-c))) + x_y = sqrt(((c*C1-C2) - b*(c-b)*y(t)**2)/(a*(c-a))) + x_z = sqrt(((b*C1-C2) - c*(b-c)*z(t)**2)/(a*(b-a))) + y_z = sqrt(((a*C1-C2) - c*(a-c)*z(t)**2)/(b*(a-b))) + sol1 = dsolve(a*diff(x(t),t) - (b-c)*y_x*z_x) + sol2 = dsolve(b*diff(y(t),t) - (c-a)*z_y*x_y) + sol3 = dsolve(c*diff(z(t),t) - (a-b)*x_z*y_z) + return [sol1, sol2, sol3] + + +def _nonlinear_3eq_order1_type2(x, y, z, t, eq): + r""" + Equations: + + .. math:: a x' = (b - c) y z f(x, y, z, t) + + .. math:: b y' = (c - a) z x f(x, y, z, t) + + .. math:: c z' = (a - b) x y f(x, y, z, t) + + First Integrals: + + .. math:: a x^{2} + b y^{2} + c z^{2} = C_1 + + .. math:: a^{2} x^{2} + b^{2} y^{2} + c^{2} z^{2} = C_2 + + where `C_1` and `C_2` are arbitrary constants. On solving the integrals for `y` and + `z` and on substituting the resulting expressions into the first equation of the + system, we arrives at a first-order differential equations on `x`. Similarly doing + that for other two equations we will arrive at first order equation on `y` and `z`. + + References + ========== + -https://eqworld.ipmnet.ru/en/solutions/sysode/sode0402.pdf + + """ + C1, C2 = get_numbered_constants(eq, num=2) + u, v, w = symbols('u, v, w') + p = Wild('p', exclude=[x(t), y(t), z(t), t]) + q = Wild('q', exclude=[x(t), y(t), z(t), t]) + s = Wild('s', exclude=[x(t), y(t), z(t), t]) + f = Wild('f') + r1 = (diff(x(t),t) - eq[0]).match(y(t)*z(t)*f) + r = collect_const(r1[f]).match(p*f) + r.update(((diff(y(t),t) - eq[1])/r[f]).match(q*z(t)*x(t))) + r.update(((diff(z(t),t) - eq[2])/r[f]).match(s*x(t)*y(t))) + n1, d1 = r[p].as_numer_denom() + n2, d2 = r[q].as_numer_denom() + n3, d3 = r[s].as_numer_denom() + val = solve([n1*u-d1*v+d1*w, d2*u+n2*v-d2*w, -d3*u+d3*v+n3*w],[u,v]) + vals = [val[v], val[u]] + c = lcm(vals[0].as_numer_denom()[1], vals[1].as_numer_denom()[1]) + a = vals[0].subs(w, c) + b = vals[1].subs(w, c) + y_x = sqrt(((c*C1-C2) - a*(c-a)*x(t)**2)/(b*(c-b))) + z_x = sqrt(((b*C1-C2) - a*(b-a)*x(t)**2)/(c*(b-c))) + z_y = sqrt(((a*C1-C2) - b*(a-b)*y(t)**2)/(c*(a-c))) + x_y = sqrt(((c*C1-C2) - b*(c-b)*y(t)**2)/(a*(c-a))) + x_z = sqrt(((b*C1-C2) - c*(b-c)*z(t)**2)/(a*(b-a))) + y_z = sqrt(((a*C1-C2) - c*(a-c)*z(t)**2)/(b*(a-b))) + sol1 = dsolve(a*diff(x(t),t) - (b-c)*y_x*z_x*r[f]) + sol2 = dsolve(b*diff(y(t),t) - (c-a)*z_y*x_y*r[f]) + sol3 = dsolve(c*diff(z(t),t) - (a-b)*x_z*y_z*r[f]) + return [sol1, sol2, sol3] + +def _nonlinear_3eq_order1_type3(x, y, z, t, eq): + r""" + Equations: + + .. math:: x' = c F_2 - b F_3, \enspace y' = a F_3 - c F_1, \enspace z' = b F_1 - a F_2 + + where `F_n = F_n(x, y, z, t)`. + + 1. First Integral: + + .. math:: a x + b y + c z = C_1, + + where C is an arbitrary constant. + + 2. If we assume function `F_n` to be independent of `t`,i.e, `F_n` = `F_n (x, y, z)` + Then, on eliminating `t` and `z` from the first two equation of the system, one + arrives at the first-order equation + + .. math:: \frac{dy}{dx} = \frac{a F_3 (x, y, z) - c F_1 (x, y, z)}{c F_2 (x, y, z) - + b F_3 (x, y, z)} + + where `z = \frac{1}{c} (C_1 - a x - b y)` + + References + ========== + -https://eqworld.ipmnet.ru/en/solutions/sysode/sode0404.pdf + + """ + C1 = get_numbered_constants(eq, num=1) + u, v, w = symbols('u, v, w') + fu, fv, fw = symbols('u, v, w', cls=Function) + p = Wild('p', exclude=[x(t), y(t), z(t), t]) + q = Wild('q', exclude=[x(t), y(t), z(t), t]) + s = Wild('s', exclude=[x(t), y(t), z(t), t]) + F1, F2, F3 = symbols('F1, F2, F3', cls=Wild) + r1 = (diff(x(t), t) - eq[0]).match(F2-F3) + r = collect_const(r1[F2]).match(s*F2) + r.update(collect_const(r1[F3]).match(q*F3)) + if eq[1].has(r[F2]) and not eq[1].has(r[F3]): + r[F2], r[F3] = r[F3], r[F2] + r[s], r[q] = -r[q], -r[s] + r.update((diff(y(t), t) - eq[1]).match(p*r[F3] - r[s]*F1)) + a = r[p]; b = r[q]; c = r[s] + F1 = r[F1].subs(x(t), u).subs(y(t),v).subs(z(t), w) + F2 = r[F2].subs(x(t), u).subs(y(t),v).subs(z(t), w) + F3 = r[F3].subs(x(t), u).subs(y(t),v).subs(z(t), w) + z_xy = (C1-a*u-b*v)/c + y_zx = (C1-a*u-c*w)/b + x_yz = (C1-b*v-c*w)/a + y_x = dsolve(diff(fv(u),u) - ((a*F3-c*F1)/(c*F2-b*F3)).subs(w,z_xy).subs(v,fv(u))).rhs + z_x = dsolve(diff(fw(u),u) - ((b*F1-a*F2)/(c*F2-b*F3)).subs(v,y_zx).subs(w,fw(u))).rhs + z_y = dsolve(diff(fw(v),v) - ((b*F1-a*F2)/(a*F3-c*F1)).subs(u,x_yz).subs(w,fw(v))).rhs + x_y = dsolve(diff(fu(v),v) - ((c*F2-b*F3)/(a*F3-c*F1)).subs(w,z_xy).subs(u,fu(v))).rhs + y_z = dsolve(diff(fv(w),w) - ((a*F3-c*F1)/(b*F1-a*F2)).subs(u,x_yz).subs(v,fv(w))).rhs + x_z = dsolve(diff(fu(w),w) - ((c*F2-b*F3)/(b*F1-a*F2)).subs(v,y_zx).subs(u,fu(w))).rhs + sol1 = dsolve(diff(fu(t),t) - (c*F2 - b*F3).subs(v,y_x).subs(w,z_x).subs(u,fu(t))).rhs + sol2 = dsolve(diff(fv(t),t) - (a*F3 - c*F1).subs(u,x_y).subs(w,z_y).subs(v,fv(t))).rhs + sol3 = dsolve(diff(fw(t),t) - (b*F1 - a*F2).subs(u,x_z).subs(v,y_z).subs(w,fw(t))).rhs + return [sol1, sol2, sol3] + +def _nonlinear_3eq_order1_type4(x, y, z, t, eq): + r""" + Equations: + + .. math:: x' = c z F_2 - b y F_3, \enspace y' = a x F_3 - c z F_1, \enspace z' = b y F_1 - a x F_2 + + where `F_n = F_n (x, y, z, t)` + + 1. First integral: + + .. math:: a x^{2} + b y^{2} + c z^{2} = C_1 + + where `C` is an arbitrary constant. + + 2. Assuming the function `F_n` is independent of `t`: `F_n = F_n (x, y, z)`. Then on + eliminating `t` and `z` from the first two equations of the system, one arrives at + the first-order equation + + .. math:: \frac{dy}{dx} = \frac{a x F_3 (x, y, z) - c z F_1 (x, y, z)} + {c z F_2 (x, y, z) - b y F_3 (x, y, z)} + + where `z = \pm \sqrt{\frac{1}{c} (C_1 - a x^{2} - b y^{2})}` + + References + ========== + -https://eqworld.ipmnet.ru/en/solutions/sysode/sode0405.pdf + + """ + C1 = get_numbered_constants(eq, num=1) + u, v, w = symbols('u, v, w') + p = Wild('p', exclude=[x(t), y(t), z(t), t]) + q = Wild('q', exclude=[x(t), y(t), z(t), t]) + s = Wild('s', exclude=[x(t), y(t), z(t), t]) + F1, F2, F3 = symbols('F1, F2, F3', cls=Wild) + r1 = eq[0].match(diff(x(t),t) - z(t)*F2 + y(t)*F3) + r = collect_const(r1[F2]).match(s*F2) + r.update(collect_const(r1[F3]).match(q*F3)) + if eq[1].has(r[F2]) and not eq[1].has(r[F3]): + r[F2], r[F3] = r[F3], r[F2] + r[s], r[q] = -r[q], -r[s] + r.update((diff(y(t),t) - eq[1]).match(p*x(t)*r[F3] - r[s]*z(t)*F1)) + a = r[p]; b = r[q]; c = r[s] + F1 = r[F1].subs(x(t),u).subs(y(t),v).subs(z(t),w) + F2 = r[F2].subs(x(t),u).subs(y(t),v).subs(z(t),w) + F3 = r[F3].subs(x(t),u).subs(y(t),v).subs(z(t),w) + x_yz = sqrt((C1 - b*v**2 - c*w**2)/a) + y_zx = sqrt((C1 - c*w**2 - a*u**2)/b) + z_xy = sqrt((C1 - a*u**2 - b*v**2)/c) + y_x = dsolve(diff(v(u),u) - ((a*u*F3-c*w*F1)/(c*w*F2-b*v*F3)).subs(w,z_xy).subs(v,v(u))).rhs + z_x = dsolve(diff(w(u),u) - ((b*v*F1-a*u*F2)/(c*w*F2-b*v*F3)).subs(v,y_zx).subs(w,w(u))).rhs + z_y = dsolve(diff(w(v),v) - ((b*v*F1-a*u*F2)/(a*u*F3-c*w*F1)).subs(u,x_yz).subs(w,w(v))).rhs + x_y = dsolve(diff(u(v),v) - ((c*w*F2-b*v*F3)/(a*u*F3-c*w*F1)).subs(w,z_xy).subs(u,u(v))).rhs + y_z = dsolve(diff(v(w),w) - ((a*u*F3-c*w*F1)/(b*v*F1-a*u*F2)).subs(u,x_yz).subs(v,v(w))).rhs + x_z = dsolve(diff(u(w),w) - ((c*w*F2-b*v*F3)/(b*v*F1-a*u*F2)).subs(v,y_zx).subs(u,u(w))).rhs + sol1 = dsolve(diff(u(t),t) - (c*w*F2 - b*v*F3).subs(v,y_x).subs(w,z_x).subs(u,u(t))).rhs + sol2 = dsolve(diff(v(t),t) - (a*u*F3 - c*w*F1).subs(u,x_y).subs(w,z_y).subs(v,v(t))).rhs + sol3 = dsolve(diff(w(t),t) - (b*v*F1 - a*u*F2).subs(u,x_z).subs(v,y_z).subs(w,w(t))).rhs + return [sol1, sol2, sol3] + +def _nonlinear_3eq_order1_type5(x, y, z, t, eq): + r""" + .. math:: x' = x (c F_2 - b F_3), \enspace y' = y (a F_3 - c F_1), \enspace z' = z (b F_1 - a F_2) + + where `F_n = F_n (x, y, z, t)` and are arbitrary functions. + + First Integral: + + .. math:: \left|x\right|^{a} \left|y\right|^{b} \left|z\right|^{c} = C_1 + + where `C` is an arbitrary constant. If the function `F_n` is independent of `t`, + then, by eliminating `t` and `z` from the first two equations of the system, one + arrives at a first-order equation. + + References + ========== + -https://eqworld.ipmnet.ru/en/solutions/sysode/sode0406.pdf + + """ + C1 = get_numbered_constants(eq, num=1) + u, v, w = symbols('u, v, w') + fu, fv, fw = symbols('u, v, w', cls=Function) + p = Wild('p', exclude=[x(t), y(t), z(t), t]) + q = Wild('q', exclude=[x(t), y(t), z(t), t]) + s = Wild('s', exclude=[x(t), y(t), z(t), t]) + F1, F2, F3 = symbols('F1, F2, F3', cls=Wild) + r1 = eq[0].match(diff(x(t), t) - x(t)*F2 + x(t)*F3) + r = collect_const(r1[F2]).match(s*F2) + r.update(collect_const(r1[F3]).match(q*F3)) + if eq[1].has(r[F2]) and not eq[1].has(r[F3]): + r[F2], r[F3] = r[F3], r[F2] + r[s], r[q] = -r[q], -r[s] + r.update((diff(y(t), t) - eq[1]).match(y(t)*(p*r[F3] - r[s]*F1))) + a = r[p]; b = r[q]; c = r[s] + F1 = r[F1].subs(x(t), u).subs(y(t), v).subs(z(t), w) + F2 = r[F2].subs(x(t), u).subs(y(t), v).subs(z(t), w) + F3 = r[F3].subs(x(t), u).subs(y(t), v).subs(z(t), w) + x_yz = (C1*v**-b*w**-c)**-a + y_zx = (C1*w**-c*u**-a)**-b + z_xy = (C1*u**-a*v**-b)**-c + y_x = dsolve(diff(fv(u), u) - ((v*(a*F3 - c*F1))/(u*(c*F2 - b*F3))).subs(w, z_xy).subs(v, fv(u))).rhs + z_x = dsolve(diff(fw(u), u) - ((w*(b*F1 - a*F2))/(u*(c*F2 - b*F3))).subs(v, y_zx).subs(w, fw(u))).rhs + z_y = dsolve(diff(fw(v), v) - ((w*(b*F1 - a*F2))/(v*(a*F3 - c*F1))).subs(u, x_yz).subs(w, fw(v))).rhs + x_y = dsolve(diff(fu(v), v) - ((u*(c*F2 - b*F3))/(v*(a*F3 - c*F1))).subs(w, z_xy).subs(u, fu(v))).rhs + y_z = dsolve(diff(fv(w), w) - ((v*(a*F3 - c*F1))/(w*(b*F1 - a*F2))).subs(u, x_yz).subs(v, fv(w))).rhs + x_z = dsolve(diff(fu(w), w) - ((u*(c*F2 - b*F3))/(w*(b*F1 - a*F2))).subs(v, y_zx).subs(u, fu(w))).rhs + sol1 = dsolve(diff(fu(t), t) - (u*(c*F2 - b*F3)).subs(v, y_x).subs(w, z_x).subs(u, fu(t))).rhs + sol2 = dsolve(diff(fv(t), t) - (v*(a*F3 - c*F1)).subs(u, x_y).subs(w, z_y).subs(v, fv(t))).rhs + sol3 = dsolve(diff(fw(t), t) - (w*(b*F1 - a*F2)).subs(u, x_z).subs(v, y_z).subs(w, fw(t))).rhs + return [sol1, sol2, sol3] + + +#This import is written at the bottom to avoid circular imports. +from .single import SingleODEProblem, SingleODESolver, solver_map diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/riccati.py b/lib/python3.12/site-packages/sympy/solvers/ode/riccati.py new file mode 100644 index 0000000000000000000000000000000000000000..2ef66ed0896d39bee8fba1b74a0c93734742fc1f --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/ode/riccati.py @@ -0,0 +1,893 @@ +r""" +This module contains :py:meth:`~sympy.solvers.ode.riccati.solve_riccati`, +a function which gives all rational particular solutions to first order +Riccati ODEs. A general first order Riccati ODE is given by - + +.. math:: y' = b_0(x) + b_1(x)w + b_2(x)w^2 + +where `b_0, b_1` and `b_2` can be arbitrary rational functions of `x` +with `b_2 \ne 0`. When `b_2 = 0`, the equation is not a Riccati ODE +anymore and becomes a Linear ODE. Similarly, when `b_0 = 0`, the equation +is a Bernoulli ODE. The algorithm presented below can find rational +solution(s) to all ODEs with `b_2 \ne 0` that have a rational solution, +or prove that no rational solution exists for the equation. + +Background +========== + +A Riccati equation can be transformed to its normal form + +.. math:: y' + y^2 = a(x) + +using the transformation + +.. math:: y = -b_2(x) - \frac{b'_2(x)}{2 b_2(x)} - \frac{b_1(x)}{2} + +where `a(x)` is given by + +.. math:: a(x) = \frac{1}{4}\left(\frac{b_2'}{b_2} + b_1\right)^2 - \frac{1}{2}\left(\frac{b_2'}{b_2} + b_1\right)' - b_0 b_2 + +Thus, we can develop an algorithm to solve for the Riccati equation +in its normal form, which would in turn give us the solution for +the original Riccati equation. + +Algorithm +========= + +The algorithm implemented here is presented in the Ph.D thesis +"Rational and Algebraic Solutions of First-Order Algebraic ODEs" +by N. Thieu Vo. The entire thesis can be found here - +https://www3.risc.jku.at/publications/download/risc_5387/PhDThesisThieu.pdf + +We have only implemented the Rational Riccati solver (Algorithm 11, +Pg 78-82 in Thesis). Before we proceed towards the implementation +of the algorithm, a few definitions to understand are - + +1. Valuation of a Rational Function at `\infty`: + The valuation of a rational function `p(x)` at `\infty` is equal + to the difference between the degree of the denominator and the + numerator of `p(x)`. + + NOTE: A general definition of valuation of a rational function + at any value of `x` can be found in Pg 63 of the thesis, but + is not of any interest for this algorithm. + +2. Zeros and Poles of a Rational Function: + Let `a(x) = \frac{S(x)}{T(x)}, T \ne 0` be a rational function + of `x`. Then - + + a. The Zeros of `a(x)` are the roots of `S(x)`. + b. The Poles of `a(x)` are the roots of `T(x)`. However, `\infty` + can also be a pole of a(x). We say that `a(x)` has a pole at + `\infty` if `a(\frac{1}{x})` has a pole at 0. + +Every pole is associated with an order that is equal to the multiplicity +of its appearance as a root of `T(x)`. A pole is called a simple pole if +it has an order 1. Similarly, a pole is called a multiple pole if it has +an order `\ge` 2. + +Necessary Conditions +==================== + +For a Riccati equation in its normal form, + +.. math:: y' + y^2 = a(x) + +we can define + +a. A pole is called a movable pole if it is a pole of `y(x)` and is not +a pole of `a(x)`. +b. Similarly, a pole is called a non-movable pole if it is a pole of both +`y(x)` and `a(x)`. + +Then, the algorithm states that a rational solution exists only if - + +a. Every pole of `a(x)` must be either a simple pole or a multiple pole +of even order. +b. The valuation of `a(x)` at `\infty` must be even or be `\ge` 2. + +This algorithm finds all possible rational solutions for the Riccati ODE. +If no rational solutions are found, it means that no rational solutions +exist. + +The algorithm works for Riccati ODEs where the coefficients are rational +functions in the independent variable `x` with rational number coefficients +i.e. in `Q(x)`. The coefficients in the rational function cannot be floats, +irrational numbers, symbols or any other kind of expression. The reasons +for this are - + +1. When using symbols, different symbols could take the same value and this +would affect the multiplicity of poles if symbols are present here. + +2. An integer degree bound is required to calculate a polynomial solution +to an auxiliary differential equation, which in turn gives the particular +solution for the original ODE. If symbols/floats/irrational numbers are +present, we cannot determine if the expression for the degree bound is an +integer or not. + +Solution +======== + +With these definitions, we can state a general form for the solution of +the equation. `y(x)` must have the form - + +.. math:: y(x) = \sum_{i=1}^{n} \sum_{j=1}^{r_i} \frac{c_{ij}}{(x - x_i)^j} + \sum_{i=1}^{m} \frac{1}{x - \chi_i} + \sum_{i=0}^{N} d_i x^i + +where `x_1, x_2, \dots, x_n` are non-movable poles of `a(x)`, +`\chi_1, \chi_2, \dots, \chi_m` are movable poles of `a(x)`, and the values +of `N, n, r_1, r_2, \dots, r_n` can be determined from `a(x)`. The +coefficient vectors `(d_0, d_1, \dots, d_N)` and `(c_{i1}, c_{i2}, \dots, c_{i r_i})` +can be determined from `a(x)`. We will have 2 choices each of these vectors +and part of the procedure is figuring out which of the 2 should be used +to get the solution correctly. + +Implementation +============== + +In this implementation, we use ``Poly`` to represent a rational function +rather than using ``Expr`` since ``Poly`` is much faster. Since we cannot +represent rational functions directly using ``Poly``, we instead represent +a rational function with 2 ``Poly`` objects - one for its numerator and +the other for its denominator. + +The code is written to match the steps given in the thesis (Pg 82) + +Step 0 : Match the equation - +Find `b_0, b_1` and `b_2`. If `b_2 = 0` or no such functions exist, raise +an error + +Step 1 : Transform the equation to its normal form as explained in the +theory section. + +Step 2 : Initialize an empty set of solutions, ``sol``. + +Step 3 : If `a(x) = 0`, append `\frac{1}/{(x - C1)}` to ``sol``. + +Step 4 : If `a(x)` is a rational non-zero number, append `\pm \sqrt{a}` +to ``sol``. + +Step 5 : Find the poles and their multiplicities of `a(x)`. Let +the number of poles be `n`. Also find the valuation of `a(x)` at +`\infty` using ``val_at_inf``. + +NOTE: Although the algorithm considers `\infty` as a pole, it is +not mentioned if it a part of the set of finite poles. `\infty` +is NOT a part of the set of finite poles. If a pole exists at +`\infty`, we use its multiplicity to find the laurent series of +`a(x)` about `\infty`. + +Step 6 : Find `n` c-vectors (one for each pole) and 1 d-vector using +``construct_c`` and ``construct_d``. Now, determine all the ``2**(n + 1)`` +combinations of choosing between 2 choices for each of the `n` c-vectors +and 1 d-vector. + +NOTE: The equation for `d_{-1}` in Case 4 (Pg 80) has a printinig +mistake. The term `- d_N` must be replaced with `-N d_N`. The same +has been explained in the code as well. + +For each of these above combinations, do + +Step 8 : Compute `m` in ``compute_m_ybar``. `m` is the degree bound of +the polynomial solution we must find for the auxiliary equation. + +Step 9 : In ``compute_m_ybar``, compute ybar as well where ``ybar`` is +one part of y(x) - + +.. math:: \overline{y}(x) = \sum_{i=1}^{n} \sum_{j=1}^{r_i} \frac{c_{ij}}{(x - x_i)^j} + \sum_{i=0}^{N} d_i x^i + +Step 10 : If `m` is a non-negative integer - + +Step 11: Find a polynomial solution of degree `m` for the auxiliary equation. + +There are 2 cases possible - + + a. `m` is a non-negative integer: We can solve for the coefficients + in `p(x)` using Undetermined Coefficients. + + b. `m` is not a non-negative integer: In this case, we cannot find + a polynomial solution to the auxiliary equation, and hence, we ignore + this value of `m`. + +Step 12 : For each `p(x)` that exists, append `ybar + \frac{p'(x)}{p(x)}` +to ``sol``. + +Step 13 : For each solution in ``sol``, apply an inverse transformation, +so that the solutions of the original equation are found using the +solutions of the equation in its normal form. +""" + + +from itertools import product +from sympy.core import S +from sympy.core.add import Add +from sympy.core.numbers import oo, Float +from sympy.core.function import count_ops +from sympy.core.relational import Eq +from sympy.core.symbol import symbols, Symbol, Dummy +from sympy.functions import sqrt, exp +from sympy.functions.elementary.complexes import sign +from sympy.integrals.integrals import Integral +from sympy.polys.domains import ZZ +from sympy.polys.polytools import Poly +from sympy.polys.polyroots import roots +from sympy.solvers.solveset import linsolve + + +def riccati_normal(w, x, b1, b2): + """ + Given a solution `w(x)` to the equation + + .. math:: w'(x) = b_0(x) + b_1(x)*w(x) + b_2(x)*w(x)^2 + + and rational function coefficients `b_1(x)` and + `b_2(x)`, this function transforms the solution to + give a solution `y(x)` for its corresponding normal + Riccati ODE + + .. math:: y'(x) + y(x)^2 = a(x) + + using the transformation + + .. math:: y(x) = -b_2(x)*w(x) - b'_2(x)/(2*b_2(x)) - b_1(x)/2 + """ + return -b2*w - b2.diff(x)/(2*b2) - b1/2 + + +def riccati_inverse_normal(y, x, b1, b2, bp=None): + """ + Inverse transforming the solution to the normal + Riccati ODE to get the solution to the Riccati ODE. + """ + # bp is the expression which is independent of the solution + # and hence, it need not be computed again + if bp is None: + bp = -b2.diff(x)/(2*b2**2) - b1/(2*b2) + # w(x) = -y(x)/b2(x) - b2'(x)/(2*b2(x)^2) - b1(x)/(2*b2(x)) + return -y/b2 + bp + + +def riccati_reduced(eq, f, x): + """ + Convert a Riccati ODE into its corresponding + normal Riccati ODE. + """ + match, funcs = match_riccati(eq, f, x) + # If equation is not a Riccati ODE, exit + if not match: + return False + # Using the rational functions, find the expression for a(x) + b0, b1, b2 = funcs + a = -b0*b2 + b1**2/4 - b1.diff(x)/2 + 3*b2.diff(x)**2/(4*b2**2) + b1*b2.diff(x)/(2*b2) - \ + b2.diff(x, 2)/(2*b2) + # Normal form of Riccati ODE is f'(x) + f(x)^2 = a(x) + return f(x).diff(x) + f(x)**2 - a + +def linsolve_dict(eq, syms): + """ + Get the output of linsolve as a dict + """ + # Convert tuple type return value of linsolve + # to a dictionary for ease of use + sol = linsolve(eq, syms) + if not sol: + return {} + return dict(zip(syms, list(sol)[0])) + + +def match_riccati(eq, f, x): + """ + A function that matches and returns the coefficients + if an equation is a Riccati ODE + + Parameters + ========== + + eq: Equation to be matched + f: Dependent variable + x: Independent variable + + Returns + ======= + + match: True if equation is a Riccati ODE, False otherwise + funcs: [b0, b1, b2] if match is True, [] otherwise. Here, + b0, b1 and b2 are rational functions which match the equation. + """ + # Group terms based on f(x) + if isinstance(eq, Eq): + eq = eq.lhs - eq.rhs + eq = eq.expand().collect(f(x)) + cf = eq.coeff(f(x).diff(x)) + + # There must be an f(x).diff(x) term. + # eq must be an Add object since we are using the expanded + # equation and it must have atleast 2 terms (b2 != 0) + if cf != 0 and isinstance(eq, Add): + + # Divide all coefficients by the coefficient of f(x).diff(x) + # and add the terms again to get the same equation + eq = Add(*((x/cf).cancel() for x in eq.args)).collect(f(x)) + + # Match the equation with the pattern + b1 = -eq.coeff(f(x)) + b2 = -eq.coeff(f(x)**2) + b0 = (f(x).diff(x) - b1*f(x) - b2*f(x)**2 - eq).expand() + funcs = [b0, b1, b2] + + # Check if coefficients are not symbols and floats + if any(len(x.atoms(Symbol)) > 1 or len(x.atoms(Float)) for x in funcs): + return False, [] + + # If b_0(x) contains f(x), it is not a Riccati ODE + if len(b0.atoms(f)) or not all((b2 != 0, b0.is_rational_function(x), + b1.is_rational_function(x), b2.is_rational_function(x))): + return False, [] + return True, funcs + return False, [] + + +def val_at_inf(num, den, x): + # Valuation of a rational function at oo = deg(denom) - deg(numer) + return den.degree(x) - num.degree(x) + + +def check_necessary_conds(val_inf, muls): + """ + The necessary conditions for a rational solution + to exist are as follows - + + i) Every pole of a(x) must be either a simple pole + or a multiple pole of even order. + + ii) The valuation of a(x) at infinity must be even + or be greater than or equal to 2. + + Here, a simple pole is a pole with multiplicity 1 + and a multiple pole is a pole with multiplicity + greater than 1. + """ + return (val_inf >= 2 or (val_inf <= 0 and val_inf%2 == 0)) and \ + all(mul == 1 or (mul%2 == 0 and mul >= 2) for mul in muls) + + +def inverse_transform_poly(num, den, x): + """ + A function to make the substitution + x -> 1/x in a rational function that + is represented using Poly objects for + numerator and denominator. + """ + # Declare for reuse + one = Poly(1, x) + xpoly = Poly(x, x) + + # Check if degree of numerator is same as denominator + pwr = val_at_inf(num, den, x) + if pwr >= 0: + # Denominator has greater degree. Substituting x with + # 1/x would make the extra power go to the numerator + if num.expr != 0: + num = num.transform(one, xpoly) * x**pwr + den = den.transform(one, xpoly) + else: + # Numerator has greater degree. Substituting x with + # 1/x would make the extra power go to the denominator + num = num.transform(one, xpoly) + den = den.transform(one, xpoly) * x**(-pwr) + return num.cancel(den, include=True) + + +def limit_at_inf(num, den, x): + """ + Find the limit of a rational function + at oo + """ + # pwr = degree(num) - degree(den) + pwr = -val_at_inf(num, den, x) + # Numerator has a greater degree than denominator + # Limit at infinity would depend on the sign of the + # leading coefficients of numerator and denominator + if pwr > 0: + return oo*sign(num.LC()/den.LC()) + # Degree of numerator is equal to that of denominator + # Limit at infinity is just the ratio of leading coeffs + elif pwr == 0: + return num.LC()/den.LC() + # Degree of numerator is less than that of denominator + # Limit at infinity is just 0 + else: + return 0 + + +def construct_c_case_1(num, den, x, pole): + # Find the coefficient of 1/(x - pole)**2 in the + # Laurent series expansion of a(x) about pole. + num1, den1 = (num*Poly((x - pole)**2, x, extension=True)).cancel(den, include=True) + r = (num1.subs(x, pole))/(den1.subs(x, pole)) + + # If multiplicity is 2, the coefficient to be added + # in the c-vector is c = (1 +- sqrt(1 + 4*r))/2 + if r != -S(1)/4: + return [[(1 + sqrt(1 + 4*r))/2], [(1 - sqrt(1 + 4*r))/2]] + return [[S.Half]] + + +def construct_c_case_2(num, den, x, pole, mul): + # Generate the coefficients using the recurrence + # relation mentioned in (5.14) in the thesis (Pg 80) + + # r_i = mul/2 + ri = mul//2 + + # Find the Laurent series coefficients about the pole + ser = rational_laurent_series(num, den, x, pole, mul, 6) + + # Start with an empty memo to store the coefficients + # This is for the plus case + cplus = [0 for i in range(ri)] + + # Base Case + cplus[ri-1] = sqrt(ser[2*ri]) + + # Iterate backwards to find all coefficients + s = ri - 1 + sm = 0 + for s in range(ri-1, 0, -1): + sm = 0 + for j in range(s+1, ri): + sm += cplus[j-1]*cplus[ri+s-j-1] + if s!= 1: + cplus[s-1] = (ser[ri+s] - sm)/(2*cplus[ri-1]) + + # Memo for the minus case + cminus = [-x for x in cplus] + + # Find the 0th coefficient in the recurrence + cplus[0] = (ser[ri+s] - sm - ri*cplus[ri-1])/(2*cplus[ri-1]) + cminus[0] = (ser[ri+s] - sm - ri*cminus[ri-1])/(2*cminus[ri-1]) + + # Add both the plus and minus cases' coefficients + if cplus != cminus: + return [cplus, cminus] + return cplus + + +def construct_c_case_3(): + # If multiplicity is 1, the coefficient to be added + # in the c-vector is 1 (no choice) + return [[1]] + + +def construct_c(num, den, x, poles, muls): + """ + Helper function to calculate the coefficients + in the c-vector for each pole. + """ + c = [] + for pole, mul in zip(poles, muls): + c.append([]) + + # Case 3 + if mul == 1: + # Add the coefficients from Case 3 + c[-1].extend(construct_c_case_3()) + + # Case 1 + elif mul == 2: + # Add the coefficients from Case 1 + c[-1].extend(construct_c_case_1(num, den, x, pole)) + + # Case 2 + else: + # Add the coefficients from Case 2 + c[-1].extend(construct_c_case_2(num, den, x, pole, mul)) + + return c + + +def construct_d_case_4(ser, N): + # Initialize an empty vector + dplus = [0 for i in range(N+2)] + # d_N = sqrt(a_{2*N}) + dplus[N] = sqrt(ser[2*N]) + + # Use the recurrence relations to find + # the value of d_s + for s in range(N-1, -2, -1): + sm = 0 + for j in range(s+1, N): + sm += dplus[j]*dplus[N+s-j] + if s != -1: + dplus[s] = (ser[N+s] - sm)/(2*dplus[N]) + + # Coefficients for the case of d_N = -sqrt(a_{2*N}) + dminus = [-x for x in dplus] + + # The third equation in Eq 5.15 of the thesis is WRONG! + # d_N must be replaced with N*d_N in that equation. + dplus[-1] = (ser[N+s] - N*dplus[N] - sm)/(2*dplus[N]) + dminus[-1] = (ser[N+s] - N*dminus[N] - sm)/(2*dminus[N]) + + if dplus != dminus: + return [dplus, dminus] + return dplus + + +def construct_d_case_5(ser): + # List to store coefficients for plus case + dplus = [0, 0] + + # d_0 = sqrt(a_0) + dplus[0] = sqrt(ser[0]) + + # d_(-1) = a_(-1)/(2*d_0) + dplus[-1] = ser[-1]/(2*dplus[0]) + + # Coefficients for the minus case are just the negative + # of the coefficients for the positive case. + dminus = [-x for x in dplus] + + if dplus != dminus: + return [dplus, dminus] + return dplus + + +def construct_d_case_6(num, den, x): + # s_oo = lim x->0 1/x**2 * a(1/x) which is equivalent to + # s_oo = lim x->oo x**2 * a(x) + s_inf = limit_at_inf(Poly(x**2, x)*num, den, x) + + # d_(-1) = (1 +- sqrt(1 + 4*s_oo))/2 + if s_inf != -S(1)/4: + return [[(1 + sqrt(1 + 4*s_inf))/2], [(1 - sqrt(1 + 4*s_inf))/2]] + return [[S.Half]] + + +def construct_d(num, den, x, val_inf): + """ + Helper function to calculate the coefficients + in the d-vector based on the valuation of the + function at oo. + """ + N = -val_inf//2 + # Multiplicity of oo as a pole + mul = -val_inf if val_inf < 0 else 0 + ser = rational_laurent_series(num, den, x, oo, mul, 1) + + # Case 4 + if val_inf < 0: + d = construct_d_case_4(ser, N) + + # Case 5 + elif val_inf == 0: + d = construct_d_case_5(ser) + + # Case 6 + else: + d = construct_d_case_6(num, den, x) + + return d + + +def rational_laurent_series(num, den, x, r, m, n): + r""" + The function computes the Laurent series coefficients + of a rational function. + + Parameters + ========== + + num: A Poly object that is the numerator of `f(x)`. + den: A Poly object that is the denominator of `f(x)`. + x: The variable of expansion of the series. + r: The point of expansion of the series. + m: Multiplicity of r if r is a pole of `f(x)`. Should + be zero otherwise. + n: Order of the term upto which the series is expanded. + + Returns + ======= + + series: A dictionary that has power of the term as key + and coefficient of that term as value. + + Below is a basic outline of how the Laurent series of a + rational function `f(x)` about `x_0` is being calculated - + + 1. Substitute `x + x_0` in place of `x`. If `x_0` + is a pole of `f(x)`, multiply the expression by `x^m` + where `m` is the multiplicity of `x_0`. Denote the + the resulting expression as g(x). We do this substitution + so that we can now find the Laurent series of g(x) about + `x = 0`. + + 2. We can then assume that the Laurent series of `g(x)` + takes the following form - + + .. math:: g(x) = \frac{num(x)}{den(x)} = \sum_{m = 0}^{\infty} a_m x^m + + where `a_m` denotes the Laurent series coefficients. + + 3. Multiply the denominator to the RHS of the equation + and form a recurrence relation for the coefficients `a_m`. + """ + one = Poly(1, x, extension=True) + + if r == oo: + # Series at x = oo is equal to first transforming + # the function from x -> 1/x and finding the + # series at x = 0 + num, den = inverse_transform_poly(num, den, x) + r = S(0) + + if r: + # For an expansion about a non-zero point, a + # transformation from x -> x + r must be made + num = num.transform(Poly(x + r, x, extension=True), one) + den = den.transform(Poly(x + r, x, extension=True), one) + + # Remove the pole from the denominator if the series + # expansion is about one of the poles + num, den = (num*x**m).cancel(den, include=True) + + # Equate coefficients for the first terms (base case) + maxdegree = 1 + max(num.degree(), den.degree()) + syms = symbols(f'a:{maxdegree}', cls=Dummy) + diff = num - den * Poly(syms[::-1], x) + coeff_diffs = diff.all_coeffs()[::-1][:maxdegree] + (coeffs, ) = linsolve(coeff_diffs, syms) + + # Use the recursion relation for the rest + recursion = den.all_coeffs()[::-1] + div, rec_rhs = recursion[0], recursion[1:] + series = list(coeffs) + while len(series) < n: + next_coeff = Add(*(c*series[-1-n] for n, c in enumerate(rec_rhs))) / div + series.append(-next_coeff) + series = {m - i: val for i, val in enumerate(series)} + return series + +def compute_m_ybar(x, poles, choice, N): + """ + Helper function to calculate - + + 1. m - The degree bound for the polynomial + solution that must be found for the auxiliary + differential equation. + + 2. ybar - Part of the solution which can be + computed using the poles, c and d vectors. + """ + ybar = 0 + m = Poly(choice[-1][-1], x, extension=True) + + # Calculate the first (nested) summation for ybar + # as given in Step 9 of the Thesis (Pg 82) + dybar = [] + for i, polei in enumerate(poles): + for j, cij in enumerate(choice[i]): + dybar.append(cij/(x - polei)**(j + 1)) + m -=Poly(choice[i][0], x, extension=True) # can't accumulate Poly and use with Add + ybar += Add(*dybar) + + # Calculate the second summation for ybar + for i in range(N+1): + ybar += choice[-1][i]*x**i + return (m.expr, ybar) + + +def solve_aux_eq(numa, dena, numy, deny, x, m): + """ + Helper function to find a polynomial solution + of degree m for the auxiliary differential + equation. + """ + # Assume that the solution is of the type + # p(x) = C_0 + C_1*x + ... + C_{m-1}*x**(m-1) + x**m + psyms = symbols(f'C0:{m}', cls=Dummy) + K = ZZ[psyms] + psol = Poly(K.gens, x, domain=K) + Poly(x**m, x, domain=K) + + # Eq (5.16) in Thesis - Pg 81 + auxeq = (dena*(numy.diff(x)*deny - numy*deny.diff(x) + numy**2) - numa*deny**2)*psol + if m >= 1: + px = psol.diff(x) + auxeq += px*(2*numy*deny*dena) + if m >= 2: + auxeq += px.diff(x)*(deny**2*dena) + if m != 0: + # m is a non-zero integer. Find the constant terms using undetermined coefficients + return psol, linsolve_dict(auxeq.all_coeffs(), psyms), True + else: + # m == 0 . Check if 1 (x**0) is a solution to the auxiliary equation + return S.One, auxeq, auxeq == 0 + + +def remove_redundant_sols(sol1, sol2, x): + """ + Helper function to remove redundant + solutions to the differential equation. + """ + # If y1 and y2 are redundant solutions, there is + # some value of the arbitrary constant for which + # they will be equal + + syms1 = sol1.atoms(Symbol, Dummy) + syms2 = sol2.atoms(Symbol, Dummy) + num1, den1 = [Poly(e, x, extension=True) for e in sol1.together().as_numer_denom()] + num2, den2 = [Poly(e, x, extension=True) for e in sol2.together().as_numer_denom()] + # Cross multiply + e = num1*den2 - den1*num2 + # Check if there are any constants + syms = list(e.atoms(Symbol, Dummy)) + if len(syms): + # Find values of constants for which solutions are equal + redn = linsolve(e.all_coeffs(), syms) + if len(redn): + # Return the general solution over a particular solution + if len(syms1) > len(syms2): + return sol2 + # If both have constants, return the lesser complex solution + elif len(syms1) == len(syms2): + return sol1 if count_ops(syms1) >= count_ops(syms2) else sol2 + else: + return sol1 + + +def get_gen_sol_from_part_sol(part_sols, a, x): + """" + Helper function which computes the general + solution for a Riccati ODE from its particular + solutions. + + There are 3 cases to find the general solution + from the particular solutions for a Riccati ODE + depending on the number of particular solution(s) + we have - 1, 2 or 3. + + For more information, see Section 6 of + "Methods of Solution of the Riccati Differential Equation" + by D. R. Haaheim and F. M. Stein + """ + + # If no particular solutions are found, a general + # solution cannot be found + if len(part_sols) == 0: + return [] + + # In case of a single particular solution, the general + # solution can be found by using the substitution + # y = y1 + 1/z and solving a Bernoulli ODE to find z. + elif len(part_sols) == 1: + y1 = part_sols[0] + i = exp(Integral(2*y1, x)) + z = i * Integral(a/i, x) + z = z.doit() + if a == 0 or z == 0: + return y1 + return y1 + 1/z + + # In case of 2 particular solutions, the general solution + # can be found by solving a separable equation. This is + # the most common case, i.e. most Riccati ODEs have 2 + # rational particular solutions. + elif len(part_sols) == 2: + y1, y2 = part_sols + # One of them already has a constant + if len(y1.atoms(Dummy)) + len(y2.atoms(Dummy)) > 0: + u = exp(Integral(y2 - y1, x)).doit() + # Introduce a constant + else: + C1 = Dummy('C1') + u = C1*exp(Integral(y2 - y1, x)).doit() + if u == 1: + return y2 + return (y2*u - y1)/(u - 1) + + # In case of 3 particular solutions, a closed form + # of the general solution can be obtained directly + else: + y1, y2, y3 = part_sols[:3] + C1 = Dummy('C1') + return (C1 + 1)*y2*(y1 - y3)/(C1*y1 + y2 - (C1 + 1)*y3) + + +def solve_riccati(fx, x, b0, b1, b2, gensol=False): + """ + The main function that gives particular/general + solutions to Riccati ODEs that have atleast 1 + rational particular solution. + """ + # Step 1 : Convert to Normal Form + a = -b0*b2 + b1**2/4 - b1.diff(x)/2 + 3*b2.diff(x)**2/(4*b2**2) + b1*b2.diff(x)/(2*b2) - \ + b2.diff(x, 2)/(2*b2) + a_t = a.together() + num, den = [Poly(e, x, extension=True) for e in a_t.as_numer_denom()] + num, den = num.cancel(den, include=True) + + # Step 2 + presol = [] + + # Step 3 : a(x) is 0 + if num == 0: + presol.append(1/(x + Dummy('C1'))) + + # Step 4 : a(x) is a non-zero constant + elif x not in num.free_symbols.union(den.free_symbols): + presol.extend([sqrt(a), -sqrt(a)]) + + # Step 5 : Find poles and valuation at infinity + poles = roots(den, x) + poles, muls = list(poles.keys()), list(poles.values()) + val_inf = val_at_inf(num, den, x) + + if len(poles): + # Check necessary conditions (outlined in the module docstring) + if not check_necessary_conds(val_inf, muls): + raise ValueError("Rational Solution doesn't exist") + + # Step 6 + # Construct c-vectors for each singular point + c = construct_c(num, den, x, poles, muls) + + # Construct d vectors for each singular point + d = construct_d(num, den, x, val_inf) + + # Step 7 : Iterate over all possible combinations and return solutions + # For each possible combination, generate an array of 0's and 1's + # where 0 means pick 1st choice and 1 means pick the second choice. + + # NOTE: We could exit from the loop if we find 3 particular solutions, + # but it is not implemented here as - + # a. Finding 3 particular solutions is very rare. Most of the time, + # only 2 particular solutions are found. + # b. In case we exit after finding 3 particular solutions, it might + # happen that 1 or 2 of them are redundant solutions. So, instead of + # spending some more time in computing the particular solutions, + # we will end up computing the general solution from a single + # particular solution which is usually slower than computing the + # general solution from 2 or 3 particular solutions. + c.append(d) + choices = product(*c) + for choice in choices: + m, ybar = compute_m_ybar(x, poles, choice, -val_inf//2) + numy, deny = [Poly(e, x, extension=True) for e in ybar.together().as_numer_denom()] + # Step 10 : Check if a valid solution exists. If yes, also check + # if m is a non-negative integer + if m.is_nonnegative == True and m.is_integer == True: + + # Step 11 : Find polynomial solutions of degree m for the auxiliary equation + psol, coeffs, exists = solve_aux_eq(num, den, numy, deny, x, m) + + # Step 12 : If valid polynomial solution exists, append solution. + if exists: + # m == 0 case + if psol == 1 and coeffs == 0: + # p(x) = 1, so p'(x)/p(x) term need not be added + presol.append(ybar) + # m is a positive integer and there are valid coefficients + elif len(coeffs): + # Substitute the valid coefficients to get p(x) + psol = psol.xreplace(coeffs) + # y(x) = ybar(x) + p'(x)/p(x) + presol.append(ybar + psol.diff(x)/psol) + + # Remove redundant solutions from the list of existing solutions + remove = set() + for i in range(len(presol)): + for j in range(i+1, len(presol)): + rem = remove_redundant_sols(presol[i], presol[j], x) + if rem is not None: + remove.add(rem) + sols = [x for x in presol if x not in remove] + + # Step 15 : Inverse transform the solutions of the equation in normal form + bp = -b2.diff(x)/(2*b2**2) - b1/(2*b2) + + # If general solution is required, compute it from the particular solutions + if gensol: + sols = [get_gen_sol_from_part_sol(sols, a, x)] + + # Inverse transform the particular solutions + presol = [Eq(fx, riccati_inverse_normal(y, x, b1, b2, bp).cancel(extension=True)) for y in sols] + return presol diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/single.py b/lib/python3.12/site-packages/sympy/solvers/ode/single.py new file mode 100644 index 0000000000000000000000000000000000000000..7d46931122f11a1592097e6a7117192d39bae10e --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/ode/single.py @@ -0,0 +1,2979 @@ +# +# This is the module for ODE solver classes for single ODEs. +# + +from __future__ import annotations +from typing import ClassVar, Iterator + +from .riccati import match_riccati, solve_riccati +from sympy.core import Add, S, Pow, Rational +from sympy.core.cache import cached_property +from sympy.core.exprtools import factor_terms +from sympy.core.expr import Expr +from sympy.core.function import AppliedUndef, Derivative, diff, Function, expand, Subs, _mexpand +from sympy.core.numbers import zoo +from sympy.core.relational import Equality, Eq +from sympy.core.symbol import Symbol, Dummy, Wild +from sympy.core.mul import Mul +from sympy.functions import exp, tan, log, sqrt, besselj, bessely, cbrt, airyai, airybi +from sympy.integrals import Integral +from sympy.polys import Poly +from sympy.polys.polytools import cancel, factor, degree +from sympy.simplify import collect, simplify, separatevars, logcombine, posify # type: ignore +from sympy.simplify.radsimp import fraction +from sympy.utilities import numbered_symbols +from sympy.solvers.solvers import solve +from sympy.solvers.deutils import ode_order, _preprocess +from sympy.polys.matrices.linsolve import _lin_eq2dict +from sympy.polys.solvers import PolyNonlinearError +from .hypergeometric import equivalence_hypergeometric, match_2nd_2F1_hypergeometric, \ + get_sol_2F1_hypergeometric, match_2nd_hypergeometric +from .nonhomogeneous import _get_euler_characteristic_eq_sols, _get_const_characteristic_eq_sols, \ + _solve_undetermined_coefficients, _solve_variation_of_parameters, _test_term, _undetermined_coefficients_match, \ + _get_simplified_sol +from .lie_group import _ode_lie_group + + +class ODEMatchError(NotImplementedError): + """Raised if a SingleODESolver is asked to solve an ODE it does not match""" + pass + + +class SingleODEProblem: + """Represents an ordinary differential equation (ODE) + + This class is used internally in the by dsolve and related + functions/classes so that properties of an ODE can be computed + efficiently. + + Examples + ======== + + This class is used internally by dsolve. To instantiate an instance + directly first define an ODE problem: + + >>> from sympy import Function, Symbol + >>> x = Symbol('x') + >>> f = Function('f') + >>> eq = f(x).diff(x, 2) + + Now you can create a SingleODEProblem instance and query its properties: + + >>> from sympy.solvers.ode.single import SingleODEProblem + >>> problem = SingleODEProblem(f(x).diff(x), f(x), x) + >>> problem.eq + Derivative(f(x), x) + >>> problem.func + f(x) + >>> problem.sym + x + """ + + # Instance attributes: + eq = None # type: Expr + func = None # type: AppliedUndef + sym = None # type: Symbol + _order = None # type: int + _eq_expanded = None # type: Expr + _eq_preprocessed = None # type: Expr + _eq_high_order_free = None + + def __init__(self, eq, func, sym, prep=True, **kwargs): + assert isinstance(eq, Expr) + assert isinstance(func, AppliedUndef) + assert isinstance(sym, Symbol) + assert isinstance(prep, bool) + self.eq = eq + self.func = func + self.sym = sym + self.prep = prep + self.params = kwargs + + @cached_property + def order(self) -> int: + return ode_order(self.eq, self.func) + + @cached_property + def eq_preprocessed(self) -> Expr: + return self._get_eq_preprocessed() + + @cached_property + def eq_high_order_free(self) -> Expr: + a = Wild('a', exclude=[self.func]) + c1 = Wild('c1', exclude=[self.sym]) + # Precondition to try remove f(x) from highest order derivative + reduced_eq = None + if self.eq.is_Add: + deriv_coef = self.eq.coeff(self.func.diff(self.sym, self.order)) + if deriv_coef not in (1, 0): + r = deriv_coef.match(a*self.func**c1) + if r and r[c1]: + den = self.func**r[c1] + reduced_eq = Add(*[arg/den for arg in self.eq.args]) + if not reduced_eq: + reduced_eq = expand(self.eq) + return reduced_eq + + @cached_property + def eq_expanded(self) -> Expr: + return expand(self.eq_preprocessed) + + def _get_eq_preprocessed(self) -> Expr: + if self.prep: + process_eq, process_func = _preprocess(self.eq, self.func) + if process_func != self.func: + raise ValueError + else: + process_eq = self.eq + return process_eq + + def get_numbered_constants(self, num=1, start=1, prefix='C') -> list[Symbol]: + """ + Returns a list of constants that do not occur + in eq already. + """ + ncs = self.iter_numbered_constants(start, prefix) + Cs = [next(ncs) for i in range(num)] + return Cs + + def iter_numbered_constants(self, start=1, prefix='C') -> Iterator[Symbol]: + """ + Returns an iterator of constants that do not occur + in eq already. + """ + atom_set = self.eq.free_symbols + func_set = self.eq.atoms(Function) + if func_set: + atom_set |= {Symbol(str(f.func)) for f in func_set} + return numbered_symbols(start=start, prefix=prefix, exclude=atom_set) + + @cached_property + def is_autonomous(self): + u = Dummy('u') + x = self.sym + syms = self.eq.subs(self.func, u).free_symbols + return x not in syms + + def get_linear_coefficients(self, eq, func, order): + r""" + Matches a differential equation to the linear form: + + .. math:: a_n(x) y^{(n)} + \cdots + a_1(x)y' + a_0(x) y + B(x) = 0 + + Returns a dict of order:coeff terms, where order is the order of the + derivative on each term, and coeff is the coefficient of that derivative. + The key ``-1`` holds the function `B(x)`. Returns ``None`` if the ODE is + not linear. This function assumes that ``func`` has already been checked + to be good. + + Examples + ======== + + >>> from sympy import Function, cos, sin + >>> from sympy.abc import x + >>> from sympy.solvers.ode.single import SingleODEProblem + >>> f = Function('f') + >>> eq = f(x).diff(x, 3) + 2*f(x).diff(x) + \ + ... x*f(x).diff(x, 2) + cos(x)*f(x).diff(x) + x - f(x) - \ + ... sin(x) + >>> obj = SingleODEProblem(eq, f(x), x) + >>> obj.get_linear_coefficients(eq, f(x), 3) + {-1: x - sin(x), 0: -1, 1: cos(x) + 2, 2: x, 3: 1} + >>> eq = f(x).diff(x, 3) + 2*f(x).diff(x) + \ + ... x*f(x).diff(x, 2) + cos(x)*f(x).diff(x) + x - f(x) - \ + ... sin(f(x)) + >>> obj = SingleODEProblem(eq, f(x), x) + >>> obj.get_linear_coefficients(eq, f(x), 3) == None + True + + """ + f = func.func + x = func.args[0] + symset = {Derivative(f(x), x, i) for i in range(order+1)} + try: + rhs, lhs_terms = _lin_eq2dict(eq, symset) + except PolyNonlinearError: + return None + + if rhs.has(func) or any(c.has(func) for c in lhs_terms.values()): + return None + terms = {i: lhs_terms.get(f(x).diff(x, i), S.Zero) for i in range(order+1)} + terms[-1] = rhs + return terms + + # TODO: Add methods that can be used by many ODE solvers: + # order + # is_linear() + # get_linear_coefficients() + # eq_prepared (the ODE in prepared form) + + +class SingleODESolver: + """ + Base class for Single ODE solvers. + + Subclasses should implement the _matches and _get_general_solution + methods. This class is not intended to be instantiated directly but its + subclasses are as part of dsolve. + + Examples + ======== + + You can use a subclass of SingleODEProblem to solve a particular type of + ODE. We first define a particular ODE problem: + + >>> from sympy import Function, Symbol + >>> x = Symbol('x') + >>> f = Function('f') + >>> eq = f(x).diff(x, 2) + + Now we solve this problem using the NthAlgebraic solver which is a + subclass of SingleODESolver: + + >>> from sympy.solvers.ode.single import NthAlgebraic, SingleODEProblem + >>> problem = SingleODEProblem(eq, f(x), x) + >>> solver = NthAlgebraic(problem) + >>> solver.get_general_solution() + [Eq(f(x), _C*x + _C)] + + The normal way to solve an ODE is to use dsolve (which would use + NthAlgebraic and other solvers internally). When using dsolve a number of + other things are done such as evaluating integrals, simplifying the + solution and renumbering the constants: + + >>> from sympy import dsolve + >>> dsolve(eq, hint='nth_algebraic') + Eq(f(x), C1 + C2*x) + """ + + # Subclasses should store the hint name (the argument to dsolve) in this + # attribute + hint: ClassVar[str] + + # Subclasses should define this to indicate if they support an _Integral + # hint. + has_integral: ClassVar[bool] + + # The ODE to be solved + ode_problem = None # type: SingleODEProblem + + # Cache whether or not the equation has matched the method + _matched: bool | None = None + + # Subclasses should store in this attribute the list of order(s) of ODE + # that subclass can solve or leave it to None if not specific to any order + order: list | None = None + + def __init__(self, ode_problem): + self.ode_problem = ode_problem + + def matches(self) -> bool: + if self.order is not None and self.ode_problem.order not in self.order: + self._matched = False + return self._matched + + if self._matched is None: + self._matched = self._matches() + return self._matched + + def get_general_solution(self, *, simplify: bool = True) -> list[Equality]: + if not self.matches(): + msg = "%s solver cannot solve:\n%s" + raise ODEMatchError(msg % (self.hint, self.ode_problem.eq)) + return self._get_general_solution(simplify_flag=simplify) + + def _matches(self) -> bool: + msg = "Subclasses of SingleODESolver should implement matches." + raise NotImplementedError(msg) + + def _get_general_solution(self, *, simplify_flag: bool = True) -> list[Equality]: + msg = "Subclasses of SingleODESolver should implement get_general_solution." + raise NotImplementedError(msg) + + +class SinglePatternODESolver(SingleODESolver): + '''Superclass for ODE solvers based on pattern matching''' + + def wilds(self): + prob = self.ode_problem + f = prob.func.func + x = prob.sym + order = prob.order + return self._wilds(f, x, order) + + def wilds_match(self): + match = self._wilds_match + return [match.get(w, S.Zero) for w in self.wilds()] + + def _matches(self): + eq = self.ode_problem.eq_expanded + f = self.ode_problem.func.func + x = self.ode_problem.sym + order = self.ode_problem.order + df = f(x).diff(x, order) + + if order not in [1, 2]: + return False + + pattern = self._equation(f(x), x, order) + + if not pattern.coeff(df).has(Wild): + eq = expand(eq / eq.coeff(df)) + eq = eq.collect([f(x).diff(x), f(x)], func = cancel) + + self._wilds_match = match = eq.match(pattern) + if match is not None: + return self._verify(f(x)) + return False + + def _verify(self, fx) -> bool: + return True + + def _wilds(self, f, x, order): + msg = "Subclasses of SingleODESolver should implement _wilds" + raise NotImplementedError(msg) + + def _equation(self, fx, x, order): + msg = "Subclasses of SingleODESolver should implement _equation" + raise NotImplementedError(msg) + + +class NthAlgebraic(SingleODESolver): + r""" + Solves an `n`\th order ordinary differential equation using algebra and + integrals. + + There is no general form for the kind of equation that this can solve. The + the equation is solved algebraically treating differentiation as an + invertible algebraic function. + + Examples + ======== + + >>> from sympy import Function, dsolve, Eq + >>> from sympy.abc import x + >>> f = Function('f') + >>> eq = Eq(f(x) * (f(x).diff(x)**2 - 1), 0) + >>> dsolve(eq, f(x), hint='nth_algebraic') + [Eq(f(x), 0), Eq(f(x), C1 - x), Eq(f(x), C1 + x)] + + Note that this solver can return algebraic solutions that do not have any + integration constants (f(x) = 0 in the above example). + """ + + hint = 'nth_algebraic' + has_integral = True # nth_algebraic_Integral hint + + def _matches(self): + r""" + Matches any differential equation that nth_algebraic can solve. Uses + `sympy.solve` but teaches it how to integrate derivatives. + + This involves calling `sympy.solve` and does most of the work of finding a + solution (apart from evaluating the integrals). + """ + eq = self.ode_problem.eq + func = self.ode_problem.func + var = self.ode_problem.sym + + # Derivative that solve can handle: + diffx = self._get_diffx(var) + + # Replace derivatives wrt the independent variable with diffx + def replace(eq, var): + def expand_diffx(*args): + differand, diffs = args[0], args[1:] + toreplace = differand + for v, n in diffs: + for _ in range(n): + if v == var: + toreplace = diffx(toreplace) + else: + toreplace = Derivative(toreplace, v) + return toreplace + return eq.replace(Derivative, expand_diffx) + + # Restore derivatives in solution afterwards + def unreplace(eq, var): + return eq.replace(diffx, lambda e: Derivative(e, var)) + + subs_eqn = replace(eq, var) + try: + # turn off simplification to protect Integrals that have + # _t instead of fx in them and would otherwise factor + # as t_*Integral(1, x) + solns = solve(subs_eqn, func, simplify=False) + except NotImplementedError: + solns = [] + + solns = [simplify(unreplace(soln, var)) for soln in solns] + solns = [Equality(func, soln) for soln in solns] + + self.solutions = solns + return len(solns) != 0 + + def _get_general_solution(self, *, simplify_flag: bool = True): + return self.solutions + + # This needs to produce an invertible function but the inverse depends + # which variable we are integrating with respect to. Since the class can + # be stored in cached results we need to ensure that we always get the + # same class back for each particular integration variable so we store these + # classes in a global dict: + _diffx_stored: dict[Symbol, type[Function]] = {} + + @staticmethod + def _get_diffx(var): + diffcls = NthAlgebraic._diffx_stored.get(var, None) + + if diffcls is None: + # A class that behaves like Derivative wrt var but is "invertible". + class diffx(Function): + def inverse(self): + # don't use integrate here because fx has been replaced by _t + # in the equation; integrals will not be correct while solve + # is at work. + return lambda expr: Integral(expr, var) + Dummy('C') + + diffcls = NthAlgebraic._diffx_stored.setdefault(var, diffx) + + return diffcls + + +class FirstExact(SinglePatternODESolver): + r""" + Solves 1st order exact ordinary differential equations. + + A 1st order differential equation is called exact if it is the total + differential of a function. That is, the differential equation + + .. math:: P(x, y) \,\partial{}x + Q(x, y) \,\partial{}y = 0 + + is exact if there is some function `F(x, y)` such that `P(x, y) = + \partial{}F/\partial{}x` and `Q(x, y) = \partial{}F/\partial{}y`. It can + be shown that a necessary and sufficient condition for a first order ODE + to be exact is that `\partial{}P/\partial{}y = \partial{}Q/\partial{}x`. + Then, the solution will be as given below:: + + >>> from sympy import Function, Eq, Integral, symbols, pprint + >>> x, y, t, x0, y0, C1= symbols('x,y,t,x0,y0,C1') + >>> P, Q, F= map(Function, ['P', 'Q', 'F']) + >>> pprint(Eq(Eq(F(x, y), Integral(P(t, y), (t, x0, x)) + + ... Integral(Q(x0, t), (t, y0, y))), C1)) + x y + / / + | | + F(x, y) = | P(t, y) dt + | Q(x0, t) dt = C1 + | | + / / + x0 y0 + + Where the first partials of `P` and `Q` exist and are continuous in a + simply connected region. + + A note: SymPy currently has no way to represent inert substitution on an + expression, so the hint ``1st_exact_Integral`` will return an integral + with `dy`. This is supposed to represent the function that you are + solving for. + + Examples + ======== + + >>> from sympy import Function, dsolve, cos, sin + >>> from sympy.abc import x + >>> f = Function('f') + >>> dsolve(cos(f(x)) - (x*sin(f(x)) - f(x)**2)*f(x).diff(x), + ... f(x), hint='1st_exact') + Eq(x*cos(f(x)) + f(x)**3/3, C1) + + References + ========== + + - https://en.wikipedia.org/wiki/Exact_differential_equation + - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", + Dover 1963, pp. 73 + + # indirect doctest + + """ + hint = "1st_exact" + has_integral = True + order = [1] + + def _wilds(self, f, x, order): + P = Wild('P', exclude=[f(x).diff(x)]) + Q = Wild('Q', exclude=[f(x).diff(x)]) + return P, Q + + def _equation(self, fx, x, order): + P, Q = self.wilds() + return P + Q*fx.diff(x) + + def _verify(self, fx) -> bool: + P, Q = self.wilds() + x = self.ode_problem.sym + y = Dummy('y') + + m, n = self.wilds_match() + + m = m.subs(fx, y) + n = n.subs(fx, y) + numerator = cancel(m.diff(y) - n.diff(x)) + + if numerator.is_zero: + # Is exact + return True + else: + # The following few conditions try to convert a non-exact + # differential equation into an exact one. + # References: + # 1. Differential equations with applications + # and historical notes - George E. Simmons + # 2. https://math.okstate.edu/people/binegar/2233-S99/2233-l12.pdf + + factor_n = cancel(numerator/n) + factor_m = cancel(-numerator/m) + if y not in factor_n.free_symbols: + # If (dP/dy - dQ/dx) / Q = f(x) + # then exp(integral(f(x))*equation becomes exact + factor = factor_n + integration_variable = x + elif x not in factor_m.free_symbols: + # If (dP/dy - dQ/dx) / -P = f(y) + # then exp(integral(f(y))*equation becomes exact + factor = factor_m + integration_variable = y + else: + # Couldn't convert to exact + return False + + factor = exp(Integral(factor, integration_variable)) + m *= factor + n *= factor + self._wilds_match[P] = m.subs(y, fx) + self._wilds_match[Q] = n.subs(y, fx) + return True + + def _get_general_solution(self, *, simplify_flag: bool = True): + m, n = self.wilds_match() + fx = self.ode_problem.func + x = self.ode_problem.sym + (C1,) = self.ode_problem.get_numbered_constants(num=1) + y = Dummy('y') + + m = m.subs(fx, y) + n = n.subs(fx, y) + + gen_sol = Eq(Subs(Integral(m, x) + + Integral(n - Integral(m, x).diff(y), y), y, fx), C1) + return [gen_sol] + + +class FirstLinear(SinglePatternODESolver): + r""" + Solves 1st order linear differential equations. + + These are differential equations of the form + + .. math:: dy/dx + P(x) y = Q(x)\text{.} + + These kinds of differential equations can be solved in a general way. The + integrating factor `e^{\int P(x) \,dx}` will turn the equation into a + separable equation. The general solution is:: + + >>> from sympy import Function, dsolve, Eq, pprint, diff, sin + >>> from sympy.abc import x + >>> f, P, Q = map(Function, ['f', 'P', 'Q']) + >>> genform = Eq(f(x).diff(x) + P(x)*f(x), Q(x)) + >>> pprint(genform) + d + P(x)*f(x) + --(f(x)) = Q(x) + dx + >>> pprint(dsolve(genform, f(x), hint='1st_linear_Integral')) + / / \ + | | | + | | / | / + | | | | | + | | | P(x) dx | - | P(x) dx + | | | | | + | | / | / + f(x) = |C1 + | Q(x)*e dx|*e + | | | + \ / / + + + Examples + ======== + + >>> f = Function('f') + >>> pprint(dsolve(Eq(x*diff(f(x), x) - f(x), x**2*sin(x)), + ... f(x), '1st_linear')) + f(x) = x*(C1 - cos(x)) + + References + ========== + + - https://en.wikipedia.org/wiki/Linear_differential_equation#First-order_equation_with_variable_coefficients + - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", + Dover 1963, pp. 92 + + # indirect doctest + + """ + hint = '1st_linear' + has_integral = True + order = [1] + + def _wilds(self, f, x, order): + P = Wild('P', exclude=[f(x)]) + Q = Wild('Q', exclude=[f(x), f(x).diff(x)]) + return P, Q + + def _equation(self, fx, x, order): + P, Q = self.wilds() + return fx.diff(x) + P*fx - Q + + def _get_general_solution(self, *, simplify_flag: bool = True): + P, Q = self.wilds_match() + fx = self.ode_problem.func + x = self.ode_problem.sym + (C1,) = self.ode_problem.get_numbered_constants(num=1) + gensol = Eq(fx, ((C1 + Integral(Q*exp(Integral(P, x)), x)) + * exp(-Integral(P, x)))) + return [gensol] + + +class AlmostLinear(SinglePatternODESolver): + r""" + Solves an almost-linear differential equation. + + The general form of an almost linear differential equation is + + .. math:: a(x) g'(f(x)) f'(x) + b(x) g(f(x)) + c(x) + + Here `f(x)` is the function to be solved for (the dependent variable). + The substitution `g(f(x)) = u(x)` leads to a linear differential equation + for `u(x)` of the form `a(x) u' + b(x) u + c(x) = 0`. This can be solved + for `u(x)` by the `first_linear` hint and then `f(x)` is found by solving + `g(f(x)) = u(x)`. + + See Also + ======== + :obj:`sympy.solvers.ode.single.FirstLinear` + + Examples + ======== + + >>> from sympy import dsolve, Function, pprint, sin, cos + >>> from sympy.abc import x + >>> f = Function('f') + >>> d = f(x).diff(x) + >>> eq = x*d + x*f(x) + 1 + >>> dsolve(eq, f(x), hint='almost_linear') + Eq(f(x), (C1 - Ei(x))*exp(-x)) + >>> pprint(dsolve(eq, f(x), hint='almost_linear')) + -x + f(x) = (C1 - Ei(x))*e + >>> example = cos(f(x))*f(x).diff(x) + sin(f(x)) + 1 + >>> pprint(example) + d + sin(f(x)) + cos(f(x))*--(f(x)) + 1 + dx + >>> pprint(dsolve(example, f(x), hint='almost_linear')) + / -x \ / -x \ + [f(x) = pi - asin\C1*e - 1/, f(x) = asin\C1*e - 1/] + + + References + ========== + + - Joel Moses, "Symbolic Integration - The Stormy Decade", Communications + of the ACM, Volume 14, Number 8, August 1971, pp. 558 + """ + hint = "almost_linear" + has_integral = True + order = [1] + + def _wilds(self, f, x, order): + P = Wild('P', exclude=[f(x).diff(x)]) + Q = Wild('Q', exclude=[f(x).diff(x)]) + return P, Q + + def _equation(self, fx, x, order): + P, Q = self.wilds() + return P*fx.diff(x) + Q + + def _verify(self, fx): + a, b = self.wilds_match() + c, b = b.as_independent(fx) if b.is_Add else (S.Zero, b) + # a, b and c are the function a(x), b(x) and c(x) respectively. + # c(x) is obtained by separating out b as terms with and without fx i.e, l(y) + # The following conditions checks if the given equation is an almost-linear differential equation using the fact that + # a(x)*(l(y))' / l(y)' is independent of l(y) + + if b.diff(fx) != 0 and not simplify(b.diff(fx)/a).has(fx): + self.ly = factor_terms(b).as_independent(fx, as_Add=False)[1] # Gives the term containing fx i.e., l(y) + self.ax = a / self.ly.diff(fx) + self.cx = -c # cx is taken as -c(x) to simplify expression in the solution integral + self.bx = factor_terms(b) / self.ly + return True + + return False + + def _get_general_solution(self, *, simplify_flag: bool = True): + x = self.ode_problem.sym + (C1,) = self.ode_problem.get_numbered_constants(num=1) + gensol = Eq(self.ly, ((C1 + Integral((self.cx/self.ax)*exp(Integral(self.bx/self.ax, x)), x)) + * exp(-Integral(self.bx/self.ax, x)))) + + return [gensol] + + +class Bernoulli(SinglePatternODESolver): + r""" + Solves Bernoulli differential equations. + + These are equations of the form + + .. math:: dy/dx + P(x) y = Q(x) y^n\text{, }n \ne 1`\text{.} + + The substitution `w = 1/y^{1-n}` will transform an equation of this form + into one that is linear (see the docstring of + :obj:`~sympy.solvers.ode.single.FirstLinear`). The general solution is:: + + >>> from sympy import Function, dsolve, Eq, pprint + >>> from sympy.abc import x, n + >>> f, P, Q = map(Function, ['f', 'P', 'Q']) + >>> genform = Eq(f(x).diff(x) + P(x)*f(x), Q(x)*f(x)**n) + >>> pprint(genform) + d n + P(x)*f(x) + --(f(x)) = Q(x)*f (x) + dx + >>> pprint(dsolve(genform, f(x), hint='Bernoulli_Integral'), num_columns=110) + -1 + ----- + n - 1 + // / / \ \ + || | | | | + || | / | / | / | + || | | | | | | | + || | -(n - 1)* | P(x) dx | -(n - 1)* | P(x) dx | (n - 1)* | P(x) dx| + || | | | | | | | + || | / | / | / | + f(x) = ||C1 - n* | Q(x)*e dx + | Q(x)*e dx|*e | + || | | | | + \\ / / / / + + + Note that the equation is separable when `n = 1` (see the docstring of + :obj:`~sympy.solvers.ode.single.Separable`). + + >>> pprint(dsolve(Eq(f(x).diff(x) + P(x)*f(x), Q(x)*f(x)), f(x), + ... hint='separable_Integral')) + f(x) + / + | / + | 1 | + | - dy = C1 + | (-P(x) + Q(x)) dx + | y | + | / + / + + + Examples + ======== + + >>> from sympy import Function, dsolve, Eq, pprint, log + >>> from sympy.abc import x + >>> f = Function('f') + + >>> pprint(dsolve(Eq(x*f(x).diff(x) + f(x), log(x)*f(x)**2), + ... f(x), hint='Bernoulli')) + 1 + f(x) = ----------------- + C1*x + log(x) + 1 + + References + ========== + + - https://en.wikipedia.org/wiki/Bernoulli_differential_equation + + - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", + Dover 1963, pp. 95 + + # indirect doctest + + """ + hint = "Bernoulli" + has_integral = True + order = [1] + + def _wilds(self, f, x, order): + P = Wild('P', exclude=[f(x)]) + Q = Wild('Q', exclude=[f(x)]) + n = Wild('n', exclude=[x, f(x), f(x).diff(x)]) + return P, Q, n + + def _equation(self, fx, x, order): + P, Q, n = self.wilds() + return fx.diff(x) + P*fx - Q*fx**n + + def _get_general_solution(self, *, simplify_flag: bool = True): + P, Q, n = self.wilds_match() + fx = self.ode_problem.func + x = self.ode_problem.sym + (C1,) = self.ode_problem.get_numbered_constants(num=1) + if n==1: + gensol = Eq(log(fx), ( + C1 + Integral((-P + Q), x) + )) + else: + gensol = Eq(fx**(1-n), ( + (C1 - (n - 1) * Integral(Q*exp(-n*Integral(P, x)) + * exp(Integral(P, x)), x) + ) * exp(-(1 - n)*Integral(P, x))) + ) + return [gensol] + + +class Factorable(SingleODESolver): + r""" + Solves equations having a solvable factor. + + This function is used to solve the equation having factors. Factors may be of type algebraic or ode. It + will try to solve each factor independently. Factors will be solved by calling dsolve. We will return the + list of solutions. + + Examples + ======== + + >>> from sympy import Function, dsolve, pprint + >>> from sympy.abc import x + >>> f = Function('f') + >>> eq = (f(x)**2-4)*(f(x).diff(x)+f(x)) + >>> pprint(dsolve(eq, f(x))) + -x + [f(x) = 2, f(x) = -2, f(x) = C1*e ] + + + """ + hint = "factorable" + has_integral = False + + def _matches(self): + eq_orig = self.ode_problem.eq + f = self.ode_problem.func.func + x = self.ode_problem.sym + df = f(x).diff(x) + self.eqs = [] + eq = eq_orig.collect(f(x), func = cancel) + eq = fraction(factor(eq))[0] + factors = Mul.make_args(factor(eq)) + roots = [fac.as_base_exp() for fac in factors if len(fac.args)!=0] + if len(roots)>1 or roots[0][1]>1: + for base, expo in roots: + if base.has(f(x)): + self.eqs.append(base) + if len(self.eqs)>0: + return True + roots = solve(eq, df) + if len(roots)>0: + self.eqs = [(df - root) for root in roots] + # Avoid infinite recursion + matches = self.eqs != [eq_orig] + return matches + for i in factors: + if i.has(f(x)): + self.eqs.append(i) + return len(self.eqs)>0 and len(factors)>1 + + def _get_general_solution(self, *, simplify_flag: bool = True): + func = self.ode_problem.func.func + x = self.ode_problem.sym + eqns = self.eqs + sols = [] + for eq in eqns: + try: + sol = dsolve(eq, func(x)) + except NotImplementedError: + continue + else: + if isinstance(sol, list): + sols.extend(sol) + else: + sols.append(sol) + + if sols == []: + raise NotImplementedError("The given ODE " + str(eq) + " cannot be solved by" + + " the factorable group method") + return sols + + +class RiccatiSpecial(SinglePatternODESolver): + r""" + The general Riccati equation has the form + + .. math:: dy/dx = f(x) y^2 + g(x) y + h(x)\text{.} + + While it does not have a general solution [1], the "special" form, `dy/dx + = a y^2 - b x^c`, does have solutions in many cases [2]. This routine + returns a solution for `a(dy/dx) = b y^2 + c y/x + d/x^2` that is obtained + by using a suitable change of variables to reduce it to the special form + and is valid when neither `a` nor `b` are zero and either `c` or `d` is + zero. + + >>> from sympy.abc import x, a, b, c, d + >>> from sympy import dsolve, checkodesol, pprint, Function + >>> f = Function('f') + >>> y = f(x) + >>> genform = a*y.diff(x) - (b*y**2 + c*y/x + d/x**2) + >>> sol = dsolve(genform, y, hint="Riccati_special_minus2") + >>> pprint(sol, wrap_line=False) + / / __________________ \\ + | __________________ | / 2 || + | / 2 | \/ 4*b*d - (a + c) *log(x)|| + -|a + c - \/ 4*b*d - (a + c) *tan|C1 + ----------------------------|| + \ \ 2*a // + f(x) = ------------------------------------------------------------------------ + 2*b*x + + >>> checkodesol(genform, sol, order=1)[0] + True + + References + ========== + + - https://www.maplesoft.com/support/help/Maple/view.aspx?path=odeadvisor/Riccati + - https://eqworld.ipmnet.ru/en/solutions/ode/ode0106.pdf - + https://eqworld.ipmnet.ru/en/solutions/ode/ode0123.pdf + """ + hint = "Riccati_special_minus2" + has_integral = False + order = [1] + + def _wilds(self, f, x, order): + a = Wild('a', exclude=[x, f(x), f(x).diff(x), 0]) + b = Wild('b', exclude=[x, f(x), f(x).diff(x), 0]) + c = Wild('c', exclude=[x, f(x), f(x).diff(x)]) + d = Wild('d', exclude=[x, f(x), f(x).diff(x)]) + return a, b, c, d + + def _equation(self, fx, x, order): + a, b, c, d = self.wilds() + return a*fx.diff(x) + b*fx**2 + c*fx/x + d/x**2 + + def _get_general_solution(self, *, simplify_flag: bool = True): + a, b, c, d = self.wilds_match() + fx = self.ode_problem.func + x = self.ode_problem.sym + (C1,) = self.ode_problem.get_numbered_constants(num=1) + mu = sqrt(4*d*b - (a - c)**2) + + gensol = Eq(fx, (a - c - mu*tan(mu/(2*a)*log(x) + C1))/(2*b*x)) + return [gensol] + + +class RationalRiccati(SinglePatternODESolver): + r""" + Gives general solutions to the first order Riccati differential + equations that have atleast one rational particular solution. + + .. math :: y' = b_0(x) + b_1(x) y + b_2(x) y^2 + + where `b_0`, `b_1` and `b_2` are rational functions of `x` + with `b_2 \ne 0` (`b_2 = 0` would make it a Bernoulli equation). + + Examples + ======== + + >>> from sympy import Symbol, Function, dsolve, checkodesol + >>> f = Function('f') + >>> x = Symbol('x') + + >>> eq = -x**4*f(x)**2 + x**3*f(x).diff(x) + x**2*f(x) + 20 + >>> sol = dsolve(eq, hint="1st_rational_riccati") + >>> sol + Eq(f(x), (4*C1 - 5*x**9 - 4)/(x**2*(C1 + x**9 - 1))) + >>> checkodesol(eq, sol) + (True, 0) + + References + ========== + + - Riccati ODE: https://en.wikipedia.org/wiki/Riccati_equation + - N. Thieu Vo - Rational and Algebraic Solutions of First-Order Algebraic ODEs: + Algorithm 11, pp. 78 - https://www3.risc.jku.at/publications/download/risc_5387/PhDThesisThieu.pdf + """ + has_integral = False + hint = "1st_rational_riccati" + order = [1] + + def _wilds(self, f, x, order): + b0 = Wild('b0', exclude=[f(x), f(x).diff(x)]) + b1 = Wild('b1', exclude=[f(x), f(x).diff(x)]) + b2 = Wild('b2', exclude=[f(x), f(x).diff(x)]) + return (b0, b1, b2) + + def _equation(self, fx, x, order): + b0, b1, b2 = self.wilds() + return fx.diff(x) - b0 - b1*fx - b2*fx**2 + + def _matches(self): + eq = self.ode_problem.eq_expanded + f = self.ode_problem.func.func + x = self.ode_problem.sym + order = self.ode_problem.order + + if order != 1: + return False + + match, funcs = match_riccati(eq, f, x) + if not match: + return False + _b0, _b1, _b2 = funcs + b0, b1, b2 = self.wilds() + self._wilds_match = match = {b0: _b0, b1: _b1, b2: _b2} + return True + + def _get_general_solution(self, *, simplify_flag: bool = True): + # Match the equation + b0, b1, b2 = self.wilds_match() + fx = self.ode_problem.func + x = self.ode_problem.sym + return solve_riccati(fx, x, b0, b1, b2, gensol=True) + + +class SecondNonlinearAutonomousConserved(SinglePatternODESolver): + r""" + Gives solution for the autonomous second order nonlinear + differential equation of the form + + .. math :: f''(x) = g(f(x)) + + The solution for this differential equation can be computed + by multiplying by `f'(x)` and integrating on both sides, + converting it into a first order differential equation. + + Examples + ======== + + >>> from sympy import Function, symbols, dsolve + >>> f, g = symbols('f g', cls=Function) + >>> x = symbols('x') + + >>> eq = f(x).diff(x, 2) - g(f(x)) + >>> dsolve(eq, simplify=False) + [Eq(Integral(1/sqrt(C1 + 2*Integral(g(_u), _u)), (_u, f(x))), C2 + x), + Eq(Integral(1/sqrt(C1 + 2*Integral(g(_u), _u)), (_u, f(x))), C2 - x)] + + >>> from sympy import exp, log + >>> eq = f(x).diff(x, 2) - exp(f(x)) + log(f(x)) + >>> dsolve(eq, simplify=False) + [Eq(Integral(1/sqrt(-2*_u*log(_u) + 2*_u + C1 + 2*exp(_u)), (_u, f(x))), C2 + x), + Eq(Integral(1/sqrt(-2*_u*log(_u) + 2*_u + C1 + 2*exp(_u)), (_u, f(x))), C2 - x)] + + References + ========== + + - https://eqworld.ipmnet.ru/en/solutions/ode/ode0301.pdf + """ + hint = "2nd_nonlinear_autonomous_conserved" + has_integral = True + order = [2] + + def _wilds(self, f, x, order): + fy = Wild('fy', exclude=[0, f(x).diff(x), f(x).diff(x, 2)]) + return (fy, ) + + def _equation(self, fx, x, order): + fy = self.wilds()[0] + return fx.diff(x, 2) + fy + + def _verify(self, fx): + return self.ode_problem.is_autonomous + + def _get_general_solution(self, *, simplify_flag: bool = True): + g = self.wilds_match()[0] + fx = self.ode_problem.func + x = self.ode_problem.sym + u = Dummy('u') + g = g.subs(fx, u) + C1, C2 = self.ode_problem.get_numbered_constants(num=2) + inside = -2*Integral(g, u) + C1 + lhs = Integral(1/sqrt(inside), (u, fx)) + return [Eq(lhs, C2 + x), Eq(lhs, C2 - x)] + + +class Liouville(SinglePatternODESolver): + r""" + Solves 2nd order Liouville differential equations. + + The general form of a Liouville ODE is + + .. math:: \frac{d^2 y}{dx^2} + g(y) \left(\! + \frac{dy}{dx}\!\right)^2 + h(x) + \frac{dy}{dx}\text{.} + + The general solution is: + + >>> from sympy import Function, dsolve, Eq, pprint, diff + >>> from sympy.abc import x + >>> f, g, h = map(Function, ['f', 'g', 'h']) + >>> genform = Eq(diff(f(x),x,x) + g(f(x))*diff(f(x),x)**2 + + ... h(x)*diff(f(x),x), 0) + >>> pprint(genform) + 2 2 + /d \ d d + g(f(x))*|--(f(x))| + h(x)*--(f(x)) + ---(f(x)) = 0 + \dx / dx 2 + dx + >>> pprint(dsolve(genform, f(x), hint='Liouville_Integral')) + f(x) + / / + | | + | / | / + | | | | + | - | h(x) dx | | g(y) dy + | | | | + | / | / + C1 + C2* | e dx + | e dy = 0 + | | + / / + + Examples + ======== + + >>> from sympy import Function, dsolve, Eq, pprint + >>> from sympy.abc import x + >>> f = Function('f') + >>> pprint(dsolve(diff(f(x), x, x) + diff(f(x), x)**2/f(x) + + ... diff(f(x), x)/x, f(x), hint='Liouville')) + ________________ ________________ + [f(x) = -\/ C1 + C2*log(x) , f(x) = \/ C1 + C2*log(x) ] + + References + ========== + + - Goldstein and Braun, "Advanced Methods for the Solution of Differential + Equations", pp. 98 + - https://www.maplesoft.com/support/help/Maple/view.aspx?path=odeadvisor/Liouville + + # indirect doctest + + """ + hint = "Liouville" + has_integral = True + order = [2] + + def _wilds(self, f, x, order): + d = Wild('d', exclude=[f(x).diff(x), f(x).diff(x, 2)]) + e = Wild('e', exclude=[f(x).diff(x)]) + k = Wild('k', exclude=[f(x).diff(x)]) + return d, e, k + + def _equation(self, fx, x, order): + # Liouville ODE in the form + # f(x).diff(x, 2) + g(f(x))*(f(x).diff(x))**2 + h(x)*f(x).diff(x) + # See Goldstein and Braun, "Advanced Methods for the Solution of + # Differential Equations", pg. 98 + d, e, k = self.wilds() + return d*fx.diff(x, 2) + e*fx.diff(x)**2 + k*fx.diff(x) + + def _verify(self, fx): + d, e, k = self.wilds_match() + self.y = Dummy('y') + x = self.ode_problem.sym + self.g = simplify(e/d).subs(fx, self.y) + self.h = simplify(k/d).subs(fx, self.y) + if self.y in self.h.free_symbols or x in self.g.free_symbols: + return False + return True + + def _get_general_solution(self, *, simplify_flag: bool = True): + d, e, k = self.wilds_match() + fx = self.ode_problem.func + x = self.ode_problem.sym + C1, C2 = self.ode_problem.get_numbered_constants(num=2) + int = Integral(exp(Integral(self.g, self.y)), (self.y, None, fx)) + gen_sol = Eq(int + C1*Integral(exp(-Integral(self.h, x)), x) + C2, 0) + + return [gen_sol] + + +class Separable(SinglePatternODESolver): + r""" + Solves separable 1st order differential equations. + + This is any differential equation that can be written as `P(y) + \tfrac{dy}{dx} = Q(x)`. The solution can then just be found by + rearranging terms and integrating: `\int P(y) \,dy = \int Q(x) \,dx`. + This hint uses :py:meth:`sympy.simplify.simplify.separatevars` as its back + end, so if a separable equation is not caught by this solver, it is most + likely the fault of that function. + :py:meth:`~sympy.simplify.simplify.separatevars` is + smart enough to do most expansion and factoring necessary to convert a + separable equation `F(x, y)` into the proper form `P(x)\cdot{}Q(y)`. The + general solution is:: + + >>> from sympy import Function, dsolve, Eq, pprint + >>> from sympy.abc import x + >>> a, b, c, d, f = map(Function, ['a', 'b', 'c', 'd', 'f']) + >>> genform = Eq(a(x)*b(f(x))*f(x).diff(x), c(x)*d(f(x))) + >>> pprint(genform) + d + a(x)*b(f(x))*--(f(x)) = c(x)*d(f(x)) + dx + >>> pprint(dsolve(genform, f(x), hint='separable_Integral')) + f(x) + / / + | | + | b(y) | c(x) + | ---- dy = C1 + | ---- dx + | d(y) | a(x) + | | + / / + + Examples + ======== + + >>> from sympy import Function, dsolve, Eq + >>> from sympy.abc import x + >>> f = Function('f') + >>> pprint(dsolve(Eq(f(x)*f(x).diff(x) + x, 3*x*f(x)**2), f(x), + ... hint='separable', simplify=False)) + / 2 \ 2 + log\3*f (x) - 1/ x + ---------------- = C1 + -- + 6 2 + + References + ========== + + - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", + Dover 1963, pp. 52 + + # indirect doctest + + """ + hint = "separable" + has_integral = True + order = [1] + + def _wilds(self, f, x, order): + d = Wild('d', exclude=[f(x).diff(x), f(x).diff(x, 2)]) + e = Wild('e', exclude=[f(x).diff(x)]) + return d, e + + def _equation(self, fx, x, order): + d, e = self.wilds() + return d + e*fx.diff(x) + + def _verify(self, fx): + d, e = self.wilds_match() + self.y = Dummy('y') + x = self.ode_problem.sym + d = separatevars(d.subs(fx, self.y)) + e = separatevars(e.subs(fx, self.y)) + # m1[coeff]*m1[x]*m1[y] + m2[coeff]*m2[x]*m2[y]*y' + self.m1 = separatevars(d, dict=True, symbols=(x, self.y)) + self.m2 = separatevars(e, dict=True, symbols=(x, self.y)) + if self.m1 and self.m2: + return True + return False + + def _get_match_object(self): + fx = self.ode_problem.func + x = self.ode_problem.sym + return self.m1, self.m2, x, fx + + def _get_general_solution(self, *, simplify_flag: bool = True): + m1, m2, x, fx = self._get_match_object() + (C1,) = self.ode_problem.get_numbered_constants(num=1) + int = Integral(m2['coeff']*m2[self.y]/m1[self.y], + (self.y, None, fx)) + gen_sol = Eq(int, Integral(-m1['coeff']*m1[x]/ + m2[x], x) + C1) + return [gen_sol] + + +class SeparableReduced(Separable): + r""" + Solves a differential equation that can be reduced to the separable form. + + The general form of this equation is + + .. math:: y' + (y/x) H(x^n y) = 0\text{}. + + This can be solved by substituting `u(y) = x^n y`. The equation then + reduces to the separable form `\frac{u'}{u (\mathrm{power} - H(u))} - + \frac{1}{x} = 0`. + + The general solution is: + + >>> from sympy import Function, dsolve, pprint + >>> from sympy.abc import x, n + >>> f, g = map(Function, ['f', 'g']) + >>> genform = f(x).diff(x) + (f(x)/x)*g(x**n*f(x)) + >>> pprint(genform) + / n \ + d f(x)*g\x *f(x)/ + --(f(x)) + --------------- + dx x + >>> pprint(dsolve(genform, hint='separable_reduced')) + n + x *f(x) + / + | + | 1 + | ------------ dy = C1 + log(x) + | y*(n - g(y)) + | + / + + See Also + ======== + :obj:`sympy.solvers.ode.single.Separable` + + Examples + ======== + + >>> from sympy import dsolve, Function, pprint + >>> from sympy.abc import x + >>> f = Function('f') + >>> d = f(x).diff(x) + >>> eq = (x - x**2*f(x))*d - f(x) + >>> dsolve(eq, hint='separable_reduced') + [Eq(f(x), (1 - sqrt(C1*x**2 + 1))/x), Eq(f(x), (sqrt(C1*x**2 + 1) + 1)/x)] + >>> pprint(dsolve(eq, hint='separable_reduced')) + ___________ ___________ + / 2 / 2 + 1 - \/ C1*x + 1 \/ C1*x + 1 + 1 + [f(x) = ------------------, f(x) = ------------------] + x x + + References + ========== + + - Joel Moses, "Symbolic Integration - The Stormy Decade", Communications + of the ACM, Volume 14, Number 8, August 1971, pp. 558 + """ + hint = "separable_reduced" + has_integral = True + order = [1] + + def _degree(self, expr, x): + # Made this function to calculate the degree of + # x in an expression. If expr will be of form + # x**p*y, (wheare p can be variables/rationals) then it + # will return p. + for val in expr: + if val.has(x): + if isinstance(val, Pow) and val.as_base_exp()[0] == x: + return (val.as_base_exp()[1]) + elif val == x: + return (val.as_base_exp()[1]) + else: + return self._degree(val.args, x) + return 0 + + def _powers(self, expr): + # this function will return all the different relative power of x w.r.t f(x). + # expr = x**p * f(x)**q then it will return {p/q}. + pows = set() + fx = self.ode_problem.func + x = self.ode_problem.sym + self.y = Dummy('y') + if isinstance(expr, Add): + exprs = expr.atoms(Add) + elif isinstance(expr, Mul): + exprs = expr.atoms(Mul) + elif isinstance(expr, Pow): + exprs = expr.atoms(Pow) + else: + exprs = {expr} + + for arg in exprs: + if arg.has(x): + _, u = arg.as_independent(x, fx) + pow = self._degree((u.subs(fx, self.y), ), x)/self._degree((u.subs(fx, self.y), ), self.y) + pows.add(pow) + return pows + + def _verify(self, fx): + num, den = self.wilds_match() + x = self.ode_problem.sym + factor = simplify(x/fx*num/den) + # Try representing factor in terms of x^n*y + # where n is lowest power of x in factor; + # first remove terms like sqrt(2)*3 from factor.atoms(Mul) + num, dem = factor.as_numer_denom() + num = expand(num) + dem = expand(dem) + pows = self._powers(num) + pows.update(self._powers(dem)) + pows = list(pows) + if(len(pows)==1) and pows[0]!=zoo: + self.t = Dummy('t') + self.r2 = {'t': self.t} + num = num.subs(x**pows[0]*fx, self.t) + dem = dem.subs(x**pows[0]*fx, self.t) + test = num/dem + free = test.free_symbols + if len(free) == 1 and free.pop() == self.t: + self.r2.update({'power' : pows[0], 'u' : test}) + return True + return False + return False + + def _get_match_object(self): + fx = self.ode_problem.func + x = self.ode_problem.sym + u = self.r2['u'].subs(self.r2['t'], self.y) + ycoeff = 1/(self.y*(self.r2['power'] - u)) + m1 = {self.y: 1, x: -1/x, 'coeff': 1} + m2 = {self.y: ycoeff, x: 1, 'coeff': 1} + return m1, m2, x, x**self.r2['power']*fx + + +class HomogeneousCoeffSubsDepDivIndep(SinglePatternODESolver): + r""" + Solves a 1st order differential equation with homogeneous coefficients + using the substitution `u_1 = \frac{\text{}}{\text{}}`. + + This is a differential equation + + .. math:: P(x, y) + Q(x, y) dy/dx = 0 + + such that `P` and `Q` are homogeneous and of the same order. A function + `F(x, y)` is homogeneous of order `n` if `F(x t, y t) = t^n F(x, y)`. + Equivalently, `F(x, y)` can be rewritten as `G(y/x)` or `H(x/y)`. See + also the docstring of :py:meth:`~sympy.solvers.ode.homogeneous_order`. + + If the coefficients `P` and `Q` in the differential equation above are + homogeneous functions of the same order, then it can be shown that the + substitution `y = u_1 x` (i.e. `u_1 = y/x`) will turn the differential + equation into an equation separable in the variables `x` and `u`. If + `h(u_1)` is the function that results from making the substitution `u_1 = + f(x)/x` on `P(x, f(x))` and `g(u_2)` is the function that results from the + substitution on `Q(x, f(x))` in the differential equation `P(x, f(x)) + + Q(x, f(x)) f'(x) = 0`, then the general solution is:: + + >>> from sympy import Function, dsolve, pprint + >>> from sympy.abc import x + >>> f, g, h = map(Function, ['f', 'g', 'h']) + >>> genform = g(f(x)/x) + h(f(x)/x)*f(x).diff(x) + >>> pprint(genform) + /f(x)\ /f(x)\ d + g|----| + h|----|*--(f(x)) + \ x / \ x / dx + >>> pprint(dsolve(genform, f(x), + ... hint='1st_homogeneous_coeff_subs_dep_div_indep_Integral')) + f(x) + ---- + x + / + | + | -h(u1) + log(x) = C1 + | ---------------- d(u1) + | u1*h(u1) + g(u1) + | + / + + Where `u_1 h(u_1) + g(u_1) \ne 0` and `x \ne 0`. + + See also the docstrings of + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffBest` and + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep`. + + Examples + ======== + + >>> from sympy import Function, dsolve + >>> from sympy.abc import x + >>> f = Function('f') + >>> pprint(dsolve(2*x*f(x) + (x**2 + f(x)**2)*f(x).diff(x), f(x), + ... hint='1st_homogeneous_coeff_subs_dep_div_indep', simplify=False)) + / 3 \ + |3*f(x) f (x)| + log|------ + -----| + | x 3 | + \ x / + log(x) = log(C1) - ------------------- + 3 + + References + ========== + + - https://en.wikipedia.org/wiki/Homogeneous_differential_equation + - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", + Dover 1963, pp. 59 + + # indirect doctest + + """ + hint = "1st_homogeneous_coeff_subs_dep_div_indep" + has_integral = True + order = [1] + + def _wilds(self, f, x, order): + d = Wild('d', exclude=[f(x).diff(x), f(x).diff(x, 2)]) + e = Wild('e', exclude=[f(x).diff(x)]) + return d, e + + def _equation(self, fx, x, order): + d, e = self.wilds() + return d + e*fx.diff(x) + + def _verify(self, fx): + self.d, self.e = self.wilds_match() + self.y = Dummy('y') + x = self.ode_problem.sym + self.d = separatevars(self.d.subs(fx, self.y)) + self.e = separatevars(self.e.subs(fx, self.y)) + ordera = homogeneous_order(self.d, x, self.y) + orderb = homogeneous_order(self.e, x, self.y) + if ordera == orderb and ordera is not None: + self.u = Dummy('u') + if simplify((self.d + self.u*self.e).subs({x: 1, self.y: self.u})) != 0: + return True + return False + return False + + def _get_match_object(self): + fx = self.ode_problem.func + x = self.ode_problem.sym + self.u1 = Dummy('u1') + xarg = 0 + yarg = 0 + return [self.d, self.e, fx, x, self.u, self.u1, self.y, xarg, yarg] + + def _get_general_solution(self, *, simplify_flag: bool = True): + d, e, fx, x, u, u1, y, xarg, yarg = self._get_match_object() + (C1,) = self.ode_problem.get_numbered_constants(num=1) + int = Integral( + (-e/(d + u1*e)).subs({x: 1, y: u1}), + (u1, None, fx/x)) + sol = logcombine(Eq(log(x), int + log(C1)), force=True) + gen_sol = sol.subs(fx, u).subs(((u, u - yarg), (x, x - xarg), (u, fx))) + return [gen_sol] + + +class HomogeneousCoeffSubsIndepDivDep(SinglePatternODESolver): + r""" + Solves a 1st order differential equation with homogeneous coefficients + using the substitution `u_2 = \frac{\text{}}{\text{}}`. + + This is a differential equation + + .. math:: P(x, y) + Q(x, y) dy/dx = 0 + + such that `P` and `Q` are homogeneous and of the same order. A function + `F(x, y)` is homogeneous of order `n` if `F(x t, y t) = t^n F(x, y)`. + Equivalently, `F(x, y)` can be rewritten as `G(y/x)` or `H(x/y)`. See + also the docstring of :py:meth:`~sympy.solvers.ode.homogeneous_order`. + + If the coefficients `P` and `Q` in the differential equation above are + homogeneous functions of the same order, then it can be shown that the + substitution `x = u_2 y` (i.e. `u_2 = x/y`) will turn the differential + equation into an equation separable in the variables `y` and `u_2`. If + `h(u_2)` is the function that results from making the substitution `u_2 = + x/f(x)` on `P(x, f(x))` and `g(u_2)` is the function that results from the + substitution on `Q(x, f(x))` in the differential equation `P(x, f(x)) + + Q(x, f(x)) f'(x) = 0`, then the general solution is: + + >>> from sympy import Function, dsolve, pprint + >>> from sympy.abc import x + >>> f, g, h = map(Function, ['f', 'g', 'h']) + >>> genform = g(x/f(x)) + h(x/f(x))*f(x).diff(x) + >>> pprint(genform) + / x \ / x \ d + g|----| + h|----|*--(f(x)) + \f(x)/ \f(x)/ dx + >>> pprint(dsolve(genform, f(x), + ... hint='1st_homogeneous_coeff_subs_indep_div_dep_Integral')) + x + ---- + f(x) + / + | + | -g(u1) + | ---------------- d(u1) + | u1*g(u1) + h(u1) + | + / + + f(x) = C1*e + + Where `u_1 g(u_1) + h(u_1) \ne 0` and `f(x) \ne 0`. + + See also the docstrings of + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffBest` and + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep`. + + Examples + ======== + + >>> from sympy import Function, pprint, dsolve + >>> from sympy.abc import x + >>> f = Function('f') + >>> pprint(dsolve(2*x*f(x) + (x**2 + f(x)**2)*f(x).diff(x), f(x), + ... hint='1st_homogeneous_coeff_subs_indep_div_dep', + ... simplify=False)) + / 2 \ + |3*x | + log|----- + 1| + | 2 | + \f (x) / + log(f(x)) = log(C1) - -------------- + 3 + + References + ========== + + - https://en.wikipedia.org/wiki/Homogeneous_differential_equation + - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", + Dover 1963, pp. 59 + + # indirect doctest + + """ + hint = "1st_homogeneous_coeff_subs_indep_div_dep" + has_integral = True + order = [1] + + def _wilds(self, f, x, order): + d = Wild('d', exclude=[f(x).diff(x), f(x).diff(x, 2)]) + e = Wild('e', exclude=[f(x).diff(x)]) + return d, e + + def _equation(self, fx, x, order): + d, e = self.wilds() + return d + e*fx.diff(x) + + def _verify(self, fx): + self.d, self.e = self.wilds_match() + self.y = Dummy('y') + x = self.ode_problem.sym + self.d = separatevars(self.d.subs(fx, self.y)) + self.e = separatevars(self.e.subs(fx, self.y)) + ordera = homogeneous_order(self.d, x, self.y) + orderb = homogeneous_order(self.e, x, self.y) + if ordera == orderb and ordera is not None: + self.u = Dummy('u') + if simplify((self.e + self.u*self.d).subs({x: self.u, self.y: 1})) != 0: + return True + return False + return False + + def _get_match_object(self): + fx = self.ode_problem.func + x = self.ode_problem.sym + self.u1 = Dummy('u1') + xarg = 0 + yarg = 0 + return [self.d, self.e, fx, x, self.u, self.u1, self.y, xarg, yarg] + + def _get_general_solution(self, *, simplify_flag: bool = True): + d, e, fx, x, u, u1, y, xarg, yarg = self._get_match_object() + (C1,) = self.ode_problem.get_numbered_constants(num=1) + int = Integral(simplify((-d/(e + u1*d)).subs({x: u1, y: 1})), (u1, None, x/fx)) # type: ignore + sol = logcombine(Eq(log(fx), int + log(C1)), force=True) + gen_sol = sol.subs(fx, u).subs(((u, u - yarg), (x, x - xarg), (u, fx))) + return [gen_sol] + + +class HomogeneousCoeffBest(HomogeneousCoeffSubsIndepDivDep, HomogeneousCoeffSubsDepDivIndep): + r""" + Returns the best solution to an ODE from the two hints + ``1st_homogeneous_coeff_subs_dep_div_indep`` and + ``1st_homogeneous_coeff_subs_indep_div_dep``. + + This is as determined by :py:meth:`~sympy.solvers.ode.ode.ode_sol_simplicity`. + + See the + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep` + and + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` + docstrings for more information on these hints. Note that there is no + ``ode_1st_homogeneous_coeff_best_Integral`` hint. + + Examples + ======== + + >>> from sympy import Function, dsolve, pprint + >>> from sympy.abc import x + >>> f = Function('f') + >>> pprint(dsolve(2*x*f(x) + (x**2 + f(x)**2)*f(x).diff(x), f(x), + ... hint='1st_homogeneous_coeff_best', simplify=False)) + / 2 \ + |3*x | + log|----- + 1| + | 2 | + \f (x) / + log(f(x)) = log(C1) - -------------- + 3 + + References + ========== + + - https://en.wikipedia.org/wiki/Homogeneous_differential_equation + - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", + Dover 1963, pp. 59 + + # indirect doctest + + """ + hint = "1st_homogeneous_coeff_best" + has_integral = False + order = [1] + + def _verify(self, fx): + if HomogeneousCoeffSubsIndepDivDep._verify(self, fx) and HomogeneousCoeffSubsDepDivIndep._verify(self, fx): + return True + return False + + def _get_general_solution(self, *, simplify_flag: bool = True): + # There are two substitutions that solve the equation, u1=y/x and u2=x/y + # # They produce different integrals, so try them both and see which + # # one is easier + sol1 = HomogeneousCoeffSubsIndepDivDep._get_general_solution(self) + sol2 = HomogeneousCoeffSubsDepDivIndep._get_general_solution(self) + fx = self.ode_problem.func + if simplify_flag: + sol1 = odesimp(self.ode_problem.eq, *sol1, fx, "1st_homogeneous_coeff_subs_indep_div_dep") + sol2 = odesimp(self.ode_problem.eq, *sol2, fx, "1st_homogeneous_coeff_subs_dep_div_indep") + return min([sol1, sol2], key=lambda x: ode_sol_simplicity(x, fx, trysolving=not simplify)) + + +class LinearCoefficients(HomogeneousCoeffBest): + r""" + Solves a differential equation with linear coefficients. + + The general form of a differential equation with linear coefficients is + + .. math:: y' + F\left(\!\frac{a_1 x + b_1 y + c_1}{a_2 x + b_2 y + + c_2}\!\right) = 0\text{,} + + where `a_1`, `b_1`, `c_1`, `a_2`, `b_2`, `c_2` are constants and `a_1 b_2 + - a_2 b_1 \ne 0`. + + This can be solved by substituting: + + .. math:: x = x' + \frac{b_2 c_1 - b_1 c_2}{a_2 b_1 - a_1 b_2} + + y = y' + \frac{a_1 c_2 - a_2 c_1}{a_2 b_1 - a_1 + b_2}\text{.} + + This substitution reduces the equation to a homogeneous differential + equation. + + See Also + ======== + :obj:`sympy.solvers.ode.single.HomogeneousCoeffBest` + :obj:`sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep` + :obj:`sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` + + Examples + ======== + + >>> from sympy import dsolve, Function, pprint + >>> from sympy.abc import x + >>> f = Function('f') + >>> df = f(x).diff(x) + >>> eq = (x + f(x) + 1)*df + (f(x) - 6*x + 1) + >>> dsolve(eq, hint='linear_coefficients') + [Eq(f(x), -x - sqrt(C1 + 7*x**2) - 1), Eq(f(x), -x + sqrt(C1 + 7*x**2) - 1)] + >>> pprint(dsolve(eq, hint='linear_coefficients')) + ___________ ___________ + / 2 / 2 + [f(x) = -x - \/ C1 + 7*x - 1, f(x) = -x + \/ C1 + 7*x - 1] + + + References + ========== + + - Joel Moses, "Symbolic Integration - The Stormy Decade", Communications + of the ACM, Volume 14, Number 8, August 1971, pp. 558 + """ + hint = "linear_coefficients" + has_integral = True + order = [1] + + def _wilds(self, f, x, order): + d = Wild('d', exclude=[f(x).diff(x), f(x).diff(x, 2)]) + e = Wild('e', exclude=[f(x).diff(x)]) + return d, e + + def _equation(self, fx, x, order): + d, e = self.wilds() + return d + e*fx.diff(x) + + def _verify(self, fx): + self.d, self.e = self.wilds_match() + a, b = self.wilds() + F = self.d/self.e + x = self.ode_problem.sym + params = self._linear_coeff_match(F, fx) + if params: + self.xarg, self.yarg = params + u = Dummy('u') + t = Dummy('t') + self.y = Dummy('y') + # Dummy substitution for df and f(x). + dummy_eq = self.ode_problem.eq.subs(((fx.diff(x), t), (fx, u))) + reps = ((x, x + self.xarg), (u, u + self.yarg), (t, fx.diff(x)), (u, fx)) + dummy_eq = simplify(dummy_eq.subs(reps)) + # get the re-cast values for e and d + r2 = collect(expand(dummy_eq), [fx.diff(x), fx]).match(a*fx.diff(x) + b) + if r2: + self.d, self.e = r2[b], r2[a] + orderd = homogeneous_order(self.d, x, fx) + ordere = homogeneous_order(self.e, x, fx) + if orderd == ordere and orderd is not None: + self.d = self.d.subs(fx, self.y) + self.e = self.e.subs(fx, self.y) + return True + return False + return False + + def _linear_coeff_match(self, expr, func): + r""" + Helper function to match hint ``linear_coefficients``. + + Matches the expression to the form `(a_1 x + b_1 f(x) + c_1)/(a_2 x + b_2 + f(x) + c_2)` where the following conditions hold: + + 1. `a_1`, `b_1`, `c_1`, `a_2`, `b_2`, `c_2` are Rationals; + 2. `c_1` or `c_2` are not equal to zero; + 3. `a_2 b_1 - a_1 b_2` is not equal to zero. + + Return ``xarg``, ``yarg`` where + + 1. ``xarg`` = `(b_2 c_1 - b_1 c_2)/(a_2 b_1 - a_1 b_2)` + 2. ``yarg`` = `(a_1 c_2 - a_2 c_1)/(a_2 b_1 - a_1 b_2)` + + + Examples + ======== + + >>> from sympy import Function, sin + >>> from sympy.abc import x + >>> from sympy.solvers.ode.single import LinearCoefficients + >>> f = Function('f') + >>> eq = (-25*f(x) - 8*x + 62)/(4*f(x) + 11*x - 11) + >>> obj = LinearCoefficients(eq) + >>> obj._linear_coeff_match(eq, f(x)) + (1/9, 22/9) + >>> eq = sin((-5*f(x) - 8*x + 6)/(4*f(x) + x - 1)) + >>> obj = LinearCoefficients(eq) + >>> obj._linear_coeff_match(eq, f(x)) + (19/27, 2/27) + >>> eq = sin(f(x)/x) + >>> obj = LinearCoefficients(eq) + >>> obj._linear_coeff_match(eq, f(x)) + + """ + f = func.func + x = func.args[0] + def abc(eq): + r''' + Internal function of _linear_coeff_match + that returns Rationals a, b, c + if eq is a*x + b*f(x) + c, else None. + ''' + eq = _mexpand(eq) + c = eq.as_independent(x, f(x), as_Add=True)[0] + if not c.is_Rational: + return + a = eq.coeff(x) + if not a.is_Rational: + return + b = eq.coeff(f(x)) + if not b.is_Rational: + return + if eq == a*x + b*f(x) + c: + return a, b, c + + def match(arg): + r''' + Internal function of _linear_coeff_match that returns Rationals a1, + b1, c1, a2, b2, c2 and a2*b1 - a1*b2 of the expression (a1*x + b1*f(x) + + c1)/(a2*x + b2*f(x) + c2) if one of c1 or c2 and a2*b1 - a1*b2 is + non-zero, else None. + ''' + n, d = arg.together().as_numer_denom() + m = abc(n) + if m is not None: + a1, b1, c1 = m + m = abc(d) + if m is not None: + a2, b2, c2 = m + d = a2*b1 - a1*b2 + if (c1 or c2) and d: + return a1, b1, c1, a2, b2, c2, d + + m = [fi.args[0] for fi in expr.atoms(Function) if fi.func != f and + len(fi.args) == 1 and not fi.args[0].is_Function] or {expr} + m1 = match(m.pop()) + if m1 and all(match(mi) == m1 for mi in m): + a1, b1, c1, a2, b2, c2, denom = m1 + return (b2*c1 - b1*c2)/denom, (a1*c2 - a2*c1)/denom + + def _get_match_object(self): + fx = self.ode_problem.func + x = self.ode_problem.sym + self.u1 = Dummy('u1') + u = Dummy('u') + return [self.d, self.e, fx, x, u, self.u1, self.y, self.xarg, self.yarg] + + +class NthOrderReducible(SingleODESolver): + r""" + Solves ODEs that only involve derivatives of the dependent variable using + a substitution of the form `f^n(x) = g(x)`. + + For example any second order ODE of the form `f''(x) = h(f'(x), x)` can be + transformed into a pair of 1st order ODEs `g'(x) = h(g(x), x)` and + `f'(x) = g(x)`. Usually the 1st order ODE for `g` is easier to solve. If + that gives an explicit solution for `g` then `f` is found simply by + integration. + + + Examples + ======== + + >>> from sympy import Function, dsolve, Eq + >>> from sympy.abc import x + >>> f = Function('f') + >>> eq = Eq(x*f(x).diff(x)**2 + f(x).diff(x, 2), 0) + >>> dsolve(eq, f(x), hint='nth_order_reducible') + ... # doctest: +NORMALIZE_WHITESPACE + Eq(f(x), C1 - sqrt(-1/C2)*log(-C2*sqrt(-1/C2) + x) + sqrt(-1/C2)*log(C2*sqrt(-1/C2) + x)) + + """ + hint = "nth_order_reducible" + has_integral = False + + def _matches(self): + # Any ODE that can be solved with a substitution and + # repeated integration e.g.: + # `d^2/dx^2(y) + x*d/dx(y) = constant + #f'(x) must be finite for this to work + eq = self.ode_problem.eq_preprocessed + func = self.ode_problem.func + x = self.ode_problem.sym + r""" + Matches any differential equation that can be rewritten with a smaller + order. Only derivatives of ``func`` alone, wrt a single variable, + are considered, and only in them should ``func`` appear. + """ + # ODE only handles functions of 1 variable so this affirms that state + assert len(func.args) == 1 + vc = [d.variable_count[0] for d in eq.atoms(Derivative) + if d.expr == func and len(d.variable_count) == 1] + ords = [c for v, c in vc if v == x] + if len(ords) < 2: + return False + self.smallest = min(ords) + # make sure func does not appear outside of derivatives + D = Dummy() + if eq.subs(func.diff(x, self.smallest), D).has(func): + return False + return True + + def _get_general_solution(self, *, simplify_flag: bool = True): + eq = self.ode_problem.eq + f = self.ode_problem.func.func + x = self.ode_problem.sym + n = self.smallest + # get a unique function name for g + names = [a.name for a in eq.atoms(AppliedUndef)] + while True: + name = Dummy().name + if name not in names: + g = Function(name) + break + w = f(x).diff(x, n) + geq = eq.subs(w, g(x)) + gsol = dsolve(geq, g(x)) + + if not isinstance(gsol, list): + gsol = [gsol] + + # Might be multiple solutions to the reduced ODE: + fsol = [] + for gsoli in gsol: + fsoli = dsolve(gsoli.subs(g(x), w), f(x)) # or do integration n times + fsol.append(fsoli) + + return fsol + + +class SecondHypergeometric(SingleODESolver): + r""" + Solves 2nd order linear differential equations. + + It computes special function solutions which can be expressed using the + 2F1, 1F1 or 0F1 hypergeometric functions. + + .. math:: y'' + A(x) y' + B(x) y = 0\text{,} + + where `A` and `B` are rational functions. + + These kinds of differential equations have solution of non-Liouvillian form. + + Given linear ODE can be obtained from 2F1 given by + + .. math:: (x^2 - x) y'' + ((a + b + 1) x - c) y' + b a y = 0\text{,} + + where {a, b, c} are arbitrary constants. + + Notes + ===== + + The algorithm should find any solution of the form + + .. math:: y = P(x) _pF_q(..; ..;\frac{\alpha x^k + \beta}{\gamma x^k + \delta})\text{,} + + where pFq is any of 2F1, 1F1 or 0F1 and `P` is an "arbitrary function". + Currently only the 2F1 case is implemented in SymPy but the other cases are + described in the paper and could be implemented in future (contributions + welcome!). + + + Examples + ======== + + >>> from sympy import Function, dsolve, pprint + >>> from sympy.abc import x + >>> f = Function('f') + >>> eq = (x*x - x)*f(x).diff(x,2) + (5*x - 1)*f(x).diff(x) + 4*f(x) + >>> pprint(dsolve(eq, f(x), '2nd_hypergeometric')) + _ + / / 4 \\ |_ /-1, -1 | \ + |C1 + C2*|log(x) + -----||* | | | x| + \ \ x + 1// 2 1 \ 1 | / + f(x) = -------------------------------------------- + 3 + (x - 1) + + + References + ========== + + - "Non-Liouvillian solutions for second order linear ODEs" by L. Chan, E.S. Cheb-Terrab + + """ + hint = "2nd_hypergeometric" + has_integral = True + + def _matches(self): + eq = self.ode_problem.eq_preprocessed + func = self.ode_problem.func + r = match_2nd_hypergeometric(eq, func) + self.match_object = None + if r: + A, B = r + d = equivalence_hypergeometric(A, B, func) + if d: + if d['type'] == "2F1": + self.match_object = match_2nd_2F1_hypergeometric(d['I0'], d['k'], d['sing_point'], func) + if self.match_object is not None: + self.match_object.update({'A':A, 'B':B}) + # We can extend it for 1F1 and 0F1 type also. + return self.match_object is not None + + def _get_general_solution(self, *, simplify_flag: bool = True): + eq = self.ode_problem.eq + func = self.ode_problem.func + if self.match_object['type'] == "2F1": + sol = get_sol_2F1_hypergeometric(eq, func, self.match_object) + if sol is None: + raise NotImplementedError("The given ODE " + str(eq) + " cannot be solved by" + + " the hypergeometric method") + + return [sol] + + +class NthLinearConstantCoeffHomogeneous(SingleODESolver): + r""" + Solves an `n`\th order linear homogeneous differential equation with + constant coefficients. + + This is an equation of the form + + .. math:: a_n f^{(n)}(x) + a_{n-1} f^{(n-1)}(x) + \cdots + a_1 f'(x) + + a_0 f(x) = 0\text{.} + + These equations can be solved in a general manner, by taking the roots of + the characteristic equation `a_n m^n + a_{n-1} m^{n-1} + \cdots + a_1 m + + a_0 = 0`. The solution will then be the sum of `C_n x^i e^{r x}` terms, + for each where `C_n` is an arbitrary constant, `r` is a root of the + characteristic equation and `i` is one of each from 0 to the multiplicity + of the root - 1 (for example, a root 3 of multiplicity 2 would create the + terms `C_1 e^{3 x} + C_2 x e^{3 x}`). The exponential is usually expanded + for complex roots using Euler's equation `e^{I x} = \cos(x) + I \sin(x)`. + Complex roots always come in conjugate pairs in polynomials with real + coefficients, so the two roots will be represented (after simplifying the + constants) as `e^{a x} \left(C_1 \cos(b x) + C_2 \sin(b x)\right)`. + + If SymPy cannot find exact roots to the characteristic equation, a + :py:class:`~sympy.polys.rootoftools.ComplexRootOf` instance will be return + instead. + + >>> from sympy import Function, dsolve + >>> from sympy.abc import x + >>> f = Function('f') + >>> dsolve(f(x).diff(x, 5) + 10*f(x).diff(x) - 2*f(x), f(x), + ... hint='nth_linear_constant_coeff_homogeneous') + ... # doctest: +NORMALIZE_WHITESPACE + Eq(f(x), C5*exp(x*CRootOf(_x**5 + 10*_x - 2, 0)) + + (C1*sin(x*im(CRootOf(_x**5 + 10*_x - 2, 1))) + + C2*cos(x*im(CRootOf(_x**5 + 10*_x - 2, 1))))*exp(x*re(CRootOf(_x**5 + 10*_x - 2, 1))) + + (C3*sin(x*im(CRootOf(_x**5 + 10*_x - 2, 3))) + + C4*cos(x*im(CRootOf(_x**5 + 10*_x - 2, 3))))*exp(x*re(CRootOf(_x**5 + 10*_x - 2, 3)))) + + Note that because this method does not involve integration, there is no + ``nth_linear_constant_coeff_homogeneous_Integral`` hint. + + Examples + ======== + + >>> from sympy import Function, dsolve, pprint + >>> from sympy.abc import x + >>> f = Function('f') + >>> pprint(dsolve(f(x).diff(x, 4) + 2*f(x).diff(x, 3) - + ... 2*f(x).diff(x, 2) - 6*f(x).diff(x) + 5*f(x), f(x), + ... hint='nth_linear_constant_coeff_homogeneous')) + x -2*x + f(x) = (C1 + C2*x)*e + (C3*sin(x) + C4*cos(x))*e + + References + ========== + + - https://en.wikipedia.org/wiki/Linear_differential_equation section: + Nonhomogeneous_equation_with_constant_coefficients + - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", + Dover 1963, pp. 211 + + # indirect doctest + + """ + hint = "nth_linear_constant_coeff_homogeneous" + has_integral = False + + def _matches(self): + eq = self.ode_problem.eq_high_order_free + func = self.ode_problem.func + order = self.ode_problem.order + x = self.ode_problem.sym + self.r = self.ode_problem.get_linear_coefficients(eq, func, order) + if order and self.r and not any(self.r[i].has(x) for i in self.r if i >= 0): + if not self.r[-1]: + return True + else: + return False + return False + + def _get_general_solution(self, *, simplify_flag: bool = True): + fx = self.ode_problem.func + order = self.ode_problem.order + roots, collectterms = _get_const_characteristic_eq_sols(self.r, fx, order) + # A generator of constants + constants = self.ode_problem.get_numbered_constants(num=len(roots)) + gsol = Add(*[i*j for (i, j) in zip(constants, roots)]) + gsol = Eq(fx, gsol) + if simplify_flag: + gsol = _get_simplified_sol([gsol], fx, collectterms) + + return [gsol] + + +class NthLinearConstantCoeffVariationOfParameters(SingleODESolver): + r""" + Solves an `n`\th order linear differential equation with constant + coefficients using the method of variation of parameters. + + This method works on any differential equations of the form + + .. math:: f^{(n)}(x) + a_{n-1} f^{(n-1)}(x) + \cdots + a_1 f'(x) + a_0 + f(x) = P(x)\text{.} + + This method works by assuming that the particular solution takes the form + + .. math:: \sum_{x=1}^{n} c_i(x) y_i(x)\text{,} + + where `y_i` is the `i`\th solution to the homogeneous equation. The + solution is then solved using Wronskian's and Cramer's Rule. The + particular solution is given by + + .. math:: \sum_{x=1}^n \left( \int \frac{W_i(x)}{W(x)} \,dx + \right) y_i(x) \text{,} + + where `W(x)` is the Wronskian of the fundamental system (the system of `n` + linearly independent solutions to the homogeneous equation), and `W_i(x)` + is the Wronskian of the fundamental system with the `i`\th column replaced + with `[0, 0, \cdots, 0, P(x)]`. + + This method is general enough to solve any `n`\th order inhomogeneous + linear differential equation with constant coefficients, but sometimes + SymPy cannot simplify the Wronskian well enough to integrate it. If this + method hangs, try using the + ``nth_linear_constant_coeff_variation_of_parameters_Integral`` hint and + simplifying the integrals manually. Also, prefer using + ``nth_linear_constant_coeff_undetermined_coefficients`` when it + applies, because it does not use integration, making it faster and more + reliable. + + Warning, using simplify=False with + 'nth_linear_constant_coeff_variation_of_parameters' in + :py:meth:`~sympy.solvers.ode.dsolve` may cause it to hang, because it will + not attempt to simplify the Wronskian before integrating. It is + recommended that you only use simplify=False with + 'nth_linear_constant_coeff_variation_of_parameters_Integral' for this + method, especially if the solution to the homogeneous equation has + trigonometric functions in it. + + Examples + ======== + + >>> from sympy import Function, dsolve, pprint, exp, log + >>> from sympy.abc import x + >>> f = Function('f') + >>> pprint(dsolve(f(x).diff(x, 3) - 3*f(x).diff(x, 2) + + ... 3*f(x).diff(x) - f(x) - exp(x)*log(x), f(x), + ... hint='nth_linear_constant_coeff_variation_of_parameters')) + / / / x*log(x) 11*x\\\ x + f(x) = |C1 + x*|C2 + x*|C3 + -------- - ----|||*e + \ \ \ 6 36 /// + + References + ========== + + - https://en.wikipedia.org/wiki/Variation_of_parameters + - https://planetmath.org/VariationOfParameters + - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", + Dover 1963, pp. 233 + + # indirect doctest + + """ + hint = "nth_linear_constant_coeff_variation_of_parameters" + has_integral = True + + def _matches(self): + eq = self.ode_problem.eq_high_order_free + func = self.ode_problem.func + order = self.ode_problem.order + x = self.ode_problem.sym + self.r = self.ode_problem.get_linear_coefficients(eq, func, order) + + if order and self.r and not any(self.r[i].has(x) for i in self.r if i >= 0): + if self.r[-1]: + return True + else: + return False + return False + + def _get_general_solution(self, *, simplify_flag: bool = True): + eq = self.ode_problem.eq_high_order_free + f = self.ode_problem.func.func + x = self.ode_problem.sym + order = self.ode_problem.order + roots, collectterms = _get_const_characteristic_eq_sols(self.r, f(x), order) + # A generator of constants + constants = self.ode_problem.get_numbered_constants(num=len(roots)) + homogen_sol = Add(*[i*j for (i, j) in zip(constants, roots)]) + homogen_sol = Eq(f(x), homogen_sol) + homogen_sol = _solve_variation_of_parameters(eq, f(x), roots, homogen_sol, order, self.r, simplify_flag) + if simplify_flag: + homogen_sol = _get_simplified_sol([homogen_sol], f(x), collectterms) + return [homogen_sol] + + +class NthLinearConstantCoeffUndeterminedCoefficients(SingleODESolver): + r""" + Solves an `n`\th order linear differential equation with constant + coefficients using the method of undetermined coefficients. + + This method works on differential equations of the form + + .. math:: a_n f^{(n)}(x) + a_{n-1} f^{(n-1)}(x) + \cdots + a_1 f'(x) + + a_0 f(x) = P(x)\text{,} + + where `P(x)` is a function that has a finite number of linearly + independent derivatives. + + Functions that fit this requirement are finite sums functions of the form + `a x^i e^{b x} \sin(c x + d)` or `a x^i e^{b x} \cos(c x + d)`, where `i` + is a non-negative integer and `a`, `b`, `c`, and `d` are constants. For + example any polynomial in `x`, functions like `x^2 e^{2 x}`, `x \sin(x)`, + and `e^x \cos(x)` can all be used. Products of `\sin`'s and `\cos`'s have + a finite number of derivatives, because they can be expanded into `\sin(a + x)` and `\cos(b x)` terms. However, SymPy currently cannot do that + expansion, so you will need to manually rewrite the expression in terms of + the above to use this method. So, for example, you will need to manually + convert `\sin^2(x)` into `(1 + \cos(2 x))/2` to properly apply the method + of undetermined coefficients on it. + + This method works by creating a trial function from the expression and all + of its linear independent derivatives and substituting them into the + original ODE. The coefficients for each term will be a system of linear + equations, which are be solved for and substituted, giving the solution. + If any of the trial functions are linearly dependent on the solution to + the homogeneous equation, they are multiplied by sufficient `x` to make + them linearly independent. + + Examples + ======== + + >>> from sympy import Function, dsolve, pprint, exp, cos + >>> from sympy.abc import x + >>> f = Function('f') + >>> pprint(dsolve(f(x).diff(x, 2) + 2*f(x).diff(x) + f(x) - + ... 4*exp(-x)*x**2 + cos(2*x), f(x), + ... hint='nth_linear_constant_coeff_undetermined_coefficients')) + / / 3\\ + | | x || -x 4*sin(2*x) 3*cos(2*x) + f(x) = |C1 + x*|C2 + --||*e - ---------- + ---------- + \ \ 3 // 25 25 + + References + ========== + + - https://en.wikipedia.org/wiki/Method_of_undetermined_coefficients + - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", + Dover 1963, pp. 221 + + # indirect doctest + + """ + hint = "nth_linear_constant_coeff_undetermined_coefficients" + has_integral = False + + def _matches(self): + eq = self.ode_problem.eq_high_order_free + func = self.ode_problem.func + order = self.ode_problem.order + x = self.ode_problem.sym + self.r = self.ode_problem.get_linear_coefficients(eq, func, order) + does_match = False + if order and self.r and not any(self.r[i].has(x) for i in self.r if i >= 0): + if self.r[-1]: + eq_homogeneous = Add(eq, -self.r[-1]) + undetcoeff = _undetermined_coefficients_match(self.r[-1], x, func, eq_homogeneous) + if undetcoeff['test']: + self.trialset = undetcoeff['trialset'] + does_match = True + return does_match + + def _get_general_solution(self, *, simplify_flag: bool = True): + eq = self.ode_problem.eq + f = self.ode_problem.func.func + x = self.ode_problem.sym + order = self.ode_problem.order + roots, collectterms = _get_const_characteristic_eq_sols(self.r, f(x), order) + # A generator of constants + constants = self.ode_problem.get_numbered_constants(num=len(roots)) + homogen_sol = Add(*[i*j for (i, j) in zip(constants, roots)]) + homogen_sol = Eq(f(x), homogen_sol) + self.r.update({'list': roots, 'sol': homogen_sol, 'simpliy_flag': simplify_flag}) + gsol = _solve_undetermined_coefficients(eq, f(x), order, self.r, self.trialset) + if simplify_flag: + gsol = _get_simplified_sol([gsol], f(x), collectterms) + return [gsol] + + +class NthLinearEulerEqHomogeneous(SingleODESolver): + r""" + Solves an `n`\th order linear homogeneous variable-coefficient + Cauchy-Euler equidimensional ordinary differential equation. + + This is an equation with form `0 = a_0 f(x) + a_1 x f'(x) + a_2 x^2 f''(x) + \cdots`. + + These equations can be solved in a general manner, by substituting + solutions of the form `f(x) = x^r`, and deriving a characteristic equation + for `r`. When there are repeated roots, we include extra terms of the + form `C_{r k} \ln^k(x) x^r`, where `C_{r k}` is an arbitrary integration + constant, `r` is a root of the characteristic equation, and `k` ranges + over the multiplicity of `r`. In the cases where the roots are complex, + solutions of the form `C_1 x^a \sin(b \log(x)) + C_2 x^a \cos(b \log(x))` + are returned, based on expansions with Euler's formula. The general + solution is the sum of the terms found. If SymPy cannot find exact roots + to the characteristic equation, a + :py:obj:`~.ComplexRootOf` instance will be returned + instead. + + >>> from sympy import Function, dsolve + >>> from sympy.abc import x + >>> f = Function('f') + >>> dsolve(4*x**2*f(x).diff(x, 2) + f(x), f(x), + ... hint='nth_linear_euler_eq_homogeneous') + ... # doctest: +NORMALIZE_WHITESPACE + Eq(f(x), sqrt(x)*(C1 + C2*log(x))) + + Note that because this method does not involve integration, there is no + ``nth_linear_euler_eq_homogeneous_Integral`` hint. + + The following is for internal use: + + - ``returns = 'sol'`` returns the solution to the ODE. + - ``returns = 'list'`` returns a list of linearly independent solutions, + corresponding to the fundamental solution set, for use with non + homogeneous solution methods like variation of parameters and + undetermined coefficients. Note that, though the solutions should be + linearly independent, this function does not explicitly check that. You + can do ``assert simplify(wronskian(sollist)) != 0`` to check for linear + independence. Also, ``assert len(sollist) == order`` will need to pass. + - ``returns = 'both'``, return a dictionary ``{'sol': , + 'list': }``. + + Examples + ======== + + >>> from sympy import Function, dsolve, pprint + >>> from sympy.abc import x + >>> f = Function('f') + >>> eq = f(x).diff(x, 2)*x**2 - 4*f(x).diff(x)*x + 6*f(x) + >>> pprint(dsolve(eq, f(x), + ... hint='nth_linear_euler_eq_homogeneous')) + 2 + f(x) = x *(C1 + C2*x) + + References + ========== + + - https://en.wikipedia.org/wiki/Cauchy%E2%80%93Euler_equation + - C. Bender & S. Orszag, "Advanced Mathematical Methods for Scientists and + Engineers", Springer 1999, pp. 12 + + # indirect doctest + + """ + hint = "nth_linear_euler_eq_homogeneous" + has_integral = False + + def _matches(self): + eq = self.ode_problem.eq_preprocessed + f = self.ode_problem.func.func + order = self.ode_problem.order + x = self.ode_problem.sym + match = self.ode_problem.get_linear_coefficients(eq, f(x), order) + self.r = None + does_match = False + + if order and match: + coeff = match[order] + factor = x**order / coeff + self.r = {i: factor*match[i] for i in match} + if self.r and all(_test_term(self.r[i], f(x), i) for i in + self.r if i >= 0): + if not self.r[-1]: + does_match = True + return does_match + + def _get_general_solution(self, *, simplify_flag: bool = True): + fx = self.ode_problem.func + eq = self.ode_problem.eq + homogen_sol = _get_euler_characteristic_eq_sols(eq, fx, self.r)[0] + return [homogen_sol] + + +class NthLinearEulerEqNonhomogeneousVariationOfParameters(SingleODESolver): + r""" + Solves an `n`\th order linear non homogeneous Cauchy-Euler equidimensional + ordinary differential equation using variation of parameters. + + This is an equation with form `g(x) = a_0 f(x) + a_1 x f'(x) + a_2 x^2 f''(x) + \cdots`. + + This method works by assuming that the particular solution takes the form + + .. math:: \sum_{x=1}^{n} c_i(x) y_i(x) {a_n} {x^n} \text{, } + + where `y_i` is the `i`\th solution to the homogeneous equation. The + solution is then solved using Wronskian's and Cramer's Rule. The + particular solution is given by multiplying eq given below with `a_n x^{n}` + + .. math:: \sum_{x=1}^n \left( \int \frac{W_i(x)}{W(x)} \, dx + \right) y_i(x) \text{, } + + where `W(x)` is the Wronskian of the fundamental system (the system of `n` + linearly independent solutions to the homogeneous equation), and `W_i(x)` + is the Wronskian of the fundamental system with the `i`\th column replaced + with `[0, 0, \cdots, 0, \frac{x^{- n}}{a_n} g{\left(x \right)}]`. + + This method is general enough to solve any `n`\th order inhomogeneous + linear differential equation, but sometimes SymPy cannot simplify the + Wronskian well enough to integrate it. If this method hangs, try using the + ``nth_linear_constant_coeff_variation_of_parameters_Integral`` hint and + simplifying the integrals manually. Also, prefer using + ``nth_linear_constant_coeff_undetermined_coefficients`` when it + applies, because it does not use integration, making it faster and more + reliable. + + Warning, using simplify=False with + 'nth_linear_constant_coeff_variation_of_parameters' in + :py:meth:`~sympy.solvers.ode.dsolve` may cause it to hang, because it will + not attempt to simplify the Wronskian before integrating. It is + recommended that you only use simplify=False with + 'nth_linear_constant_coeff_variation_of_parameters_Integral' for this + method, especially if the solution to the homogeneous equation has + trigonometric functions in it. + + Examples + ======== + + >>> from sympy import Function, dsolve, Derivative + >>> from sympy.abc import x + >>> f = Function('f') + >>> eq = x**2*Derivative(f(x), x, x) - 2*x*Derivative(f(x), x) + 2*f(x) - x**4 + >>> dsolve(eq, f(x), + ... hint='nth_linear_euler_eq_nonhomogeneous_variation_of_parameters').expand() + Eq(f(x), C1*x + C2*x**2 + x**4/6) + + """ + hint = "nth_linear_euler_eq_nonhomogeneous_variation_of_parameters" + has_integral = True + + def _matches(self): + eq = self.ode_problem.eq_preprocessed + f = self.ode_problem.func.func + order = self.ode_problem.order + x = self.ode_problem.sym + match = self.ode_problem.get_linear_coefficients(eq, f(x), order) + self.r = None + does_match = False + + if order and match: + coeff = match[order] + factor = x**order / coeff + self.r = {i: factor*match[i] for i in match} + if self.r and all(_test_term(self.r[i], f(x), i) for i in + self.r if i >= 0): + if self.r[-1]: + does_match = True + + return does_match + + def _get_general_solution(self, *, simplify_flag: bool = True): + eq = self.ode_problem.eq + f = self.ode_problem.func.func + x = self.ode_problem.sym + order = self.ode_problem.order + homogen_sol, roots = _get_euler_characteristic_eq_sols(eq, f(x), self.r) + self.r[-1] = self.r[-1]/self.r[order] + sol = _solve_variation_of_parameters(eq, f(x), roots, homogen_sol, order, self.r, simplify_flag) + + return [Eq(f(x), homogen_sol.rhs + (sol.rhs - homogen_sol.rhs)*self.r[order])] + + +class NthLinearEulerEqNonhomogeneousUndeterminedCoefficients(SingleODESolver): + r""" + Solves an `n`\th order linear non homogeneous Cauchy-Euler equidimensional + ordinary differential equation using undetermined coefficients. + + This is an equation with form `g(x) = a_0 f(x) + a_1 x f'(x) + a_2 x^2 f''(x) + \cdots`. + + These equations can be solved in a general manner, by substituting + solutions of the form `x = exp(t)`, and deriving a characteristic equation + of form `g(exp(t)) = b_0 f(t) + b_1 f'(t) + b_2 f''(t) \cdots` which can + be then solved by nth_linear_constant_coeff_undetermined_coefficients if + g(exp(t)) has finite number of linearly independent derivatives. + + Functions that fit this requirement are finite sums functions of the form + `a x^i e^{b x} \sin(c x + d)` or `a x^i e^{b x} \cos(c x + d)`, where `i` + is a non-negative integer and `a`, `b`, `c`, and `d` are constants. For + example any polynomial in `x`, functions like `x^2 e^{2 x}`, `x \sin(x)`, + and `e^x \cos(x)` can all be used. Products of `\sin`'s and `\cos`'s have + a finite number of derivatives, because they can be expanded into `\sin(a + x)` and `\cos(b x)` terms. However, SymPy currently cannot do that + expansion, so you will need to manually rewrite the expression in terms of + the above to use this method. So, for example, you will need to manually + convert `\sin^2(x)` into `(1 + \cos(2 x))/2` to properly apply the method + of undetermined coefficients on it. + + After replacement of x by exp(t), this method works by creating a trial function + from the expression and all of its linear independent derivatives and + substituting them into the original ODE. The coefficients for each term + will be a system of linear equations, which are be solved for and + substituted, giving the solution. If any of the trial functions are linearly + dependent on the solution to the homogeneous equation, they are multiplied + by sufficient `x` to make them linearly independent. + + Examples + ======== + + >>> from sympy import dsolve, Function, Derivative, log + >>> from sympy.abc import x + >>> f = Function('f') + >>> eq = x**2*Derivative(f(x), x, x) - 2*x*Derivative(f(x), x) + 2*f(x) - log(x) + >>> dsolve(eq, f(x), + ... hint='nth_linear_euler_eq_nonhomogeneous_undetermined_coefficients').expand() + Eq(f(x), C1*x + C2*x**2 + log(x)/2 + 3/4) + + """ + hint = "nth_linear_euler_eq_nonhomogeneous_undetermined_coefficients" + has_integral = False + + def _matches(self): + eq = self.ode_problem.eq_high_order_free + f = self.ode_problem.func.func + order = self.ode_problem.order + x = self.ode_problem.sym + match = self.ode_problem.get_linear_coefficients(eq, f(x), order) + self.r = None + does_match = False + + if order and match: + coeff = match[order] + factor = x**order / coeff + self.r = {i: factor*match[i] for i in match} + if self.r and all(_test_term(self.r[i], f(x), i) for i in + self.r if i >= 0): + if self.r[-1]: + e, re = posify(self.r[-1].subs(x, exp(x))) + undetcoeff = _undetermined_coefficients_match(e.subs(re), x) + if undetcoeff['test']: + does_match = True + return does_match + + def _get_general_solution(self, *, simplify_flag: bool = True): + f = self.ode_problem.func.func + x = self.ode_problem.sym + chareq, eq, symbol = S.Zero, S.Zero, Dummy('x') + for i in self.r.keys(): + if i >= 0: + chareq += (self.r[i]*diff(x**symbol, x, i)*x**-symbol).expand() + + for i in range(1, degree(Poly(chareq, symbol))+1): + eq += chareq.coeff(symbol**i)*diff(f(x), x, i) + + if chareq.as_coeff_add(symbol)[0]: + eq += chareq.as_coeff_add(symbol)[0]*f(x) + e, re = posify(self.r[-1].subs(x, exp(x))) + eq += e.subs(re) + + self.const_undet_instance = NthLinearConstantCoeffUndeterminedCoefficients(SingleODEProblem(eq, f(x), x)) + sol = self.const_undet_instance.get_general_solution(simplify = simplify_flag)[0] + sol = sol.subs(x, log(x)) + sol = sol.subs(f(log(x)), f(x)).expand() + + return [sol] + + +class SecondLinearBessel(SingleODESolver): + r""" + Gives solution of the Bessel differential equation + + .. math :: x^2 \frac{d^2y}{dx^2} + x \frac{dy}{dx} y(x) + (x^2-n^2) y(x) + + if `n` is integer then the solution is of the form ``Eq(f(x), C0 besselj(n,x) + + C1 bessely(n,x))`` as both the solutions are linearly independent else if + `n` is a fraction then the solution is of the form ``Eq(f(x), C0 besselj(n,x) + + C1 besselj(-n,x))`` which can also transform into ``Eq(f(x), C0 besselj(n,x) + + C1 bessely(n,x))``. + + Examples + ======== + + >>> from sympy.abc import x + >>> from sympy import Symbol + >>> v = Symbol('v', positive=True) + >>> from sympy import dsolve, Function + >>> f = Function('f') + >>> y = f(x) + >>> genform = x**2*y.diff(x, 2) + x*y.diff(x) + (x**2 - v**2)*y + >>> dsolve(genform) + Eq(f(x), C1*besselj(v, x) + C2*bessely(v, x)) + + References + ========== + + https://math24.net/bessel-differential-equation.html + + """ + hint = "2nd_linear_bessel" + has_integral = False + + def _matches(self): + eq = self.ode_problem.eq_high_order_free + f = self.ode_problem.func + order = self.ode_problem.order + x = self.ode_problem.sym + df = f.diff(x) + a = Wild('a', exclude=[f,df]) + b = Wild('b', exclude=[x, f,df]) + a4 = Wild('a4', exclude=[x,f,df]) + b4 = Wild('b4', exclude=[x,f,df]) + c4 = Wild('c4', exclude=[x,f,df]) + d4 = Wild('d4', exclude=[x,f,df]) + a3 = Wild('a3', exclude=[f, df, f.diff(x, 2)]) + b3 = Wild('b3', exclude=[f, df, f.diff(x, 2)]) + c3 = Wild('c3', exclude=[f, df, f.diff(x, 2)]) + deq = a3*(f.diff(x, 2)) + b3*df + c3*f + r = collect(eq, + [f.diff(x, 2), df, f]).match(deq) + if order == 2 and r: + if not all(r[key].is_polynomial() for key in r): + n, d = eq.as_numer_denom() + eq = expand(n) + r = collect(eq, + [f.diff(x, 2), df, f]).match(deq) + + if r and r[a3] != 0: + # leading coeff of f(x).diff(x, 2) + coeff = factor(r[a3]).match(a4*(x-b)**b4) + + if coeff: + # if coeff[b4] = 0 means constant coefficient + if coeff[b4] == 0: + return False + point = coeff[b] + else: + return False + + if point: + r[a3] = simplify(r[a3].subs(x, x+point)) + r[b3] = simplify(r[b3].subs(x, x+point)) + r[c3] = simplify(r[c3].subs(x, x+point)) + + # making a3 in the form of x**2 + r[a3] = cancel(r[a3]/(coeff[a4]*(x)**(-2+coeff[b4]))) + r[b3] = cancel(r[b3]/(coeff[a4]*(x)**(-2+coeff[b4]))) + r[c3] = cancel(r[c3]/(coeff[a4]*(x)**(-2+coeff[b4]))) + # checking if b3 is of form c*(x-b) + coeff1 = factor(r[b3]).match(a4*(x)) + if coeff1 is None: + return False + # c3 maybe of very complex form so I am simply checking (a - b) form + # if yes later I will match with the standerd form of bessel in a and b + # a, b are wild variable defined above. + _coeff2 = expand(r[c3]).match(a - b) + if _coeff2 is None: + return False + # matching with standerd form for c3 + coeff2 = factor(_coeff2[a]).match(c4**2*(x)**(2*a4)) + if coeff2 is None: + return False + + if _coeff2[b] == 0: + coeff2[d4] = 0 + else: + coeff2[d4] = factor(_coeff2[b]).match(d4**2)[d4] + + self.rn = {'n':coeff2[d4], 'a4':coeff2[c4], 'd4':coeff2[a4]} + self.rn['c4'] = coeff1[a4] + self.rn['b4'] = point + return True + return False + + def _get_general_solution(self, *, simplify_flag: bool = True): + f = self.ode_problem.func.func + x = self.ode_problem.sym + n = self.rn['n'] + a4 = self.rn['a4'] + c4 = self.rn['c4'] + d4 = self.rn['d4'] + b4 = self.rn['b4'] + n = sqrt(n**2 + Rational(1, 4)*(c4 - 1)**2) + (C1, C2) = self.ode_problem.get_numbered_constants(num=2) + return [Eq(f(x), ((x**(Rational(1-c4,2)))*(C1*besselj(n/d4,a4*x**d4/d4) + + C2*bessely(n/d4,a4*x**d4/d4))).subs(x, x-b4))] + + +class SecondLinearAiry(SingleODESolver): + r""" + Gives solution of the Airy differential equation + + .. math :: \frac{d^2y}{dx^2} + (a + b x) y(x) = 0 + + in terms of Airy special functions airyai and airybi. + + Examples + ======== + + >>> from sympy import dsolve, Function + >>> from sympy.abc import x + >>> f = Function("f") + >>> eq = f(x).diff(x, 2) - x*f(x) + >>> dsolve(eq) + Eq(f(x), C1*airyai(x) + C2*airybi(x)) + """ + hint = "2nd_linear_airy" + has_integral = False + + def _matches(self): + eq = self.ode_problem.eq_high_order_free + f = self.ode_problem.func + order = self.ode_problem.order + x = self.ode_problem.sym + df = f.diff(x) + a4 = Wild('a4', exclude=[x,f,df]) + b4 = Wild('b4', exclude=[x,f,df]) + match = self.ode_problem.get_linear_coefficients(eq, f, order) + does_match = False + if order == 2 and match and match[2] != 0: + if match[1].is_zero: + self.rn = cancel(match[0]/match[2]).match(a4+b4*x) + if self.rn and self.rn[b4] != 0: + self.rn = {'b':self.rn[a4],'m':self.rn[b4]} + does_match = True + return does_match + + def _get_general_solution(self, *, simplify_flag: bool = True): + f = self.ode_problem.func.func + x = self.ode_problem.sym + (C1, C2) = self.ode_problem.get_numbered_constants(num=2) + b = self.rn['b'] + m = self.rn['m'] + if m.is_positive: + arg = - b/cbrt(m)**2 - cbrt(m)*x + elif m.is_negative: + arg = - b/cbrt(-m)**2 + cbrt(-m)*x + else: + arg = - b/cbrt(-m)**2 + cbrt(-m)*x + + return [Eq(f(x), C1*airyai(arg) + C2*airybi(arg))] + + +class LieGroup(SingleODESolver): + r""" + This hint implements the Lie group method of solving first order differential + equations. The aim is to convert the given differential equation from the + given coordinate system into another coordinate system where it becomes + invariant under the one-parameter Lie group of translations. The converted + ODE can be easily solved by quadrature. It makes use of the + :py:meth:`sympy.solvers.ode.infinitesimals` function which returns the + infinitesimals of the transformation. + + The coordinates `r` and `s` can be found by solving the following Partial + Differential Equations. + + .. math :: \xi\frac{\partial r}{\partial x} + \eta\frac{\partial r}{\partial y} + = 0 + + .. math :: \xi\frac{\partial s}{\partial x} + \eta\frac{\partial s}{\partial y} + = 1 + + The differential equation becomes separable in the new coordinate system + + .. math :: \frac{ds}{dr} = \frac{\frac{\partial s}{\partial x} + + h(x, y)\frac{\partial s}{\partial y}}{ + \frac{\partial r}{\partial x} + h(x, y)\frac{\partial r}{\partial y}} + + After finding the solution by integration, it is then converted back to the original + coordinate system by substituting `r` and `s` in terms of `x` and `y` again. + + Examples + ======== + + >>> from sympy import Function, dsolve, exp, pprint + >>> from sympy.abc import x + >>> f = Function('f') + >>> pprint(dsolve(f(x).diff(x) + 2*x*f(x) - x*exp(-x**2), f(x), + ... hint='lie_group')) + / 2\ 2 + | x | -x + f(x) = |C1 + --|*e + \ 2 / + + + References + ========== + + - Solving differential equations by Symmetry Groups, + John Starrett, pp. 1 - pp. 14 + + """ + hint = "lie_group" + has_integral = False + + def _has_additional_params(self): + return 'xi' in self.ode_problem.params and 'eta' in self.ode_problem.params + + def _matches(self): + eq = self.ode_problem.eq + f = self.ode_problem.func.func + order = self.ode_problem.order + x = self.ode_problem.sym + df = f(x).diff(x) + y = Dummy('y') + d = Wild('d', exclude=[df, f(x).diff(x, 2)]) + e = Wild('e', exclude=[df]) + does_match = False + if self._has_additional_params() and order == 1: + xi = self.ode_problem.params['xi'] + eta = self.ode_problem.params['eta'] + self.r3 = {'xi': xi, 'eta': eta} + r = collect(eq, df, exact=True).match(d + e * df) + if r: + r['d'] = d + r['e'] = e + r['y'] = y + r[d] = r[d].subs(f(x), y) + r[e] = r[e].subs(f(x), y) + self.r3.update(r) + does_match = True + return does_match + + def _get_general_solution(self, *, simplify_flag: bool = True): + eq = self.ode_problem.eq + x = self.ode_problem.sym + func = self.ode_problem.func + order = self.ode_problem.order + df = func.diff(x) + + try: + eqsol = solve(eq, df) + except NotImplementedError: + eqsol = [] + + desols = [] + for s in eqsol: + sol = _ode_lie_group(s, func, order, match=self.r3) + if sol: + desols.extend(sol) + + if desols == []: + raise NotImplementedError("The given ODE " + str(eq) + " cannot be solved by" + + " the lie group method") + return desols + + +solver_map = { + 'factorable': Factorable, + 'nth_linear_constant_coeff_homogeneous': NthLinearConstantCoeffHomogeneous, + 'nth_linear_euler_eq_homogeneous': NthLinearEulerEqHomogeneous, + 'nth_linear_constant_coeff_undetermined_coefficients': NthLinearConstantCoeffUndeterminedCoefficients, + 'nth_linear_euler_eq_nonhomogeneous_undetermined_coefficients': NthLinearEulerEqNonhomogeneousUndeterminedCoefficients, + 'separable': Separable, + '1st_exact': FirstExact, + '1st_linear': FirstLinear, + 'Bernoulli': Bernoulli, + 'Riccati_special_minus2': RiccatiSpecial, + '1st_rational_riccati': RationalRiccati, + '1st_homogeneous_coeff_best': HomogeneousCoeffBest, + '1st_homogeneous_coeff_subs_indep_div_dep': HomogeneousCoeffSubsIndepDivDep, + '1st_homogeneous_coeff_subs_dep_div_indep': HomogeneousCoeffSubsDepDivIndep, + 'almost_linear': AlmostLinear, + 'linear_coefficients': LinearCoefficients, + 'separable_reduced': SeparableReduced, + 'nth_linear_constant_coeff_variation_of_parameters': NthLinearConstantCoeffVariationOfParameters, + 'nth_linear_euler_eq_nonhomogeneous_variation_of_parameters': NthLinearEulerEqNonhomogeneousVariationOfParameters, + 'Liouville': Liouville, + '2nd_linear_airy': SecondLinearAiry, + '2nd_linear_bessel': SecondLinearBessel, + '2nd_hypergeometric': SecondHypergeometric, + 'nth_order_reducible': NthOrderReducible, + '2nd_nonlinear_autonomous_conserved': SecondNonlinearAutonomousConserved, + 'nth_algebraic': NthAlgebraic, + 'lie_group': LieGroup, + } + +# Avoid circular import: +from .ode import dsolve, ode_sol_simplicity, odesimp, homogeneous_order diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/subscheck.py b/lib/python3.12/site-packages/sympy/solvers/ode/subscheck.py new file mode 100644 index 0000000000000000000000000000000000000000..6ac7fba7d364bf599e928ccf591b5bef096576d0 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/ode/subscheck.py @@ -0,0 +1,392 @@ +from sympy.core import S, Pow +from sympy.core.function import (Derivative, AppliedUndef, diff) +from sympy.core.relational import Equality, Eq +from sympy.core.symbol import Dummy +from sympy.core.sympify import sympify + +from sympy.logic.boolalg import BooleanAtom +from sympy.functions import exp +from sympy.series import Order +from sympy.simplify.simplify import simplify, posify, besselsimp +from sympy.simplify.trigsimp import trigsimp +from sympy.simplify.sqrtdenest import sqrtdenest +from sympy.solvers import solve +from sympy.solvers.deutils import _preprocess, ode_order +from sympy.utilities.iterables import iterable, is_sequence + + +def sub_func_doit(eq, func, new): + r""" + When replacing the func with something else, we usually want the + derivative evaluated, so this function helps in making that happen. + + Examples + ======== + + >>> from sympy import Derivative, symbols, Function + >>> from sympy.solvers.ode.subscheck import sub_func_doit + >>> x, z = symbols('x, z') + >>> y = Function('y') + + >>> sub_func_doit(3*Derivative(y(x), x) - 1, y(x), x) + 2 + + >>> sub_func_doit(x*Derivative(y(x), x) - y(x)**2 + y(x), y(x), + ... 1/(x*(z + 1/x))) + x*(-1/(x**2*(z + 1/x)) + 1/(x**3*(z + 1/x)**2)) + 1/(x*(z + 1/x)) + ...- 1/(x**2*(z + 1/x)**2) + """ + reps= {func: new} + for d in eq.atoms(Derivative): + if d.expr == func: + reps[d] = new.diff(*d.variable_count) + else: + reps[d] = d.xreplace({func: new}).doit(deep=False) + return eq.xreplace(reps) + + +def checkodesol(ode, sol, func=None, order='auto', solve_for_func=True): + r""" + Substitutes ``sol`` into ``ode`` and checks that the result is ``0``. + + This works when ``func`` is one function, like `f(x)` or a list of + functions like `[f(x), g(x)]` when `ode` is a system of ODEs. ``sol`` can + be a single solution or a list of solutions. Each solution may be an + :py:class:`~sympy.core.relational.Equality` that the solution satisfies, + e.g. ``Eq(f(x), C1), Eq(f(x) + C1, 0)``; or simply an + :py:class:`~sympy.core.expr.Expr`, e.g. ``f(x) - C1``. In most cases it + will not be necessary to explicitly identify the function, but if the + function cannot be inferred from the original equation it can be supplied + through the ``func`` argument. + + If a sequence of solutions is passed, the same sort of container will be + used to return the result for each solution. + + It tries the following methods, in order, until it finds zero equivalence: + + 1. Substitute the solution for `f` in the original equation. This only + works if ``ode`` is solved for `f`. It will attempt to solve it first + unless ``solve_for_func == False``. + 2. Take `n` derivatives of the solution, where `n` is the order of + ``ode``, and check to see if that is equal to the solution. This only + works on exact ODEs. + 3. Take the 1st, 2nd, ..., `n`\th derivatives of the solution, each time + solving for the derivative of `f` of that order (this will always be + possible because `f` is a linear operator). Then back substitute each + derivative into ``ode`` in reverse order. + + This function returns a tuple. The first item in the tuple is ``True`` if + the substitution results in ``0``, and ``False`` otherwise. The second + item in the tuple is what the substitution results in. It should always + be ``0`` if the first item is ``True``. Sometimes this function will + return ``False`` even when an expression is identically equal to ``0``. + This happens when :py:meth:`~sympy.simplify.simplify.simplify` does not + reduce the expression to ``0``. If an expression returned by this + function vanishes identically, then ``sol`` really is a solution to + the ``ode``. + + If this function seems to hang, it is probably because of a hard + simplification. + + To use this function to test, test the first item of the tuple. + + Examples + ======== + + >>> from sympy import (Eq, Function, checkodesol, symbols, + ... Derivative, exp) + >>> x, C1, C2 = symbols('x,C1,C2') + >>> f, g = symbols('f g', cls=Function) + >>> checkodesol(f(x).diff(x), Eq(f(x), C1)) + (True, 0) + >>> assert checkodesol(f(x).diff(x), C1)[0] + >>> assert not checkodesol(f(x).diff(x), x)[0] + >>> checkodesol(f(x).diff(x, 2), x**2) + (False, 2) + + >>> eqs = [Eq(Derivative(f(x), x), f(x)), Eq(Derivative(g(x), x), g(x))] + >>> sol = [Eq(f(x), C1*exp(x)), Eq(g(x), C2*exp(x))] + >>> checkodesol(eqs, sol) + (True, [0, 0]) + + """ + if iterable(ode): + return checksysodesol(ode, sol, func=func) + + if not isinstance(ode, Equality): + ode = Eq(ode, 0) + if func is None: + try: + _, func = _preprocess(ode.lhs) + except ValueError: + funcs = [s.atoms(AppliedUndef) for s in ( + sol if is_sequence(sol, set) else [sol])] + funcs = set().union(*funcs) + if len(funcs) != 1: + raise ValueError( + 'must pass func arg to checkodesol for this case.') + func = funcs.pop() + if not isinstance(func, AppliedUndef) or len(func.args) != 1: + raise ValueError( + "func must be a function of one variable, not %s" % func) + if is_sequence(sol, set): + return type(sol)([checkodesol(ode, i, order=order, solve_for_func=solve_for_func) for i in sol]) + + if not isinstance(sol, Equality): + sol = Eq(func, sol) + elif sol.rhs == func: + sol = sol.reversed + + if order == 'auto': + order = ode_order(ode, func) + solved = sol.lhs == func and not sol.rhs.has(func) + if solve_for_func and not solved: + rhs = solve(sol, func) + if rhs: + eqs = [Eq(func, t) for t in rhs] + if len(rhs) == 1: + eqs = eqs[0] + return checkodesol(ode, eqs, order=order, + solve_for_func=False) + + x = func.args[0] + + # Handle series solutions here + if sol.has(Order): + assert sol.lhs == func + Oterm = sol.rhs.getO() + solrhs = sol.rhs.removeO() + + Oexpr = Oterm.expr + assert isinstance(Oexpr, Pow) + sorder = Oexpr.exp + assert Oterm == Order(x**sorder) + + odesubs = (ode.lhs-ode.rhs).subs(func, solrhs).doit().expand() + + neworder = Order(x**(sorder - order)) + odesubs = odesubs + neworder + assert odesubs.getO() == neworder + residual = odesubs.removeO() + + return (residual == 0, residual) + + s = True + testnum = 0 + while s: + if testnum == 0: + # First pass, try substituting a solved solution directly into the + # ODE. This has the highest chance of succeeding. + ode_diff = ode.lhs - ode.rhs + + if sol.lhs == func: + s = sub_func_doit(ode_diff, func, sol.rhs) + s = besselsimp(s) + else: + testnum += 1 + continue + ss = simplify(s.rewrite(exp)) + if ss: + # with the new numer_denom in power.py, if we do a simple + # expansion then testnum == 0 verifies all solutions. + s = ss.expand(force=True) + else: + s = 0 + testnum += 1 + elif testnum == 1: + # Second pass. If we cannot substitute f, try seeing if the nth + # derivative is equal, this will only work for odes that are exact, + # by definition. + s = simplify( + trigsimp(diff(sol.lhs, x, order) - diff(sol.rhs, x, order)) - + trigsimp(ode.lhs) + trigsimp(ode.rhs)) + # s2 = simplify( + # diff(sol.lhs, x, order) - diff(sol.rhs, x, order) - \ + # ode.lhs + ode.rhs) + testnum += 1 + elif testnum == 2: + # Third pass. Try solving for df/dx and substituting that into the + # ODE. Thanks to Chris Smith for suggesting this method. Many of + # the comments below are his, too. + # The method: + # - Take each of 1..n derivatives of the solution. + # - Solve each nth derivative for d^(n)f/dx^(n) + # (the differential of that order) + # - Back substitute into the ODE in decreasing order + # (i.e., n, n-1, ...) + # - Check the result for zero equivalence + if sol.lhs == func and not sol.rhs.has(func): + diffsols = {0: sol.rhs} + elif sol.rhs == func and not sol.lhs.has(func): + diffsols = {0: sol.lhs} + else: + diffsols = {} + sol = sol.lhs - sol.rhs + for i in range(1, order + 1): + # Differentiation is a linear operator, so there should always + # be 1 solution. Nonetheless, we test just to make sure. + # We only need to solve once. After that, we automatically + # have the solution to the differential in the order we want. + if i == 1: + ds = sol.diff(x) + try: + sdf = solve(ds, func.diff(x, i)) + if not sdf: + raise NotImplementedError + except NotImplementedError: + testnum += 1 + break + else: + diffsols[i] = sdf[0] + else: + # This is what the solution says df/dx should be. + diffsols[i] = diffsols[i - 1].diff(x) + + # Make sure the above didn't fail. + if testnum > 2: + continue + else: + # Substitute it into ODE to check for self consistency. + lhs, rhs = ode.lhs, ode.rhs + for i in range(order, -1, -1): + if i == 0 and 0 not in diffsols: + # We can only substitute f(x) if the solution was + # solved for f(x). + break + lhs = sub_func_doit(lhs, func.diff(x, i), diffsols[i]) + rhs = sub_func_doit(rhs, func.diff(x, i), diffsols[i]) + ode_or_bool = Eq(lhs, rhs) + ode_or_bool = simplify(ode_or_bool) + + if isinstance(ode_or_bool, (bool, BooleanAtom)): + if ode_or_bool: + lhs = rhs = S.Zero + else: + lhs = ode_or_bool.lhs + rhs = ode_or_bool.rhs + # No sense in overworking simplify -- just prove that the + # numerator goes to zero + num = trigsimp((lhs - rhs).as_numer_denom()[0]) + # since solutions are obtained using force=True we test + # using the same level of assumptions + ## replace function with dummy so assumptions will work + _func = Dummy('func') + num = num.subs(func, _func) + ## posify the expression + num, reps = posify(num) + s = simplify(num).xreplace(reps).xreplace({_func: func}) + testnum += 1 + else: + break + + if not s: + return (True, s) + elif s is True: # The code above never was able to change s + raise NotImplementedError("Unable to test if " + str(sol) + + " is a solution to " + str(ode) + ".") + else: + return (False, s) + + +def checksysodesol(eqs, sols, func=None): + r""" + Substitutes corresponding ``sols`` for each functions into each ``eqs`` and + checks that the result of substitutions for each equation is ``0``. The + equations and solutions passed can be any iterable. + + This only works when each ``sols`` have one function only, like `x(t)` or `y(t)`. + For each function, ``sols`` can have a single solution or a list of solutions. + In most cases it will not be necessary to explicitly identify the function, + but if the function cannot be inferred from the original equation it + can be supplied through the ``func`` argument. + + When a sequence of equations is passed, the same sequence is used to return + the result for each equation with each function substituted with corresponding + solutions. + + It tries the following method to find zero equivalence for each equation: + + Substitute the solutions for functions, like `x(t)` and `y(t)` into the + original equations containing those functions. + This function returns a tuple. The first item in the tuple is ``True`` if + the substitution results for each equation is ``0``, and ``False`` otherwise. + The second item in the tuple is what the substitution results in. Each element + of the ``list`` should always be ``0`` corresponding to each equation if the + first item is ``True``. Note that sometimes this function may return ``False``, + but with an expression that is identically equal to ``0``, instead of returning + ``True``. This is because :py:meth:`~sympy.simplify.simplify.simplify` cannot + reduce the expression to ``0``. If an expression returned by each function + vanishes identically, then ``sols`` really is a solution to ``eqs``. + + If this function seems to hang, it is probably because of a difficult simplification. + + Examples + ======== + + >>> from sympy import Eq, diff, symbols, sin, cos, exp, sqrt, S, Function + >>> from sympy.solvers.ode.subscheck import checksysodesol + >>> C1, C2 = symbols('C1:3') + >>> t = symbols('t') + >>> x, y = symbols('x, y', cls=Function) + >>> eq = (Eq(diff(x(t),t), x(t) + y(t) + 17), Eq(diff(y(t),t), -2*x(t) + y(t) + 12)) + >>> sol = [Eq(x(t), (C1*sin(sqrt(2)*t) + C2*cos(sqrt(2)*t))*exp(t) - S(5)/3), + ... Eq(y(t), (sqrt(2)*C1*cos(sqrt(2)*t) - sqrt(2)*C2*sin(sqrt(2)*t))*exp(t) - S(46)/3)] + >>> checksysodesol(eq, sol) + (True, [0, 0]) + >>> eq = (Eq(diff(x(t),t),x(t)*y(t)**4), Eq(diff(y(t),t),y(t)**3)) + >>> sol = [Eq(x(t), C1*exp(-1/(4*(C2 + t)))), Eq(y(t), -sqrt(2)*sqrt(-1/(C2 + t))/2), + ... Eq(x(t), C1*exp(-1/(4*(C2 + t)))), Eq(y(t), sqrt(2)*sqrt(-1/(C2 + t))/2)] + >>> checksysodesol(eq, sol) + (True, [0, 0]) + + """ + def _sympify(eq): + return list(map(sympify, eq if iterable(eq) else [eq])) + eqs = _sympify(eqs) + for i in range(len(eqs)): + if isinstance(eqs[i], Equality): + eqs[i] = eqs[i].lhs - eqs[i].rhs + if func is None: + funcs = [] + for eq in eqs: + derivs = eq.atoms(Derivative) + func = set().union(*[d.atoms(AppliedUndef) for d in derivs]) + funcs.extend(func) + funcs = list(set(funcs)) + if not all(isinstance(func, AppliedUndef) and len(func.args) == 1 for func in funcs)\ + and len({func.args for func in funcs})!=1: + raise ValueError("func must be a function of one variable, not %s" % func) + for sol in sols: + if len(sol.atoms(AppliedUndef)) != 1: + raise ValueError("solutions should have one function only") + if len(funcs) != len({sol.lhs for sol in sols}): + raise ValueError("number of solutions provided does not match the number of equations") + dictsol = {} + for sol in sols: + func = list(sol.atoms(AppliedUndef))[0] + if sol.rhs == func: + sol = sol.reversed + solved = sol.lhs == func and not sol.rhs.has(func) + if not solved: + rhs = solve(sol, func) + if not rhs: + raise NotImplementedError + else: + rhs = sol.rhs + dictsol[func] = rhs + checkeq = [] + for eq in eqs: + for func in funcs: + eq = sub_func_doit(eq, func, dictsol[func]) + ss = simplify(eq) + if ss != 0: + eq = ss.expand(force=True) + if eq != 0: + eq = sqrtdenest(eq).simplify() + else: + eq = 0 + checkeq.append(eq) + if len(set(checkeq)) == 1 and list(set(checkeq))[0] == 0: + return (True, checkeq) + else: + return (False, checkeq) diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/systems.py b/lib/python3.12/site-packages/sympy/solvers/ode/systems.py new file mode 100644 index 0000000000000000000000000000000000000000..2d2c9b57a969c7fb5c67c06ce952fa398e22a48d --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/ode/systems.py @@ -0,0 +1,2135 @@ +from sympy.core import Add, Mul, S +from sympy.core.containers import Tuple +from sympy.core.exprtools import factor_terms +from sympy.core.numbers import I +from sympy.core.relational import Eq, Equality +from sympy.core.sorting import default_sort_key, ordered +from sympy.core.symbol import Dummy, Symbol +from sympy.core.function import (expand_mul, expand, Derivative, + AppliedUndef, Function, Subs) +from sympy.functions import (exp, im, cos, sin, re, Piecewise, + piecewise_fold, sqrt, log) +from sympy.functions.combinatorial.factorials import factorial +from sympy.matrices import zeros, Matrix, NonSquareMatrixError, MatrixBase, eye +from sympy.polys import Poly, together +from sympy.simplify import collect, radsimp, signsimp # type: ignore +from sympy.simplify.powsimp import powdenest, powsimp +from sympy.simplify.ratsimp import ratsimp +from sympy.simplify.simplify import simplify +from sympy.sets.sets import FiniteSet +from sympy.solvers.deutils import ode_order +from sympy.solvers.solveset import NonlinearError, solveset +from sympy.utilities.iterables import (connected_components, iterable, + strongly_connected_components) +from sympy.utilities.misc import filldedent +from sympy.integrals.integrals import Integral, integrate + + +def _get_func_order(eqs, funcs): + return {func: max(ode_order(eq, func) for eq in eqs) for func in funcs} + + +class ODEOrderError(ValueError): + """Raised by linear_ode_to_matrix if the system has the wrong order""" + pass + + +class ODENonlinearError(NonlinearError): + """Raised by linear_ode_to_matrix if the system is nonlinear""" + pass + + +def _simpsol(soleq): + lhs = soleq.lhs + sol = soleq.rhs + sol = powsimp(sol) + gens = list(sol.atoms(exp)) + p = Poly(sol, *gens, expand=False) + gens = [factor_terms(g) for g in gens] + if not gens: + gens = p.gens + syms = [Symbol('C1'), Symbol('C2')] + terms = [] + for coeff, monom in zip(p.coeffs(), p.monoms()): + coeff = piecewise_fold(coeff) + if isinstance(coeff, Piecewise): + coeff = Piecewise(*((ratsimp(coef).collect(syms), cond) for coef, cond in coeff.args)) + else: + coeff = ratsimp(coeff).collect(syms) + monom = Mul(*(g ** i for g, i in zip(gens, monom))) + terms.append(coeff * monom) + return Eq(lhs, Add(*terms)) + + +def _solsimp(e, t): + no_t, has_t = powsimp(expand_mul(e)).as_independent(t) + + no_t = ratsimp(no_t) + has_t = has_t.replace(exp, lambda a: exp(factor_terms(a))) + + return no_t + has_t + + +def simpsol(sol, wrt1, wrt2, doit=True): + """Simplify solutions from dsolve_system.""" + + # The parameter sol is the solution as returned by dsolve (list of Eq). + # + # The parameters wrt1 and wrt2 are lists of symbols to be collected for + # with those in wrt1 being collected for first. This allows for collecting + # on any factors involving the independent variable before collecting on + # the integration constants or vice versa using e.g.: + # + # sol = simpsol(sol, [t], [C1, C2]) # t first, constants after + # sol = simpsol(sol, [C1, C2], [t]) # constants first, t after + # + # If doit=True (default) then simpsol will begin by evaluating any + # unevaluated integrals. Since many integrals will appear multiple times + # in the solutions this is done intelligently by computing each integral + # only once. + # + # The strategy is to first perform simple cancellation with factor_terms + # and then multiply out all brackets with expand_mul. This gives an Add + # with many terms. + # + # We split each term into two multiplicative factors dep and coeff where + # all factors that involve wrt1 are in dep and any constant factors are in + # coeff e.g. + # sqrt(2)*C1*exp(t) -> ( exp(t), sqrt(2)*C1 ) + # + # The dep factors are simplified using powsimp to combine expanded + # exponential factors e.g. + # exp(a*t)*exp(b*t) -> exp(t*(a+b)) + # + # We then collect coefficients for all terms having the same (simplified) + # dep. The coefficients are then simplified using together and ratsimp and + # lastly by recursively applying the same transformation to the + # coefficients to collect on wrt2. + # + # Finally the result is recombined into an Add and signsimp is used to + # normalise any minus signs. + + def simprhs(rhs, rep, wrt1, wrt2): + """Simplify the rhs of an ODE solution""" + if rep: + rhs = rhs.subs(rep) + rhs = factor_terms(rhs) + rhs = simp_coeff_dep(rhs, wrt1, wrt2) + rhs = signsimp(rhs) + return rhs + + def simp_coeff_dep(expr, wrt1, wrt2=None): + """Split rhs into terms, split terms into dep and coeff and collect on dep""" + add_dep_terms = lambda e: e.is_Add and e.has(*wrt1) + expandable = lambda e: e.is_Mul and any(map(add_dep_terms, e.args)) + expand_func = lambda e: expand_mul(e, deep=False) + expand_mul_mod = lambda e: e.replace(expandable, expand_func) + terms = Add.make_args(expand_mul_mod(expr)) + dc = {} + for term in terms: + coeff, dep = term.as_independent(*wrt1, as_Add=False) + # Collect together the coefficients for terms that have the same + # dependence on wrt1 (after dep is normalised using simpdep). + dep = simpdep(dep, wrt1) + + # See if the dependence on t cancels out... + if dep is not S.One: + dep2 = factor_terms(dep) + if not dep2.has(*wrt1): + coeff *= dep2 + dep = S.One + + if dep not in dc: + dc[dep] = coeff + else: + dc[dep] += coeff + # Apply the method recursively to the coefficients but this time + # collecting on wrt2 rather than wrt2. + termpairs = ((simpcoeff(c, wrt2), d) for d, c in dc.items()) + if wrt2 is not None: + termpairs = ((simp_coeff_dep(c, wrt2), d) for c, d in termpairs) + return Add(*(c * d for c, d in termpairs)) + + def simpdep(term, wrt1): + """Normalise factors involving t with powsimp and recombine exp""" + def canonicalise(a): + # Using factor_terms here isn't quite right because it leads to things + # like exp(t*(1+t)) that we don't want. We do want to cancel factors + # and pull out a common denominator but ideally the numerator would be + # expressed as a standard form polynomial in t so we expand_mul + # and collect afterwards. + a = factor_terms(a) + num, den = a.as_numer_denom() + num = expand_mul(num) + num = collect(num, wrt1) + return num / den + + term = powsimp(term) + rep = {e: exp(canonicalise(e.args[0])) for e in term.atoms(exp)} + term = term.subs(rep) + return term + + def simpcoeff(coeff, wrt2): + """Bring to a common fraction and cancel with ratsimp""" + coeff = together(coeff) + if coeff.is_polynomial(): + # Calling ratsimp can be expensive. The main reason is to simplify + # sums of terms with irrational denominators so we limit ourselves + # to the case where the expression is polynomial in any symbols. + # Maybe there's a better approach... + coeff = ratsimp(radsimp(coeff)) + # collect on secondary variables first and any remaining symbols after + if wrt2 is not None: + syms = list(wrt2) + list(ordered(coeff.free_symbols - set(wrt2))) + else: + syms = list(ordered(coeff.free_symbols)) + coeff = collect(coeff, syms) + coeff = together(coeff) + return coeff + + # There are often repeated integrals. Collect unique integrals and + # evaluate each once and then substitute into the final result to replace + # all occurrences in each of the solution equations. + if doit: + integrals = set().union(*(s.atoms(Integral) for s in sol)) + rep = {i: factor_terms(i).doit() for i in integrals} + else: + rep = {} + + sol = [Eq(s.lhs, simprhs(s.rhs, rep, wrt1, wrt2)) for s in sol] + return sol + + +def linodesolve_type(A, t, b=None): + r""" + Helper function that determines the type of the system of ODEs for solving with :obj:`sympy.solvers.ode.systems.linodesolve()` + + Explanation + =========== + + This function takes in the coefficient matrix and/or the non-homogeneous term + and returns the type of the equation that can be solved by :obj:`sympy.solvers.ode.systems.linodesolve()`. + + If the system is constant coefficient homogeneous, then "type1" is returned + + If the system is constant coefficient non-homogeneous, then "type2" is returned + + If the system is non-constant coefficient homogeneous, then "type3" is returned + + If the system is non-constant coefficient non-homogeneous, then "type4" is returned + + If the system has a non-constant coefficient matrix which can be factorized into constant + coefficient matrix, then "type5" or "type6" is returned for when the system is homogeneous or + non-homogeneous respectively. + + Note that, if the system of ODEs is of "type3" or "type4", then along with the type, + the commutative antiderivative of the coefficient matrix is also returned. + + If the system cannot be solved by :obj:`sympy.solvers.ode.systems.linodesolve()`, then + NotImplementedError is raised. + + Parameters + ========== + + A : Matrix + Coefficient matrix of the system of ODEs + b : Matrix or None + Non-homogeneous term of the system. The default value is None. + If this argument is None, then the system is assumed to be homogeneous. + + Examples + ======== + + >>> from sympy import symbols, Matrix + >>> from sympy.solvers.ode.systems import linodesolve_type + >>> t = symbols("t") + >>> A = Matrix([[1, 1], [2, 3]]) + >>> b = Matrix([t, 1]) + + >>> linodesolve_type(A, t) + {'antiderivative': None, 'type_of_equation': 'type1'} + + >>> linodesolve_type(A, t, b=b) + {'antiderivative': None, 'type_of_equation': 'type2'} + + >>> A_t = Matrix([[1, t], [-t, 1]]) + + >>> linodesolve_type(A_t, t) + {'antiderivative': Matrix([ + [ t, t**2/2], + [-t**2/2, t]]), 'type_of_equation': 'type3'} + + >>> linodesolve_type(A_t, t, b=b) + {'antiderivative': Matrix([ + [ t, t**2/2], + [-t**2/2, t]]), 'type_of_equation': 'type4'} + + >>> A_non_commutative = Matrix([[1, t], [t, -1]]) + >>> linodesolve_type(A_non_commutative, t) + Traceback (most recent call last): + ... + NotImplementedError: + The system does not have a commutative antiderivative, it cannot be + solved by linodesolve. + + Returns + ======= + + Dict + + Raises + ====== + + NotImplementedError + When the coefficient matrix does not have a commutative antiderivative + + See Also + ======== + + linodesolve: Function for which linodesolve_type gets the information + + """ + + match = {} + is_non_constant = not _matrix_is_constant(A, t) + is_non_homogeneous = not (b is None or b.is_zero_matrix) + type = "type{}".format(int("{}{}".format(int(is_non_constant), int(is_non_homogeneous)), 2) + 1) + + B = None + match.update({"type_of_equation": type, "antiderivative": B}) + + if is_non_constant: + B, is_commuting = _is_commutative_anti_derivative(A, t) + if not is_commuting: + raise NotImplementedError(filldedent(''' + The system does not have a commutative antiderivative, it cannot be solved + by linodesolve. + ''')) + + match['antiderivative'] = B + match.update(_first_order_type5_6_subs(A, t, b=b)) + + return match + + +def _first_order_type5_6_subs(A, t, b=None): + match = {} + + factor_terms = _factor_matrix(A, t) + is_homogeneous = b is None or b.is_zero_matrix + + if factor_terms is not None: + t_ = Symbol("{}_".format(t)) + F_t = integrate(factor_terms[0], t) + inverse = solveset(Eq(t_, F_t), t) + + # Note: A simple way to check if a function is invertible + # or not. + if isinstance(inverse, FiniteSet) and not inverse.has(Piecewise)\ + and len(inverse) == 1: + + A = factor_terms[1] + if not is_homogeneous: + b = b / factor_terms[0] + b = b.subs(t, list(inverse)[0]) + type = "type{}".format(5 + (not is_homogeneous)) + match.update({'func_coeff': A, 'tau': F_t, + 't_': t_, 'type_of_equation': type, 'rhs': b}) + + return match + + +def linear_ode_to_matrix(eqs, funcs, t, order): + r""" + Convert a linear system of ODEs to matrix form + + Explanation + =========== + + Express a system of linear ordinary differential equations as a single + matrix differential equation [1]. For example the system $x' = x + y + 1$ + and $y' = x - y$ can be represented as + + .. math:: A_1 X' = A_0 X + b + + where $A_1$ and $A_0$ are $2 \times 2$ matrices and $b$, $X$ and $X'$ are + $2 \times 1$ matrices with $X = [x, y]^T$. + + Higher-order systems are represented with additional matrices e.g. a + second-order system would look like + + .. math:: A_2 X'' = A_1 X' + A_0 X + b + + Examples + ======== + + >>> from sympy import Function, Symbol, Matrix, Eq + >>> from sympy.solvers.ode.systems import linear_ode_to_matrix + >>> t = Symbol('t') + >>> x = Function('x') + >>> y = Function('y') + + We can create a system of linear ODEs like + + >>> eqs = [ + ... Eq(x(t).diff(t), x(t) + y(t) + 1), + ... Eq(y(t).diff(t), x(t) - y(t)), + ... ] + >>> funcs = [x(t), y(t)] + >>> order = 1 # 1st order system + + Now ``linear_ode_to_matrix`` can represent this as a matrix + differential equation. + + >>> (A1, A0), b = linear_ode_to_matrix(eqs, funcs, t, order) + >>> A1 + Matrix([ + [1, 0], + [0, 1]]) + >>> A0 + Matrix([ + [1, 1], + [1, -1]]) + >>> b + Matrix([ + [1], + [0]]) + + The original equations can be recovered from these matrices: + + >>> eqs_mat = Matrix([eq.lhs - eq.rhs for eq in eqs]) + >>> X = Matrix(funcs) + >>> A1 * X.diff(t) - A0 * X - b == eqs_mat + True + + If the system of equations has a maximum order greater than the + order of the system specified, a ODEOrderError exception is raised. + + >>> eqs = [Eq(x(t).diff(t, 2), x(t).diff(t) + x(t)), Eq(y(t).diff(t), y(t) + x(t))] + >>> linear_ode_to_matrix(eqs, funcs, t, 1) + Traceback (most recent call last): + ... + ODEOrderError: Cannot represent system in 1-order form + + If the system of equations is nonlinear, then ODENonlinearError is + raised. + + >>> eqs = [Eq(x(t).diff(t), x(t) + y(t)), Eq(y(t).diff(t), y(t)**2 + x(t))] + >>> linear_ode_to_matrix(eqs, funcs, t, 1) + Traceback (most recent call last): + ... + ODENonlinearError: The system of ODEs is nonlinear. + + Parameters + ========== + + eqs : list of SymPy expressions or equalities + The equations as expressions (assumed equal to zero). + funcs : list of applied functions + The dependent variables of the system of ODEs. + t : symbol + The independent variable. + order : int + The order of the system of ODEs. + + Returns + ======= + + The tuple ``(As, b)`` where ``As`` is a tuple of matrices and ``b`` is the + the matrix representing the rhs of the matrix equation. + + Raises + ====== + + ODEOrderError + When the system of ODEs have an order greater than what was specified + ODENonlinearError + When the system of ODEs is nonlinear + + See Also + ======== + + linear_eq_to_matrix: for systems of linear algebraic equations. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Matrix_differential_equation + + """ + from sympy.solvers.solveset import linear_eq_to_matrix + + if any(ode_order(eq, func) > order for eq in eqs for func in funcs): + msg = "Cannot represent system in {}-order form" + raise ODEOrderError(msg.format(order)) + + As = [] + + for o in range(order, -1, -1): + # Work from the highest derivative down + syms = [func.diff(t, o) for func in funcs] + + # Ai is the matrix for X(t).diff(t, o) + # eqs is minus the remainder of the equations. + try: + Ai, b = linear_eq_to_matrix(eqs, syms) + except NonlinearError: + raise ODENonlinearError("The system of ODEs is nonlinear.") + + Ai = Ai.applyfunc(expand_mul) + + As.append(Ai if o == order else -Ai) + + if o: + eqs = [-eq for eq in b] + else: + rhs = b + + return As, rhs + + +def matrix_exp(A, t): + r""" + Matrix exponential $\exp(A*t)$ for the matrix ``A`` and scalar ``t``. + + Explanation + =========== + + This functions returns the $\exp(A*t)$ by doing a simple + matrix multiplication: + + .. math:: \exp(A*t) = P * expJ * P^{-1} + + where $expJ$ is $\exp(J*t)$. $J$ is the Jordan normal + form of $A$ and $P$ is matrix such that: + + .. math:: A = P * J * P^{-1} + + The matrix exponential $\exp(A*t)$ appears in the solution of linear + differential equations. For example if $x$ is a vector and $A$ is a matrix + then the initial value problem + + .. math:: \frac{dx(t)}{dt} = A \times x(t), x(0) = x0 + + has the unique solution + + .. math:: x(t) = \exp(A t) x0 + + Examples + ======== + + >>> from sympy import Symbol, Matrix, pprint + >>> from sympy.solvers.ode.systems import matrix_exp + >>> t = Symbol('t') + + We will consider a 2x2 matrix for comupting the exponential + + >>> A = Matrix([[2, -5], [2, -4]]) + >>> pprint(A) + [2 -5] + [ ] + [2 -4] + + Now, exp(A*t) is given as follows: + + >>> pprint(matrix_exp(A, t)) + [ -t -t -t ] + [3*e *sin(t) + e *cos(t) -5*e *sin(t) ] + [ ] + [ -t -t -t ] + [ 2*e *sin(t) - 3*e *sin(t) + e *cos(t)] + + Parameters + ========== + + A : Matrix + The matrix $A$ in the expression $\exp(A*t)$ + t : Symbol + The independent variable + + See Also + ======== + + matrix_exp_jordan_form: For exponential of Jordan normal form + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Jordan_normal_form + .. [2] https://en.wikipedia.org/wiki/Matrix_exponential + + """ + P, expJ = matrix_exp_jordan_form(A, t) + return P * expJ * P.inv() + + +def matrix_exp_jordan_form(A, t): + r""" + Matrix exponential $\exp(A*t)$ for the matrix *A* and scalar *t*. + + Explanation + =========== + + Returns the Jordan form of the $\exp(A*t)$ along with the matrix $P$ such that: + + .. math:: + \exp(A*t) = P * expJ * P^{-1} + + Examples + ======== + + >>> from sympy import Matrix, Symbol + >>> from sympy.solvers.ode.systems import matrix_exp, matrix_exp_jordan_form + >>> t = Symbol('t') + + We will consider a 2x2 defective matrix. This shows that our method + works even for defective matrices. + + >>> A = Matrix([[1, 1], [0, 1]]) + + It can be observed that this function gives us the Jordan normal form + and the required invertible matrix P. + + >>> P, expJ = matrix_exp_jordan_form(A, t) + + Here, it is shown that P and expJ returned by this function is correct + as they satisfy the formula: P * expJ * P_inverse = exp(A*t). + + >>> P * expJ * P.inv() == matrix_exp(A, t) + True + + Parameters + ========== + + A : Matrix + The matrix $A$ in the expression $\exp(A*t)$ + t : Symbol + The independent variable + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Defective_matrix + .. [2] https://en.wikipedia.org/wiki/Jordan_matrix + .. [3] https://en.wikipedia.org/wiki/Jordan_normal_form + + """ + + N, M = A.shape + if N != M: + raise ValueError('Needed square matrix but got shape (%s, %s)' % (N, M)) + elif A.has(t): + raise ValueError('Matrix A should not depend on t') + + def jordan_chains(A): + '''Chains from Jordan normal form analogous to M.eigenvects(). + Returns a dict with eignevalues as keys like: + {e1: [[v111,v112,...], [v121, v122,...]], e2:...} + where vijk is the kth vector in the jth chain for eigenvalue i. + ''' + P, blocks = A.jordan_cells() + basis = [P[:,i] for i in range(P.shape[1])] + n = 0 + chains = {} + for b in blocks: + eigval = b[0, 0] + size = b.shape[0] + if eigval not in chains: + chains[eigval] = [] + chains[eigval].append(basis[n:n+size]) + n += size + return chains + + eigenchains = jordan_chains(A) + + # Needed for consistency across Python versions + eigenchains_iter = sorted(eigenchains.items(), key=default_sort_key) + isreal = not A.has(I) + + blocks = [] + vectors = [] + seen_conjugate = set() + for e, chains in eigenchains_iter: + for chain in chains: + n = len(chain) + if isreal and e != e.conjugate() and e.conjugate() in eigenchains: + if e in seen_conjugate: + continue + seen_conjugate.add(e.conjugate()) + exprt = exp(re(e) * t) + imrt = im(e) * t + imblock = Matrix([[cos(imrt), sin(imrt)], + [-sin(imrt), cos(imrt)]]) + expJblock2 = Matrix(n, n, lambda i,j: + imblock * t**(j-i) / factorial(j-i) if j >= i + else zeros(2, 2)) + expJblock = Matrix(2*n, 2*n, lambda i,j: expJblock2[i//2,j//2][i%2,j%2]) + + blocks.append(exprt * expJblock) + for i in range(n): + vectors.append(re(chain[i])) + vectors.append(im(chain[i])) + else: + vectors.extend(chain) + fun = lambda i,j: t**(j-i)/factorial(j-i) if j >= i else 0 + expJblock = Matrix(n, n, fun) + blocks.append(exp(e * t) * expJblock) + + expJ = Matrix.diag(*blocks) + P = Matrix(N, N, lambda i,j: vectors[j][i]) + + return P, expJ + + +# Note: To add a docstring example with tau +def linodesolve(A, t, b=None, B=None, type="auto", doit=False, + tau=None): + r""" + System of n equations linear first-order differential equations + + Explanation + =========== + + This solver solves the system of ODEs of the following form: + + .. math:: + X'(t) = A(t) X(t) + b(t) + + Here, $A(t)$ is the coefficient matrix, $X(t)$ is the vector of n independent variables, + $b(t)$ is the non-homogeneous term and $X'(t)$ is the derivative of $X(t)$ + + Depending on the properties of $A(t)$ and $b(t)$, this solver evaluates the solution + differently. + + When $A(t)$ is constant coefficient matrix and $b(t)$ is zero vector i.e. system is homogeneous, + the system is "type1". The solution is: + + .. math:: + X(t) = \exp(A t) C + + Here, $C$ is a vector of constants and $A$ is the constant coefficient matrix. + + When $A(t)$ is constant coefficient matrix and $b(t)$ is non-zero i.e. system is non-homogeneous, + the system is "type2". The solution is: + + .. math:: + X(t) = e^{A t} ( \int e^{- A t} b \,dt + C) + + When $A(t)$ is coefficient matrix such that its commutative with its antiderivative $B(t)$ and + $b(t)$ is a zero vector i.e. system is homogeneous, the system is "type3". The solution is: + + .. math:: + X(t) = \exp(B(t)) C + + When $A(t)$ is commutative with its antiderivative $B(t)$ and $b(t)$ is non-zero i.e. system is + non-homogeneous, the system is "type4". The solution is: + + .. math:: + X(t) = e^{B(t)} ( \int e^{-B(t)} b(t) \,dt + C) + + When $A(t)$ is a coefficient matrix such that it can be factorized into a scalar and a constant + coefficient matrix: + + .. math:: + A(t) = f(t) * A + + Where $f(t)$ is a scalar expression in the independent variable $t$ and $A$ is a constant matrix, + then we can do the following substitutions: + + .. math:: + tau = \int f(t) dt, X(t) = Y(tau), b(t) = b(f^{-1}(tau)) + + Here, the substitution for the non-homogeneous term is done only when its non-zero. + Using these substitutions, our original system becomes: + + .. math:: + Y'(tau) = A * Y(tau) + b(tau)/f(tau) + + The above system can be easily solved using the solution for "type1" or "type2" depending + on the homogeneity of the system. After we get the solution for $Y(tau)$, we substitute the + solution for $tau$ as $t$ to get back $X(t)$ + + .. math:: + X(t) = Y(tau) + + Systems of "type5" and "type6" have a commutative antiderivative but we use this solution + because its faster to compute. + + The final solution is the general solution for all the four equations since a constant coefficient + matrix is always commutative with its antidervative. + + An additional feature of this function is, if someone wants to substitute for value of the independent + variable, they can pass the substitution `tau` and the solution will have the independent variable + substituted with the passed expression(`tau`). + + Parameters + ========== + + A : Matrix + Coefficient matrix of the system of linear first order ODEs. + t : Symbol + Independent variable in the system of ODEs. + b : Matrix or None + Non-homogeneous term in the system of ODEs. If None is passed, + a homogeneous system of ODEs is assumed. + B : Matrix or None + Antiderivative of the coefficient matrix. If the antiderivative + is not passed and the solution requires the term, then the solver + would compute it internally. + type : String + Type of the system of ODEs passed. Depending on the type, the + solution is evaluated. The type values allowed and the corresponding + system it solves are: "type1" for constant coefficient homogeneous + "type2" for constant coefficient non-homogeneous, "type3" for non-constant + coefficient homogeneous, "type4" for non-constant coefficient non-homogeneous, + "type5" and "type6" for non-constant coefficient homogeneous and non-homogeneous + systems respectively where the coefficient matrix can be factorized to a constant + coefficient matrix. + The default value is "auto" which will let the solver decide the correct type of + the system passed. + doit : Boolean + Evaluate the solution if True, default value is False + tau: Expression + Used to substitute for the value of `t` after we get the solution of the system. + + Examples + ======== + + To solve the system of ODEs using this function directly, several things must be + done in the right order. Wrong inputs to the function will lead to incorrect results. + + >>> from sympy import symbols, Function, Eq + >>> from sympy.solvers.ode.systems import canonical_odes, linear_ode_to_matrix, linodesolve, linodesolve_type + >>> from sympy.solvers.ode.subscheck import checkodesol + >>> f, g = symbols("f, g", cls=Function) + >>> x, a = symbols("x, a") + >>> funcs = [f(x), g(x)] + >>> eqs = [Eq(f(x).diff(x) - f(x), a*g(x) + 1), Eq(g(x).diff(x) + g(x), a*f(x))] + + Here, it is important to note that before we derive the coefficient matrix, it is + important to get the system of ODEs into the desired form. For that we will use + :obj:`sympy.solvers.ode.systems.canonical_odes()`. + + >>> eqs = canonical_odes(eqs, funcs, x) + >>> eqs + [[Eq(Derivative(f(x), x), a*g(x) + f(x) + 1), Eq(Derivative(g(x), x), a*f(x) - g(x))]] + + Now, we will use :obj:`sympy.solvers.ode.systems.linear_ode_to_matrix()` to get the coefficient matrix and the + non-homogeneous term if it is there. + + >>> eqs = eqs[0] + >>> (A1, A0), b = linear_ode_to_matrix(eqs, funcs, x, 1) + >>> A = A0 + + We have the coefficient matrices and the non-homogeneous term ready. Now, we can use + :obj:`sympy.solvers.ode.systems.linodesolve_type()` to get the information for the system of ODEs + to finally pass it to the solver. + + >>> system_info = linodesolve_type(A, x, b=b) + >>> sol_vector = linodesolve(A, x, b=b, B=system_info['antiderivative'], type=system_info['type_of_equation']) + + Now, we can prove if the solution is correct or not by using :obj:`sympy.solvers.ode.checkodesol()` + + >>> sol = [Eq(f, s) for f, s in zip(funcs, sol_vector)] + >>> checkodesol(eqs, sol) + (True, [0, 0]) + + We can also use the doit method to evaluate the solutions passed by the function. + + >>> sol_vector_evaluated = linodesolve(A, x, b=b, type="type2", doit=True) + + Now, we will look at a system of ODEs which is non-constant. + + >>> eqs = [Eq(f(x).diff(x), f(x) + x*g(x)), Eq(g(x).diff(x), -x*f(x) + g(x))] + + The system defined above is already in the desired form, so we do not have to convert it. + + >>> (A1, A0), b = linear_ode_to_matrix(eqs, funcs, x, 1) + >>> A = A0 + + A user can also pass the commutative antiderivative required for type3 and type4 system of ODEs. + Passing an incorrect one will lead to incorrect results. If the coefficient matrix is not commutative + with its antiderivative, then :obj:`sympy.solvers.ode.systems.linodesolve_type()` raises a NotImplementedError. + If it does have a commutative antiderivative, then the function just returns the information about the system. + + >>> system_info = linodesolve_type(A, x, b=b) + + Now, we can pass the antiderivative as an argument to get the solution. If the system information is not + passed, then the solver will compute the required arguments internally. + + >>> sol_vector = linodesolve(A, x, b=b) + + Once again, we can verify the solution obtained. + + >>> sol = [Eq(f, s) for f, s in zip(funcs, sol_vector)] + >>> checkodesol(eqs, sol) + (True, [0, 0]) + + Returns + ======= + + List + + Raises + ====== + + ValueError + This error is raised when the coefficient matrix, non-homogeneous term + or the antiderivative, if passed, are not a matrix or + do not have correct dimensions + NonSquareMatrixError + When the coefficient matrix or its antiderivative, if passed is not a + square matrix + NotImplementedError + If the coefficient matrix does not have a commutative antiderivative + + See Also + ======== + + linear_ode_to_matrix: Coefficient matrix computation function + canonical_odes: System of ODEs representation change + linodesolve_type: Getting information about systems of ODEs to pass in this solver + + """ + + if not isinstance(A, MatrixBase): + raise ValueError(filldedent('''\ + The coefficients of the system of ODEs should be of type Matrix + ''')) + + if not A.is_square: + raise NonSquareMatrixError(filldedent('''\ + The coefficient matrix must be a square + ''')) + + if b is not None: + if not isinstance(b, MatrixBase): + raise ValueError(filldedent('''\ + The non-homogeneous terms of the system of ODEs should be of type Matrix + ''')) + + if A.rows != b.rows: + raise ValueError(filldedent('''\ + The system of ODEs should have the same number of non-homogeneous terms and the number of + equations + ''')) + + if B is not None: + if not isinstance(B, MatrixBase): + raise ValueError(filldedent('''\ + The antiderivative of coefficients of the system of ODEs should be of type Matrix + ''')) + + if not B.is_square: + raise NonSquareMatrixError(filldedent('''\ + The antiderivative of the coefficient matrix must be a square + ''')) + + if A.rows != B.rows: + raise ValueError(filldedent('''\ + The coefficient matrix and its antiderivative should have same dimensions + ''')) + + if not any(type == "type{}".format(i) for i in range(1, 7)) and not type == "auto": + raise ValueError(filldedent('''\ + The input type should be a valid one + ''')) + + n = A.rows + + # constants = numbered_symbols(prefix='C', cls=Dummy, start=const_idx+1) + Cvect = Matrix([Dummy() for _ in range(n)]) + + if b is None and any(type == typ for typ in ["type2", "type4", "type6"]): + b = zeros(n, 1) + + is_transformed = tau is not None + passed_type = type + + if type == "auto": + system_info = linodesolve_type(A, t, b=b) + type = system_info["type_of_equation"] + B = system_info["antiderivative"] + + if type in ("type5", "type6"): + is_transformed = True + if passed_type != "auto": + if tau is None: + system_info = _first_order_type5_6_subs(A, t, b=b) + if not system_info: + raise ValueError(filldedent(''' + The system passed isn't {}. + '''.format(type))) + + tau = system_info['tau'] + t = system_info['t_'] + A = system_info['A'] + b = system_info['b'] + + intx_wrtt = lambda x: Integral(x, t) if x else 0 + if type in ("type1", "type2", "type5", "type6"): + P, J = matrix_exp_jordan_form(A, t) + P = simplify(P) + + if type in ("type1", "type5"): + sol_vector = P * (J * Cvect) + else: + Jinv = J.subs(t, -t) + sol_vector = P * J * ((Jinv * P.inv() * b).applyfunc(intx_wrtt) + Cvect) + else: + if B is None: + B, _ = _is_commutative_anti_derivative(A, t) + + if type == "type3": + sol_vector = B.exp() * Cvect + else: + sol_vector = B.exp() * (((-B).exp() * b).applyfunc(intx_wrtt) + Cvect) + + if is_transformed: + sol_vector = sol_vector.subs(t, tau) + + gens = sol_vector.atoms(exp) + + if type != "type1": + sol_vector = [expand_mul(s) for s in sol_vector] + + sol_vector = [collect(s, ordered(gens), exact=True) for s in sol_vector] + + if doit: + sol_vector = [s.doit() for s in sol_vector] + + return sol_vector + + +def _matrix_is_constant(M, t): + """Checks if the matrix M is independent of t or not.""" + return all(coef.as_independent(t, as_Add=True)[1] == 0 for coef in M) + + +def canonical_odes(eqs, funcs, t): + r""" + Function that solves for highest order derivatives in a system + + Explanation + =========== + + This function inputs a system of ODEs and based on the system, + the dependent variables and their highest order, returns the system + in the following form: + + .. math:: + X'(t) = A(t) X(t) + b(t) + + Here, $X(t)$ is the vector of dependent variables of lower order, $A(t)$ is + the coefficient matrix, $b(t)$ is the non-homogeneous term and $X'(t)$ is the + vector of dependent variables in their respective highest order. We use the term + canonical form to imply the system of ODEs which is of the above form. + + If the system passed has a non-linear term with multiple solutions, then a list of + systems is returned in its canonical form. + + Parameters + ========== + + eqs : List + List of the ODEs + funcs : List + List of dependent variables + t : Symbol + Independent variable + + Examples + ======== + + >>> from sympy import symbols, Function, Eq, Derivative + >>> from sympy.solvers.ode.systems import canonical_odes + >>> f, g = symbols("f g", cls=Function) + >>> x, y = symbols("x y") + >>> funcs = [f(x), g(x)] + >>> eqs = [Eq(f(x).diff(x) - 7*f(x), 12*g(x)), Eq(g(x).diff(x) + g(x), 20*f(x))] + + >>> canonical_eqs = canonical_odes(eqs, funcs, x) + >>> canonical_eqs + [[Eq(Derivative(f(x), x), 7*f(x) + 12*g(x)), Eq(Derivative(g(x), x), 20*f(x) - g(x))]] + + >>> system = [Eq(Derivative(f(x), x)**2 - 2*Derivative(f(x), x) + 1, 4), Eq(-y*f(x) + Derivative(g(x), x), 0)] + + >>> canonical_system = canonical_odes(system, funcs, x) + >>> canonical_system + [[Eq(Derivative(f(x), x), -1), Eq(Derivative(g(x), x), y*f(x))], [Eq(Derivative(f(x), x), 3), Eq(Derivative(g(x), x), y*f(x))]] + + Returns + ======= + + List + + """ + from sympy.solvers.solvers import solve + + order = _get_func_order(eqs, funcs) + + canon_eqs = solve(eqs, *[func.diff(t, order[func]) for func in funcs], dict=True) + + systems = [] + for eq in canon_eqs: + system = [Eq(func.diff(t, order[func]), eq[func.diff(t, order[func])]) for func in funcs] + systems.append(system) + + return systems + + +def _is_commutative_anti_derivative(A, t): + r""" + Helper function for determining if the Matrix passed is commutative with its antiderivative + + Explanation + =========== + + This function checks if the Matrix $A$ passed is commutative with its antiderivative with respect + to the independent variable $t$. + + .. math:: + B(t) = \int A(t) dt + + The function outputs two values, first one being the antiderivative $B(t)$, second one being a + boolean value, if True, then the matrix $A(t)$ passed is commutative with $B(t)$, else the matrix + passed isn't commutative with $B(t)$. + + Parameters + ========== + + A : Matrix + The matrix which has to be checked + t : Symbol + Independent variable + + Examples + ======== + + >>> from sympy import symbols, Matrix + >>> from sympy.solvers.ode.systems import _is_commutative_anti_derivative + >>> t = symbols("t") + >>> A = Matrix([[1, t], [-t, 1]]) + + >>> B, is_commuting = _is_commutative_anti_derivative(A, t) + >>> is_commuting + True + + Returns + ======= + + Matrix, Boolean + + """ + B = integrate(A, t) + is_commuting = (B*A - A*B).applyfunc(expand).applyfunc(factor_terms).is_zero_matrix + + is_commuting = False if is_commuting is None else is_commuting + + return B, is_commuting + + +def _factor_matrix(A, t): + term = None + for element in A: + temp_term = element.as_independent(t)[1] + if temp_term.has(t): + term = temp_term + break + + if term is not None: + A_factored = (A/term).applyfunc(ratsimp) + can_factor = _matrix_is_constant(A_factored, t) + term = (term, A_factored) if can_factor else None + + return term + + +def _is_second_order_type2(A, t): + term = _factor_matrix(A, t) + is_type2 = False + + if term is not None: + term = 1/term[0] + is_type2 = term.is_polynomial() + + if is_type2: + poly = Poly(term.expand(), t) + monoms = poly.monoms() + + if monoms[0][0] in (2, 4): + cs = _get_poly_coeffs(poly, 4) + a, b, c, d, e = cs + + a1 = powdenest(sqrt(a), force=True) + c1 = powdenest(sqrt(e), force=True) + b1 = powdenest(sqrt(c - 2*a1*c1), force=True) + + is_type2 = (b == 2*a1*b1) and (d == 2*b1*c1) + term = a1*t**2 + b1*t + c1 + + else: + is_type2 = False + + return is_type2, term + + +def _get_poly_coeffs(poly, order): + cs = [0 for _ in range(order+1)] + for c, m in zip(poly.coeffs(), poly.monoms()): + cs[-1-m[0]] = c + return cs + + +def _match_second_order_type(A1, A0, t, b=None): + r""" + Works only for second order system in its canonical form. + + Type 0: Constant coefficient matrix, can be simply solved by + introducing dummy variables. + Type 1: When the substitution: $U = t*X' - X$ works for reducing + the second order system to first order system. + Type 2: When the system is of the form: $poly * X'' = A*X$ where + $poly$ is square of a quadratic polynomial with respect to + *t* and $A$ is a constant coefficient matrix. + + """ + match = {"type_of_equation": "type0"} + n = A1.shape[0] + + if _matrix_is_constant(A1, t) and _matrix_is_constant(A0, t): + return match + + if (A1 + A0*t).applyfunc(expand_mul).is_zero_matrix: + match.update({"type_of_equation": "type1", "A1": A1}) + + elif A1.is_zero_matrix and (b is None or b.is_zero_matrix): + is_type2, term = _is_second_order_type2(A0, t) + if is_type2: + a, b, c = _get_poly_coeffs(Poly(term, t), 2) + A = (A0*(term**2).expand()).applyfunc(ratsimp) + (b**2/4 - a*c)*eye(n, n) + tau = integrate(1/term, t) + t_ = Symbol("{}_".format(t)) + match.update({"type_of_equation": "type2", "A0": A, + "g(t)": sqrt(term), "tau": tau, "is_transformed": True, + "t_": t_}) + + return match + + +def _second_order_subs_type1(A, b, funcs, t): + r""" + For a linear, second order system of ODEs, a particular substitution. + + A system of the below form can be reduced to a linear first order system of + ODEs: + .. math:: + X'' = A(t) * (t*X' - X) + b(t) + + By substituting: + .. math:: U = t*X' - X + + To get the system: + .. math:: U' = t*(A(t)*U + b(t)) + + Where $U$ is the vector of dependent variables, $X$ is the vector of dependent + variables in `funcs` and $X'$ is the first order derivative of $X$ with respect to + $t$. It may or may not reduce the system into linear first order system of ODEs. + + Then a check is made to determine if the system passed can be reduced or not, if + this substitution works, then the system is reduced and its solved for the new + substitution. After we get the solution for $U$: + + .. math:: U = a(t) + + We substitute and return the reduced system: + + .. math:: + a(t) = t*X' - X + + Parameters + ========== + + A: Matrix + Coefficient matrix($A(t)*t$) of the second order system of this form. + b: Matrix + Non-homogeneous term($b(t)$) of the system of ODEs. + funcs: List + List of dependent variables + t: Symbol + Independent variable of the system of ODEs. + + Returns + ======= + + List + + """ + + U = Matrix([t*func.diff(t) - func for func in funcs]) + + sol = linodesolve(A, t, t*b) + reduced_eqs = [Eq(u, s) for s, u in zip(sol, U)] + reduced_eqs = canonical_odes(reduced_eqs, funcs, t)[0] + + return reduced_eqs + + +def _second_order_subs_type2(A, funcs, t_): + r""" + Returns a second order system based on the coefficient matrix passed. + + Explanation + =========== + + This function returns a system of second order ODE of the following form: + + .. math:: + X'' = A * X + + Here, $X$ is the vector of dependent variables, but a bit modified, $A$ is the + coefficient matrix passed. + + Along with returning the second order system, this function also returns the new + dependent variables with the new independent variable `t_` passed. + + Parameters + ========== + + A: Matrix + Coefficient matrix of the system + funcs: List + List of old dependent variables + t_: Symbol + New independent variable + + Returns + ======= + + List, List + + """ + func_names = [func.func.__name__ for func in funcs] + new_funcs = [Function(Dummy("{}_".format(name)))(t_) for name in func_names] + rhss = A * Matrix(new_funcs) + new_eqs = [Eq(func.diff(t_, 2), rhs) for func, rhs in zip(new_funcs, rhss)] + + return new_eqs, new_funcs + + +def _is_euler_system(As, t): + return all(_matrix_is_constant((A*t**i).applyfunc(ratsimp), t) for i, A in enumerate(As)) + + +def _classify_linear_system(eqs, funcs, t, is_canon=False): + r""" + Returns a dictionary with details of the eqs if the system passed is linear + and can be classified by this function else returns None + + Explanation + =========== + + This function takes the eqs, converts it into a form Ax = b where x is a vector of terms + containing dependent variables and their derivatives till their maximum order. If it is + possible to convert eqs into Ax = b, then all the equations in eqs are linear otherwise + they are non-linear. + + To check if the equations are constant coefficient, we need to check if all the terms in + A obtained above are constant or not. + + To check if the equations are homogeneous or not, we need to check if b is a zero matrix + or not. + + Parameters + ========== + + eqs: List + List of ODEs + funcs: List + List of dependent variables + t: Symbol + Independent variable of the equations in eqs + is_canon: Boolean + If True, then this function will not try to get the + system in canonical form. Default value is False + + Returns + ======= + + match = { + 'no_of_equation': len(eqs), + 'eq': eqs, + 'func': funcs, + 'order': order, + 'is_linear': is_linear, + 'is_constant': is_constant, + 'is_homogeneous': is_homogeneous, + } + + Dict or list of Dicts or None + Dict with values for keys: + 1. no_of_equation: Number of equations + 2. eq: The set of equations + 3. func: List of dependent variables + 4. order: A dictionary that gives the order of the + dependent variable in eqs + 5. is_linear: Boolean value indicating if the set of + equations are linear or not. + 6. is_constant: Boolean value indicating if the set of + equations have constant coefficients or not. + 7. is_homogeneous: Boolean value indicating if the set of + equations are homogeneous or not. + 8. commutative_antiderivative: Antiderivative of the coefficient + matrix if the coefficient matrix is non-constant + and commutative with its antiderivative. This key + may or may not exist. + 9. is_general: Boolean value indicating if the system of ODEs is + solvable using one of the general case solvers or not. + 10. rhs: rhs of the non-homogeneous system of ODEs in Matrix form. This + key may or may not exist. + 11. is_higher_order: True if the system passed has an order greater than 1. + This key may or may not exist. + 12. is_second_order: True if the system passed is a second order ODE. This + key may or may not exist. + This Dict is the answer returned if the eqs are linear and constant + coefficient. Otherwise, None is returned. + + """ + + # Error for i == 0 can be added but isn't for now + + # Check for len(funcs) == len(eqs) + if len(funcs) != len(eqs): + raise ValueError("Number of functions given is not equal to the number of equations %s" % funcs) + + # ValueError when functions have more than one arguments + for func in funcs: + if len(func.args) != 1: + raise ValueError("dsolve() and classify_sysode() work with " + "functions of one variable only, not %s" % func) + + # Getting the func_dict and order using the helper + # function + order = _get_func_order(eqs, funcs) + system_order = max(order[func] for func in funcs) + is_higher_order = system_order > 1 + is_second_order = system_order == 2 and all(order[func] == 2 for func in funcs) + + # Not adding the check if the len(func.args) for + # every func in funcs is 1 + + # Linearity check + try: + + canon_eqs = canonical_odes(eqs, funcs, t) if not is_canon else [eqs] + if len(canon_eqs) == 1: + As, b = linear_ode_to_matrix(canon_eqs[0], funcs, t, system_order) + else: + + match = { + 'is_implicit': True, + 'canon_eqs': canon_eqs + } + + return match + + # When the system of ODEs is non-linear, an ODENonlinearError is raised. + # This function catches the error and None is returned. + except ODENonlinearError: + return None + + is_linear = True + + # Homogeneous check + is_homogeneous = True if b.is_zero_matrix else False + + # Is general key is used to identify if the system of ODEs can be solved by + # one of the general case solvers or not. + match = { + 'no_of_equation': len(eqs), + 'eq': eqs, + 'func': funcs, + 'order': order, + 'is_linear': is_linear, + 'is_homogeneous': is_homogeneous, + 'is_general': True + } + + if not is_homogeneous: + match['rhs'] = b + + is_constant = all(_matrix_is_constant(A_, t) for A_ in As) + + # The match['is_linear'] check will be added in the future when this + # function becomes ready to deal with non-linear systems of ODEs + + if not is_higher_order: + A = As[1] + match['func_coeff'] = A + + # Constant coefficient check + is_constant = _matrix_is_constant(A, t) + match['is_constant'] = is_constant + + try: + system_info = linodesolve_type(A, t, b=b) + except NotImplementedError: + return None + + match.update(system_info) + antiderivative = match.pop("antiderivative") + + if not is_constant: + match['commutative_antiderivative'] = antiderivative + + return match + else: + match['type_of_equation'] = "type0" + + if is_second_order: + A1, A0 = As[1:] + + match_second_order = _match_second_order_type(A1, A0, t) + match.update(match_second_order) + + match['is_second_order'] = True + + # If system is constant, then no need to check if its in euler + # form or not. It will be easier and faster to directly proceed + # to solve it. + if match['type_of_equation'] == "type0" and not is_constant: + is_euler = _is_euler_system(As, t) + if is_euler: + t_ = Symbol('{}_'.format(t)) + match.update({'is_transformed': True, 'type_of_equation': 'type1', + 't_': t_}) + else: + is_jordan = lambda M: M == Matrix.jordan_block(M.shape[0], M[0, 0]) + terms = _factor_matrix(As[-1], t) + if all(A.is_zero_matrix for A in As[1:-1]) and terms is not None and not is_jordan(terms[1]): + P, J = terms[1].jordan_form() + match.update({'type_of_equation': 'type2', 'J': J, + 'f(t)': terms[0], 'P': P, 'is_transformed': True}) + + if match['type_of_equation'] != 'type0' and is_second_order: + match.pop('is_second_order', None) + + match['is_higher_order'] = is_higher_order + + return match + +def _preprocess_eqs(eqs): + processed_eqs = [] + for eq in eqs: + processed_eqs.append(eq if isinstance(eq, Equality) else Eq(eq, 0)) + + return processed_eqs + + +def _eqs2dict(eqs, funcs): + eqsorig = {} + eqsmap = {} + funcset = set(funcs) + for eq in eqs: + f1, = eq.lhs.atoms(AppliedUndef) + f2s = (eq.rhs.atoms(AppliedUndef) - {f1}) & funcset + eqsmap[f1] = f2s + eqsorig[f1] = eq + return eqsmap, eqsorig + + +def _dict2graph(d): + nodes = list(d) + edges = [(f1, f2) for f1, f2s in d.items() for f2 in f2s] + G = (nodes, edges) + return G + + +def _is_type1(scc, t): + eqs, funcs = scc + + try: + (A1, A0), b = linear_ode_to_matrix(eqs, funcs, t, 1) + except (ODENonlinearError, ODEOrderError): + return False + + if _matrix_is_constant(A0, t) and b.is_zero_matrix: + return True + + return False + + +def _combine_type1_subsystems(subsystem, funcs, t): + indices = [i for i, sys in enumerate(zip(subsystem, funcs)) if _is_type1(sys, t)] + remove = set() + for ip, i in enumerate(indices): + for j in indices[ip+1:]: + if any(eq2.has(funcs[i]) for eq2 in subsystem[j]): + subsystem[j] = subsystem[i] + subsystem[j] + remove.add(i) + subsystem = [sys for i, sys in enumerate(subsystem) if i not in remove] + return subsystem + + +def _component_division(eqs, funcs, t): + + # Assuming that each eq in eqs is in canonical form, + # that is, [f(x).diff(x) = .., g(x).diff(x) = .., etc] + # and that the system passed is in its first order + eqsmap, eqsorig = _eqs2dict(eqs, funcs) + + subsystems = [] + for cc in connected_components(_dict2graph(eqsmap)): + eqsmap_c = {f: eqsmap[f] for f in cc} + sccs = strongly_connected_components(_dict2graph(eqsmap_c)) + subsystem = [[eqsorig[f] for f in scc] for scc in sccs] + subsystem = _combine_type1_subsystems(subsystem, sccs, t) + subsystems.append(subsystem) + + return subsystems + + +# Returns: List of equations +def _linear_ode_solver(match): + t = match['t'] + funcs = match['func'] + + rhs = match.get('rhs', None) + tau = match.get('tau', None) + t = match['t_'] if 't_' in match else t + A = match['func_coeff'] + + # Note: To make B None when the matrix has constant + # coefficient + B = match.get('commutative_antiderivative', None) + type = match['type_of_equation'] + + sol_vector = linodesolve(A, t, b=rhs, B=B, + type=type, tau=tau) + + sol = [Eq(f, s) for f, s in zip(funcs, sol_vector)] + + return sol + + +def _select_equations(eqs, funcs, key=lambda x: x): + eq_dict = {e.lhs: e.rhs for e in eqs} + return [Eq(f, eq_dict[key(f)]) for f in funcs] + + +def _higher_order_ode_solver(match): + eqs = match["eq"] + funcs = match["func"] + t = match["t"] + sysorder = match['order'] + type = match.get('type_of_equation', "type0") + + is_second_order = match.get('is_second_order', False) + is_transformed = match.get('is_transformed', False) + is_euler = is_transformed and type == "type1" + is_higher_order_type2 = is_transformed and type == "type2" and 'P' in match + + if is_second_order: + new_eqs, new_funcs = _second_order_to_first_order(eqs, funcs, t, + A1=match.get("A1", None), A0=match.get("A0", None), + b=match.get("rhs", None), type=type, + t_=match.get("t_", None)) + else: + new_eqs, new_funcs = _higher_order_to_first_order(eqs, sysorder, t, funcs=funcs, + type=type, J=match.get('J', None), + f_t=match.get('f(t)', None), + P=match.get('P', None), b=match.get('rhs', None)) + + if is_transformed: + t = match.get('t_', t) + + if not is_higher_order_type2: + new_eqs = _select_equations(new_eqs, [f.diff(t) for f in new_funcs]) + + sol = None + + # NotImplementedError may be raised when the system may be actually + # solvable if it can be just divided into sub-systems + try: + if not is_higher_order_type2: + sol = _strong_component_solver(new_eqs, new_funcs, t) + except NotImplementedError: + sol = None + + # Dividing the system only when it becomes essential + if sol is None: + try: + sol = _component_solver(new_eqs, new_funcs, t) + except NotImplementedError: + sol = None + + if sol is None: + return sol + + is_second_order_type2 = is_second_order and type == "type2" + + underscores = '__' if is_transformed else '_' + + sol = _select_equations(sol, funcs, + key=lambda x: Function(Dummy('{}{}0'.format(x.func.__name__, underscores)))(t)) + + if match.get("is_transformed", False): + if is_second_order_type2: + g_t = match["g(t)"] + tau = match["tau"] + sol = [Eq(s.lhs, s.rhs.subs(t, tau) * g_t) for s in sol] + elif is_euler: + t = match['t'] + tau = match['t_'] + sol = [s.subs(tau, log(t)) for s in sol] + elif is_higher_order_type2: + P = match['P'] + sol_vector = P * Matrix([s.rhs for s in sol]) + sol = [Eq(f, s) for f, s in zip(funcs, sol_vector)] + + return sol + + +# Returns: List of equations or None +# If None is returned by this solver, then the system +# of ODEs cannot be solved directly by dsolve_system. +def _strong_component_solver(eqs, funcs, t): + from sympy.solvers.ode.ode import dsolve, constant_renumber + + match = _classify_linear_system(eqs, funcs, t, is_canon=True) + sol = None + + # Assuming that we can't get an implicit system + # since we are already canonical equations from + # dsolve_system + if match: + match['t'] = t + + if match.get('is_higher_order', False): + sol = _higher_order_ode_solver(match) + + elif match.get('is_linear', False): + sol = _linear_ode_solver(match) + + # Note: For now, only linear systems are handled by this function + # hence, the match condition is added. This can be removed later. + if sol is None and len(eqs) == 1: + sol = dsolve(eqs[0], func=funcs[0]) + variables = Tuple(eqs[0]).free_symbols + new_constants = [Dummy() for _ in range(ode_order(eqs[0], funcs[0]))] + sol = constant_renumber(sol, variables=variables, newconstants=new_constants) + sol = [sol] + + # To add non-linear case here in future + + return sol + + +def _get_funcs_from_canon(eqs): + return [eq.lhs.args[0] for eq in eqs] + + +# Returns: List of Equations(a solution) +def _weak_component_solver(wcc, t): + + # We will divide the systems into sccs + # only when the wcc cannot be solved as + # a whole + eqs = [] + for scc in wcc: + eqs += scc + funcs = _get_funcs_from_canon(eqs) + + sol = _strong_component_solver(eqs, funcs, t) + if sol: + return sol + + sol = [] + + for scc in wcc: + eqs = scc + funcs = _get_funcs_from_canon(eqs) + + # Substituting solutions for the dependent + # variables solved in previous SCC, if any solved. + comp_eqs = [eq.subs({s.lhs: s.rhs for s in sol}) for eq in eqs] + scc_sol = _strong_component_solver(comp_eqs, funcs, t) + + if scc_sol is None: + raise NotImplementedError(filldedent(''' + The system of ODEs passed cannot be solved by dsolve_system. + ''')) + + # scc_sol: List of equations + # scc_sol is a solution + sol += scc_sol + + return sol + + +# Returns: List of Equations(a solution) +def _component_solver(eqs, funcs, t): + components = _component_division(eqs, funcs, t) + sol = [] + + for wcc in components: + + # wcc_sol: List of Equations + sol += _weak_component_solver(wcc, t) + + # sol: List of Equations + return sol + + +def _second_order_to_first_order(eqs, funcs, t, type="auto", A1=None, + A0=None, b=None, t_=None): + r""" + Expects the system to be in second order and in canonical form + + Explanation + =========== + + Reduces a second order system into a first order one depending on the type of second + order system. + 1. "type0": If this is passed, then the system will be reduced to first order by + introducing dummy variables. + 2. "type1": If this is passed, then a particular substitution will be used to reduce the + the system into first order. + 3. "type2": If this is passed, then the system will be transformed with new dependent + variables and independent variables. This transformation is a part of solving + the corresponding system of ODEs. + + `A1` and `A0` are the coefficient matrices from the system and it is assumed that the + second order system has the form given below: + + .. math:: + A2 * X'' = A1 * X' + A0 * X + b + + Here, $A2$ is the coefficient matrix for the vector $X''$ and $b$ is the non-homogeneous + term. + + Default value for `b` is None but if `A1` and `A0` are passed and `b` is not passed, then the + system will be assumed homogeneous. + + """ + is_a1 = A1 is None + is_a0 = A0 is None + + if (type == "type1" and is_a1) or (type == "type2" and is_a0)\ + or (type == "auto" and (is_a1 or is_a0)): + (A2, A1, A0), b = linear_ode_to_matrix(eqs, funcs, t, 2) + + if not A2.is_Identity: + raise ValueError(filldedent(''' + The system must be in its canonical form. + ''')) + + if type == "auto": + match = _match_second_order_type(A1, A0, t) + type = match["type_of_equation"] + A1 = match.get("A1", None) + A0 = match.get("A0", None) + + sys_order = dict.fromkeys(funcs, 2) + + if type == "type1": + if b is None: + b = zeros(len(eqs)) + eqs = _second_order_subs_type1(A1, b, funcs, t) + sys_order = dict.fromkeys(funcs, 1) + + if type == "type2": + if t_ is None: + t_ = Symbol("{}_".format(t)) + t = t_ + eqs, funcs = _second_order_subs_type2(A0, funcs, t_) + sys_order = dict.fromkeys(funcs, 2) + + return _higher_order_to_first_order(eqs, sys_order, t, funcs=funcs) + + +def _higher_order_type2_to_sub_systems(J, f_t, funcs, t, max_order, b=None, P=None): + + # Note: To add a test for this ValueError + if J is None or f_t is None or not _matrix_is_constant(J, t): + raise ValueError(filldedent(''' + Correctly input for args 'A' and 'f_t' for Linear, Higher Order, + Type 2 + ''')) + + if P is None and b is not None and not b.is_zero_matrix: + raise ValueError(filldedent(''' + Provide the keyword 'P' for matrix P in A = P * J * P-1. + ''')) + + new_funcs = Matrix([Function(Dummy('{}__0'.format(f.func.__name__)))(t) for f in funcs]) + new_eqs = new_funcs.diff(t, max_order) - f_t * J * new_funcs + + if b is not None and not b.is_zero_matrix: + new_eqs -= P.inv() * b + + new_eqs = canonical_odes(new_eqs, new_funcs, t)[0] + + return new_eqs, new_funcs + + +def _higher_order_to_first_order(eqs, sys_order, t, funcs=None, type="type0", **kwargs): + if funcs is None: + funcs = sys_order.keys() + + # Standard Cauchy Euler system + if type == "type1": + t_ = Symbol('{}_'.format(t)) + new_funcs = [Function(Dummy('{}_'.format(f.func.__name__)))(t_) for f in funcs] + max_order = max(sys_order[func] for func in funcs) + subs_dict = dict(zip(funcs, new_funcs)) + subs_dict[t] = exp(t_) + + free_function = Function(Dummy()) + + def _get_coeffs_from_subs_expression(expr): + if isinstance(expr, Subs): + free_symbol = expr.args[1][0] + term = expr.args[0] + return {ode_order(term, free_symbol): 1} + + if isinstance(expr, Mul): + coeff = expr.args[0] + order = list(_get_coeffs_from_subs_expression(expr.args[1]).keys())[0] + return {order: coeff} + + if isinstance(expr, Add): + coeffs = {} + for arg in expr.args: + + if isinstance(arg, Mul): + coeffs.update(_get_coeffs_from_subs_expression(arg)) + + else: + order = list(_get_coeffs_from_subs_expression(arg).keys())[0] + coeffs[order] = 1 + + return coeffs + + for o in range(1, max_order + 1): + expr = free_function(log(t_)).diff(t_, o)*t_**o + coeff_dict = _get_coeffs_from_subs_expression(expr) + coeffs = [coeff_dict[order] if order in coeff_dict else 0 for order in range(o + 1)] + expr_to_subs = sum(free_function(t_).diff(t_, i) * c for i, c in + enumerate(coeffs)) / t**o + subs_dict.update({f.diff(t, o): expr_to_subs.subs(free_function(t_), nf) + for f, nf in zip(funcs, new_funcs)}) + + new_eqs = [eq.subs(subs_dict) for eq in eqs] + new_sys_order = {nf: sys_order[f] for f, nf in zip(funcs, new_funcs)} + + new_eqs = canonical_odes(new_eqs, new_funcs, t_)[0] + + return _higher_order_to_first_order(new_eqs, new_sys_order, t_, funcs=new_funcs) + + # Systems of the form: X(n)(t) = f(t)*A*X + b + # where X(n)(t) is the nth derivative of the vector of dependent variables + # with respect to the independent variable and A is a constant matrix. + if type == "type2": + J = kwargs.get('J', None) + f_t = kwargs.get('f_t', None) + b = kwargs.get('b', None) + P = kwargs.get('P', None) + max_order = max(sys_order[func] for func in funcs) + + return _higher_order_type2_to_sub_systems(J, f_t, funcs, t, max_order, P=P, b=b) + + # Note: To be changed to this after doit option is disabled for default cases + # new_sysorder = _get_func_order(new_eqs, new_funcs) + # + # return _higher_order_to_first_order(new_eqs, new_sysorder, t, funcs=new_funcs) + + new_funcs = [] + + for prev_func in funcs: + func_name = prev_func.func.__name__ + func = Function(Dummy('{}_0'.format(func_name)))(t) + new_funcs.append(func) + subs_dict = {prev_func: func} + new_eqs = [] + + for i in range(1, sys_order[prev_func]): + new_func = Function(Dummy('{}_{}'.format(func_name, i)))(t) + subs_dict[prev_func.diff(t, i)] = new_func + new_funcs.append(new_func) + + prev_f = subs_dict[prev_func.diff(t, i-1)] + new_eq = Eq(prev_f.diff(t), new_func) + new_eqs.append(new_eq) + + eqs = [eq.subs(subs_dict) for eq in eqs] + new_eqs + + return eqs, new_funcs + + +def dsolve_system(eqs, funcs=None, t=None, ics=None, doit=False, simplify=True): + r""" + Solves any(supported) system of Ordinary Differential Equations + + Explanation + =========== + + This function takes a system of ODEs as an input, determines if the + it is solvable by this function, and returns the solution if found any. + + This function can handle: + 1. Linear, First Order, Constant coefficient homogeneous system of ODEs + 2. Linear, First Order, Constant coefficient non-homogeneous system of ODEs + 3. Linear, First Order, non-constant coefficient homogeneous system of ODEs + 4. Linear, First Order, non-constant coefficient non-homogeneous system of ODEs + 5. Any implicit system which can be divided into system of ODEs which is of the above 4 forms + 6. Any higher order linear system of ODEs that can be reduced to one of the 5 forms of systems described above. + + The types of systems described above are not limited by the number of equations, i.e. this + function can solve the above types irrespective of the number of equations in the system passed. + But, the bigger the system, the more time it will take to solve the system. + + This function returns a list of solutions. Each solution is a list of equations where LHS is + the dependent variable and RHS is an expression in terms of the independent variable. + + Among the non constant coefficient types, not all the systems are solvable by this function. Only + those which have either a coefficient matrix with a commutative antiderivative or those systems which + may be divided further so that the divided systems may have coefficient matrix with commutative antiderivative. + + Parameters + ========== + + eqs : List + system of ODEs to be solved + funcs : List or None + List of dependent variables that make up the system of ODEs + t : Symbol or None + Independent variable in the system of ODEs + ics : Dict or None + Set of initial boundary/conditions for the system of ODEs + doit : Boolean + Evaluate the solutions if True. Default value is True. Can be + set to false if the integral evaluation takes too much time and/or + is not required. + simplify: Boolean + Simplify the solutions for the systems. Default value is True. + Can be set to false if simplification takes too much time and/or + is not required. + + Examples + ======== + + >>> from sympy import symbols, Eq, Function + >>> from sympy.solvers.ode.systems import dsolve_system + >>> f, g = symbols("f g", cls=Function) + >>> x = symbols("x") + + >>> eqs = [Eq(f(x).diff(x), g(x)), Eq(g(x).diff(x), f(x))] + >>> dsolve_system(eqs) + [[Eq(f(x), -C1*exp(-x) + C2*exp(x)), Eq(g(x), C1*exp(-x) + C2*exp(x))]] + + You can also pass the initial conditions for the system of ODEs: + + >>> dsolve_system(eqs, ics={f(0): 1, g(0): 0}) + [[Eq(f(x), exp(x)/2 + exp(-x)/2), Eq(g(x), exp(x)/2 - exp(-x)/2)]] + + Optionally, you can pass the dependent variables and the independent + variable for which the system is to be solved: + + >>> funcs = [f(x), g(x)] + >>> dsolve_system(eqs, funcs=funcs, t=x) + [[Eq(f(x), -C1*exp(-x) + C2*exp(x)), Eq(g(x), C1*exp(-x) + C2*exp(x))]] + + Lets look at an implicit system of ODEs: + + >>> eqs = [Eq(f(x).diff(x)**2, g(x)**2), Eq(g(x).diff(x), g(x))] + >>> dsolve_system(eqs) + [[Eq(f(x), C1 - C2*exp(x)), Eq(g(x), C2*exp(x))], [Eq(f(x), C1 + C2*exp(x)), Eq(g(x), C2*exp(x))]] + + Returns + ======= + + List of List of Equations + + Raises + ====== + + NotImplementedError + When the system of ODEs is not solvable by this function. + ValueError + When the parameters passed are not in the required form. + + """ + from sympy.solvers.ode.ode import solve_ics, _extract_funcs, constant_renumber + + if not iterable(eqs): + raise ValueError(filldedent(''' + List of equations should be passed. The input is not valid. + ''')) + + eqs = _preprocess_eqs(eqs) + + if funcs is not None and not isinstance(funcs, list): + raise ValueError(filldedent(''' + Input to the funcs should be a list of functions. + ''')) + + if funcs is None: + funcs = _extract_funcs(eqs) + + if any(len(func.args) != 1 for func in funcs): + raise ValueError(filldedent(''' + dsolve_system can solve a system of ODEs with only one independent + variable. + ''')) + + if len(eqs) != len(funcs): + raise ValueError(filldedent(''' + Number of equations and number of functions do not match + ''')) + + if t is not None and not isinstance(t, Symbol): + raise ValueError(filldedent(''' + The independent variable must be of type Symbol + ''')) + + if t is None: + t = list(list(eqs[0].atoms(Derivative))[0].atoms(Symbol))[0] + + sols = [] + canon_eqs = canonical_odes(eqs, funcs, t) + + for canon_eq in canon_eqs: + try: + sol = _strong_component_solver(canon_eq, funcs, t) + except NotImplementedError: + sol = None + + if sol is None: + sol = _component_solver(canon_eq, funcs, t) + + sols.append(sol) + + if sols: + final_sols = [] + variables = Tuple(*eqs).free_symbols + + for sol in sols: + + sol = _select_equations(sol, funcs) + sol = constant_renumber(sol, variables=variables) + + if ics: + constants = Tuple(*sol).free_symbols - variables + solved_constants = solve_ics(sol, funcs, constants, ics) + sol = [s.subs(solved_constants) for s in sol] + + if simplify: + constants = Tuple(*sol).free_symbols - variables + sol = simpsol(sol, [t], constants, doit=doit) + + final_sols.append(sol) + + sols = final_sols + + return sols diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/tests/__init__.py b/lib/python3.12/site-packages/sympy/solvers/ode/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/tests/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/ode/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..039c897126fec7b3202bb4948b4b925bf5fbbfbf Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/ode/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/tests/__pycache__/test_lie_group.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/ode/tests/__pycache__/test_lie_group.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..734e1556bc3495a02383867583d2f2eeeda3f123 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/ode/tests/__pycache__/test_lie_group.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/tests/__pycache__/test_riccati.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/ode/tests/__pycache__/test_riccati.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cec79df036b5d0a4a3c53376eb328ad9d96f97ca Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/ode/tests/__pycache__/test_riccati.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/tests/__pycache__/test_subscheck.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/ode/tests/__pycache__/test_subscheck.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2565c6fc9327fa4ae2ccf2b2dbc0288574550b97 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/ode/tests/__pycache__/test_subscheck.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_lie_group.py b/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_lie_group.py new file mode 100644 index 0000000000000000000000000000000000000000..153d30ff563773819e49c989f447c1ec7962169b --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_lie_group.py @@ -0,0 +1,152 @@ +from sympy.core.function import Function +from sympy.core.numbers import Rational +from sympy.core.relational import Eq +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (atan, sin, tan) + +from sympy.solvers.ode import (classify_ode, checkinfsol, dsolve, infinitesimals) + +from sympy.solvers.ode.subscheck import checkodesol + +from sympy.testing.pytest import XFAIL + + +C1 = Symbol('C1') +x, y = symbols("x y") +f = Function('f') +xi = Function('xi') +eta = Function('eta') + + +def test_heuristic1(): + a, b, c, a4, a3, a2, a1, a0 = symbols("a b c a4 a3 a2 a1 a0") + df = f(x).diff(x) + eq = Eq(df, x**2*f(x)) + eq1 = f(x).diff(x) + a*f(x) - c*exp(b*x) + eq2 = f(x).diff(x) + 2*x*f(x) - x*exp(-x**2) + eq3 = (1 + 2*x)*df + 2 - 4*exp(-f(x)) + eq4 = f(x).diff(x) - (a4*x**4 + a3*x**3 + a2*x**2 + a1*x + a0)**Rational(-1, 2) + eq5 = x**2*df - f(x) + x**2*exp(x - (1/x)) + eqlist = [eq, eq1, eq2, eq3, eq4, eq5] + + i = infinitesimals(eq, hint='abaco1_simple') + assert i == [{eta(x, f(x)): exp(x**3/3), xi(x, f(x)): 0}, + {eta(x, f(x)): f(x), xi(x, f(x)): 0}, + {eta(x, f(x)): 0, xi(x, f(x)): x**(-2)}] + i1 = infinitesimals(eq1, hint='abaco1_simple') + assert i1 == [{eta(x, f(x)): exp(-a*x), xi(x, f(x)): 0}] + i2 = infinitesimals(eq2, hint='abaco1_simple') + assert i2 == [{eta(x, f(x)): exp(-x**2), xi(x, f(x)): 0}] + i3 = infinitesimals(eq3, hint='abaco1_simple') + assert i3 == [{eta(x, f(x)): 0, xi(x, f(x)): 2*x + 1}, + {eta(x, f(x)): 0, xi(x, f(x)): 1/(exp(f(x)) - 2)}] + i4 = infinitesimals(eq4, hint='abaco1_simple') + assert i4 == [{eta(x, f(x)): 1, xi(x, f(x)): 0}, + {eta(x, f(x)): 0, + xi(x, f(x)): sqrt(a0 + a1*x + a2*x**2 + a3*x**3 + a4*x**4)}] + i5 = infinitesimals(eq5, hint='abaco1_simple') + assert i5 == [{xi(x, f(x)): 0, eta(x, f(x)): exp(-1/x)}] + + ilist = [i, i1, i2, i3, i4, i5] + for eq, i in (zip(eqlist, ilist)): + check = checkinfsol(eq, i) + assert check[0] + + # This ODE can be solved by the Lie Group method, when there are + # better assumptions + eq6 = df - (f(x)/x)*(x*log(x**2/f(x)) + 2) + i = infinitesimals(eq6, hint='abaco1_product') + assert i == [{eta(x, f(x)): f(x)*exp(-x), xi(x, f(x)): 0}] + assert checkinfsol(eq6, i)[0] + + eq7 = x*(f(x).diff(x)) + 1 - f(x)**2 + i = infinitesimals(eq7, hint='chi') + assert checkinfsol(eq7, i)[0] + + +def test_heuristic3(): + a, b = symbols("a b") + df = f(x).diff(x) + + eq = x**2*df + x*f(x) + f(x)**2 + x**2 + i = infinitesimals(eq, hint='bivariate') + assert i == [{eta(x, f(x)): f(x), xi(x, f(x)): x}] + assert checkinfsol(eq, i)[0] + + eq = x**2*(-f(x)**2 + df)- a*x**2*f(x) + 2 - a*x + i = infinitesimals(eq, hint='bivariate') + assert checkinfsol(eq, i)[0] + + +def test_heuristic_function_sum(): + eq = f(x).diff(x) - (3*(1 + x**2/f(x)**2)*atan(f(x)/x) + (1 - 2*f(x))/x + + (1 - 3*f(x))*(x/f(x)**2)) + i = infinitesimals(eq, hint='function_sum') + assert i == [{eta(x, f(x)): f(x)**(-2) + x**(-2), xi(x, f(x)): 0}] + assert checkinfsol(eq, i)[0] + + +def test_heuristic_abaco2_similar(): + a, b = symbols("a b") + F = Function('F') + eq = f(x).diff(x) - F(a*x + b*f(x)) + i = infinitesimals(eq, hint='abaco2_similar') + assert i == [{eta(x, f(x)): -a/b, xi(x, f(x)): 1}] + assert checkinfsol(eq, i)[0] + + eq = f(x).diff(x) - (f(x)**2 / (sin(f(x) - x) - x**2 + 2*x*f(x))) + i = infinitesimals(eq, hint='abaco2_similar') + assert i == [{eta(x, f(x)): f(x)**2, xi(x, f(x)): f(x)**2}] + assert checkinfsol(eq, i)[0] + + +def test_heuristic_abaco2_unique_unknown(): + + a, b = symbols("a b") + F = Function('F') + eq = f(x).diff(x) - x**(a - 1)*(f(x)**(1 - b))*F(x**a/a + f(x)**b/b) + i = infinitesimals(eq, hint='abaco2_unique_unknown') + assert i == [{eta(x, f(x)): -f(x)*f(x)**(-b), xi(x, f(x)): x*x**(-a)}] + assert checkinfsol(eq, i)[0] + + eq = f(x).diff(x) + tan(F(x**2 + f(x)**2) + atan(x/f(x))) + i = infinitesimals(eq, hint='abaco2_unique_unknown') + assert i == [{eta(x, f(x)): x, xi(x, f(x)): -f(x)}] + assert checkinfsol(eq, i)[0] + + eq = (x*f(x).diff(x) + f(x) + 2*x)**2 -4*x*f(x) -4*x**2 -4*a + i = infinitesimals(eq, hint='abaco2_unique_unknown') + assert checkinfsol(eq, i)[0] + + +def test_heuristic_linear(): + a, b, m, n = symbols("a b m n") + + eq = x**(n*(m + 1) - m)*(f(x).diff(x)) - a*f(x)**n -b*x**(n*(m + 1)) + i = infinitesimals(eq, hint='linear') + assert checkinfsol(eq, i)[0] + + +@XFAIL +def test_kamke(): + a, b, alpha, c = symbols("a b alpha c") + eq = x**2*(a*f(x)**2+(f(x).diff(x))) + b*x**alpha + c + i = infinitesimals(eq, hint='sum_function') # XFAIL + assert checkinfsol(eq, i)[0] + + +def test_user_infinitesimals(): + x = Symbol("x") # assuming x is real generates an error + eq = x*(f(x).diff(x)) + 1 - f(x)**2 + sol = Eq(f(x), (C1 + x**2)/(C1 - x**2)) + infinitesimals = {'xi':sqrt(f(x) - 1)/sqrt(f(x) + 1), 'eta':0} + assert dsolve(eq, hint='lie_group', **infinitesimals) == sol + assert checkodesol(eq, sol) == (True, 0) + + +@XFAIL +def test_lie_group_issue15219(): + eqn = exp(f(x).diff(x)-f(x)) + assert 'lie_group' not in classify_ode(eqn, f(x)) diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_ode.py b/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_ode.py new file mode 100644 index 0000000000000000000000000000000000000000..b1ddcc784fde15c1176feb23ca47f4adf6fddbff --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_ode.py @@ -0,0 +1,1104 @@ +from sympy.core.function import (Derivative, Function, Subs, diff) +from sympy.core.numbers import (E, I, Rational, pi) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.complexes import (im, re) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.hyperbolic import acosh +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (atan2, cos, sin, tan) +from sympy.integrals.integrals import Integral +from sympy.polys.polytools import Poly +from sympy.series.order import O +from sympy.simplify.radsimp import collect + +from sympy.solvers.ode import (classify_ode, + homogeneous_order, dsolve) + +from sympy.solvers.ode.subscheck import checkodesol +from sympy.solvers.ode.ode import (classify_sysode, + constant_renumber, constantsimp, get_numbered_constants, solve_ics) + +from sympy.solvers.ode.nonhomogeneous import _undetermined_coefficients_match +from sympy.solvers.ode.single import LinearCoefficients +from sympy.solvers.deutils import ode_order +from sympy.testing.pytest import XFAIL, raises, slow, SKIP +from sympy.utilities.misc import filldedent + + +C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10 = symbols('C0:11') +u, x, y, z = symbols('u,x:z', real=True) +f = Function('f') +g = Function('g') +h = Function('h') + +# Note: Examples which were specifically testing Single ODE solver are moved to test_single.py +# and all the system of ode examples are moved to test_systems.py +# Note: the tests below may fail (but still be correct) if ODE solver, +# the integral engine, solve(), or even simplify() changes. Also, in +# differently formatted solutions, the arbitrary constants might not be +# equal. Using specific hints in tests can help to avoid this. + +# Tests of order higher than 1 should run the solutions through +# constant_renumber because it will normalize it (constant_renumber causes +# dsolve() to return different results on different machines) + + +def test_get_numbered_constants(): + with raises(ValueError): + get_numbered_constants(None) + + +def test_dsolve_all_hint(): + eq = f(x).diff(x) + output = dsolve(eq, hint='all') + + # Match the Dummy variables: + sol1 = output['separable_Integral'] + _y = sol1.lhs.args[1][0] + sol1 = output['1st_homogeneous_coeff_subs_dep_div_indep_Integral'] + _u1 = sol1.rhs.args[1].args[1][0] + + expected = {'Bernoulli_Integral': Eq(f(x), C1 + Integral(0, x)), + '1st_homogeneous_coeff_best': Eq(f(x), C1), + 'Bernoulli': Eq(f(x), C1), + 'nth_algebraic': Eq(f(x), C1), + 'nth_linear_euler_eq_homogeneous': Eq(f(x), C1), + 'nth_linear_constant_coeff_homogeneous': Eq(f(x), C1), + 'separable': Eq(f(x), C1), + '1st_homogeneous_coeff_subs_indep_div_dep': Eq(f(x), C1), + 'nth_algebraic_Integral': Eq(f(x), C1), + '1st_linear': Eq(f(x), C1), + '1st_linear_Integral': Eq(f(x), C1 + Integral(0, x)), + '1st_exact': Eq(f(x), C1), + '1st_exact_Integral': Eq(Subs(Integral(0, x) + Integral(1, _y), _y, f(x)), C1), + 'lie_group': Eq(f(x), C1), + '1st_homogeneous_coeff_subs_dep_div_indep': Eq(f(x), C1), + '1st_homogeneous_coeff_subs_dep_div_indep_Integral': Eq(log(x), C1 + Integral(-1/_u1, (_u1, f(x)/x))), + '1st_power_series': Eq(f(x), C1), + 'separable_Integral': Eq(Integral(1, (_y, f(x))), C1 + Integral(0, x)), + '1st_homogeneous_coeff_subs_indep_div_dep_Integral': Eq(f(x), C1), + 'best': Eq(f(x), C1), + 'best_hint': 'nth_algebraic', + 'default': 'nth_algebraic', + 'order': 1} + assert output == expected + + assert dsolve(eq, hint='best') == Eq(f(x), C1) + + +def test_dsolve_ics(): + # Maybe this should just use one of the solutions instead of raising... + with raises(NotImplementedError): + dsolve(f(x).diff(x) - sqrt(f(x)), ics={f(1):1}) + + +@slow +def test_dsolve_options(): + eq = x*f(x).diff(x) + f(x) + a = dsolve(eq, hint='all') + b = dsolve(eq, hint='all', simplify=False) + c = dsolve(eq, hint='all_Integral') + keys = ['1st_exact', '1st_exact_Integral', '1st_homogeneous_coeff_best', + '1st_homogeneous_coeff_subs_dep_div_indep', + '1st_homogeneous_coeff_subs_dep_div_indep_Integral', + '1st_homogeneous_coeff_subs_indep_div_dep', + '1st_homogeneous_coeff_subs_indep_div_dep_Integral', '1st_linear', + '1st_linear_Integral', 'Bernoulli', 'Bernoulli_Integral', + 'almost_linear', 'almost_linear_Integral', 'best', 'best_hint', + 'default', 'factorable', 'lie_group', + 'nth_linear_euler_eq_homogeneous', 'order', + 'separable', 'separable_Integral'] + Integral_keys = ['1st_exact_Integral', + '1st_homogeneous_coeff_subs_dep_div_indep_Integral', + '1st_homogeneous_coeff_subs_indep_div_dep_Integral', '1st_linear_Integral', + 'Bernoulli_Integral', 'almost_linear_Integral', 'best', 'best_hint', 'default', + 'factorable', 'nth_linear_euler_eq_homogeneous', + 'order', 'separable_Integral'] + assert sorted(a.keys()) == keys + assert a['order'] == ode_order(eq, f(x)) + assert a['best'] == Eq(f(x), C1/x) + assert dsolve(eq, hint='best') == Eq(f(x), C1/x) + assert a['default'] == 'factorable' + assert a['best_hint'] == 'factorable' + assert not a['1st_exact'].has(Integral) + assert not a['separable'].has(Integral) + assert not a['1st_homogeneous_coeff_best'].has(Integral) + assert not a['1st_homogeneous_coeff_subs_dep_div_indep'].has(Integral) + assert not a['1st_homogeneous_coeff_subs_indep_div_dep'].has(Integral) + assert not a['1st_linear'].has(Integral) + assert a['1st_linear_Integral'].has(Integral) + assert a['1st_exact_Integral'].has(Integral) + assert a['1st_homogeneous_coeff_subs_dep_div_indep_Integral'].has(Integral) + assert a['1st_homogeneous_coeff_subs_indep_div_dep_Integral'].has(Integral) + assert a['separable_Integral'].has(Integral) + assert sorted(b.keys()) == keys + assert b['order'] == ode_order(eq, f(x)) + assert b['best'] == Eq(f(x), C1/x) + assert dsolve(eq, hint='best', simplify=False) == Eq(f(x), C1/x) + assert b['default'] == 'factorable' + assert b['best_hint'] == 'factorable' + assert a['separable'] != b['separable'] + assert a['1st_homogeneous_coeff_subs_dep_div_indep'] != \ + b['1st_homogeneous_coeff_subs_dep_div_indep'] + assert a['1st_homogeneous_coeff_subs_indep_div_dep'] != \ + b['1st_homogeneous_coeff_subs_indep_div_dep'] + assert not b['1st_exact'].has(Integral) + assert not b['separable'].has(Integral) + assert not b['1st_homogeneous_coeff_best'].has(Integral) + assert not b['1st_homogeneous_coeff_subs_dep_div_indep'].has(Integral) + assert not b['1st_homogeneous_coeff_subs_indep_div_dep'].has(Integral) + assert not b['1st_linear'].has(Integral) + assert b['1st_linear_Integral'].has(Integral) + assert b['1st_exact_Integral'].has(Integral) + assert b['1st_homogeneous_coeff_subs_dep_div_indep_Integral'].has(Integral) + assert b['1st_homogeneous_coeff_subs_indep_div_dep_Integral'].has(Integral) + assert b['separable_Integral'].has(Integral) + assert sorted(c.keys()) == Integral_keys + raises(ValueError, lambda: dsolve(eq, hint='notarealhint')) + raises(ValueError, lambda: dsolve(eq, hint='Liouville')) + assert dsolve(f(x).diff(x) - 1/f(x)**2, hint='all')['best'] == \ + dsolve(f(x).diff(x) - 1/f(x)**2, hint='best') + assert dsolve(f(x) + f(x).diff(x) + sin(x).diff(x) + 1, f(x), + hint="1st_linear_Integral") == \ + Eq(f(x), (C1 + Integral((-sin(x).diff(x) - 1)* + exp(Integral(1, x)), x))*exp(-Integral(1, x))) + + +def test_classify_ode(): + assert classify_ode(f(x).diff(x, 2), f(x)) == \ + ( + 'nth_algebraic', + 'nth_linear_constant_coeff_homogeneous', + 'nth_linear_euler_eq_homogeneous', + 'Liouville', + '2nd_power_series_ordinary', + 'nth_algebraic_Integral', + 'Liouville_Integral', + ) + assert classify_ode(f(x), f(x)) == ('nth_algebraic', 'nth_algebraic_Integral') + assert classify_ode(Eq(f(x).diff(x), 0), f(x)) == ( + 'nth_algebraic', + 'separable', + '1st_exact', + '1st_linear', + 'Bernoulli', + '1st_homogeneous_coeff_best', + '1st_homogeneous_coeff_subs_indep_div_dep', + '1st_homogeneous_coeff_subs_dep_div_indep', + '1st_power_series', 'lie_group', + 'nth_linear_constant_coeff_homogeneous', + 'nth_linear_euler_eq_homogeneous', + 'nth_algebraic_Integral', + 'separable_Integral', + '1st_exact_Integral', + '1st_linear_Integral', + 'Bernoulli_Integral', + '1st_homogeneous_coeff_subs_indep_div_dep_Integral', + '1st_homogeneous_coeff_subs_dep_div_indep_Integral') + assert classify_ode(f(x).diff(x)**2, f(x)) == ('factorable', + 'nth_algebraic', + 'separable', + '1st_exact', + '1st_linear', + 'Bernoulli', + '1st_homogeneous_coeff_best', + '1st_homogeneous_coeff_subs_indep_div_dep', + '1st_homogeneous_coeff_subs_dep_div_indep', + '1st_power_series', + 'lie_group', + 'nth_linear_euler_eq_homogeneous', + 'nth_algebraic_Integral', + 'separable_Integral', + '1st_exact_Integral', + '1st_linear_Integral', + 'Bernoulli_Integral', + '1st_homogeneous_coeff_subs_indep_div_dep_Integral', + '1st_homogeneous_coeff_subs_dep_div_indep_Integral') + # issue 4749: f(x) should be cleared from highest derivative before classifying + a = classify_ode(Eq(f(x).diff(x) + f(x), x), f(x)) + b = classify_ode(f(x).diff(x)*f(x) + f(x)*f(x) - x*f(x), f(x)) + c = classify_ode(f(x).diff(x)/f(x) + f(x)/f(x) - x/f(x), f(x)) + assert a == ('1st_exact', + '1st_linear', + 'Bernoulli', + 'almost_linear', + '1st_power_series', "lie_group", + 'nth_linear_constant_coeff_undetermined_coefficients', + 'nth_linear_constant_coeff_variation_of_parameters', + '1st_exact_Integral', + '1st_linear_Integral', + 'Bernoulli_Integral', + 'almost_linear_Integral', + 'nth_linear_constant_coeff_variation_of_parameters_Integral') + assert b == ('factorable', + '1st_linear', + 'Bernoulli', + '1st_power_series', + 'lie_group', + 'nth_linear_constant_coeff_undetermined_coefficients', + 'nth_linear_constant_coeff_variation_of_parameters', + '1st_linear_Integral', + 'Bernoulli_Integral', + 'nth_linear_constant_coeff_variation_of_parameters_Integral') + assert c == ('factorable', + '1st_linear', + 'Bernoulli', + '1st_power_series', + 'lie_group', + 'nth_linear_constant_coeff_undetermined_coefficients', + 'nth_linear_constant_coeff_variation_of_parameters', + '1st_linear_Integral', + 'Bernoulli_Integral', + 'nth_linear_constant_coeff_variation_of_parameters_Integral') + + assert classify_ode( + 2*x*f(x)*f(x).diff(x) + (1 + x)*f(x)**2 - exp(x), f(x) + ) == ('factorable', '1st_exact', 'Bernoulli', 'almost_linear', 'lie_group', + '1st_exact_Integral', 'Bernoulli_Integral', 'almost_linear_Integral') + assert 'Riccati_special_minus2' in \ + classify_ode(2*f(x).diff(x) + f(x)**2 - f(x)/x + 3*x**(-2), f(x)) + raises(ValueError, lambda: classify_ode(x + f(x, y).diff(x).diff( + y), f(x, y))) + # issue 5176 + k = Symbol('k') + assert classify_ode(f(x).diff(x)/(k*f(x) + k*x*f(x)) + 2*f(x)/(k*f(x) + + k*x*f(x)) + x*f(x).diff(x)/(k*f(x) + k*x*f(x)) + z, f(x)) == \ + ('factorable', 'separable', '1st_exact', '1st_linear', 'Bernoulli', + '1st_power_series', 'lie_group', 'separable_Integral', '1st_exact_Integral', + '1st_linear_Integral', 'Bernoulli_Integral') + # preprocessing + ans = ('factorable', 'nth_algebraic', 'separable', '1st_exact', '1st_linear', 'Bernoulli', + '1st_homogeneous_coeff_best', + '1st_homogeneous_coeff_subs_indep_div_dep', + '1st_homogeneous_coeff_subs_dep_div_indep', + '1st_power_series', 'lie_group', + 'nth_linear_constant_coeff_undetermined_coefficients', + 'nth_linear_euler_eq_nonhomogeneous_undetermined_coefficients', + 'nth_linear_constant_coeff_variation_of_parameters', + 'nth_linear_euler_eq_nonhomogeneous_variation_of_parameters', + 'nth_algebraic_Integral', + 'separable_Integral', '1st_exact_Integral', + '1st_linear_Integral', + 'Bernoulli_Integral', + '1st_homogeneous_coeff_subs_indep_div_dep_Integral', + '1st_homogeneous_coeff_subs_dep_div_indep_Integral', + 'nth_linear_constant_coeff_variation_of_parameters_Integral', + 'nth_linear_euler_eq_nonhomogeneous_variation_of_parameters_Integral') + # w/o f(x) given + assert classify_ode(diff(f(x) + x, x) + diff(f(x), x)) == ans + # w/ f(x) and prep=True + assert classify_ode(diff(f(x) + x, x) + diff(f(x), x), f(x), + prep=True) == ans + + assert classify_ode(Eq(2*x**3*f(x).diff(x), 0), f(x)) == \ + ('factorable', 'nth_algebraic', 'separable', '1st_exact', + '1st_linear', 'Bernoulli', '1st_power_series', + 'lie_group', 'nth_linear_euler_eq_homogeneous', + 'nth_algebraic_Integral', 'separable_Integral', '1st_exact_Integral', + '1st_linear_Integral', 'Bernoulli_Integral') + + + assert classify_ode(Eq(2*f(x)**3*f(x).diff(x), 0), f(x)) == \ + ('factorable', 'nth_algebraic', 'separable', '1st_exact', '1st_linear', + 'Bernoulli', '1st_power_series', 'lie_group', 'nth_algebraic_Integral', + 'separable_Integral', '1st_exact_Integral', '1st_linear_Integral', + 'Bernoulli_Integral') + # test issue 13864 + assert classify_ode(Eq(diff(f(x), x) - f(x)**x, 0), f(x)) == \ + ('1st_power_series', 'lie_group') + assert isinstance(classify_ode(Eq(f(x), 5), f(x), dict=True), dict) + + #This is for new behavior of classify_ode when called internally with default, It should + # return the first hint which matches therefore, 'ordered_hints' key will not be there. + assert sorted(classify_ode(Eq(f(x).diff(x), 0), f(x), dict=True).keys()) == \ + ['default', 'nth_linear_constant_coeff_homogeneous', 'order'] + a = classify_ode(2*x*f(x)*f(x).diff(x) + (1 + x)*f(x)**2 - exp(x), f(x), dict=True, hint='Bernoulli') + assert sorted(a.keys()) == ['Bernoulli', 'Bernoulli_Integral', 'default', 'order', 'ordered_hints'] + + # test issue 22155 + a = classify_ode(f(x).diff(x) - exp(f(x) - x), f(x)) + assert a == ('separable', + '1st_exact', '1st_power_series', + 'lie_group', 'separable_Integral', + '1st_exact_Integral') + + +def test_classify_ode_ics(): + # Dummy + eq = f(x).diff(x, x) - f(x) + + # Not f(0) or f'(0) + ics = {x: 1} + raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics)) + + + ############################ + # f(0) type (AppliedUndef) # + ############################ + + + # Wrong function + ics = {g(0): 1} + raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics)) + + # Contains x + ics = {f(x): 1} + raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics)) + + # Too many args + ics = {f(0, 0): 1} + raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics)) + + # point contains x + ics = {f(0): f(x)} + raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics)) + + # Does not raise + ics = {f(0): f(0)} + classify_ode(eq, f(x), ics=ics) + + # Does not raise + ics = {f(0): 1} + classify_ode(eq, f(x), ics=ics) + + + ##################### + # f'(0) type (Subs) # + ##################### + + # Wrong function + ics = {g(x).diff(x).subs(x, 0): 1} + raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics)) + + # Contains x + ics = {f(y).diff(y).subs(y, x): 1} + raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics)) + + # Wrong variable + ics = {f(y).diff(y).subs(y, 0): 1} + raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics)) + + # Too many args + ics = {f(x, y).diff(x).subs(x, 0): 1} + raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics)) + + # Derivative wrt wrong vars + ics = {Derivative(f(x), x, y).subs(x, 0): 1} + raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics)) + + # point contains x + ics = {f(x).diff(x).subs(x, 0): f(x)} + raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics)) + + # Does not raise + ics = {f(x).diff(x).subs(x, 0): f(x).diff(x).subs(x, 0)} + classify_ode(eq, f(x), ics=ics) + + # Does not raise + ics = {f(x).diff(x).subs(x, 0): 1} + classify_ode(eq, f(x), ics=ics) + + ########################### + # f'(y) type (Derivative) # + ########################### + + # Wrong function + ics = {g(x).diff(x).subs(x, y): 1} + raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics)) + + # Contains x + ics = {f(y).diff(y).subs(y, x): 1} + raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics)) + + # Too many args + ics = {f(x, y).diff(x).subs(x, y): 1} + raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics)) + + # Derivative wrt wrong vars + ics = {Derivative(f(x), x, z).subs(x, y): 1} + raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics)) + + # point contains x + ics = {f(x).diff(x).subs(x, y): f(x)} + raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics)) + + # Does not raise + ics = {f(x).diff(x).subs(x, 0): f(0)} + classify_ode(eq, f(x), ics=ics) + + # Does not raise + ics = {f(x).diff(x).subs(x, y): 1} + classify_ode(eq, f(x), ics=ics) + +def test_classify_sysode(): + # Here x is assumed to be x(t) and y as y(t) for simplicity. + # Similarly diff(x,t) and diff(y,y) is assumed to be x1 and y1 respectively. + k, l, m, n = symbols('k, l, m, n', Integer=True) + k1, k2, k3, l1, l2, l3, m1, m2, m3 = symbols('k1, k2, k3, l1, l2, l3, m1, m2, m3', Integer=True) + P, Q, R, p, q, r = symbols('P, Q, R, p, q, r', cls=Function) + P1, P2, P3, Q1, Q2, R1, R2 = symbols('P1, P2, P3, Q1, Q2, R1, R2', cls=Function) + x, y, z = symbols('x, y, z', cls=Function) + t = symbols('t') + x1 = diff(x(t),t) ; y1 = diff(y(t),t) ; + + eq6 = (Eq(x1, exp(k*x(t))*P(x(t),y(t))), Eq(y1,r(y(t))*P(x(t),y(t)))) + sol6 = {'no_of_equation': 2, 'func_coeff': {(0, x(t), 0): 0, (1, x(t), 1): 0, (0, x(t), 1): 1, (1, y(t), 0): 0, \ + (1, x(t), 0): 0, (0, y(t), 1): 0, (0, y(t), 0): 0, (1, y(t), 1): 1}, 'type_of_equation': 'type2', 'func': \ + [x(t), y(t)], 'is_linear': False, 'eq': [-P(x(t), y(t))*exp(k*x(t)) + Derivative(x(t), t), -P(x(t), \ + y(t))*r(y(t)) + Derivative(y(t), t)], 'order': {y(t): 1, x(t): 1}} + assert classify_sysode(eq6) == sol6 + + eq7 = (Eq(x1, x(t)**2+y(t)/x(t)), Eq(y1, x(t)/y(t))) + sol7 = {'no_of_equation': 2, 'func_coeff': {(0, x(t), 0): 0, (1, x(t), 1): 0, (0, x(t), 1): 1, (1, y(t), 0): 0, \ + (1, x(t), 0): -1/y(t), (0, y(t), 1): 0, (0, y(t), 0): -1/x(t), (1, y(t), 1): 1}, 'type_of_equation': 'type3', \ + 'func': [x(t), y(t)], 'is_linear': False, 'eq': [-x(t)**2 + Derivative(x(t), t) - y(t)/x(t), -x(t)/y(t) + \ + Derivative(y(t), t)], 'order': {y(t): 1, x(t): 1}} + assert classify_sysode(eq7) == sol7 + + eq8 = (Eq(x1, P1(x(t))*Q1(y(t))*R(x(t),y(t),t)), Eq(y1, P1(x(t))*Q1(y(t))*R(x(t),y(t),t))) + sol8 = {'func': [x(t), y(t)], 'is_linear': False, 'type_of_equation': 'type4', 'eq': \ + [-P1(x(t))*Q1(y(t))*R(x(t), y(t), t) + Derivative(x(t), t), -P1(x(t))*Q1(y(t))*R(x(t), y(t), t) + \ + Derivative(y(t), t)], 'func_coeff': {(0, y(t), 1): 0, (1, y(t), 1): 1, (1, x(t), 1): 0, (0, y(t), 0): 0, \ + (1, x(t), 0): 0, (0, x(t), 0): 0, (1, y(t), 0): 0, (0, x(t), 1): 1}, 'order': {y(t): 1, x(t): 1}, 'no_of_equation': 2} + assert classify_sysode(eq8) == sol8 + + eq11 = (Eq(x1,x(t)*y(t)**3), Eq(y1,y(t)**5)) + sol11 = {'no_of_equation': 2, 'func_coeff': {(0, x(t), 0): -y(t)**3, (1, x(t), 1): 0, (0, x(t), 1): 1, \ + (1, y(t), 0): 0, (1, x(t), 0): 0, (0, y(t), 1): 0, (0, y(t), 0): 0, (1, y(t), 1): 1}, 'type_of_equation': \ + 'type1', 'func': [x(t), y(t)], 'is_linear': False, 'eq': [-x(t)*y(t)**3 + Derivative(x(t), t), \ + -y(t)**5 + Derivative(y(t), t)], 'order': {y(t): 1, x(t): 1}} + assert classify_sysode(eq11) == sol11 + + eq13 = (Eq(x1,x(t)*y(t)*sin(t)**2), Eq(y1,y(t)**2*sin(t)**2)) + sol13 = {'no_of_equation': 2, 'func_coeff': {(0, x(t), 0): -y(t)*sin(t)**2, (1, x(t), 1): 0, (0, x(t), 1): 1, \ + (1, y(t), 0): 0, (1, x(t), 0): 0, (0, y(t), 1): 0, (0, y(t), 0): -x(t)*sin(t)**2, (1, y(t), 1): 1}, \ + 'type_of_equation': 'type4', 'func': [x(t), y(t)], 'is_linear': False, 'eq': [-x(t)*y(t)*sin(t)**2 + \ + Derivative(x(t), t), -y(t)**2*sin(t)**2 + Derivative(y(t), t)], 'order': {y(t): 1, x(t): 1}} + assert classify_sysode(eq13) == sol13 + + +def test_solve_ics(): + # Basic tests that things work from dsolve. + assert dsolve(f(x).diff(x) - 1/f(x), f(x), ics={f(1): 2}) == \ + Eq(f(x), sqrt(2 * x + 2)) + assert dsolve(f(x).diff(x) - f(x), f(x), ics={f(0): 1}) == Eq(f(x), exp(x)) + assert dsolve(f(x).diff(x) - f(x), f(x), ics={f(x).diff(x).subs(x, 0): 1}) == Eq(f(x), exp(x)) + assert dsolve(f(x).diff(x, x) + f(x), f(x), ics={f(0): 1, + f(x).diff(x).subs(x, 0): 1}) == Eq(f(x), sin(x) + cos(x)) + assert dsolve([f(x).diff(x) - f(x) + g(x), g(x).diff(x) - g(x) - f(x)], + [f(x), g(x)], ics={f(0): 1, g(0): 0}) == [Eq(f(x), exp(x)*cos(x)), Eq(g(x), exp(x)*sin(x))] + + # Test cases where dsolve returns two solutions. + eq = (x**2*f(x)**2 - x).diff(x) + assert dsolve(eq, f(x), ics={f(1): 0}) == [Eq(f(x), + -sqrt(x - 1)/x), Eq(f(x), sqrt(x - 1)/x)] + assert dsolve(eq, f(x), ics={f(x).diff(x).subs(x, 1): 0}) == [Eq(f(x), + -sqrt(x - S.Half)/x), Eq(f(x), sqrt(x - S.Half)/x)] + + eq = cos(f(x)) - (x*sin(f(x)) - f(x)**2)*f(x).diff(x) + assert dsolve(eq, f(x), + ics={f(0):1}, hint='1st_exact', simplify=False) == Eq(x*cos(f(x)) + f(x)**3/3, Rational(1, 3)) + assert dsolve(eq, f(x), + ics={f(0):1}, hint='1st_exact', simplify=True) == Eq(x*cos(f(x)) + f(x)**3/3, Rational(1, 3)) + + assert solve_ics([Eq(f(x), C1*exp(x))], [f(x)], [C1], {f(0): 1}) == {C1: 1} + assert solve_ics([Eq(f(x), C1*sin(x) + C2*cos(x))], [f(x)], [C1, C2], + {f(0): 1, f(pi/2): 1}) == {C1: 1, C2: 1} + + assert solve_ics([Eq(f(x), C1*sin(x) + C2*cos(x))], [f(x)], [C1, C2], + {f(0): 1, f(x).diff(x).subs(x, 0): 1}) == {C1: 1, C2: 1} + + assert solve_ics([Eq(f(x), C1*sin(x) + C2*cos(x))], [f(x)], [C1, C2], {f(0): 1}) == \ + {C2: 1} + + # Some more complicated tests Refer to PR #16098 + + assert set(dsolve(f(x).diff(x)*(f(x).diff(x, 2)-x), ics={f(0):0, f(x).diff(x).subs(x, 1):0})) == \ + {Eq(f(x), 0), Eq(f(x), x ** 3 / 6 - x / 2)} + assert set(dsolve(f(x).diff(x)*(f(x).diff(x, 2)-x), ics={f(0):0})) == \ + {Eq(f(x), 0), Eq(f(x), C2*x + x**3/6)} + + K, r, f0 = symbols('K r f0') + sol = Eq(f(x), K*f0*exp(r*x)/((-K + f0)*(f0*exp(r*x)/(-K + f0) - 1))) + assert (dsolve(Eq(f(x).diff(x), r * f(x) * (1 - f(x) / K)), f(x), ics={f(0): f0})) == sol + + + #Order dependent issues Refer to PR #16098 + assert set(dsolve(f(x).diff(x)*(f(x).diff(x, 2)-x), ics={f(x).diff(x).subs(x,0):0, f(0):0})) == \ + {Eq(f(x), 0), Eq(f(x), x ** 3 / 6)} + assert set(dsolve(f(x).diff(x)*(f(x).diff(x, 2)-x), ics={f(0):0, f(x).diff(x).subs(x,0):0})) == \ + {Eq(f(x), 0), Eq(f(x), x ** 3 / 6)} + + # XXX: Ought to be ValueError + raises(ValueError, lambda: solve_ics([Eq(f(x), C1*sin(x) + C2*cos(x))], [f(x)], [C1, C2], {f(0): 1, f(pi): 1})) + + # Degenerate case. f'(0) is identically 0. + raises(ValueError, lambda: solve_ics([Eq(f(x), sqrt(C1 - x**2))], [f(x)], [C1], {f(x).diff(x).subs(x, 0): 0})) + + EI, q, L = symbols('EI q L') + + # eq = Eq(EI*diff(f(x), x, 4), q) + sols = [Eq(f(x), C1 + C2*x + C3*x**2 + C4*x**3 + q*x**4/(24*EI))] + funcs = [f(x)] + constants = [C1, C2, C3, C4] + # Test both cases, Derivative (the default from f(x).diff(x).subs(x, L)), + # and Subs + ics1 = {f(0): 0, + f(x).diff(x).subs(x, 0): 0, + f(L).diff(L, 2): 0, + f(L).diff(L, 3): 0} + ics2 = {f(0): 0, + f(x).diff(x).subs(x, 0): 0, + Subs(f(x).diff(x, 2), x, L): 0, + Subs(f(x).diff(x, 3), x, L): 0} + + solved_constants1 = solve_ics(sols, funcs, constants, ics1) + solved_constants2 = solve_ics(sols, funcs, constants, ics2) + assert solved_constants1 == solved_constants2 == { + C1: 0, + C2: 0, + C3: L**2*q/(4*EI), + C4: -L*q/(6*EI)} + + # Allow the ics to refer to f + ics = {f(0): f(0)} + assert dsolve(f(x).diff(x) - f(x), f(x), ics=ics) == Eq(f(x), f(0)*exp(x)) + + ics = {f(x).diff(x).subs(x, 0): f(x).diff(x).subs(x, 0), f(0): f(0)} + assert dsolve(f(x).diff(x, x) + f(x), f(x), ics=ics) == \ + Eq(f(x), f(0)*cos(x) + f(x).diff(x).subs(x, 0)*sin(x)) + +def test_ode_order(): + f = Function('f') + g = Function('g') + x = Symbol('x') + assert ode_order(3*x*exp(f(x)), f(x)) == 0 + assert ode_order(x*diff(f(x), x) + 3*x*f(x) - sin(x)/x, f(x)) == 1 + assert ode_order(x**2*f(x).diff(x, x) + x*diff(f(x), x) - f(x), f(x)) == 2 + assert ode_order(diff(x*exp(f(x)), x, x), f(x)) == 2 + assert ode_order(diff(x*diff(x*exp(f(x)), x, x), x), f(x)) == 3 + assert ode_order(diff(f(x), x, x), g(x)) == 0 + assert ode_order(diff(f(x), x, x)*diff(g(x), x), f(x)) == 2 + assert ode_order(diff(f(x), x, x)*diff(g(x), x), g(x)) == 1 + assert ode_order(diff(x*diff(x*exp(f(x)), x, x), x), g(x)) == 0 + # issue 5835: ode_order has to also work for unevaluated derivatives + # (ie, without using doit()). + assert ode_order(Derivative(x*f(x), x), f(x)) == 1 + assert ode_order(x*sin(Derivative(x*f(x)**2, x, x)), f(x)) == 2 + assert ode_order(Derivative(x*Derivative(x*exp(f(x)), x, x), x), g(x)) == 0 + assert ode_order(Derivative(f(x), x, x), g(x)) == 0 + assert ode_order(Derivative(x*exp(f(x)), x, x), f(x)) == 2 + assert ode_order(Derivative(f(x), x, x)*Derivative(g(x), x), g(x)) == 1 + assert ode_order(Derivative(x*Derivative(f(x), x, x), x), f(x)) == 3 + assert ode_order( + x*sin(Derivative(x*Derivative(f(x), x)**2, x, x)), f(x)) == 3 + + +def test_homogeneous_order(): + assert homogeneous_order(exp(y/x) + tan(y/x), x, y) == 0 + assert homogeneous_order(x**2 + sin(x)*cos(y), x, y) is None + assert homogeneous_order(x - y - x*sin(y/x), x, y) == 1 + assert homogeneous_order((x*y + sqrt(x**4 + y**4) + x**2*(log(x) - log(y)))/ + (pi*x**Rational(2, 3)*sqrt(y)**3), x, y) == Rational(-1, 6) + assert homogeneous_order(y/x*cos(y/x) - x/y*sin(y/x) + cos(y/x), x, y) == 0 + assert homogeneous_order(f(x), x, f(x)) == 1 + assert homogeneous_order(f(x)**2, x, f(x)) == 2 + assert homogeneous_order(x*y*z, x, y) == 2 + assert homogeneous_order(x*y*z, x, y, z) == 3 + assert homogeneous_order(x**2*f(x)/sqrt(x**2 + f(x)**2), f(x)) is None + assert homogeneous_order(f(x, y)**2, x, f(x, y), y) == 2 + assert homogeneous_order(f(x, y)**2, x, f(x), y) is None + assert homogeneous_order(f(x, y)**2, x, f(x, y)) is None + assert homogeneous_order(f(y, x)**2, x, y, f(x, y)) is None + assert homogeneous_order(f(y), f(x), x) is None + assert homogeneous_order(-f(x)/x + 1/sin(f(x)/ x), f(x), x) == 0 + assert homogeneous_order(log(1/y) + log(x**2), x, y) is None + assert homogeneous_order(log(1/y) + log(x), x, y) == 0 + assert homogeneous_order(log(x/y), x, y) == 0 + assert homogeneous_order(2*log(1/y) + 2*log(x), x, y) == 0 + a = Symbol('a') + assert homogeneous_order(a*log(1/y) + a*log(x), x, y) == 0 + assert homogeneous_order(f(x).diff(x), x, y) is None + assert homogeneous_order(-f(x).diff(x) + x, x, y) is None + assert homogeneous_order(O(x), x, y) is None + assert homogeneous_order(x + O(x**2), x, y) is None + assert homogeneous_order(x**pi, x) == pi + assert homogeneous_order(x**x, x) is None + raises(ValueError, lambda: homogeneous_order(x*y)) + + +@XFAIL +def test_noncircularized_real_imaginary_parts(): + # If this passes, lines numbered 3878-3882 (at the time of this commit) + # of sympy/solvers/ode.py for nth_linear_constant_coeff_homogeneous + # should be removed. + y = sqrt(1+x) + i, r = im(y), re(y) + assert not (i.has(atan2) and r.has(atan2)) + + +def test_collect_respecting_exponentials(): + # If this test passes, lines 1306-1311 (at the time of this commit) + # of sympy/solvers/ode.py should be removed. + sol = 1 + exp(x/2) + assert sol == collect( sol, exp(x/3)) + + +def test_undetermined_coefficients_match(): + assert _undetermined_coefficients_match(g(x), x) == {'test': False} + assert _undetermined_coefficients_match(sin(2*x + sqrt(5)), x) == \ + {'test': True, 'trialset': + {cos(2*x + sqrt(5)), sin(2*x + sqrt(5))}} + assert _undetermined_coefficients_match(sin(x)*cos(x), x) == \ + {'test': False} + s = {cos(x), x*cos(x), x**2*cos(x), x**2*sin(x), x*sin(x), sin(x)} + assert _undetermined_coefficients_match(sin(x)*(x**2 + x + 1), x) == \ + {'test': True, 'trialset': s} + assert _undetermined_coefficients_match( + sin(x)*x**2 + sin(x)*x + sin(x), x) == {'test': True, 'trialset': s} + assert _undetermined_coefficients_match( + exp(2*x)*sin(x)*(x**2 + x + 1), x + ) == { + 'test': True, 'trialset': {exp(2*x)*sin(x), x**2*exp(2*x)*sin(x), + cos(x)*exp(2*x), x**2*cos(x)*exp(2*x), x*cos(x)*exp(2*x), + x*exp(2*x)*sin(x)}} + assert _undetermined_coefficients_match(1/sin(x), x) == {'test': False} + assert _undetermined_coefficients_match(log(x), x) == {'test': False} + assert _undetermined_coefficients_match(2**(x)*(x**2 + x + 1), x) == \ + {'test': True, 'trialset': {2**x, x*2**x, x**2*2**x}} + assert _undetermined_coefficients_match(x**y, x) == {'test': False} + assert _undetermined_coefficients_match(exp(x)*exp(2*x + 1), x) == \ + {'test': True, 'trialset': {exp(1 + 3*x)}} + assert _undetermined_coefficients_match(sin(x)*(x**2 + x + 1), x) == \ + {'test': True, 'trialset': {x*cos(x), x*sin(x), x**2*cos(x), + x**2*sin(x), cos(x), sin(x)}} + assert _undetermined_coefficients_match(sin(x)*(x + sin(x)), x) == \ + {'test': False} + assert _undetermined_coefficients_match(sin(x)*(x + sin(2*x)), x) == \ + {'test': False} + assert _undetermined_coefficients_match(sin(x)*tan(x), x) == \ + {'test': False} + assert _undetermined_coefficients_match( + x**2*sin(x)*exp(x) + x*sin(x) + x, x + ) == { + 'test': True, 'trialset': {x**2*cos(x)*exp(x), x, cos(x), S.One, + exp(x)*sin(x), sin(x), x*exp(x)*sin(x), x*cos(x), x*cos(x)*exp(x), + x*sin(x), cos(x)*exp(x), x**2*exp(x)*sin(x)}} + assert _undetermined_coefficients_match(4*x*sin(x - 2), x) == { + 'trialset': {x*cos(x - 2), x*sin(x - 2), cos(x - 2), sin(x - 2)}, + 'test': True, + } + assert _undetermined_coefficients_match(2**x*x, x) == \ + {'test': True, 'trialset': {2**x, x*2**x}} + assert _undetermined_coefficients_match(2**x*exp(2*x), x) == \ + {'test': True, 'trialset': {2**x*exp(2*x)}} + assert _undetermined_coefficients_match(exp(-x)/x, x) == \ + {'test': False} + # Below are from Ordinary Differential Equations, + # Tenenbaum and Pollard, pg. 231 + assert _undetermined_coefficients_match(S(4), x) == \ + {'test': True, 'trialset': {S.One}} + assert _undetermined_coefficients_match(12*exp(x), x) == \ + {'test': True, 'trialset': {exp(x)}} + assert _undetermined_coefficients_match(exp(I*x), x) == \ + {'test': True, 'trialset': {exp(I*x)}} + assert _undetermined_coefficients_match(sin(x), x) == \ + {'test': True, 'trialset': {cos(x), sin(x)}} + assert _undetermined_coefficients_match(cos(x), x) == \ + {'test': True, 'trialset': {cos(x), sin(x)}} + assert _undetermined_coefficients_match(8 + 6*exp(x) + 2*sin(x), x) == \ + {'test': True, 'trialset': {S.One, cos(x), sin(x), exp(x)}} + assert _undetermined_coefficients_match(x**2, x) == \ + {'test': True, 'trialset': {S.One, x, x**2}} + assert _undetermined_coefficients_match(9*x*exp(x) + exp(-x), x) == \ + {'test': True, 'trialset': {x*exp(x), exp(x), exp(-x)}} + assert _undetermined_coefficients_match(2*exp(2*x)*sin(x), x) == \ + {'test': True, 'trialset': {exp(2*x)*sin(x), cos(x)*exp(2*x)}} + assert _undetermined_coefficients_match(x - sin(x), x) == \ + {'test': True, 'trialset': {S.One, x, cos(x), sin(x)}} + assert _undetermined_coefficients_match(x**2 + 2*x, x) == \ + {'test': True, 'trialset': {S.One, x, x**2}} + assert _undetermined_coefficients_match(4*x*sin(x), x) == \ + {'test': True, 'trialset': {x*cos(x), x*sin(x), cos(x), sin(x)}} + assert _undetermined_coefficients_match(x*sin(2*x), x) == \ + {'test': True, 'trialset': + {x*cos(2*x), x*sin(2*x), cos(2*x), sin(2*x)}} + assert _undetermined_coefficients_match(x**2*exp(-x), x) == \ + {'test': True, 'trialset': {x*exp(-x), x**2*exp(-x), exp(-x)}} + assert _undetermined_coefficients_match(2*exp(-x) - x**2*exp(-x), x) == \ + {'test': True, 'trialset': {x*exp(-x), x**2*exp(-x), exp(-x)}} + assert _undetermined_coefficients_match(exp(-2*x) + x**2, x) == \ + {'test': True, 'trialset': {S.One, x, x**2, exp(-2*x)}} + assert _undetermined_coefficients_match(x*exp(-x), x) == \ + {'test': True, 'trialset': {x*exp(-x), exp(-x)}} + assert _undetermined_coefficients_match(x + exp(2*x), x) == \ + {'test': True, 'trialset': {S.One, x, exp(2*x)}} + assert _undetermined_coefficients_match(sin(x) + exp(-x), x) == \ + {'test': True, 'trialset': {cos(x), sin(x), exp(-x)}} + assert _undetermined_coefficients_match(exp(x), x) == \ + {'test': True, 'trialset': {exp(x)}} + # converted from sin(x)**2 + assert _undetermined_coefficients_match(S.Half - cos(2*x)/2, x) == \ + {'test': True, 'trialset': {S.One, cos(2*x), sin(2*x)}} + # converted from exp(2*x)*sin(x)**2 + assert _undetermined_coefficients_match( + exp(2*x)*(S.Half + cos(2*x)/2), x + ) == { + 'test': True, 'trialset': {exp(2*x)*sin(2*x), cos(2*x)*exp(2*x), + exp(2*x)}} + assert _undetermined_coefficients_match(2*x + sin(x) + cos(x), x) == \ + {'test': True, 'trialset': {S.One, x, cos(x), sin(x)}} + # converted from sin(2*x)*sin(x) + assert _undetermined_coefficients_match(cos(x)/2 - cos(3*x)/2, x) == \ + {'test': True, 'trialset': {cos(x), cos(3*x), sin(x), sin(3*x)}} + assert _undetermined_coefficients_match(cos(x**2), x) == {'test': False} + assert _undetermined_coefficients_match(2**(x**2), x) == {'test': False} + + +def test_issue_4785_22462(): + from sympy.abc import A + eq = x + A*(x + diff(f(x), x) + f(x)) + diff(f(x), x) + f(x) + 2 + assert classify_ode(eq, f(x)) == ('factorable', '1st_exact', '1st_linear', + 'Bernoulli', 'almost_linear', '1st_power_series', 'lie_group', + 'nth_linear_constant_coeff_undetermined_coefficients', + 'nth_linear_constant_coeff_variation_of_parameters', + '1st_exact_Integral', '1st_linear_Integral', 'Bernoulli_Integral', + 'almost_linear_Integral', + 'nth_linear_constant_coeff_variation_of_parameters_Integral') + # issue 4864 + eq = (x**2 + f(x)**2)*f(x).diff(x) - 2*x*f(x) + assert classify_ode(eq, f(x)) == ('factorable', '1st_exact', + '1st_homogeneous_coeff_best', + '1st_homogeneous_coeff_subs_indep_div_dep', + '1st_homogeneous_coeff_subs_dep_div_indep', + '1st_power_series', + 'lie_group', '1st_exact_Integral', + '1st_homogeneous_coeff_subs_indep_div_dep_Integral', + '1st_homogeneous_coeff_subs_dep_div_indep_Integral') + + +def test_issue_4825(): + raises(ValueError, lambda: dsolve(f(x, y).diff(x) - y*f(x, y), f(x))) + assert classify_ode(f(x, y).diff(x) - y*f(x, y), f(x), dict=True) == \ + {'order': 0, 'default': None, 'ordered_hints': ()} + # See also issue 3793, test Z13. + raises(ValueError, lambda: dsolve(f(x).diff(x), f(y))) + assert classify_ode(f(x).diff(x), f(y), dict=True) == \ + {'order': 0, 'default': None, 'ordered_hints': ()} + + +def test_constant_renumber_order_issue_5308(): + from sympy.utilities.iterables import variations + + assert constant_renumber(C1*x + C2*y) == \ + constant_renumber(C1*y + C2*x) == \ + C1*x + C2*y + e = C1*(C2 + x)*(C3 + y) + for a, b, c in variations([C1, C2, C3], 3): + assert constant_renumber(a*(b + x)*(c + y)) == e + + +def test_constant_renumber(): + e1, e2, x, y = symbols("e1:3 x y") + exprs = [e2*x, e1*x + e2*y] + + assert constant_renumber(exprs[0]) == e2*x + assert constant_renumber(exprs[0], variables=[x]) == C1*x + assert constant_renumber(exprs[0], variables=[x], newconstants=[C2]) == C2*x + assert constant_renumber(exprs, variables=[x, y]) == [C1*x, C1*y + C2*x] + assert constant_renumber(exprs, variables=[x, y], newconstants=symbols("C3:5")) == [C3*x, C3*y + C4*x] + + +def test_issue_5770(): + k = Symbol("k", real=True) + t = Symbol('t') + w = Function('w') + sol = dsolve(w(t).diff(t, 6) - k**6*w(t), w(t)) + assert len([s for s in sol.free_symbols if s.name.startswith('C')]) == 6 + assert constantsimp((C1*cos(x) + C2*cos(x))*exp(x), {C1, C2}) == \ + C1*cos(x)*exp(x) + assert constantsimp(C1*cos(x) + C2*cos(x) + C3*sin(x), {C1, C2, C3}) == \ + C1*cos(x) + C3*sin(x) + assert constantsimp(exp(C1 + x), {C1}) == C1*exp(x) + assert constantsimp(x + C1 + y, {C1, y}) == C1 + x + assert constantsimp(x + C1 + Integral(x, (x, 1, 2)), {C1}) == C1 + x + + +def test_issue_5112_5430(): + assert homogeneous_order(-log(x) + acosh(x), x) is None + assert homogeneous_order(y - log(x), x, y) is None + + +def test_issue_5095(): + f = Function('f') + raises(ValueError, lambda: dsolve(f(x).diff(x)**2, f(x), 'fdsjf')) + + +def test_homogeneous_function(): + f = Function('f') + eq1 = tan(x + f(x)) + eq2 = sin((3*x)/(4*f(x))) + eq3 = cos(x*f(x)*Rational(3, 4)) + eq4 = log((3*x + 4*f(x))/(5*f(x) + 7*x)) + eq5 = exp((2*x**2)/(3*f(x)**2)) + eq6 = log((3*x + 4*f(x))/(5*f(x) + 7*x) + exp((2*x**2)/(3*f(x)**2))) + eq7 = sin((3*x)/(5*f(x) + x**2)) + assert homogeneous_order(eq1, x, f(x)) == None + assert homogeneous_order(eq2, x, f(x)) == 0 + assert homogeneous_order(eq3, x, f(x)) == None + assert homogeneous_order(eq4, x, f(x)) == 0 + assert homogeneous_order(eq5, x, f(x)) == 0 + assert homogeneous_order(eq6, x, f(x)) == 0 + assert homogeneous_order(eq7, x, f(x)) == None + + +def test_linear_coeff_match(): + n, d = z*(2*x + 3*f(x) + 5), z*(7*x + 9*f(x) + 11) + rat = n/d + eq1 = sin(rat) + cos(rat.expand()) + obj1 = LinearCoefficients(eq1) + eq2 = rat + obj2 = LinearCoefficients(eq2) + eq3 = log(sin(rat)) + obj3 = LinearCoefficients(eq3) + ans = (4, Rational(-13, 3)) + assert obj1._linear_coeff_match(eq1, f(x)) == ans + assert obj2._linear_coeff_match(eq2, f(x)) == ans + assert obj3._linear_coeff_match(eq3, f(x)) == ans + + # no c + eq4 = (3*x)/f(x) + obj4 = LinearCoefficients(eq4) + # not x and f(x) + eq5 = (3*x + 2)/x + obj5 = LinearCoefficients(eq5) + # denom will be zero + eq6 = (3*x + 2*f(x) + 1)/(3*x + 2*f(x) + 5) + obj6 = LinearCoefficients(eq6) + # not rational coefficient + eq7 = (3*x + 2*f(x) + sqrt(2))/(3*x + 2*f(x) + 5) + obj7 = LinearCoefficients(eq7) + assert obj4._linear_coeff_match(eq4, f(x)) is None + assert obj5._linear_coeff_match(eq5, f(x)) is None + assert obj6._linear_coeff_match(eq6, f(x)) is None + assert obj7._linear_coeff_match(eq7, f(x)) is None + + +def test_constantsimp_take_problem(): + c = exp(C1) + 2 + assert len(Poly(constantsimp(exp(C1) + c + c*x, [C1])).gens) == 2 + + +def test_series(): + C1 = Symbol("C1") + eq = f(x).diff(x) - f(x) + sol = Eq(f(x), C1 + C1*x + C1*x**2/2 + C1*x**3/6 + C1*x**4/24 + + C1*x**5/120 + O(x**6)) + assert dsolve(eq, hint='1st_power_series') == sol + assert checkodesol(eq, sol, order=1)[0] + + eq = f(x).diff(x) - x*f(x) + sol = Eq(f(x), C1*x**4/8 + C1*x**2/2 + C1 + O(x**6)) + assert dsolve(eq, hint='1st_power_series') == sol + assert checkodesol(eq, sol, order=1)[0] + + eq = f(x).diff(x) - sin(x*f(x)) + sol = Eq(f(x), (x - 2)**2*(1+ sin(4))*cos(4) + (x - 2)*sin(4) + 2 + O(x**3)) + assert dsolve(eq, hint='1st_power_series', ics={f(2): 2}, n=3) == sol + # FIXME: The solution here should be O((x-2)**3) so is incorrect + #assert checkodesol(eq, sol, order=1)[0] + + +@slow +def test_2nd_power_series_ordinary(): + C1, C2 = symbols("C1 C2") + + eq = f(x).diff(x, 2) - x*f(x) + assert classify_ode(eq) == ('2nd_linear_airy', '2nd_power_series_ordinary') + sol = Eq(f(x), C2*(x**3/6 + 1) + C1*x*(x**3/12 + 1) + O(x**6)) + assert dsolve(eq, hint='2nd_power_series_ordinary') == sol + assert checkodesol(eq, sol) == (True, 0) + + sol = Eq(f(x), C2*((x + 2)**4/6 + (x + 2)**3/6 - (x + 2)**2 + 1) + + C1*(x + (x + 2)**4/12 - (x + 2)**3/3 + S(2)) + + O(x**6)) + assert dsolve(eq, hint='2nd_power_series_ordinary', x0=-2) == sol + # FIXME: Solution should be O((x+2)**6) + # assert checkodesol(eq, sol) == (True, 0) + + sol = Eq(f(x), C2*x + C1 + O(x**2)) + assert dsolve(eq, hint='2nd_power_series_ordinary', n=2) == sol + assert checkodesol(eq, sol) == (True, 0) + + eq = (1 + x**2)*(f(x).diff(x, 2)) + 2*x*(f(x).diff(x)) -2*f(x) + assert classify_ode(eq) == ('factorable', '2nd_hypergeometric', '2nd_hypergeometric_Integral', + '2nd_power_series_ordinary') + + sol = Eq(f(x), C2*(-x**4/3 + x**2 + 1) + C1*x + O(x**6)) + assert dsolve(eq, hint='2nd_power_series_ordinary') == sol + assert checkodesol(eq, sol) == (True, 0) + + eq = f(x).diff(x, 2) + x*(f(x).diff(x)) + f(x) + assert classify_ode(eq) == ('factorable', '2nd_power_series_ordinary',) + sol = Eq(f(x), C2*(x**4/8 - x**2/2 + 1) + C1*x*(-x**2/3 + 1) + O(x**6)) + assert dsolve(eq) == sol + # FIXME: checkodesol fails for this solution... + # assert checkodesol(eq, sol) == (True, 0) + + eq = f(x).diff(x, 2) + f(x).diff(x) - x*f(x) + assert classify_ode(eq) == ('2nd_power_series_ordinary',) + sol = Eq(f(x), C2*(-x**4/24 + x**3/6 + 1) + + C1*x*(x**3/24 + x**2/6 - x/2 + 1) + O(x**6)) + assert dsolve(eq) == sol + # FIXME: checkodesol fails for this solution... + # assert checkodesol(eq, sol) == (True, 0) + + eq = f(x).diff(x, 2) + x*f(x) + assert classify_ode(eq) == ('2nd_linear_airy', '2nd_power_series_ordinary') + sol = Eq(f(x), C2*(x**6/180 - x**3/6 + 1) + C1*x*(-x**3/12 + 1) + O(x**7)) + assert dsolve(eq, hint='2nd_power_series_ordinary', n=7) == sol + assert checkodesol(eq, sol) == (True, 0) + + +def test_2nd_power_series_regular(): + C1, C2, a = symbols("C1 C2 a") + eq = x**2*(f(x).diff(x, 2)) - 3*x*(f(x).diff(x)) + (4*x + 4)*f(x) + sol = Eq(f(x), C1*x**2*(-16*x**3/9 + 4*x**2 - 4*x + 1) + O(x**6)) + assert dsolve(eq, hint='2nd_power_series_regular') == sol + assert checkodesol(eq, sol) == (True, 0) + + eq = 4*x**2*(f(x).diff(x, 2)) -8*x**2*(f(x).diff(x)) + (4*x**2 + + 1)*f(x) + sol = Eq(f(x), C1*sqrt(x)*(x**4/24 + x**3/6 + x**2/2 + x + 1) + O(x**6)) + assert dsolve(eq, hint='2nd_power_series_regular') == sol + assert checkodesol(eq, sol) == (True, 0) + + eq = x**2*(f(x).diff(x, 2)) - x**2*(f(x).diff(x)) + ( + x**2 - 2)*f(x) + sol = Eq(f(x), C1*(-x**6/720 - 3*x**5/80 - x**4/8 + x**2/2 + x/2 + 1)/x + + C2*x**2*(-x**3/60 + x**2/20 + x/2 + 1) + O(x**6)) + assert dsolve(eq) == sol + assert checkodesol(eq, sol) == (True, 0) + + eq = x**2*(f(x).diff(x, 2)) + x*(f(x).diff(x)) + (x**2 - Rational(1, 4))*f(x) + sol = Eq(f(x), C1*(x**4/24 - x**2/2 + 1)/sqrt(x) + + C2*sqrt(x)*(x**4/120 - x**2/6 + 1) + O(x**6)) + assert dsolve(eq, hint='2nd_power_series_regular') == sol + assert checkodesol(eq, sol) == (True, 0) + + eq = x*f(x).diff(x, 2) + f(x).diff(x) - a*x*f(x) + sol = Eq(f(x), C1*(a**2*x**4/64 + a*x**2/4 + 1) + O(x**6)) + assert dsolve(eq, f(x), hint="2nd_power_series_regular") == sol + assert checkodesol(eq, sol) == (True, 0) + + eq = f(x).diff(x, 2) + ((1 - x)/x)*f(x).diff(x) + (a/x)*f(x) + sol = Eq(f(x), C1*(-a*x**5*(a - 4)*(a - 3)*(a - 2)*(a - 1)/14400 + \ + a*x**4*(a - 3)*(a - 2)*(a - 1)/576 - a*x**3*(a - 2)*(a - 1)/36 + \ + a*x**2*(a - 1)/4 - a*x + 1) + O(x**6)) + assert dsolve(eq, f(x), hint="2nd_power_series_regular") == sol + assert checkodesol(eq, sol) == (True, 0) + + +def test_issue_15056(): + t = Symbol('t') + C3 = Symbol('C3') + assert get_numbered_constants(Symbol('C1') * Function('C2')(t)) == C3 + + +def test_issue_15913(): + eq = -C1/x - 2*x*f(x) - f(x) + Derivative(f(x), x) + sol = C2*exp(x**2 + x) + exp(x**2 + x)*Integral(C1*exp(-x**2 - x)/x, x) + assert checkodesol(eq, sol) == (True, 0) + sol = C1 + C2*exp(-x*y) + eq = Derivative(y*f(x), x) + f(x).diff(x, 2) + assert checkodesol(eq, sol, f(x)) == (True, 0) + + +def test_issue_16146(): + raises(ValueError, lambda: dsolve([f(x).diff(x), g(x).diff(x)], [f(x), g(x), h(x)])) + raises(ValueError, lambda: dsolve([f(x).diff(x), g(x).diff(x)], [f(x)])) + + +def test_dsolve_remove_redundant_solutions(): + + eq = (f(x)-2)*f(x).diff(x) + sol = Eq(f(x), C1) + assert dsolve(eq) == sol + + eq = (f(x)-sin(x))*(f(x).diff(x, 2)) + sol = {Eq(f(x), C1 + C2*x), Eq(f(x), sin(x))} + assert set(dsolve(eq)) == sol + + eq = (f(x)**2-2*f(x)+1)*f(x).diff(x, 3) + sol = Eq(f(x), C1 + C2*x + C3*x**2) + assert dsolve(eq) == sol + + +def test_issue_13060(): + A, B = symbols("A B", cls=Function) + t = Symbol("t") + eq = [Eq(Derivative(A(t), t), A(t)*B(t)), Eq(Derivative(B(t), t), A(t)*B(t))] + sol = dsolve(eq) + assert checkodesol(eq, sol) == (True, [0, 0]) + + +def test_issue_22523(): + N, s = symbols('N s') + rho = Function('rho') + # intentionally use 4.0 to confirm issue with nfloat + # works here + eqn = 4.0*N*sqrt(N - 1)*rho(s) + (4*s**2*(N - 1) + (N - 2*s*(N - 1))**2 + )*Derivative(rho(s), (s, 2)) + match = classify_ode(eqn, dict=True, hint='all') + assert match['2nd_power_series_ordinary']['terms'] == 5 + C1, C2 = symbols('C1,C2') + sol = dsolve(eqn, hint='2nd_power_series_ordinary') + # there is no r(2.0) in this result + assert filldedent(sol) == filldedent(str(''' + Eq(rho(s), C2*(1 - 4.0*s**4*sqrt(N - 1.0)/N + 0.666666666666667*s**4/N + - 2.66666666666667*s**3*sqrt(N - 1.0)/N - 2.0*s**2*sqrt(N - 1.0)/N + + 9.33333333333333*s**4*sqrt(N - 1.0)/N**2 - 0.666666666666667*s**4/N**2 + + 2.66666666666667*s**3*sqrt(N - 1.0)/N**2 - + 5.33333333333333*s**4*sqrt(N - 1.0)/N**3) + C1*s*(1.0 - + 1.33333333333333*s**3*sqrt(N - 1.0)/N - 0.666666666666667*s**2*sqrt(N + - 1.0)/N + 1.33333333333333*s**3*sqrt(N - 1.0)/N**2) + O(s**6))''')) + + +def test_issue_22604(): + x1, x2 = symbols('x1, x2', cls = Function) + t, k1, k2, m1, m2 = symbols('t k1 k2 m1 m2', real = True) + k1, k2, m1, m2 = 1, 1, 1, 1 + eq1 = Eq(m1*diff(x1(t), t, 2) + k1*x1(t) - k2*(x2(t) - x1(t)), 0) + eq2 = Eq(m2*diff(x2(t), t, 2) + k2*(x2(t) - x1(t)), 0) + eqs = [eq1, eq2] + [x1sol, x2sol] = dsolve(eqs, [x1(t), x2(t)], ics = {x1(0):0, x1(t).diff().subs(t,0):0, \ + x2(0):1, x2(t).diff().subs(t,0):0}) + assert x1sol == Eq(x1(t), sqrt(3 - sqrt(5))*(sqrt(10) + 5*sqrt(2))*cos(sqrt(2)*t*sqrt(3 - sqrt(5))/2)/20 + \ + (-5*sqrt(2) + sqrt(10))*sqrt(sqrt(5) + 3)*cos(sqrt(2)*t*sqrt(sqrt(5) + 3)/2)/20) + assert x2sol == Eq(x2(t), (sqrt(5) + 5)*cos(sqrt(2)*t*sqrt(3 - sqrt(5))/2)/10 + (5 - sqrt(5))*cos(sqrt(2)*t*sqrt(sqrt(5) + 3)/2)/10) + + +def test_issue_22462(): + for de in [ + Eq(f(x).diff(x), -20*f(x)**2 - 500*f(x)/7200), + Eq(f(x).diff(x), -2*f(x)**2 - 5*f(x)/7)]: + assert 'Bernoulli' in classify_ode(de, f(x)) + + +def test_issue_23425(): + x = symbols('x') + y = Function('y') + eq = Eq(-E**x*y(x).diff().diff() + y(x).diff(), 0) + assert classify_ode(eq) == \ + ('Liouville', 'nth_order_reducible', \ + '2nd_power_series_ordinary', 'Liouville_Integral') + + +@SKIP("too slow for @slow") +def test_issue_25820(): + x = Symbol('x') + y = Function('y') + eq = y(x)**3*y(x).diff(x, 2) + 49 + assert dsolve(eq, y(x)) is not None # doesn't raise diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_riccati.py b/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_riccati.py new file mode 100644 index 0000000000000000000000000000000000000000..548a1ee5b5e82d88f1b0aa319af09b8b9d1d9bfe --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_riccati.py @@ -0,0 +1,877 @@ +from sympy.core.random import randint +from sympy.core.function import Function +from sympy.core.mul import Mul +from sympy.core.numbers import (I, Rational, oo) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, symbols) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.hyperbolic import tanh +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import sin +from sympy.polys.polytools import Poly +from sympy.simplify.ratsimp import ratsimp +from sympy.solvers.ode.subscheck import checkodesol +from sympy.testing.pytest import slow +from sympy.solvers.ode.riccati import (riccati_normal, riccati_inverse_normal, + riccati_reduced, match_riccati, inverse_transform_poly, limit_at_inf, + check_necessary_conds, val_at_inf, construct_c_case_1, + construct_c_case_2, construct_c_case_3, construct_d_case_4, + construct_d_case_5, construct_d_case_6, rational_laurent_series, + solve_riccati) + +f = Function('f') +x = symbols('x') + +# These are the functions used to generate the tests +# SHOULD NOT BE USED DIRECTLY IN TESTS + +def rand_rational(maxint): + return Rational(randint(-maxint, maxint), randint(1, maxint)) + + +def rand_poly(x, degree, maxint): + return Poly([rand_rational(maxint) for _ in range(degree+1)], x) + + +def rand_rational_function(x, degree, maxint): + degnum = randint(1, degree) + degden = randint(1, degree) + num = rand_poly(x, degnum, maxint) + den = rand_poly(x, degden, maxint) + while den == Poly(0, x): + den = rand_poly(x, degden, maxint) + return num / den + + +def find_riccati_ode(ratfunc, x, yf): + y = ratfunc + yp = y.diff(x) + q1 = rand_rational_function(x, 1, 3) + q2 = rand_rational_function(x, 1, 3) + while q2 == 0: + q2 = rand_rational_function(x, 1, 3) + q0 = ratsimp(yp - q1*y - q2*y**2) + eq = Eq(yf.diff(), q0 + q1*yf + q2*yf**2) + sol = Eq(yf, y) + assert checkodesol(eq, sol) == (True, 0) + return eq, q0, q1, q2 + + +# Testing functions start + +def test_riccati_transformation(): + """ + This function tests the transformation of the + solution of a Riccati ODE to the solution of + its corresponding normal Riccati ODE. + + Each test case 4 values - + + 1. w - The solution to be transformed + 2. b1 - The coefficient of f(x) in the ODE. + 3. b2 - The coefficient of f(x)**2 in the ODE. + 4. y - The solution to the normal Riccati ODE. + """ + tests = [ + ( + x/(x - 1), + (x**2 + 7)/3*x, + x, + -x**2/(x - 1) - x*(x**2/3 + S(7)/3)/2 - 1/(2*x) + ), + ( + (2*x + 3)/(2*x + 2), + (3 - 3*x)/(x + 1), + 5*x, + -5*x*(2*x + 3)/(2*x + 2) - (3 - 3*x)/(Mul(2, x + 1, evaluate=False)) - 1/(2*x) + ), + ( + -1/(2*x**2 - 1), + 0, + (2 - x)/(4*x - 2), + (2 - x)/((4*x - 2)*(2*x**2 - 1)) - (4*x - 2)*(Mul(-4, 2 - x, evaluate=False)/(4*x - \ + 2)**2 - 1/(4*x - 2))/(Mul(2, 2 - x, evaluate=False)) + ), + ( + x, + (8*x - 12)/(12*x + 9), + x**3/(6*x - 9), + -x**4/(6*x - 9) - (8*x - 12)/(Mul(2, 12*x + 9, evaluate=False)) - (6*x - 9)*(-6*x**3/(6*x \ + - 9)**2 + 3*x**2/(6*x - 9))/(2*x**3) + )] + for w, b1, b2, y in tests: + assert y == riccati_normal(w, x, b1, b2) + assert w == riccati_inverse_normal(y, x, b1, b2).cancel() + + # Test bp parameter in riccati_inverse_normal + tests = [ + ( + (-2*x - 1)/(2*x**2 + 2*x - 2), + -2/x, + (-x - 1)/(4*x), + 8*x**2*(1/(4*x) + (-x - 1)/(4*x**2))/(-x - 1)**2 + 4/(-x - 1), + -2*x*(-1/(4*x) - (-x - 1)/(4*x**2))/(-x - 1) - (-2*x - 1)*(-x - 1)/(4*x*(2*x**2 + 2*x \ + - 2)) + 1/x + ), + ( + 3/(2*x**2), + -2/x, + (-x - 1)/(4*x), + 8*x**2*(1/(4*x) + (-x - 1)/(4*x**2))/(-x - 1)**2 + 4/(-x - 1), + -2*x*(-1/(4*x) - (-x - 1)/(4*x**2))/(-x - 1) + 1/x - Mul(3, -x - 1, evaluate=False)/(8*x**3) + )] + for w, b1, b2, bp, y in tests: + assert y == riccati_normal(w, x, b1, b2) + assert w == riccati_inverse_normal(y, x, b1, b2, bp).cancel() + + +def test_riccati_reduced(): + """ + This function tests the transformation of a + Riccati ODE to its normal Riccati ODE. + + Each test case 2 values - + + 1. eq - A Riccati ODE. + 2. normal_eq - The normal Riccati ODE of eq. + """ + tests = [ + ( + f(x).diff(x) - x**2 - x*f(x) - x*f(x)**2, + + f(x).diff(x) + f(x)**2 + x**3 - x**2/4 - 3/(4*x**2) + ), + ( + 6*x/(2*x + 9) + f(x).diff(x) - (x + 1)*f(x)**2/x, + + -3*x**2*(1/x + (-x - 1)/x**2)**2/(4*(-x - 1)**2) + Mul(6, \ + -x - 1, evaluate=False)/(2*x + 9) + f(x)**2 + f(x).diff(x) \ + - (-1 + (x + 1)/x)/(x*(-x - 1)) + ), + ( + f(x)**2 + f(x).diff(x) - (x - 1)*f(x)/(-x - S(1)/2), + + -(2*x - 2)**2/(4*(2*x + 1)**2) + (2*x - 2)/(2*x + 1)**2 + \ + f(x)**2 + f(x).diff(x) - 1/(2*x + 1) + ), + ( + f(x).diff(x) - f(x)**2/x, + + f(x)**2 + f(x).diff(x) + 1/(4*x**2) + ), + ( + -3*(-x**2 - x + 1)/(x**2 + 6*x + 1) + f(x).diff(x) + f(x)**2/x, + + f(x)**2 + f(x).diff(x) + (3*x**2/(x**2 + 6*x + 1) + 3*x/(x**2 \ + + 6*x + 1) - 3/(x**2 + 6*x + 1))/x + 1/(4*x**2) + ), + ( + 6*x/(2*x + 9) + f(x).diff(x) - (x + 1)*f(x)/x, + + False + ), + ( + f(x)*f(x).diff(x) - 1/x + f(x)/3 + f(x)**2/(x**2 - 2), + + False + )] + for eq, normal_eq in tests: + assert normal_eq == riccati_reduced(eq, f, x) + + +def test_match_riccati(): + """ + This function tests if an ODE is Riccati or not. + + Each test case has 5 values - + + 1. eq - The Riccati ODE. + 2. match - Boolean indicating if eq is a Riccati ODE. + 3. b0 - + 4. b1 - Coefficient of f(x) in eq. + 5. b2 - Coefficient of f(x)**2 in eq. + """ + tests = [ + # Test Rational Riccati ODEs + ( + f(x).diff(x) - (405*x**3 - 882*x**2 - 78*x + 92)/(243*x**4 \ + - 945*x**3 + 846*x**2 + 180*x - 72) - 2 - f(x)**2/(3*x + 1) \ + - (S(1)/3 - x)*f(x)/(S(1)/3 - 3*x/2), + + True, + + 45*x**3/(27*x**4 - 105*x**3 + 94*x**2 + 20*x - 8) - 98*x**2/ \ + (27*x**4 - 105*x**3 + 94*x**2 + 20*x - 8) - 26*x/(81*x**4 - \ + 315*x**3 + 282*x**2 + 60*x - 24) + 2 + 92/(243*x**4 - 945*x**3 \ + + 846*x**2 + 180*x - 72), + + Mul(-1, 2 - 6*x, evaluate=False)/(9*x - 2), + + 1/(3*x + 1) + ), + ( + f(x).diff(x) + 4*x/27 - (x/3 - 1)*f(x)**2 - (2*x/3 + \ + 1)*f(x)/(3*x + 2) - S(10)/27 - (265*x**2 + 423*x + 162) \ + /(324*x**3 + 216*x**2), + + True, + + -4*x/27 + S(10)/27 + 3/(6*x**3 + 4*x**2) + 47/(36*x**2 \ + + 24*x) + 265/(324*x + 216), + + Mul(-1, -2*x - 3, evaluate=False)/(9*x + 6), + + x/3 - 1 + ), + ( + f(x).diff(x) - (304*x**5 - 745*x**4 + 631*x**3 - 876*x**2 \ + + 198*x - 108)/(36*x**6 - 216*x**5 + 477*x**4 - 567*x**3 + \ + 360*x**2 - 108*x) - S(17)/9 - (x - S(3)/2)*f(x)/(x/2 - \ + S(3)/2) - (x/3 - 3)*f(x)**2/(3*x), + + True, + + 304*x**4/(36*x**5 - 216*x**4 + 477*x**3 - 567*x**2 + 360*x - \ + 108) - 745*x**3/(36*x**5 - 216*x**4 + 477*x**3 - 567*x**2 + \ + 360*x - 108) + 631*x**2/(36*x**5 - 216*x**4 + 477*x**3 - 567* \ + x**2 + 360*x - 108) - 292*x/(12*x**5 - 72*x**4 + 159*x**3 - \ + 189*x**2 + 120*x - 36) + S(17)/9 - 12/(4*x**6 - 24*x**5 + \ + 53*x**4 - 63*x**3 + 40*x**2 - 12*x) + 22/(4*x**5 - 24*x**4 \ + + 53*x**3 - 63*x**2 + 40*x - 12), + + Mul(-1, 3 - 2*x, evaluate=False)/(x - 3), + + Mul(-1, 9 - x, evaluate=False)/(9*x) + ), + # Test Non-Rational Riccati ODEs + ( + f(x).diff(x) - x**(S(3)/2)/(x**(S(1)/2) - 2) + x**2*f(x) + \ + x*f(x)**2/(x**(S(3)/4)), + False, 0, 0, 0 + ), + ( + f(x).diff(x) - sin(x**2) + exp(x)*f(x) + log(x)*f(x)**2, + False, 0, 0, 0 + ), + ( + f(x).diff(x) - tanh(x + sqrt(x)) + f(x) + x**4*f(x)**2, + False, 0, 0, 0 + ), + # Test Non-Riccati ODEs + ( + (1 - x**2)*f(x).diff(x, 2) - 2*x*f(x).diff(x) + 20*f(x), + False, 0, 0, 0 + ), + ( + f(x).diff(x) - x**2 + x**3*f(x) + (x**2/(x + 1))*f(x)**3, + False, 0, 0, 0 + ), + ( + f(x).diff(x)*f(x)**2 + (x**2 - 1)/(x**3 + 1)*f(x) + 1/(2*x \ + + 3) + f(x)**2, + False, 0, 0, 0 + )] + for eq, res, b0, b1, b2 in tests: + match, funcs = match_riccati(eq, f, x) + assert match == res + if res: + assert [b0, b1, b2] == funcs + + +def test_val_at_inf(): + """ + This function tests the valuation of rational + function at oo. + + Each test case has 3 values - + + 1. num - Numerator of rational function. + 2. den - Denominator of rational function. + 3. val_inf - Valuation of rational function at oo + """ + tests = [ + # degree(denom) > degree(numer) + ( + Poly(10*x**3 + 8*x**2 - 13*x + 6, x), + Poly(-13*x**10 - x**9 + 5*x**8 + 7*x**7 + 10*x**6 + 6*x**5 - 7*x**4 + 11*x**3 - 8*x**2 + 5*x + 13, x), + 7 + ), + ( + Poly(1, x), + Poly(-9*x**4 + 3*x**3 + 15*x**2 - 6*x - 14, x), + 4 + ), + # degree(denom) == degree(numer) + ( + Poly(-6*x**3 - 8*x**2 + 8*x - 6, x), + Poly(-5*x**3 + 12*x**2 - 6*x - 9, x), + 0 + ), + # degree(denom) < degree(numer) + ( + Poly(12*x**8 - 12*x**7 - 11*x**6 + 8*x**5 + 3*x**4 - x**3 + x**2 - 11*x, x), + Poly(-14*x**2 + x, x), + -6 + ), + ( + Poly(5*x**6 + 9*x**5 - 11*x**4 - 9*x**3 + x**2 - 4*x + 4, x), + Poly(15*x**4 + 3*x**3 - 8*x**2 + 15*x + 12, x), + -2 + )] + for num, den, val in tests: + assert val_at_inf(num, den, x) == val + + +def test_necessary_conds(): + """ + This function tests the necessary conditions for + a Riccati ODE to have a rational particular solution. + """ + # Valuation at Infinity is an odd negative integer + assert check_necessary_conds(-3, [1, 2, 4]) == False + # Valuation at Infinity is a positive integer lesser than 2 + assert check_necessary_conds(1, [1, 2, 4]) == False + # Multiplicity of a pole is an odd integer greater than 1 + assert check_necessary_conds(2, [3, 1, 6]) == False + # All values are correct + assert check_necessary_conds(-10, [1, 2, 8, 12]) == True + + +def test_inverse_transform_poly(): + """ + This function tests the substitution x -> 1/x + in rational functions represented using Poly. + """ + fns = [ + (15*x**3 - 8*x**2 - 2*x - 6)/(18*x + 6), + + (180*x**5 + 40*x**4 + 80*x**3 + 30*x**2 - 60*x - 80)/(180*x**3 - 150*x**2 + 75*x + 12), + + (-15*x**5 - 36*x**4 + 75*x**3 - 60*x**2 - 80*x - 60)/(80*x**4 + 60*x**3 + 60*x**2 + 60*x - 80), + + (60*x**7 + 24*x**6 - 15*x**5 - 20*x**4 + 30*x**2 + 100*x - 60)/(240*x**2 - 20*x - 30), + + (30*x**6 - 12*x**5 + 15*x**4 - 15*x**2 + 10*x + 60)/(3*x**10 - 45*x**9 + 15*x**5 + 15*x**4 - 5*x**3 \ + + 15*x**2 + 45*x - 15) + ] + for f in fns: + num, den = [Poly(e, x) for e in f.as_numer_denom()] + num, den = inverse_transform_poly(num, den, x) + assert f.subs(x, 1/x).cancel() == num/den + + +def test_limit_at_inf(): + """ + This function tests the limit at oo of a + rational function. + + Each test case has 3 values - + + 1. num - Numerator of rational function. + 2. den - Denominator of rational function. + 3. limit_at_inf - Limit of rational function at oo + """ + tests = [ + # deg(denom) > deg(numer) + ( + Poly(-12*x**2 + 20*x + 32, x), + Poly(32*x**3 + 72*x**2 + 3*x - 32, x), + 0 + ), + # deg(denom) < deg(numer) + ( + Poly(1260*x**4 - 1260*x**3 - 700*x**2 - 1260*x + 1400, x), + Poly(6300*x**3 - 1575*x**2 + 756*x - 540, x), + oo + ), + # deg(denom) < deg(numer), one of the leading coefficients is negative + ( + Poly(-735*x**8 - 1400*x**7 + 1680*x**6 - 315*x**5 - 600*x**4 + 840*x**3 - 525*x**2 \ + + 630*x + 3780, x), + Poly(1008*x**7 - 2940*x**6 - 84*x**5 + 2940*x**4 - 420*x**3 + 1512*x**2 + 105*x + 168, x), + -oo + ), + # deg(denom) == deg(numer) + ( + Poly(105*x**7 - 960*x**6 + 60*x**5 + 60*x**4 - 80*x**3 + 45*x**2 + 120*x + 15, x), + Poly(735*x**7 + 525*x**6 + 720*x**5 + 720*x**4 - 8400*x**3 - 2520*x**2 + 2800*x + 280, x), + S(1)/7 + ), + ( + Poly(288*x**4 - 450*x**3 + 280*x**2 - 900*x - 90, x), + Poly(607*x**4 + 840*x**3 - 1050*x**2 + 420*x + 420, x), + S(288)/607 + )] + for num, den, lim in tests: + assert limit_at_inf(num, den, x) == lim + + +def test_construct_c_case_1(): + """ + This function tests the Case 1 in the step + to calculate coefficients of c-vectors. + + Each test case has 4 values - + + 1. num - Numerator of the rational function a(x). + 2. den - Denominator of the rational function a(x). + 3. pole - Pole of a(x) for which c-vector is being + calculated. + 4. c - The c-vector for the pole. + """ + tests = [ + ( + Poly(-3*x**3 + 3*x**2 + 4*x - 5, x, extension=True), + Poly(4*x**8 + 16*x**7 + 9*x**5 + 12*x**4 + 6*x**3 + 12*x**2, x, extension=True), + S(0), + [[S(1)/2 + sqrt(6)*I/6], [S(1)/2 - sqrt(6)*I/6]] + ), + ( + Poly(1200*x**3 + 1440*x**2 + 816*x + 560, x, extension=True), + Poly(128*x**5 - 656*x**4 + 1264*x**3 - 1125*x**2 + 385*x + 49, x, extension=True), + S(7)/4, + [[S(1)/2 + sqrt(16367978)/634], [S(1)/2 - sqrt(16367978)/634]] + ), + ( + Poly(4*x + 2, x, extension=True), + Poly(18*x**4 + (2 - 18*sqrt(3))*x**3 + (14 - 11*sqrt(3))*x**2 + (4 - 6*sqrt(3))*x \ + + 8*sqrt(3) + 16, x, domain='QQ'), + (S(1) + sqrt(3))/2, + [[S(1)/2 + sqrt(Mul(4, 2*sqrt(3) + 4, evaluate=False)/(19*sqrt(3) + 44) + 1)/2], \ + [S(1)/2 - sqrt(Mul(4, 2*sqrt(3) + 4, evaluate=False)/(19*sqrt(3) + 44) + 1)/2]] + )] + for num, den, pole, c in tests: + assert construct_c_case_1(num, den, x, pole) == c + + +def test_construct_c_case_2(): + """ + This function tests the Case 2 in the step + to calculate coefficients of c-vectors. + + Each test case has 5 values - + + 1. num - Numerator of the rational function a(x). + 2. den - Denominator of the rational function a(x). + 3. pole - Pole of a(x) for which c-vector is being + calculated. + 4. mul - The multiplicity of the pole. + 5. c - The c-vector for the pole. + """ + tests = [ + # Testing poles with multiplicity 2 + ( + Poly(1, x, extension=True), + Poly((x - 1)**2*(x - 2), x, extension=True), + 1, 2, + [[-I*(-1 - I)/2], [I*(-1 + I)/2]] + ), + ( + Poly(3*x**5 - 12*x**4 - 7*x**3 + 1, x, extension=True), + Poly((3*x - 1)**2*(x + 2)**2, x, extension=True), + S(1)/3, 2, + [[-S(89)/98], [-S(9)/98]] + ), + # Testing poles with multiplicity 4 + ( + Poly(x**3 - x**2 + 4*x, x, extension=True), + Poly((x - 2)**4*(x + 5)**2, x, extension=True), + 2, 4, + [[7*sqrt(3)*(S(60)/343 - 4*sqrt(3)/7)/12, 2*sqrt(3)/7], \ + [-7*sqrt(3)*(S(60)/343 + 4*sqrt(3)/7)/12, -2*sqrt(3)/7]] + ), + ( + Poly(3*x**5 + x**4 + 3, x, extension=True), + Poly((4*x + 1)**4*(x + 2), x, extension=True), + -S(1)/4, 4, + [[128*sqrt(439)*(-sqrt(439)/128 - S(55)/14336)/439, sqrt(439)/256], \ + [-128*sqrt(439)*(sqrt(439)/128 - S(55)/14336)/439, -sqrt(439)/256]] + ), + # Testing poles with multiplicity 6 + ( + Poly(x**3 + 2, x, extension=True), + Poly((3*x - 1)**6*(x**2 + 1), x, extension=True), + S(1)/3, 6, + [[27*sqrt(66)*(-sqrt(66)/54 - S(131)/267300)/22, -2*sqrt(66)/1485, sqrt(66)/162], \ + [-27*sqrt(66)*(sqrt(66)/54 - S(131)/267300)/22, 2*sqrt(66)/1485, -sqrt(66)/162]] + ), + ( + Poly(x**2 + 12, x, extension=True), + Poly((x - sqrt(2))**6, x, extension=True), + sqrt(2), 6, + [[sqrt(14)*(S(6)/7 - 3*sqrt(14))/28, sqrt(7)/7, sqrt(14)], \ + [-sqrt(14)*(S(6)/7 + 3*sqrt(14))/28, -sqrt(7)/7, -sqrt(14)]] + )] + for num, den, pole, mul, c in tests: + assert construct_c_case_2(num, den, x, pole, mul) == c + + +def test_construct_c_case_3(): + """ + This function tests the Case 3 in the step + to calculate coefficients of c-vectors. + """ + assert construct_c_case_3() == [[1]] + + +def test_construct_d_case_4(): + """ + This function tests the Case 4 in the step + to calculate coefficients of the d-vector. + + Each test case has 4 values - + + 1. num - Numerator of the rational function a(x). + 2. den - Denominator of the rational function a(x). + 3. mul - Multiplicity of oo as a pole. + 4. d - The d-vector. + """ + tests = [ + # Tests with multiplicity at oo = 2 + ( + Poly(-x**5 - 2*x**4 + 4*x**3 + 2*x + 5, x, extension=True), + Poly(9*x**3 - 2*x**2 + 10*x - 2, x, extension=True), + 2, + [[10*I/27, I/3, -3*I*(S(158)/243 - I/3)/2], \ + [-10*I/27, -I/3, 3*I*(S(158)/243 + I/3)/2]] + ), + ( + Poly(-x**6 + 9*x**5 + 5*x**4 + 6*x**3 + 5*x**2 + 6*x + 7, x, extension=True), + Poly(x**4 + 3*x**3 + 12*x**2 - x + 7, x, extension=True), + 2, + [[-6*I, I, -I*(17 - I)/2], [6*I, -I, I*(17 + I)/2]] + ), + # Tests with multiplicity at oo = 4 + ( + Poly(-2*x**6 - x**5 - x**4 - 2*x**3 - x**2 - 3*x - 3, x, extension=True), + Poly(3*x**2 + 10*x + 7, x, extension=True), + 4, + [[269*sqrt(6)*I/288, -17*sqrt(6)*I/36, sqrt(6)*I/3, -sqrt(6)*I*(S(16969)/2592 \ + - 2*sqrt(6)*I/3)/4], [-269*sqrt(6)*I/288, 17*sqrt(6)*I/36, -sqrt(6)*I/3, \ + sqrt(6)*I*(S(16969)/2592 + 2*sqrt(6)*I/3)/4]] + ), + ( + Poly(-3*x**5 - 3*x**4 - 3*x**3 - x**2 - 1, x, extension=True), + Poly(12*x - 2, x, extension=True), + 4, + [[41*I/192, 7*I/24, I/2, -I*(-S(59)/6912 - I)], \ + [-41*I/192, -7*I/24, -I/2, I*(-S(59)/6912 + I)]] + ), + # Tests with multiplicity at oo = 4 + ( + Poly(-x**7 - x**5 - x**4 - x**2 - x, x, extension=True), + Poly(x + 2, x, extension=True), + 6, + [[-5*I/2, 2*I, -I, I, -I*(-9 - 3*I)/2], [5*I/2, -2*I, I, -I, I*(-9 + 3*I)/2]] + ), + ( + Poly(-x**7 - x**6 - 2*x**5 - 2*x**4 - x**3 - x**2 + 2*x - 2, x, extension=True), + Poly(2*x - 2, x, extension=True), + 6, + [[3*sqrt(2)*I/4, 3*sqrt(2)*I/4, sqrt(2)*I/2, sqrt(2)*I/2, -sqrt(2)*I*(-S(7)/8 - \ + 3*sqrt(2)*I/2)/2], [-3*sqrt(2)*I/4, -3*sqrt(2)*I/4, -sqrt(2)*I/2, -sqrt(2)*I/2, \ + sqrt(2)*I*(-S(7)/8 + 3*sqrt(2)*I/2)/2]] + )] + for num, den, mul, d in tests: + ser = rational_laurent_series(num, den, x, oo, mul, 1) + assert construct_d_case_4(ser, mul//2) == d + + +def test_construct_d_case_5(): + """ + This function tests the Case 5 in the step + to calculate coefficients of the d-vector. + + Each test case has 3 values - + + 1. num - Numerator of the rational function a(x). + 2. den - Denominator of the rational function a(x). + 3. d - The d-vector. + """ + tests = [ + ( + Poly(2*x**3 + x**2 + x - 2, x, extension=True), + Poly(9*x**3 + 5*x**2 + 2*x - 1, x, extension=True), + [[sqrt(2)/3, -sqrt(2)/108], [-sqrt(2)/3, sqrt(2)/108]] + ), + ( + Poly(3*x**5 + x**4 - x**3 + x**2 - 2*x - 2, x, domain='ZZ'), + Poly(9*x**5 + 7*x**4 + 3*x**3 + 2*x**2 + 5*x + 7, x, domain='ZZ'), + [[sqrt(3)/3, -2*sqrt(3)/27], [-sqrt(3)/3, 2*sqrt(3)/27]] + ), + ( + Poly(x**2 - x + 1, x, domain='ZZ'), + Poly(3*x**2 + 7*x + 3, x, domain='ZZ'), + [[sqrt(3)/3, -5*sqrt(3)/9], [-sqrt(3)/3, 5*sqrt(3)/9]] + )] + for num, den, d in tests: + # Multiplicity of oo is 0 + ser = rational_laurent_series(num, den, x, oo, 0, 1) + assert construct_d_case_5(ser) == d + + +def test_construct_d_case_6(): + """ + This function tests the Case 6 in the step + to calculate coefficients of the d-vector. + + Each test case has 3 values - + + 1. num - Numerator of the rational function a(x). + 2. den - Denominator of the rational function a(x). + 3. d - The d-vector. + """ + tests = [ + ( + Poly(-2*x**2 - 5, x, domain='ZZ'), + Poly(4*x**4 + 2*x**2 + 10*x + 2, x, domain='ZZ'), + [[S(1)/2 + I/2], [S(1)/2 - I/2]] + ), + ( + Poly(-2*x**3 - 4*x**2 - 2*x - 5, x, domain='ZZ'), + Poly(x**6 - x**5 + 2*x**4 - 4*x**3 - 5*x**2 - 5*x + 9, x, domain='ZZ'), + [[1], [0]] + ), + ( + Poly(-5*x**3 + x**2 + 11*x + 12, x, domain='ZZ'), + Poly(6*x**8 - 26*x**7 - 27*x**6 - 10*x**5 - 44*x**4 - 46*x**3 - 34*x**2 \ + - 27*x - 42, x, domain='ZZ'), + [[1], [0]] + )] + for num, den, d in tests: + assert construct_d_case_6(num, den, x) == d + + +def test_rational_laurent_series(): + """ + This function tests the computation of coefficients + of Laurent series of a rational function. + + Each test case has 5 values - + + 1. num - Numerator of the rational function. + 2. den - Denominator of the rational function. + 3. x0 - Point about which Laurent series is to + be calculated. + 4. mul - Multiplicity of x0 if x0 is a pole of + the rational function (0 otherwise). + 5. n - Number of terms upto which the series + is to be calculated. + """ + tests = [ + # Laurent series about simple pole (Multiplicity = 1) + ( + Poly(x**2 - 3*x + 9, x, extension=True), + Poly(x**2 - x, x, extension=True), + S(1), 1, 6, + {1: 7, 0: -8, -1: 9, -2: -9, -3: 9, -4: -9} + ), + # Laurent series about multiple pole (Multiplicity > 1) + ( + Poly(64*x**3 - 1728*x + 1216, x, extension=True), + Poly(64*x**4 - 80*x**3 - 831*x**2 + 1809*x - 972, x, extension=True), + S(9)/8, 2, 3, + {0: S(32177152)/46521675, 2: S(1019)/984, -1: S(11947565056)/28610830125, \ + 1: S(209149)/75645} + ), + ( + Poly(1, x, extension=True), + Poly(x**5 + (-4*sqrt(2) - 1)*x**4 + (4*sqrt(2) + 12)*x**3 + (-12 - 8*sqrt(2))*x**2 \ + + (4 + 8*sqrt(2))*x - 4, x, extension=True), + sqrt(2), 4, 6, + {4: 1 + sqrt(2), 3: -3 - 2*sqrt(2), 2: Mul(-1, -3 - 2*sqrt(2), evaluate=False)/(-1 \ + + sqrt(2)), 1: (-3 - 2*sqrt(2))/(-1 + sqrt(2))**2, 0: Mul(-1, -3 - 2*sqrt(2), evaluate=False \ + )/(-1 + sqrt(2))**3, -1: (-3 - 2*sqrt(2))/(-1 + sqrt(2))**4} + ), + # Laurent series about oo + ( + Poly(x**5 - 4*x**3 + 6*x**2 + 10*x - 13, x, extension=True), + Poly(x**2 - 5, x, extension=True), + oo, 3, 6, + {3: 1, 2: 0, 1: 1, 0: 6, -1: 15, -2: 17} + ), + # Laurent series at x0 where x0 is not a pole of the function + # Using multiplicity as 0 (as x0 will not be a pole) + ( + Poly(3*x**3 + 6*x**2 - 2*x + 5, x, extension=True), + Poly(9*x**4 - x**3 - 3*x**2 + 4*x + 4, x, extension=True), + S(2)/5, 0, 1, + {0: S(3345)/3304, -1: S(399325)/2729104, -2: S(3926413375)/4508479808, \ + -3: S(-5000852751875)/1862002160704, -4: S(-6683640101653125)/6152055138966016} + ), + ( + Poly(-7*x**2 + 2*x - 4, x, extension=True), + Poly(7*x**5 + 9*x**4 + 8*x**3 + 3*x**2 + 6*x + 9, x, extension=True), + oo, 0, 6, + {0: 0, -2: 0, -5: -S(71)/49, -1: 0, -3: -1, -4: S(11)/7} + )] + for num, den, x0, mul, n, ser in tests: + assert ser == rational_laurent_series(num, den, x, x0, mul, n) + + +def check_dummy_sol(eq, solse, dummy_sym): + """ + Helper function to check if actual solution + matches expected solution if actual solution + contains dummy symbols. + """ + if isinstance(eq, Eq): + eq = eq.lhs - eq.rhs + _, funcs = match_riccati(eq, f, x) + + sols = solve_riccati(f(x), x, *funcs) + C1 = Dummy('C1') + sols = [sol.subs(C1, dummy_sym) for sol in sols] + + assert all(x[0] for x in checkodesol(eq, sols)) + assert all(s1.dummy_eq(s2, dummy_sym) for s1, s2 in zip(sols, solse)) + + +def test_solve_riccati(): + """ + This function tests the computation of rational + particular solutions for a Riccati ODE. + + Each test case has 2 values - + + 1. eq - Riccati ODE to be solved. + 2. sol - Expected solution to the equation. + + Some examples have been taken from the paper - "Statistical Investigation of + First-Order Algebraic ODEs and their Rational General Solutions" by + Georg Grasegger, N. Thieu Vo, Franz Winkler + + https://www3.risc.jku.at/publications/download/risc_5197/RISCReport15-19.pdf + """ + C0 = Dummy('C0') + # Type: 1st Order Rational Riccati, dy/dx = a + b*y + c*y**2, + # a, b, c are rational functions of x + + tests = [ + # a(x) is a constant + ( + Eq(f(x).diff(x) + f(x)**2 - 2, 0), + [Eq(f(x), sqrt(2)), Eq(f(x), -sqrt(2))] + ), + # a(x) is a constant + ( + f(x)**2 + f(x).diff(x) + 4*f(x)/x + 2/x**2, + [Eq(f(x), (-2*C0 - x)/(C0*x + x**2))] + ), + # a(x) is a constant + ( + 2*x**2*f(x).diff(x) - x*(4*f(x) + f(x).diff(x) - 4) + (f(x) - 1)*f(x), + [Eq(f(x), (C0 + 2*x**2)/(C0 + x))] + ), + # Pole with multiplicity 1 + ( + Eq(f(x).diff(x), -f(x)**2 - 2/(x**3 - x**2)), + [Eq(f(x), 1/(x**2 - x))] + ), + # One pole of multiplicity 2 + ( + x**2 - (2*x + 1/x)*f(x) + f(x)**2 + f(x).diff(x), + [Eq(f(x), (C0*x + x**3 + 2*x)/(C0 + x**2)), Eq(f(x), x)] + ), + ( + x**4*f(x).diff(x) + x**2 - x*(2*f(x)**2 + f(x).diff(x)) + f(x), + [Eq(f(x), (C0*x**2 + x)/(C0 + x**2)), Eq(f(x), x**2)] + ), + # Multiple poles of multiplicity 2 + ( + -f(x)**2 + f(x).diff(x) + (15*x**2 - 20*x + 7)/((x - 1)**2*(2*x \ + - 1)**2), + [Eq(f(x), (9*C0*x - 6*C0 - 15*x**5 + 60*x**4 - 94*x**3 + 72*x**2 \ + - 30*x + 6)/(6*C0*x**2 - 9*C0*x + 3*C0 + 6*x**6 - 29*x**5 + \ + 57*x**4 - 58*x**3 + 30*x**2 - 6*x)), Eq(f(x), (3*x - 2)/(2*x**2 \ + - 3*x + 1))] + ), + # Regression: Poles with even multiplicity > 2 fixed + ( + f(x)**2 + f(x).diff(x) - (4*x**6 - 8*x**5 + 12*x**4 + 4*x**3 + \ + 7*x**2 - 20*x + 4)/(4*x**4), + [Eq(f(x), (2*x**5 - 2*x**4 - x**3 + 4*x**2 + 3*x - 2)/(2*x**4 \ + - 2*x**2))] + ), + # Regression: Poles with even multiplicity > 2 fixed + ( + Eq(f(x).diff(x), (-x**6 + 15*x**4 - 40*x**3 + 45*x**2 - 24*x + 4)/\ + (x**12 - 12*x**11 + 66*x**10 - 220*x**9 + 495*x**8 - 792*x**7 + 924*x**6 - \ + 792*x**5 + 495*x**4 - 220*x**3 + 66*x**2 - 12*x + 1) + f(x)**2 + f(x)), + [Eq(f(x), 1/(x**6 - 6*x**5 + 15*x**4 - 20*x**3 + 15*x**2 - 6*x + 1))] + ), + # More than 2 poles with multiplicity 2 + # Regression: Fixed mistake in necessary conditions + ( + Eq(f(x).diff(x), x*f(x) + 2*x + (3*x - 2)*f(x)**2/(4*x + 2) + \ + (8*x**2 - 7*x + 26)/(16*x**3 - 24*x**2 + 8) - S(3)/2), + [Eq(f(x), (1 - 4*x)/(2*x - 2))] + ), + # Regression: Fixed mistake in necessary conditions + ( + Eq(f(x).diff(x), (-12*x**2 - 48*x - 15)/(24*x**3 - 40*x**2 + 8*x + 8) \ + + 3*f(x)**2/(6*x + 2)), + [Eq(f(x), (2*x + 1)/(2*x - 2))] + ), + # Imaginary poles + ( + f(x).diff(x) + (3*x**2 + 1)*f(x)**2/x + (6*x**2 - x + 3)*f(x)/(x*(x \ + - 1)) + (3*x**2 - 2*x + 2)/(x*(x - 1)**2), + [Eq(f(x), (-C0 - x**3 + x**2 - 2*x)/(C0*x - C0 + x**4 - x**3 + x**2 \ + - x)), Eq(f(x), -1/(x - 1))], + ), + # Imaginary coefficients in equation + ( + f(x).diff(x) - 2*I*(f(x)**2 + 1)/x, + [Eq(f(x), (-I*C0 + I*x**4)/(C0 + x**4)), Eq(f(x), -I)] + ), + # Regression: linsolve returning empty solution + # Large value of m (> 10) + ( + Eq(f(x).diff(x), x*f(x)/(S(3)/2 - 2*x) + (x/2 - S(1)/3)*f(x)**2/\ + (2*x/3 - S(1)/2) - S(5)/4 + (281*x**2 - 1260*x + 756)/(16*x**3 - 12*x**2)), + [Eq(f(x), (9 - x)/x), Eq(f(x), (40*x**14 + 28*x**13 + 420*x**12 + 2940*x**11 + \ + 18480*x**10 + 103950*x**9 + 519750*x**8 + 2286900*x**7 + 8731800*x**6 + 28378350*\ + x**5 + 76403250*x**4 + 163721250*x**3 + 261954000*x**2 + 278326125*x + 147349125)/\ + ((24*x**14 + 140*x**13 + 840*x**12 + 4620*x**11 + 23100*x**10 + 103950*x**9 + \ + 415800*x**8 + 1455300*x**7 + 4365900*x**6 + 10914750*x**5 + 21829500*x**4 + 32744250\ + *x**3 + 32744250*x**2 + 16372125*x)))] + ), + # Regression: Fixed bug due to a typo in paper + ( + Eq(f(x).diff(x), 18*x**3 + 18*x**2 + (-x/2 - S(1)/2)*f(x)**2 + 6), + [Eq(f(x), 6*x)] + ), + # Regression: Fixed bug due to a typo in paper + ( + Eq(f(x).diff(x), -3*x**3/4 + 15*x/2 + (x/3 - S(4)/3)*f(x)**2 \ + + 9 + (1 - x)*f(x)/x + 3/x), + [Eq(f(x), -3*x/2 - 3)] + )] + for eq, sol in tests: + check_dummy_sol(eq, sol, C0) + + +@slow +def test_solve_riccati_slow(): + """ + This function tests the computation of rational + particular solutions for a Riccati ODE. + + Each test case has 2 values - + + 1. eq - Riccati ODE to be solved. + 2. sol - Expected solution to the equation. + """ + C0 = Dummy('C0') + tests = [ + # Very large values of m (989 and 991) + ( + Eq(f(x).diff(x), (1 - x)*f(x)/(x - 3) + (2 - 12*x)*f(x)**2/(2*x - 9) + \ + (54924*x**3 - 405264*x**2 + 1084347*x - 1087533)/(8*x**4 - 132*x**3 + 810*x**2 - \ + 2187*x + 2187) + 495), + [Eq(f(x), (18*x + 6)/(2*x - 9))] + )] + for eq, sol in tests: + check_dummy_sol(eq, sol, C0) diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_single.py b/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_single.py new file mode 100644 index 0000000000000000000000000000000000000000..7bd34add98d072a7fabdd16a16e3afaee7bfe53f --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_single.py @@ -0,0 +1,2902 @@ +# +# The main tests for the code in single.py are currently located in +# sympy/solvers/tests/test_ode.py +# +r""" +This File contains test functions for the individual hints used for solving ODEs. + +Examples of each solver will be returned by _get_examples_ode_sol_name_of_solver. + +Examples should have a key 'XFAIL' which stores the list of hints if they are +expected to fail for that hint. + +Functions that are for internal use: + +1) _ode_solver_test(ode_examples) - It takes a dictionary of examples returned by + _get_examples method and tests them with their respective hints. + +2) _test_particular_example(our_hint, example_name) - It tests the ODE example corresponding + to the hint provided. + +3) _test_all_hints(runxfail=False) - It is used to test all the examples with all the hints + currently implemented. It calls _test_all_examples_for_one_hint() which outputs whether the + given hint functions properly if it classifies the ODE example. + If runxfail flag is set to True then it will only test the examples which are expected to fail. + + Everytime the ODE of a particular solver is added, _test_all_hints() is to be executed to find + the possible failures of different solver hints. + +4) _test_all_examples_for_one_hint(our_hint, all_examples) - It takes hint as argument and checks + this hint against all the ODE examples and gives output as the number of ODEs matched, number + of ODEs which were solved correctly, list of ODEs which gives incorrect solution and list of + ODEs which raises exception. + +""" +from sympy.core.function import (Derivative, diff) +from sympy.core.mul import Mul +from sympy.core.numbers import (E, I, Rational, pi) +from sympy.core.relational import (Eq, Ne) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, symbols) +from sympy.functions.elementary.complexes import (im, re) +from sympy.functions.elementary.exponential import (LambertW, exp, log) +from sympy.functions.elementary.hyperbolic import (asinh, cosh, sinh, tanh) +from sympy.functions.elementary.miscellaneous import (cbrt, sqrt) +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (acos, asin, atan, cos, sec, sin, tan) +from sympy.functions.special.error_functions import (Ei, erfi) +from sympy.functions.special.hyper import hyper +from sympy.integrals.integrals import (Integral, integrate) +from sympy.polys.rootoftools import rootof + +from sympy.core import Function, Symbol +from sympy.functions import airyai, airybi, besselj, bessely, lowergamma +from sympy.integrals.risch import NonElementaryIntegral +from sympy.solvers.ode import classify_ode, dsolve +from sympy.solvers.ode.ode import allhints, _remove_redundant_solutions +from sympy.solvers.ode.single import (FirstLinear, ODEMatchError, + SingleODEProblem, SingleODESolver, NthOrderReducible) + +from sympy.solvers.ode.subscheck import checkodesol + +from sympy.testing.pytest import raises, slow +import traceback + + +x = Symbol('x') +u = Symbol('u') +_u = Dummy('u') +y = Symbol('y') +f = Function('f') +g = Function('g') +C1, C2, C3, C4, C5, C6, C7, C8, C9, C10 = symbols('C1:11') +a, b, c = symbols('a b c') + + +hint_message = """\ +Hint did not match the example {example}. + +The ODE is: +{eq}. + +The expected hint was +{our_hint}\ +""" + +expected_sol_message = """\ +Different solution found from dsolve for example {example}. + +The ODE is: +{eq} + +The expected solution was +{sol} + +What dsolve returned is: +{dsolve_sol}\ +""" + +checkodesol_msg = """\ +solution found is not correct for example {example}. + +The ODE is: +{eq}\ +""" + +dsol_incorrect_msg = """\ +solution returned by dsolve is incorrect when using {hint}. + +The ODE is: +{eq} + +The expected solution was +{sol} + +what dsolve returned is: +{dsolve_sol} + +You can test this with: + +eq = {eq} +sol = dsolve(eq, hint='{hint}') +print(sol) +print(checkodesol(eq, sol)) + +""" + +exception_msg = """\ +dsolve raised exception : {e} + +when using {hint} for the example {example} + +You can test this with: + +from sympy.solvers.ode.tests.test_single import _test_an_example + +_test_an_example('{hint}', example_name = '{example}') + +The ODE is: +{eq} + +\ +""" + +check_hint_msg = """\ +Tested hint was : {hint} + +Total of {matched} examples matched with this hint. + +Out of which {solve} gave correct results. + +Examples which gave incorrect results are {unsolve}. + +Examples which raised exceptions are {exceptions} +\ +""" + + +def _add_example_keys(func): + def inner(): + solver=func() + examples=[] + for example in solver['examples']: + temp={ + 'eq': solver['examples'][example]['eq'], + 'sol': solver['examples'][example]['sol'], + 'XFAIL': solver['examples'][example].get('XFAIL', []), + 'func': solver['examples'][example].get('func',solver['func']), + 'example_name': example, + 'slow': solver['examples'][example].get('slow', False), + 'simplify_flag':solver['examples'][example].get('simplify_flag',True), + 'checkodesol_XFAIL': solver['examples'][example].get('checkodesol_XFAIL', False), + 'dsolve_too_slow':solver['examples'][example].get('dsolve_too_slow',False), + 'checkodesol_too_slow':solver['examples'][example].get('checkodesol_too_slow',False), + 'hint': solver['hint'] + } + examples.append(temp) + return examples + return inner() + + +def _ode_solver_test(ode_examples, run_slow_test=False): + for example in ode_examples: + if ((not run_slow_test) and example['slow']) or (run_slow_test and (not example['slow'])): + continue + + result = _test_particular_example(example['hint'], example, solver_flag=True) + if result['xpass_msg'] != "": + print(result['xpass_msg']) + + +def _test_all_hints(runxfail=False): + all_hints = list(allhints)+["default"] + all_examples = _get_all_examples() + + for our_hint in all_hints: + if our_hint.endswith('_Integral') or 'series' in our_hint: + continue + _test_all_examples_for_one_hint(our_hint, all_examples, runxfail) + + +def _test_dummy_sol(expected_sol,dsolve_sol): + if type(dsolve_sol)==list: + return any(expected_sol.dummy_eq(sub_dsol) for sub_dsol in dsolve_sol) + else: + return expected_sol.dummy_eq(dsolve_sol) + + +def _test_an_example(our_hint, example_name): + all_examples = _get_all_examples() + for example in all_examples: + if example['example_name'] == example_name: + _test_particular_example(our_hint, example) + + +def _test_particular_example(our_hint, ode_example, solver_flag=False): + eq = ode_example['eq'] + expected_sol = ode_example['sol'] + example = ode_example['example_name'] + xfail = our_hint in ode_example['XFAIL'] + func = ode_example['func'] + result = {'msg': '', 'xpass_msg': ''} + simplify_flag=ode_example['simplify_flag'] + checkodesol_XFAIL = ode_example['checkodesol_XFAIL'] + dsolve_too_slow = ode_example['dsolve_too_slow'] + checkodesol_too_slow = ode_example['checkodesol_too_slow'] + xpass = True + if solver_flag: + if our_hint not in classify_ode(eq, func): + message = hint_message.format(example=example, eq=eq, our_hint=our_hint) + raise AssertionError(message) + + if our_hint in classify_ode(eq, func): + result['match_list'] = example + try: + if not (dsolve_too_slow): + dsolve_sol = dsolve(eq, func, simplify=simplify_flag,hint=our_hint) + else: + if len(expected_sol)==1: + dsolve_sol = expected_sol[0] + else: + dsolve_sol = expected_sol + + except Exception as e: + dsolve_sol = [] + result['exception_list'] = example + if not solver_flag: + traceback.print_exc() + result['msg'] = exception_msg.format(e=str(e), hint=our_hint, example=example, eq=eq) + if solver_flag and not xfail: + print(result['msg']) + raise + xpass = False + + if solver_flag and dsolve_sol!=[]: + expect_sol_check = False + if type(dsolve_sol)==list: + for sub_sol in expected_sol: + if sub_sol.has(Dummy): + expect_sol_check = not _test_dummy_sol(sub_sol, dsolve_sol) + else: + expect_sol_check = sub_sol not in dsolve_sol + if expect_sol_check: + break + else: + expect_sol_check = dsolve_sol not in expected_sol + for sub_sol in expected_sol: + if sub_sol.has(Dummy): + expect_sol_check = not _test_dummy_sol(sub_sol, dsolve_sol) + + if expect_sol_check: + message = expected_sol_message.format(example=example, eq=eq, sol=expected_sol, dsolve_sol=dsolve_sol) + raise AssertionError(message) + + expected_checkodesol = [(True, 0) for i in range(len(expected_sol))] + if len(expected_sol) == 1: + expected_checkodesol = (True, 0) + + if not checkodesol_too_slow: + if not checkodesol_XFAIL: + if checkodesol(eq, dsolve_sol, func, solve_for_func=False) != expected_checkodesol: + result['unsolve_list'] = example + xpass = False + message = dsol_incorrect_msg.format(hint=our_hint, eq=eq, sol=expected_sol,dsolve_sol=dsolve_sol) + if solver_flag: + message = checkodesol_msg.format(example=example, eq=eq) + raise AssertionError(message) + else: + result['msg'] = 'AssertionError: ' + message + + if xpass and xfail: + result['xpass_msg'] = example + "is now passing for the hint" + our_hint + return result + + +def _test_all_examples_for_one_hint(our_hint, all_examples=[], runxfail=None): + if all_examples == []: + all_examples = _get_all_examples() + match_list, unsolve_list, exception_list = [], [], [] + for ode_example in all_examples: + xfail = our_hint in ode_example['XFAIL'] + if runxfail and not xfail: + continue + if xfail: + continue + result = _test_particular_example(our_hint, ode_example) + match_list += result.get('match_list',[]) + unsolve_list += result.get('unsolve_list',[]) + exception_list += result.get('exception_list',[]) + if runxfail is not None: + msg = result['msg'] + if msg!='': + print(result['msg']) + # print(result.get('xpass_msg','')) + if runxfail is None: + match_count = len(match_list) + solved = len(match_list)-len(unsolve_list)-len(exception_list) + msg = check_hint_msg.format(hint=our_hint, matched=match_count, solve=solved, unsolve=unsolve_list, exceptions=exception_list) + print(msg) + + +def test_SingleODESolver(): + # Test that not implemented methods give NotImplementedError + # Subclasses should override these methods. + problem = SingleODEProblem(f(x).diff(x), f(x), x) + solver = SingleODESolver(problem) + raises(NotImplementedError, lambda: solver.matches()) + raises(NotImplementedError, lambda: solver.get_general_solution()) + raises(NotImplementedError, lambda: solver._matches()) + raises(NotImplementedError, lambda: solver._get_general_solution()) + + # This ODE can not be solved by the FirstLinear solver. Here we test that + # it does not match and the asking for a general solution gives + # ODEMatchError + + problem = SingleODEProblem(f(x).diff(x) + f(x)*f(x), f(x), x) + + solver = FirstLinear(problem) + raises(ODEMatchError, lambda: solver.get_general_solution()) + + solver = FirstLinear(problem) + assert solver.matches() is False + + #These are just test for order of ODE + + problem = SingleODEProblem(f(x).diff(x) + f(x), f(x), x) + assert problem.order == 1 + + problem = SingleODEProblem(f(x).diff(x,4) + f(x).diff(x,2) - f(x).diff(x,3), f(x), x) + assert problem.order == 4 + + problem = SingleODEProblem(f(x).diff(x, 3) + f(x).diff(x, 2) - f(x)**2, f(x), x) + assert problem.is_autonomous == True + + problem = SingleODEProblem(f(x).diff(x, 3) + x*f(x).diff(x, 2) - f(x)**2, f(x), x) + assert problem.is_autonomous == False + + +def test_linear_coefficients(): + _ode_solver_test(_get_examples_ode_sol_linear_coefficients) + + +@slow +def test_1st_homogeneous_coeff_ode(): + #These were marked as test_1st_homogeneous_coeff_corner_case + eq1 = f(x).diff(x) - f(x)/x + c1 = classify_ode(eq1, f(x)) + eq2 = x*f(x).diff(x) - f(x) + c2 = classify_ode(eq2, f(x)) + sdi = "1st_homogeneous_coeff_subs_dep_div_indep" + sid = "1st_homogeneous_coeff_subs_indep_div_dep" + assert sid not in c1 and sdi not in c1 + assert sid not in c2 and sdi not in c2 + _ode_solver_test(_get_examples_ode_sol_1st_homogeneous_coeff_subs_dep_div_indep) + _ode_solver_test(_get_examples_ode_sol_1st_homogeneous_coeff_best) + + +@slow +def test_slow_examples_1st_homogeneous_coeff_ode(): + _ode_solver_test(_get_examples_ode_sol_1st_homogeneous_coeff_subs_dep_div_indep, run_slow_test=True) + _ode_solver_test(_get_examples_ode_sol_1st_homogeneous_coeff_best, run_slow_test=True) + + +@slow +def test_nth_linear_constant_coeff_homogeneous(): + _ode_solver_test(_get_examples_ode_sol_nth_linear_constant_coeff_homogeneous) + + +@slow +def test_slow_examples_nth_linear_constant_coeff_homogeneous(): + _ode_solver_test(_get_examples_ode_sol_nth_linear_constant_coeff_homogeneous, run_slow_test=True) + + +def test_Airy_equation(): + _ode_solver_test(_get_examples_ode_sol_2nd_linear_airy) + + +@slow +def test_lie_group(): + _ode_solver_test(_get_examples_ode_sol_lie_group) + + +@slow +def test_separable_reduced(): + df = f(x).diff(x) + eq = (x / f(x))*df + tan(x**2*f(x) / (x**2*f(x) - 1)) + assert classify_ode(eq) == ('factorable', 'separable_reduced', 'lie_group', + 'separable_reduced_Integral') + _ode_solver_test(_get_examples_ode_sol_separable_reduced) + + +@slow +def test_slow_examples_separable_reduced(): + _ode_solver_test(_get_examples_ode_sol_separable_reduced, run_slow_test=True) + + +@slow +def test_2nd_2F1_hypergeometric(): + _ode_solver_test(_get_examples_ode_sol_2nd_2F1_hypergeometric) + + +def test_2nd_2F1_hypergeometric_integral(): + eq = x*(x-1)*f(x).diff(x, 2) + (-1+ S(7)/2*x)*f(x).diff(x) + f(x) + sol = Eq(f(x), (C1 + C2*Integral(exp(Integral((1 - x/2)/(x*(x - 1)), x))/(1 - + x/2)**2, x))*exp(Integral(1/(x - 1), x)/4)*exp(-Integral(7/(x - + 1), x)/4)*hyper((S(1)/2, -1), (1,), x)) + assert sol == dsolve(eq, hint='2nd_hypergeometric_Integral') + assert checkodesol(eq, sol) == (True, 0) + + +@slow +def test_2nd_nonlinear_autonomous_conserved(): + _ode_solver_test(_get_examples_ode_sol_2nd_nonlinear_autonomous_conserved) + + +def test_2nd_nonlinear_autonomous_conserved_integral(): + eq = f(x).diff(x, 2) + asin(f(x)) + actual = [Eq(Integral(1/sqrt(C1 - 2*Integral(asin(_u), _u)), (_u, f(x))), C2 + x), + Eq(Integral(1/sqrt(C1 - 2*Integral(asin(_u), _u)), (_u, f(x))), C2 - x)] + solved = dsolve(eq, hint='2nd_nonlinear_autonomous_conserved_Integral', simplify=False) + for a,s in zip(actual, solved): + assert a.dummy_eq(s) + # checkodesol unable to simplify solutions with f(x) in an integral equation + assert checkodesol(eq, [s.doit() for s in solved]) == [(True, 0), (True, 0)] + + +@slow +def test_2nd_linear_bessel_equation(): + _ode_solver_test(_get_examples_ode_sol_2nd_linear_bessel) + + +@slow +def test_nth_algebraic(): + eqn = f(x) + f(x)*f(x).diff(x) + solns = [Eq(f(x), exp(x)), + Eq(f(x), C1*exp(C2*x))] + solns_final = _remove_redundant_solutions(eqn, solns, 2, x) + assert solns_final == [Eq(f(x), C1*exp(C2*x))] + + _ode_solver_test(_get_examples_ode_sol_nth_algebraic) + + +@slow +def test_slow_examples_nth_linear_constant_coeff_var_of_parameters(): + _ode_solver_test(_get_examples_ode_sol_nth_linear_var_of_parameters, run_slow_test=True) + + +def test_nth_linear_constant_coeff_var_of_parameters(): + _ode_solver_test(_get_examples_ode_sol_nth_linear_var_of_parameters) + + +@slow +def test_nth_linear_constant_coeff_variation_of_parameters__integral(): + # solve_variation_of_parameters shouldn't attempt to simplify the + # Wronskian if simplify=False. If wronskian() ever gets good enough + # to simplify the result itself, this test might fail. + our_hint = 'nth_linear_constant_coeff_variation_of_parameters_Integral' + eq = f(x).diff(x, 5) + 2*f(x).diff(x, 3) + f(x).diff(x) - 2*x - exp(I*x) + sol_simp = dsolve(eq, f(x), hint=our_hint, simplify=True) + sol_nsimp = dsolve(eq, f(x), hint=our_hint, simplify=False) + assert sol_simp != sol_nsimp + assert checkodesol(eq, sol_simp, order=5, solve_for_func=False) == (True, 0) + assert checkodesol(eq, sol_simp, order=5, solve_for_func=False) == (True, 0) + + +@slow +def test_slow_examples_1st_exact(): + _ode_solver_test(_get_examples_ode_sol_1st_exact, run_slow_test=True) + + +@slow +def test_1st_exact(): + _ode_solver_test(_get_examples_ode_sol_1st_exact) + + +def test_1st_exact_integral(): + eq = cos(f(x)) - (x*sin(f(x)) - f(x)**2)*f(x).diff(x) + sol_1 = dsolve(eq, f(x), simplify=False, hint='1st_exact_Integral') + assert checkodesol(eq, sol_1, order=1, solve_for_func=False) + + +@slow +def test_slow_examples_nth_order_reducible(): + _ode_solver_test(_get_examples_ode_sol_nth_order_reducible, run_slow_test=True) + + +@slow +def test_slow_examples_nth_linear_constant_coeff_undetermined_coefficients(): + _ode_solver_test(_get_examples_ode_sol_nth_linear_undetermined_coefficients, run_slow_test=True) + + +@slow +def test_slow_examples_separable(): + _ode_solver_test(_get_examples_ode_sol_separable, run_slow_test=True) + + +@slow +def test_nth_linear_constant_coeff_undetermined_coefficients(): + #issue-https://github.com/sympy/sympy/issues/5787 + # This test case is to show the classification of imaginary constants under + # nth_linear_constant_coeff_undetermined_coefficients + eq = Eq(diff(f(x), x), I*f(x) + S.Half - I) + our_hint = 'nth_linear_constant_coeff_undetermined_coefficients' + assert our_hint in classify_ode(eq) + _ode_solver_test(_get_examples_ode_sol_nth_linear_undetermined_coefficients) + + +def test_nth_order_reducible(): + F = lambda eq: NthOrderReducible(SingleODEProblem(eq, f(x), x))._matches() + D = Derivative + assert F(D(y*f(x), x, y) + D(f(x), x)) == False + assert F(D(y*f(y), y, y) + D(f(y), y)) == False + assert F(f(x)*D(f(x), x) + D(f(x), x, 2))== False + assert F(D(x*f(y), y, 2) + D(u*y*f(x), x, 3)) == False # no simplification by design + assert F(D(f(y), y, 2) + D(f(y), y, 3) + D(f(x), x, 4)) == False + assert F(D(f(x), x, 2) + D(f(x), x, 3)) == True + _ode_solver_test(_get_examples_ode_sol_nth_order_reducible) + + +@slow +def test_separable(): + _ode_solver_test(_get_examples_ode_sol_separable) + + +@slow +def test_factorable(): + assert integrate(-asin(f(2*x)+pi), x) == -Integral(asin(pi + f(2*x)), x) + _ode_solver_test(_get_examples_ode_sol_factorable) + + +@slow +def test_slow_examples_factorable(): + _ode_solver_test(_get_examples_ode_sol_factorable, run_slow_test=True) + + +def test_Riccati_special_minus2(): + _ode_solver_test(_get_examples_ode_sol_riccati) + + +@slow +def test_1st_rational_riccati(): + _ode_solver_test(_get_examples_ode_sol_1st_rational_riccati) + + +def test_Bernoulli(): + _ode_solver_test(_get_examples_ode_sol_bernoulli) + + +def test_1st_linear(): + _ode_solver_test(_get_examples_ode_sol_1st_linear) + + +def test_almost_linear(): + _ode_solver_test(_get_examples_ode_sol_almost_linear) + + +@slow +def test_Liouville_ODE(): + hint = 'Liouville' + not_Liouville1 = classify_ode(diff(f(x), x)/x + f(x)*diff(f(x), x, x)/2 - + diff(f(x), x)**2/2, f(x)) + not_Liouville2 = classify_ode(diff(f(x), x)/x + diff(f(x), x, x)/2 - + x*diff(f(x), x)**2/2, f(x)) + assert hint not in not_Liouville1 + assert hint not in not_Liouville2 + assert hint + '_Integral' not in not_Liouville1 + assert hint + '_Integral' not in not_Liouville2 + + _ode_solver_test(_get_examples_ode_sol_liouville) + + +def test_nth_order_linear_euler_eq_homogeneous(): + x, t, a, b, c = symbols('x t a b c') + y = Function('y') + our_hint = "nth_linear_euler_eq_homogeneous" + + eq = diff(f(t), t, 4)*t**4 - 13*diff(f(t), t, 2)*t**2 + 36*f(t) + assert our_hint in classify_ode(eq) + + eq = a*y(t) + b*t*diff(y(t), t) + c*t**2*diff(y(t), t, 2) + assert our_hint in classify_ode(eq) + + _ode_solver_test(_get_examples_ode_sol_euler_homogeneous) + + +def test_nth_order_linear_euler_eq_nonhomogeneous_undetermined_coefficients(): + x, t = symbols('x t') + a, b, c, d = symbols('a b c d', integer=True) + our_hint = "nth_linear_euler_eq_nonhomogeneous_undetermined_coefficients" + + eq = x**4*diff(f(x), x, 4) - 13*x**2*diff(f(x), x, 2) + 36*f(x) + x + assert our_hint in classify_ode(eq, f(x)) + + eq = a*x**2*diff(f(x), x, 2) + b*x*diff(f(x), x) + c*f(x) + d*log(x) + assert our_hint in classify_ode(eq, f(x)) + + _ode_solver_test(_get_examples_ode_sol_euler_undetermined_coeff) + + +@slow +def test_nth_order_linear_euler_eq_nonhomogeneous_variation_of_parameters(): + x, t = symbols('x, t') + a, b, c, d = symbols('a, b, c, d', integer=True) + our_hint = "nth_linear_euler_eq_nonhomogeneous_variation_of_parameters" + + eq = Eq(x**2*diff(f(x),x,2) - 8*x*diff(f(x),x) + 12*f(x), x**2) + assert our_hint in classify_ode(eq, f(x)) + + eq = Eq(a*x**3*diff(f(x),x,3) + b*x**2*diff(f(x),x,2) + c*x*diff(f(x),x) + d*f(x), x*log(x)) + assert our_hint in classify_ode(eq, f(x)) + + _ode_solver_test(_get_examples_ode_sol_euler_var_para) + + +@_add_example_keys +def _get_examples_ode_sol_euler_homogeneous(): + r1, r2, r3, r4, r5 = [rootof(x**5 - 14*x**4 + 71*x**3 - 154*x**2 + 120*x - 1, n) for n in range(5)] + return { + 'hint': "nth_linear_euler_eq_homogeneous", + 'func': f(x), + 'examples':{ + 'euler_hom_01': { + 'eq': Eq(-3*diff(f(x), x)*x + 2*x**2*diff(f(x), x, x), 0), + 'sol': [Eq(f(x), C1 + C2*x**Rational(5, 2))], + }, + + 'euler_hom_02': { + 'eq': Eq(3*f(x) - 5*diff(f(x), x)*x + 2*x**2*diff(f(x), x, x), 0), + 'sol': [Eq(f(x), C1*sqrt(x) + C2*x**3)] + }, + + 'euler_hom_03': { + 'eq': Eq(4*f(x) + 5*diff(f(x), x)*x + x**2*diff(f(x), x, x), 0), + 'sol': [Eq(f(x), (C1 + C2*log(x))/x**2)] + }, + + 'euler_hom_04': { + 'eq': Eq(6*f(x) - 6*diff(f(x), x)*x + 1*x**2*diff(f(x), x, x) + x**3*diff(f(x), x, x, x), 0), + 'sol': [Eq(f(x), C1/x**2 + C2*x + C3*x**3)] + }, + + 'euler_hom_05': { + 'eq': Eq(-125*f(x) + 61*diff(f(x), x)*x - 12*x**2*diff(f(x), x, x) + x**3*diff(f(x), x, x, x), 0), + 'sol': [Eq(f(x), x**5*(C1 + C2*log(x) + C3*log(x)**2))] + }, + + 'euler_hom_06': { + 'eq': x**2*diff(f(x), x, 2) + x*diff(f(x), x) - 9*f(x), + 'sol': [Eq(f(x), C1*x**-3 + C2*x**3)] + }, + + 'euler_hom_07': { + 'eq': sin(x)*x**2*f(x).diff(x, 2) + sin(x)*x*f(x).diff(x) + sin(x)*f(x), + 'sol': [Eq(f(x), C1*sin(log(x)) + C2*cos(log(x)))], + 'XFAIL': ['2nd_power_series_regular','nth_linear_euler_eq_nonhomogeneous_undetermined_coefficients'] + }, + + 'euler_hom_08': { + 'eq': x**6 * f(x).diff(x, 6) - x*f(x).diff(x) + f(x), + 'sol': [Eq(f(x), C1*x + C2*x**r1 + C3*x**r2 + C4*x**r3 + C5*x**r4 + C6*x**r5)], + 'checkodesol_XFAIL':True + }, + + #This example is from issue: https://github.com/sympy/sympy/issues/15237 #This example is from issue: + # https://github.com/sympy/sympy/issues/15237 + 'euler_hom_09': { + 'eq': Derivative(x*f(x), x, x, x), + 'sol': [Eq(f(x), C1 + C2/x + C3*x)], + }, + } + } + + +@_add_example_keys +def _get_examples_ode_sol_euler_undetermined_coeff(): + return { + 'hint': "nth_linear_euler_eq_nonhomogeneous_undetermined_coefficients", + 'func': f(x), + 'examples':{ + 'euler_undet_01': { + 'eq': Eq(x**2*diff(f(x), x, x) + x*diff(f(x), x), 1), + 'sol': [Eq(f(x), C1 + C2*log(x) + log(x)**2/2)] + }, + + 'euler_undet_02': { + 'eq': Eq(x**2*diff(f(x), x, x) - 2*x*diff(f(x), x) + 2*f(x), x**3), + 'sol': [Eq(f(x), x*(C1 + C2*x + Rational(1, 2)*x**2))] + }, + + 'euler_undet_03': { + 'eq': Eq(x**2*diff(f(x), x, x) - x*diff(f(x), x) - 3*f(x), log(x)/x), + 'sol': [Eq(f(x), (C1 + C2*x**4 - log(x)**2/8 - log(x)/16)/x)] + }, + + 'euler_undet_04': { + 'eq': Eq(x**2*diff(f(x), x, x) + 3*x*diff(f(x), x) - 8*f(x), log(x)**3 - log(x)), + 'sol': [Eq(f(x), C1/x**4 + C2*x**2 - Rational(1,8)*log(x)**3 - Rational(3,32)*log(x)**2 - Rational(1,64)*log(x) - Rational(7, 256))] + }, + + 'euler_undet_05': { + 'eq': Eq(x**3*diff(f(x), x, x, x) - 3*x**2*diff(f(x), x, x) + 6*x*diff(f(x), x) - 6*f(x), log(x)), + 'sol': [Eq(f(x), C1*x + C2*x**2 + C3*x**3 - Rational(1, 6)*log(x) - Rational(11, 36))] + }, + + #Below examples were added for the issue: https://github.com/sympy/sympy/issues/5096 + 'euler_undet_06': { + 'eq': 2*x**2*f(x).diff(x, 2) + f(x) + sqrt(2*x)*sin(log(2*x)/2), + 'sol': [Eq(f(x), sqrt(x)*(C1*sin(log(x)/2) + C2*cos(log(x)/2) + sqrt(2)*log(x)*cos(log(2*x)/2)/2))] + }, + + 'euler_undet_07': { + 'eq': 2*x**2*f(x).diff(x, 2) + f(x) + sin(log(2*x)/2), + 'sol': [Eq(f(x), C1*sqrt(x)*sin(log(x)/2) + C2*sqrt(x)*cos(log(x)/2) - 2*sin(log(2*x)/2)/5 - 4*cos(log(2*x)/2)/5)] + }, + } + } + + +@_add_example_keys +def _get_examples_ode_sol_euler_var_para(): + return { + 'hint': "nth_linear_euler_eq_nonhomogeneous_variation_of_parameters", + 'func': f(x), + 'examples':{ + 'euler_var_01': { + 'eq': Eq(x**2*Derivative(f(x), x, x) - 2*x*Derivative(f(x), x) + 2*f(x), x**4), + 'sol': [Eq(f(x), x*(C1 + C2*x + x**3/6))] + }, + + 'euler_var_02': { + 'eq': Eq(3*x**2*diff(f(x), x, x) + 6*x*diff(f(x), x) - 6*f(x), x**3*exp(x)), + 'sol': [Eq(f(x), C1/x**2 + C2*x + x*exp(x)/3 - 4*exp(x)/3 + 8*exp(x)/(3*x) - 8*exp(x)/(3*x**2))] + }, + + 'euler_var_03': { + 'eq': Eq(x**2*Derivative(f(x), x, x) - 2*x*Derivative(f(x), x) + 2*f(x), x**4*exp(x)), + 'sol': [Eq(f(x), x*(C1 + C2*x + x*exp(x) - 2*exp(x)))] + }, + + 'euler_var_04': { + 'eq': x**2*Derivative(f(x), x, x) - 2*x*Derivative(f(x), x) + 2*f(x) - log(x), + 'sol': [Eq(f(x), C1*x + C2*x**2 + log(x)/2 + Rational(3, 4))] + }, + + 'euler_var_05': { + 'eq': -exp(x) + (x*Derivative(f(x), (x, 2)) + Derivative(f(x), x))/x, + 'sol': [Eq(f(x), C1 + C2*log(x) + exp(x) - Ei(x))] + }, + + 'euler_var_06': { + 'eq': x**2 * f(x).diff(x, 2) + x * f(x).diff(x) + 4 * f(x) - 1/x, + 'sol': [Eq(f(x), C1*sin(2*log(x)) + C2*cos(2*log(x)) + 1/(5*x))] + }, + } + } + + +@_add_example_keys +def _get_examples_ode_sol_bernoulli(): + # Type: Bernoulli, f'(x) + p(x)*f(x) == q(x)*f(x)**n + return { + 'hint': "Bernoulli", + 'func': f(x), + 'examples':{ + 'bernoulli_01': { + 'eq': Eq(x*f(x).diff(x) + f(x) - f(x)**2, 0), + 'sol': [Eq(f(x), 1/(C1*x + 1))], + 'XFAIL': ['separable_reduced'] + }, + + 'bernoulli_02': { + 'eq': f(x).diff(x) - y*f(x), + 'sol': [Eq(f(x), C1*exp(x*y))] + }, + + 'bernoulli_03': { + 'eq': f(x)*f(x).diff(x) - 1, + 'sol': [Eq(f(x), -sqrt(C1 + 2*x)), Eq(f(x), sqrt(C1 + 2*x))] + }, + } + } + + +@_add_example_keys +def _get_examples_ode_sol_riccati(): + # Type: Riccati special alpha = -2, a*dy/dx + b*y**2 + c*y/x +d/x**2 + return { + 'hint': "Riccati_special_minus2", + 'func': f(x), + 'examples':{ + 'riccati_01': { + 'eq': 2*f(x).diff(x) + f(x)**2 - f(x)/x + 3*x**(-2), + 'sol': [Eq(f(x), (-sqrt(3)*tan(C1 + sqrt(3)*log(x)/4) + 3)/(2*x))], + }, + }, + } + + +@_add_example_keys +def _get_examples_ode_sol_1st_rational_riccati(): + # Type: 1st Order Rational Riccati, dy/dx = a + b*y + c*y**2, + # a, b, c are rational functions of x + return { + 'hint': "1st_rational_riccati", + 'func': f(x), + 'examples':{ + # a(x) is a constant + "rational_riccati_01": { + "eq": Eq(f(x).diff(x) + f(x)**2 - 2, 0), + "sol": [Eq(f(x), sqrt(2)*(-C1 - exp(2*sqrt(2)*x))/(C1 - exp(2*sqrt(2)*x)))] + }, + # a(x) is a constant + "rational_riccati_02": { + "eq": f(x)**2 + Derivative(f(x), x) + 4*f(x)/x + 2/x**2, + "sol": [Eq(f(x), (-2*C1 - x)/(x*(C1 + x)))] + }, + # a(x) is a constant + "rational_riccati_03": { + "eq": 2*x**2*Derivative(f(x), x) - x*(4*f(x) + Derivative(f(x), x) - 4) + (f(x) - 1)*f(x), + "sol": [Eq(f(x), (C1 + 2*x**2)/(C1 + x))] + }, + # Constant coefficients + "rational_riccati_04": { + "eq": f(x).diff(x) - 6 - 5*f(x) - f(x)**2, + "sol": [Eq(f(x), (-2*C1 + 3*exp(x))/(C1 - exp(x)))] + }, + # One pole of multiplicity 2 + "rational_riccati_05": { + "eq": x**2 - (2*x + 1/x)*f(x) + f(x)**2 + Derivative(f(x), x), + "sol": [Eq(f(x), x*(C1 + x**2 + 1)/(C1 + x**2 - 1))] + }, + # One pole of multiplicity 2 + "rational_riccati_06": { + "eq": x**4*Derivative(f(x), x) + x**2 - x*(2*f(x)**2 + Derivative(f(x), x)) + f(x), + "sol": [Eq(f(x), x*(C1*x - x + 1)/(C1 + x**2 - 1))] + }, + # Multiple poles of multiplicity 2 + "rational_riccati_07": { + "eq": -f(x)**2 + Derivative(f(x), x) + (15*x**2 - 20*x + 7)/((x - 1)**2*(2*x \ + - 1)**2), + "sol": [Eq(f(x), (9*C1*x - 6*C1 - 15*x**5 + 60*x**4 - 94*x**3 + 72*x**2 - \ + 33*x + 8)/(6*C1*x**2 - 9*C1*x + 3*C1 + 6*x**6 - 29*x**5 + 57*x**4 - \ + 58*x**3 + 28*x**2 - 3*x - 1))] + }, + # Imaginary poles + "rational_riccati_08": { + "eq": Derivative(f(x), x) + (3*x**2 + 1)*f(x)**2/x + (6*x**2 - x + 3)*f(x)/(x*(x \ + - 1)) + (3*x**2 - 2*x + 2)/(x*(x - 1)**2), + "sol": [Eq(f(x), (-C1 - x**3 + x**2 - 2*x + 1)/(C1*x - C1 + x**4 - x**3 + x**2 - \ + 2*x + 1))], + }, + # Imaginary coefficients in equation + "rational_riccati_09": { + "eq": Derivative(f(x), x) - 2*I*(f(x)**2 + 1)/x, + "sol": [Eq(f(x), (-I*C1 + I*x**4 + I)/(C1 + x**4 - 1))] + }, + # Regression: linsolve returning empty solution + # Large value of m (> 10) + "rational_riccati_10": { + "eq": Eq(Derivative(f(x), x), x*f(x)/(S(3)/2 - 2*x) + (x/2 - S(1)/3)*f(x)**2/\ + (2*x/3 - S(1)/2) - S(5)/4 + (281*x**2 - 1260*x + 756)/(16*x**3 - 12*x**2)), + "sol": [Eq(f(x), (40*C1*x**14 + 28*C1*x**13 + 420*C1*x**12 + 2940*C1*x**11 + \ + 18480*C1*x**10 + 103950*C1*x**9 + 519750*C1*x**8 + 2286900*C1*x**7 + \ + 8731800*C1*x**6 + 28378350*C1*x**5 + 76403250*C1*x**4 + 163721250*C1*x**3 \ + + 261954000*C1*x**2 + 278326125*C1*x + 147349125*C1 + x*exp(2*x) - 9*exp(2*x) \ + )/(x*(24*C1*x**13 + 140*C1*x**12 + 840*C1*x**11 + 4620*C1*x**10 + 23100*C1*x**9 \ + + 103950*C1*x**8 + 415800*C1*x**7 + 1455300*C1*x**6 + 4365900*C1*x**5 + \ + 10914750*C1*x**4 + 21829500*C1*x**3 + 32744250*C1*x**2 + 32744250*C1*x + \ + 16372125*C1 - exp(2*x))))] + } + } + } + + + +@_add_example_keys +def _get_examples_ode_sol_1st_linear(): + # Type: first order linear form f'(x)+p(x)f(x)=q(x) + return { + 'hint': "1st_linear", + 'func': f(x), + 'examples':{ + 'linear_01': { + 'eq': Eq(f(x).diff(x) + x*f(x), x**2), + 'sol': [Eq(f(x), (C1 + x*exp(x**2/2)- sqrt(2)*sqrt(pi)*erfi(sqrt(2)*x/2)/2)*exp(-x**2/2))], + }, + }, + } + + +@_add_example_keys +def _get_examples_ode_sol_factorable(): + """ some hints are marked as xfail for examples because they missed additional algebraic solution + which could be found by Factorable hint. Fact_01 raise exception for + nth_linear_constant_coeff_undetermined_coefficients""" + + y = Dummy('y') + a0,a1,a2,a3,a4 = symbols('a0, a1, a2, a3, a4') + return { + 'hint': "factorable", + 'func': f(x), + 'examples':{ + 'fact_01': { + 'eq': f(x) + f(x)*f(x).diff(x), + 'sol': [Eq(f(x), 0), Eq(f(x), C1 - x)], + 'XFAIL': ['separable', '1st_exact', '1st_linear', 'Bernoulli', '1st_homogeneous_coeff_best', + '1st_homogeneous_coeff_subs_indep_div_dep', '1st_homogeneous_coeff_subs_dep_div_indep', + 'lie_group', 'nth_linear_euler_eq_nonhomogeneous_undetermined_coefficients', + 'nth_linear_constant_coeff_variation_of_parameters', + 'nth_linear_euler_eq_nonhomogeneous_variation_of_parameters', + 'nth_linear_constant_coeff_undetermined_coefficients'] + }, + + 'fact_02': { + 'eq': f(x)*(f(x).diff(x)+f(x)*x+2), + 'sol': [Eq(f(x), (C1 - sqrt(2)*sqrt(pi)*erfi(sqrt(2)*x/2))*exp(-x**2/2)), Eq(f(x), 0)], + 'XFAIL': ['Bernoulli', '1st_linear', 'lie_group'] + }, + + 'fact_03': { + 'eq': (f(x).diff(x)+f(x)*x**2)*(f(x).diff(x, 2) + x*f(x)), + 'sol': [Eq(f(x), C1*airyai(-x) + C2*airybi(-x)),Eq(f(x), C1*exp(-x**3/3))] + }, + + 'fact_04': { + 'eq': (f(x).diff(x)+f(x)*x**2)*(f(x).diff(x, 2) + f(x)), + 'sol': [Eq(f(x), C1*exp(-x**3/3)), Eq(f(x), C1*sin(x) + C2*cos(x))] + }, + + 'fact_05': { + 'eq': (f(x).diff(x)**2-1)*(f(x).diff(x)**2-4), + 'sol': [Eq(f(x), C1 - x), Eq(f(x), C1 + x), Eq(f(x), C1 + 2*x), Eq(f(x), C1 - 2*x)] + }, + + 'fact_06': { + 'eq': (f(x).diff(x, 2)-exp(f(x)))*f(x).diff(x), + 'sol': [ + Eq(f(x), log(-C1/(cos(sqrt(-C1)*(C2 + x)) + 1))), + Eq(f(x), log(-C1/(cos(sqrt(-C1)*(C2 - x)) + 1))), + Eq(f(x), C1) + ], + 'slow': True, + }, + + 'fact_07': { + 'eq': (f(x).diff(x)**2-1)*(f(x)*f(x).diff(x)-1), + 'sol': [Eq(f(x), C1 - x), Eq(f(x), -sqrt(C1 + 2*x)),Eq(f(x), sqrt(C1 + 2*x)), Eq(f(x), C1 + x)] + }, + + 'fact_08': { + 'eq': Derivative(f(x), x)**4 - 2*Derivative(f(x), x)**2 + 1, + 'sol': [Eq(f(x), C1 - x), Eq(f(x), C1 + x)] + }, + + 'fact_09': { + 'eq': f(x)**2*Derivative(f(x), x)**6 - 2*f(x)**2*Derivative(f(x), + x)**4 + f(x)**2*Derivative(f(x), x)**2 - 2*f(x)*Derivative(f(x), + x)**5 + 4*f(x)*Derivative(f(x), x)**3 - 2*f(x)*Derivative(f(x), + x) + Derivative(f(x), x)**4 - 2*Derivative(f(x), x)**2 + 1, + 'sol': [ + Eq(f(x), C1 - x), Eq(f(x), -sqrt(C1 + 2*x)), + Eq(f(x), sqrt(C1 + 2*x)), Eq(f(x), C1 + x) + ] + }, + + 'fact_10': { + 'eq': x**4*f(x)**2 + 2*x**4*f(x)*Derivative(f(x), (x, 2)) + x**4*Derivative(f(x), + (x, 2))**2 + 2*x**3*f(x)*Derivative(f(x), x) + 2*x**3*Derivative(f(x), + x)*Derivative(f(x), (x, 2)) - 7*x**2*f(x)**2 - 7*x**2*f(x)*Derivative(f(x), + (x, 2)) + x**2*Derivative(f(x), x)**2 - 7*x*f(x)*Derivative(f(x), x) + 12*f(x)**2, + 'sol': [ + Eq(f(x), C1*besselj(2, x) + C2*bessely(2, x)), + Eq(f(x), C1*besselj(sqrt(3), x) + C2*bessely(sqrt(3), x)) + ], + 'slow': True, + }, + + 'fact_11': { + 'eq': (f(x).diff(x, 2)-exp(f(x)))*(f(x).diff(x, 2)+exp(f(x))), + 'sol': [ + Eq(f(x), log(C1/(cos(C1*sqrt(-1/C1)*(C2 + x)) - 1))), + Eq(f(x), log(C1/(cos(C1*sqrt(-1/C1)*(C2 - x)) - 1))), + Eq(f(x), log(C1/(1 - cos(C1*sqrt(-1/C1)*(C2 + x))))), + Eq(f(x), log(C1/(1 - cos(C1*sqrt(-1/C1)*(C2 - x))))) + ], + 'dsolve_too_slow': True, + }, + + #Below examples were added for the issue: https://github.com/sympy/sympy/issues/15889 + 'fact_12': { + 'eq': exp(f(x).diff(x))-f(x)**2, + 'sol': [Eq(NonElementaryIntegral(1/log(y**2), (y, f(x))), C1 + x)], + 'XFAIL': ['lie_group'] #It shows not implemented error for lie_group. + }, + + 'fact_13': { + 'eq': f(x).diff(x)**2 - f(x)**3, + 'sol': [Eq(f(x), 4/(C1**2 - 2*C1*x + x**2))], + 'XFAIL': ['lie_group'] #It shows not implemented error for lie_group. + }, + + 'fact_14': { + 'eq': f(x).diff(x)**2 - f(x), + 'sol': [Eq(f(x), C1**2/4 - C1*x/2 + x**2/4)] + }, + + 'fact_15': { + 'eq': f(x).diff(x)**2 - f(x)**2, + 'sol': [Eq(f(x), C1*exp(x)), Eq(f(x), C1*exp(-x))] + }, + + 'fact_16': { + 'eq': f(x).diff(x)**2 - f(x)**3, + 'sol': [Eq(f(x), 4/(C1**2 - 2*C1*x + x**2))], + }, + + # kamke ode 1.1 + 'fact_17': { + 'eq': f(x).diff(x)-(a4*x**4 + a3*x**3 + a2*x**2 + a1*x + a0)**(-1/2), + 'sol': [Eq(f(x), C1 + Integral(1/sqrt(a0 + a1*x + a2*x**2 + a3*x**3 + a4*x**4), x))], + 'slow': True + }, + + # This is from issue: https://github.com/sympy/sympy/issues/9446 + 'fact_18':{ + 'eq': Eq(f(2 * x), sin(Derivative(f(x)))), + 'sol': [Eq(f(x), C1 + Integral(pi - asin(f(2*x)), x)), Eq(f(x), C1 + Integral(asin(f(2*x)), x))], + 'checkodesol_XFAIL':True + }, + + # This is from issue: https://github.com/sympy/sympy/issues/7093 + 'fact_19': { + 'eq': Derivative(f(x), x)**2 - x**3, + 'sol': [Eq(f(x), C1 - 2*x**Rational(5,2)/5), Eq(f(x), C1 + 2*x**Rational(5,2)/5)], + }, + + 'fact_20': { + 'eq': x*f(x).diff(x, 2) - x*f(x), + 'sol': [Eq(f(x), C1*exp(-x) + C2*exp(x))], + }, + } + } + + + +@_add_example_keys +def _get_examples_ode_sol_almost_linear(): + from sympy.functions.special.error_functions import Ei + A = Symbol('A', positive=True) + f = Function('f') + d = f(x).diff(x) + + return { + 'hint': "almost_linear", + 'func': f(x), + 'examples':{ + 'almost_lin_01': { + 'eq': x**2*f(x)**2*d + f(x)**3 + 1, + 'sol': [Eq(f(x), (C1*exp(3/x) - 1)**Rational(1, 3)), + Eq(f(x), (-1 - sqrt(3)*I)*(C1*exp(3/x) - 1)**Rational(1, 3)/2), + Eq(f(x), (-1 + sqrt(3)*I)*(C1*exp(3/x) - 1)**Rational(1, 3)/2)], + + }, + + 'almost_lin_02': { + 'eq': x*f(x)*d + 2*x*f(x)**2 + 1, + 'sol': [Eq(f(x), -sqrt((C1 - 2*Ei(4*x))*exp(-4*x))), Eq(f(x), sqrt((C1 - 2*Ei(4*x))*exp(-4*x)))] + }, + + 'almost_lin_03': { + 'eq': x*d + x*f(x) + 1, + 'sol': [Eq(f(x), (C1 - Ei(x))*exp(-x))] + }, + + 'almost_lin_04': { + 'eq': x*exp(f(x))*d + exp(f(x)) + 3*x, + 'sol': [Eq(f(x), log(C1/x - x*Rational(3, 2)))], + }, + + 'almost_lin_05': { + 'eq': x + A*(x + diff(f(x), x) + f(x)) + diff(f(x), x) + f(x) + 2, + 'sol': [Eq(f(x), (C1 + Piecewise( + (x, Eq(A + 1, 0)), ((-A*x + A - x - 1)*exp(x)/(A + 1), True)))*exp(-x))], + }, + } + } + + +@_add_example_keys +def _get_examples_ode_sol_liouville(): + n = Symbol('n') + _y = Dummy('y') + return { + 'hint': "Liouville", + 'func': f(x), + 'examples':{ + 'liouville_01': { + 'eq': diff(f(x), x)/x + diff(f(x), x, x)/2 - diff(f(x), x)**2/2, + 'sol': [Eq(f(x), log(x/(C1 + C2*x)))], + + }, + + 'liouville_02': { + 'eq': diff(x*exp(-f(x)), x, x), + 'sol': [Eq(f(x), log(x/(C1 + C2*x)))] + }, + + 'liouville_03': { + 'eq': ((diff(f(x), x)/x + diff(f(x), x, x)/2 - diff(f(x), x)**2/2)*exp(-f(x))/exp(f(x))).expand(), + 'sol': [Eq(f(x), log(x/(C1 + C2*x)))] + }, + + 'liouville_04': { + 'eq': diff(f(x), x, x) + 1/f(x)*(diff(f(x), x))**2 + 1/x*diff(f(x), x), + 'sol': [Eq(f(x), -sqrt(C1 + C2*log(x))), Eq(f(x), sqrt(C1 + C2*log(x)))], + }, + + 'liouville_05': { + 'eq': x*diff(f(x), x, x) + x/f(x)*diff(f(x), x)**2 + x*diff(f(x), x), + 'sol': [Eq(f(x), -sqrt(C1 + C2*exp(-x))), Eq(f(x), sqrt(C1 + C2*exp(-x)))], + }, + + 'liouville_06': { + 'eq': Eq((x*exp(f(x))).diff(x, x), 0), + 'sol': [Eq(f(x), log(C1 + C2/x))], + }, + + 'liouville_07': { + 'eq': (diff(f(x), x)/x + diff(f(x), x, x)/2 - diff(f(x), x)**2/2)*exp(-f(x))/exp(f(x)), + 'sol': [Eq(f(x), log(x/(C1 + C2*x)))], + }, + + 'liouville_08': { + 'eq': x**2*diff(f(x),x) + (n*f(x) + f(x)**2)*diff(f(x),x)**2 + diff(f(x), (x, 2)), + 'sol': [Eq(C1 + C2*lowergamma(Rational(1,3), x**3/3) + NonElementaryIntegral(exp(_y**3/3)*exp(_y**2*n/2), (_y, f(x))), 0)], + }, + } + } + + +@_add_example_keys +def _get_examples_ode_sol_nth_algebraic(): + M, m, r, t = symbols('M m r t') + phi = Function('phi') + k = Symbol('k') + # This one needs a substitution f' = g. + # 'algeb_12': { + # 'eq': -exp(x) + (x*Derivative(f(x), (x, 2)) + Derivative(f(x), x))/x, + # 'sol': [Eq(f(x), C1 + C2*log(x) + exp(x) - Ei(x))], + # }, + return { + 'hint': "nth_algebraic", + 'func': f(x), + 'examples':{ + 'algeb_01': { + 'eq': f(x) * f(x).diff(x) * f(x).diff(x, x) * (f(x) - 1) * (f(x).diff(x) - x), + 'sol': [Eq(f(x), C1 + x**2/2), Eq(f(x), C1 + C2*x)] + }, + + 'algeb_02': { + 'eq': f(x) * f(x).diff(x) * f(x).diff(x, x) * (f(x) - 1), + 'sol': [Eq(f(x), C1 + C2*x)] + }, + + 'algeb_03': { + 'eq': f(x) * f(x).diff(x) * f(x).diff(x, x), + 'sol': [Eq(f(x), C1 + C2*x)] + }, + + 'algeb_04': { + 'eq': Eq(-M * phi(t).diff(t), + Rational(3, 2) * m * r**2 * phi(t).diff(t) * phi(t).diff(t,t)), + 'sol': [Eq(phi(t), C1), Eq(phi(t), C1 + C2*t - M*t**2/(3*m*r**2))], + 'func': phi(t) + }, + + 'algeb_05': { + 'eq': (1 - sin(f(x))) * f(x).diff(x), + 'sol': [Eq(f(x), C1)], + 'XFAIL': ['separable'] #It raised exception. + }, + + 'algeb_06': { + 'eq': (diff(f(x)) - x)*(diff(f(x)) + x), + 'sol': [Eq(f(x), C1 - x**2/2), Eq(f(x), C1 + x**2/2)] + }, + + 'algeb_07': { + 'eq': Eq(Derivative(f(x), x), Derivative(g(x), x)), + 'sol': [Eq(f(x), C1 + g(x))], + }, + + 'algeb_08': { + 'eq': f(x).diff(x) - C1, #this example is from issue 15999 + 'sol': [Eq(f(x), C1*x + C2)], + }, + + 'algeb_09': { + 'eq': f(x)*f(x).diff(x), + 'sol': [Eq(f(x), C1)], + }, + + 'algeb_10': { + 'eq': (diff(f(x)) - x)*(diff(f(x)) + x), + 'sol': [Eq(f(x), C1 - x**2/2), Eq(f(x), C1 + x**2/2)], + }, + + 'algeb_11': { + 'eq': f(x) + f(x)*f(x).diff(x), + 'sol': [Eq(f(x), 0), Eq(f(x), C1 - x)], + 'XFAIL': ['separable', '1st_exact', '1st_linear', 'Bernoulli', '1st_homogeneous_coeff_best', + '1st_homogeneous_coeff_subs_indep_div_dep', '1st_homogeneous_coeff_subs_dep_div_indep', + 'lie_group', 'nth_linear_constant_coeff_undetermined_coefficients', + 'nth_linear_euler_eq_nonhomogeneous_undetermined_coefficients', + 'nth_linear_constant_coeff_variation_of_parameters', + 'nth_linear_euler_eq_nonhomogeneous_variation_of_parameters'] + #nth_linear_constant_coeff_undetermined_coefficients raises exception rest all of them misses a solution. + }, + + 'algeb_12': { + 'eq': Derivative(x*f(x), x, x, x), + 'sol': [Eq(f(x), (C1 + C2*x + C3*x**2) / x)], + 'XFAIL': ['nth_algebraic'] # It passes only when prep=False is set in dsolve. + }, + + 'algeb_13': { + 'eq': Eq(Derivative(x*Derivative(f(x), x), x)/x, exp(x)), + 'sol': [Eq(f(x), C1 + C2*log(x) + exp(x) - Ei(x))], + 'XFAIL': ['nth_algebraic'] # It passes only when prep=False is set in dsolve. + }, + + # These are simple tests from the old ode module example 14-18 + 'algeb_14': { + 'eq': Eq(f(x).diff(x), 0), + 'sol': [Eq(f(x), C1)], + }, + + 'algeb_15': { + 'eq': Eq(3*f(x).diff(x) - 5, 0), + 'sol': [Eq(f(x), C1 + x*Rational(5, 3))], + }, + + 'algeb_16': { + 'eq': Eq(3*f(x).diff(x), 5), + 'sol': [Eq(f(x), C1 + x*Rational(5, 3))], + }, + + # Type: 2nd order, constant coefficients (two complex roots) + 'algeb_17': { + 'eq': Eq(3*f(x).diff(x) - 1, 0), + 'sol': [Eq(f(x), C1 + x/3)], + }, + + 'algeb_18': { + 'eq': Eq(x*f(x).diff(x) - 1, 0), + 'sol': [Eq(f(x), C1 + log(x))], + }, + + # https://github.com/sympy/sympy/issues/6989 + 'algeb_19': { + 'eq': f(x).diff(x) - x*exp(-k*x), + 'sol': [Eq(f(x), C1 + Piecewise(((-k*x - 1)*exp(-k*x)/k**2, Ne(k**2, 0)),(x**2/2, True)))], + }, + + 'algeb_20': { + 'eq': -f(x).diff(x) + x*exp(-k*x), + 'sol': [Eq(f(x), C1 + Piecewise(((-k*x - 1)*exp(-k*x)/k**2, Ne(k**2, 0)),(x**2/2, True)))], + }, + + # https://github.com/sympy/sympy/issues/10867 + 'algeb_21': { + 'eq': Eq(g(x).diff(x).diff(x), (x-2)**2 + (x-3)**3), + 'sol': [Eq(g(x), C1 + C2*x + x**5/20 - 2*x**4/3 + 23*x**3/6 - 23*x**2/2)], + 'func': g(x), + }, + + # https://github.com/sympy/sympy/issues/13691 + 'algeb_22': { + 'eq': f(x).diff(x) - C1*g(x).diff(x), + 'sol': [Eq(f(x), C2 + C1*g(x))], + 'func': f(x), + }, + + # https://github.com/sympy/sympy/issues/4838 + 'algeb_23': { + 'eq': f(x).diff(x) - 3*C1 - 3*x**2, + 'sol': [Eq(f(x), C2 + 3*C1*x + x**3)], + }, + } + } + + +@_add_example_keys +def _get_examples_ode_sol_nth_order_reducible(): + return { + 'hint': "nth_order_reducible", + 'func': f(x), + 'examples':{ + 'reducible_01': { + 'eq': Eq(x*Derivative(f(x), x)**2 + Derivative(f(x), x, 2), 0), + 'sol': [Eq(f(x),C1 - sqrt(-1/C2)*log(-C2*sqrt(-1/C2) + x) + + sqrt(-1/C2)*log(C2*sqrt(-1/C2) + x))], + 'slow': True, + }, + + 'reducible_02': { + 'eq': -exp(x) + (x*Derivative(f(x), (x, 2)) + Derivative(f(x), x))/x, + 'sol': [Eq(f(x), C1 + C2*log(x) + exp(x) - Ei(x))], + 'slow': True, + }, + + 'reducible_03': { + 'eq': Eq(sqrt(2) * f(x).diff(x,x,x) + f(x).diff(x), 0), + 'sol': [Eq(f(x), C1 + C2*sin(2**Rational(3, 4)*x/2) + C3*cos(2**Rational(3, 4)*x/2))], + 'slow': True, + }, + + 'reducible_04': { + 'eq': f(x).diff(x, 2) + 2*f(x).diff(x), + 'sol': [Eq(f(x), C1 + C2*exp(-2*x))], + }, + + 'reducible_05': { + 'eq': f(x).diff(x, 3) + f(x).diff(x, 2) - 6*f(x).diff(x), + 'sol': [Eq(f(x), C1 + C2*exp(-3*x) + C3*exp(2*x))], + 'slow': True, + }, + + 'reducible_06': { + 'eq': f(x).diff(x, 4) - f(x).diff(x, 3) - 4*f(x).diff(x, 2) + \ + 4*f(x).diff(x), + 'sol': [Eq(f(x), C1 + C2*exp(-2*x) + C3*exp(x) + C4*exp(2*x))], + 'slow': True, + }, + + 'reducible_07': { + 'eq': f(x).diff(x, 4) + 3*f(x).diff(x, 3), + 'sol': [Eq(f(x), C1 + C2*x + C3*x**2 + C4*exp(-3*x))], + 'slow': True, + }, + + 'reducible_08': { + 'eq': f(x).diff(x, 4) - 2*f(x).diff(x, 2), + 'sol': [Eq(f(x), C1 + C2*x + C3*exp(-sqrt(2)*x) + C4*exp(sqrt(2)*x))], + 'slow': True, + }, + + 'reducible_09': { + 'eq': f(x).diff(x, 4) + 4*f(x).diff(x, 2), + 'sol': [Eq(f(x), C1 + C2*x + C3*sin(2*x) + C4*cos(2*x))], + 'slow': True, + }, + + 'reducible_10': { + 'eq': f(x).diff(x, 5) + 2*f(x).diff(x, 3) + f(x).diff(x), + 'sol': [Eq(f(x), C1 + C2*x*sin(x) + C2*cos(x) - C3*x*cos(x) + C3*sin(x) + C4*sin(x) + C5*cos(x))], + 'slow': True, + }, + + 'reducible_11': { + 'eq': f(x).diff(x, 2) - f(x).diff(x)**3, + 'sol': [Eq(f(x), C1 - sqrt(2)*sqrt(-1/(C2 + x))*(C2 + x)), + Eq(f(x), C1 + sqrt(2)*sqrt(-1/(C2 + x))*(C2 + x))], + 'slow': True, + }, + + # Needs to be a way to know how to combine derivatives in the expression + 'reducible_12': { + 'eq': Derivative(x*f(x), x, x, x) + Derivative(f(x), x, x, x), + 'sol': [Eq(f(x), C1 + C3/Mul(2, (x**2 + 2*x + 1), evaluate=False) + + x*(C2 + C3/Mul(2, (x**2 + 2*x + 1), evaluate=False)))], # 2-arg Mul! + 'slow': True, + }, + } + } + + + +@_add_example_keys +def _get_examples_ode_sol_nth_linear_undetermined_coefficients(): + # examples 3-27 below are from Ordinary Differential Equations, + # Tenenbaum and Pollard, pg. 231 + g = exp(-x) + f2 = f(x).diff(x, 2) + c = 3*f(x).diff(x, 3) + 5*f2 + f(x).diff(x) - f(x) - x + t = symbols("t") + u = symbols("u",cls=Function) + R, L, C, E_0, alpha = symbols("R L C E_0 alpha",positive=True) + omega = Symbol('omega') + return { + 'hint': "nth_linear_constant_coeff_undetermined_coefficients", + 'func': f(x), + 'examples':{ + 'undet_01': { + 'eq': c - x*g, + 'sol': [Eq(f(x), C3*exp(x/3) - x + (C1 + x*(C2 - x**2/24 - 3*x/32))*exp(-x) - 1)], + 'slow': True, + }, + + 'undet_02': { + 'eq': c - g, + 'sol': [Eq(f(x), C3*exp(x/3) - x + (C1 + x*(C2 - x/8))*exp(-x) - 1)], + 'slow': True, + }, + + 'undet_03': { + 'eq': f2 + 3*f(x).diff(x) + 2*f(x) - 4, + 'sol': [Eq(f(x), C1*exp(-2*x) + C2*exp(-x) + 2)], + 'slow': True, + }, + + 'undet_04': { + 'eq': f2 + 3*f(x).diff(x) + 2*f(x) - 12*exp(x), + 'sol': [Eq(f(x), C1*exp(-2*x) + C2*exp(-x) + 2*exp(x))], + 'slow': True, + }, + + 'undet_05': { + 'eq': f2 + 3*f(x).diff(x) + 2*f(x) - exp(I*x), + 'sol': [Eq(f(x), (S(3)/10 + I/10)*(C1*exp(-2*x) + C2*exp(-x) - I*exp(I*x)))], + 'slow': True, + }, + + 'undet_06': { + 'eq': f2 + 3*f(x).diff(x) + 2*f(x) - sin(x), + 'sol': [Eq(f(x), C1*exp(-2*x) + C2*exp(-x) + sin(x)/10 - 3*cos(x)/10)], + 'slow': True, + }, + + 'undet_07': { + 'eq': f2 + 3*f(x).diff(x) + 2*f(x) - cos(x), + 'sol': [Eq(f(x), C1*exp(-2*x) + C2*exp(-x) + 3*sin(x)/10 + cos(x)/10)], + 'slow': True, + }, + + 'undet_08': { + 'eq': f2 + 3*f(x).diff(x) + 2*f(x) - (8 + 6*exp(x) + 2*sin(x)), + 'sol': [Eq(f(x), C1*exp(-2*x) + C2*exp(-x) + exp(x) + sin(x)/5 - 3*cos(x)/5 + 4)], + 'slow': True, + }, + + 'undet_09': { + 'eq': f2 + f(x).diff(x) + f(x) - x**2, + 'sol': [Eq(f(x), -2*x + x**2 + (C1*sin(x*sqrt(3)/2) + C2*cos(x*sqrt(3)/2))*exp(-x/2))], + 'slow': True, + }, + + 'undet_10': { + 'eq': f2 - 2*f(x).diff(x) - 8*f(x) - 9*x*exp(x) - 10*exp(-x), + 'sol': [Eq(f(x), -x*exp(x) - 2*exp(-x) + C1*exp(-2*x) + C2*exp(4*x))], + 'slow': True, + }, + + 'undet_11': { + 'eq': f2 - 3*f(x).diff(x) - 2*exp(2*x)*sin(x), + 'sol': [Eq(f(x), C1 + C2*exp(3*x) - 3*exp(2*x)*sin(x)/5 - exp(2*x)*cos(x)/5)], + 'slow': True, + }, + + 'undet_12': { + 'eq': f(x).diff(x, 4) - 2*f2 + f(x) - x + sin(x), + 'sol': [Eq(f(x), x - sin(x)/4 + (C1 + C2*x)*exp(-x) + (C3 + C4*x)*exp(x))], + 'slow': True, + }, + + 'undet_13': { + 'eq': f2 + f(x).diff(x) - x**2 - 2*x, + 'sol': [Eq(f(x), C1 + x**3/3 + C2*exp(-x))], + 'slow': True, + }, + + 'undet_14': { + 'eq': f2 + f(x).diff(x) - x - sin(2*x), + 'sol': [Eq(f(x), C1 - x - sin(2*x)/5 - cos(2*x)/10 + x**2/2 + C2*exp(-x))], + 'slow': True, + }, + + 'undet_15': { + 'eq': f2 + f(x) - 4*x*sin(x), + 'sol': [Eq(f(x), (C1 - x**2)*cos(x) + (C2 + x)*sin(x))], + 'slow': True, + }, + + 'undet_16': { + 'eq': f2 + 4*f(x) - x*sin(2*x), + 'sol': [Eq(f(x), (C1 - x**2/8)*cos(2*x) + (C2 + x/16)*sin(2*x))], + 'slow': True, + }, + + 'undet_17': { + 'eq': f2 + 2*f(x).diff(x) + f(x) - x**2*exp(-x), + 'sol': [Eq(f(x), (C1 + x*(C2 + x**3/12))*exp(-x))], + 'slow': True, + }, + + 'undet_18': { + 'eq': f(x).diff(x, 3) + 3*f2 + 3*f(x).diff(x) + f(x) - 2*exp(-x) + \ + x**2*exp(-x), + 'sol': [Eq(f(x), (C1 + x*(C2 + x*(C3 - x**3/60 + x/3)))*exp(-x))], + 'slow': True, + }, + + 'undet_19': { + 'eq': f2 + 3*f(x).diff(x) + 2*f(x) - exp(-2*x) - x**2, + 'sol': [Eq(f(x), C2*exp(-x) + x**2/2 - x*Rational(3,2) + (C1 - x)*exp(-2*x) + Rational(7,4))], + 'slow': True, + }, + + 'undet_20': { + 'eq': f2 - 3*f(x).diff(x) + 2*f(x) - x*exp(-x), + 'sol': [Eq(f(x), C1*exp(x) + C2*exp(2*x) + (6*x + 5)*exp(-x)/36)], + 'slow': True, + }, + + 'undet_21': { + 'eq': f2 + f(x).diff(x) - 6*f(x) - x - exp(2*x), + 'sol': [Eq(f(x), Rational(-1, 36) - x/6 + C2*exp(-3*x) + (C1 + x/5)*exp(2*x))], + 'slow': True, + }, + + 'undet_22': { + 'eq': f2 + f(x) - sin(x) - exp(-x), + 'sol': [Eq(f(x), C2*sin(x) + (C1 - x/2)*cos(x) + exp(-x)/2)], + 'slow': True, + }, + + 'undet_23': { + 'eq': f(x).diff(x, 3) - 3*f2 + 3*f(x).diff(x) - f(x) - exp(x), + 'sol': [Eq(f(x), (C1 + x*(C2 + x*(C3 + x/6)))*exp(x))], + 'slow': True, + }, + + 'undet_24': { + 'eq': f2 + f(x) - S.Half - cos(2*x)/2, + 'sol': [Eq(f(x), S.Half - cos(2*x)/6 + C1*sin(x) + C2*cos(x))], + 'slow': True, + }, + + 'undet_25': { + 'eq': f(x).diff(x, 3) - f(x).diff(x) - exp(2*x)*(S.Half - cos(2*x)/2), + 'sol': [Eq(f(x), C1 + C2*exp(-x) + C3*exp(x) + (-21*sin(2*x) + 27*cos(2*x) + 130)*exp(2*x)/1560)], + 'slow': True, + }, + + #Note: 'undet_26' is referred in 'undet_37' + 'undet_26': { + 'eq': (f(x).diff(x, 5) + 2*f(x).diff(x, 3) + f(x).diff(x) - 2*x - + sin(x) - cos(x)), + 'sol': [Eq(f(x), C1 + x**2 + (C2 + x*(C3 - x/8))*sin(x) + (C4 + x*(C5 + x/8))*cos(x))], + 'slow': True, + }, + + 'undet_27': { + 'eq': f2 + f(x) - cos(x)/2 + cos(3*x)/2, + 'sol': [Eq(f(x), cos(3*x)/16 + C2*cos(x) + (C1 + x/4)*sin(x))], + 'slow': True, + }, + + 'undet_28': { + 'eq': f(x).diff(x) - 1, + 'sol': [Eq(f(x), C1 + x)], + 'slow': True, + }, + + # https://github.com/sympy/sympy/issues/19358 + 'undet_29': { + 'eq': f2 + f(x).diff(x) + exp(x-C1), + 'sol': [Eq(f(x), C2 + C3*exp(-x) - exp(-C1 + x)/2)], + 'slow': True, + }, + + # https://github.com/sympy/sympy/issues/18408 + 'undet_30': { + 'eq': f(x).diff(x, 3) - f(x).diff(x) - sinh(x), + 'sol': [Eq(f(x), C1 + C2*exp(-x) + C3*exp(x) + x*sinh(x)/2)], + }, + + 'undet_31': { + 'eq': f(x).diff(x, 2) - 49*f(x) - sinh(3*x), + 'sol': [Eq(f(x), C1*exp(-7*x) + C2*exp(7*x) - sinh(3*x)/40)], + }, + + 'undet_32': { + 'eq': f(x).diff(x, 3) - f(x).diff(x) - sinh(x) - exp(x), + 'sol': [Eq(f(x), C1 + C3*exp(-x) + x*sinh(x)/2 + (C2 + x/2)*exp(x))], + }, + + # https://github.com/sympy/sympy/issues/5096 + 'undet_33': { + 'eq': f(x).diff(x, x) + f(x) - x*sin(x - 2), + 'sol': [Eq(f(x), C1*sin(x) + C2*cos(x) - x**2*cos(x - 2)/4 + x*sin(x - 2)/4)], + }, + + 'undet_34': { + 'eq': f(x).diff(x, 2) + f(x) - x**4*sin(x-1), + 'sol': [ Eq(f(x), C1*sin(x) + C2*cos(x) - x**5*cos(x - 1)/10 + x**4*sin(x - 1)/4 + x**3*cos(x - 1)/2 - 3*x**2*sin(x - 1)/4 - 3*x*cos(x - 1)/4)], + }, + + 'undet_35': { + 'eq': f(x).diff(x, 2) - f(x) - exp(x - 1), + 'sol': [Eq(f(x), C2*exp(-x) + (C1 + x*exp(-1)/2)*exp(x))], + }, + + 'undet_36': { + 'eq': f(x).diff(x, 2)+f(x)-(sin(x-2)+1), + 'sol': [Eq(f(x), C1*sin(x) + C2*cos(x) - x*cos(x - 2)/2 + 1)], + }, + + # Equivalent to example_name 'undet_26'. + # This previously failed because the algorithm for undetermined coefficients + # didn't know to multiply exp(I*x) by sufficient x because it is linearly + # dependent on sin(x) and cos(x). + 'undet_37': { + 'eq': f(x).diff(x, 5) + 2*f(x).diff(x, 3) + f(x).diff(x) - 2*x - exp(I*x), + 'sol': [Eq(f(x), C1 + x**2*(I*exp(I*x)/8 + 1) + (C2 + C3*x)*sin(x) + (C4 + C5*x)*cos(x))], + }, + + # https://github.com/sympy/sympy/issues/12623 + 'undet_38': { + 'eq': Eq( u(t).diff(t,t) + R /L*u(t).diff(t) + 1/(L*C)*u(t), alpha), + 'sol': [Eq(u(t), C*L*alpha + C2*exp(-t*(R + sqrt(C*R**2 - 4*L)/sqrt(C))/(2*L)) + + C1*exp(t*(-R + sqrt(C*R**2 - 4*L)/sqrt(C))/(2*L)))], + 'func': u(t) + }, + + 'undet_39': { + 'eq': Eq( L*C*u(t).diff(t,t) + R*C*u(t).diff(t) + u(t), E_0*exp(I*omega*t) ), + 'sol': [Eq(u(t), C2*exp(-t*(R + sqrt(C*R**2 - 4*L)/sqrt(C))/(2*L)) + + C1*exp(t*(-R + sqrt(C*R**2 - 4*L)/sqrt(C))/(2*L)) + - E_0*exp(I*omega*t)/(C*L*omega**2 - I*C*R*omega - 1))], + 'func': u(t), + }, + + # https://github.com/sympy/sympy/issues/6879 + 'undet_40': { + 'eq': Eq(Derivative(f(x), x, 2) - 2*Derivative(f(x), x) + f(x), sin(x)), + 'sol': [Eq(f(x), (C1 + C2*x)*exp(x) + cos(x)/2)], + }, + } + } + + +@_add_example_keys +def _get_examples_ode_sol_separable(): + # test_separable1-5 are from Ordinary Differential Equations, Tenenbaum and + # Pollard, pg. 55 + t,a = symbols('a,t') + m = 96 + g = 9.8 + k = .2 + f1 = g * m + v = Function('v') + return { + 'hint': "separable", + 'func': f(x), + 'examples':{ + 'separable_01': { + 'eq': f(x).diff(x) - f(x), + 'sol': [Eq(f(x), C1*exp(x))], + }, + + 'separable_02': { + 'eq': x*f(x).diff(x) - f(x), + 'sol': [Eq(f(x), C1*x)], + }, + + 'separable_03': { + 'eq': f(x).diff(x) + sin(x), + 'sol': [Eq(f(x), C1 + cos(x))], + }, + + 'separable_04': { + 'eq': f(x)**2 + 1 - (x**2 + 1)*f(x).diff(x), + 'sol': [Eq(f(x), tan(C1 + atan(x)))], + }, + + 'separable_05': { + 'eq': f(x).diff(x)/tan(x) - f(x) - 2, + 'sol': [Eq(f(x), C1/cos(x) - 2)], + }, + + 'separable_06': { + 'eq': f(x).diff(x) * (1 - sin(f(x))) - 1, + 'sol': [Eq(-x + f(x) + cos(f(x)), C1)], + }, + + 'separable_07': { + 'eq': f(x)*x**2*f(x).diff(x) - f(x)**3 - 2*x**2*f(x).diff(x), + 'sol': [Eq(f(x), (-x - sqrt(x*(4*C1*x + x - 4)))/(C1*x - 1)/2), + Eq(f(x), (-x + sqrt(x*(4*C1*x + x - 4)))/(C1*x - 1)/2)], + 'slow': True, + }, + + 'separable_08': { + 'eq': f(x)**2 - 1 - (2*f(x) + x*f(x))*f(x).diff(x), + 'sol': [Eq(f(x), -sqrt(C1*x**2 + 4*C1*x + 4*C1 + 1)), + Eq(f(x), sqrt(C1*x**2 + 4*C1*x + 4*C1 + 1))], + 'slow': True, + }, + + 'separable_09': { + 'eq': x*log(x)*f(x).diff(x) + sqrt(1 + f(x)**2), + 'sol': [Eq(f(x), sinh(C1 - log(log(x))))], #One more solution is f(x)=I + 'slow': True, + 'checkodesol_XFAIL': True, + }, + + 'separable_10': { + 'eq': exp(x + 1)*tan(f(x)) + cos(f(x))*f(x).diff(x), + 'sol': [Eq(E*exp(x) + log(cos(f(x)) - 1)/2 - log(cos(f(x)) + 1)/2 + cos(f(x)), C1)], + 'slow': True, + }, + + 'separable_11': { + 'eq': (x*cos(f(x)) + x**2*sin(f(x))*f(x).diff(x) - a**2*sin(f(x))*f(x).diff(x)), + 'sol': [ + Eq(f(x), -acos(C1*sqrt(-a**2 + x**2)) + 2*pi), + Eq(f(x), acos(C1*sqrt(-a**2 + x**2))) + ], + 'slow': True, + }, + + 'separable_12': { + 'eq': f(x).diff(x) - f(x)*tan(x), + 'sol': [Eq(f(x), C1/cos(x))], + }, + + 'separable_13': { + 'eq': (x - 1)*cos(f(x))*f(x).diff(x) - 2*x*sin(f(x)), + 'sol': [ + Eq(f(x), pi - asin(C1*(x**2 - 2*x + 1)*exp(2*x))), + Eq(f(x), asin(C1*(x**2 - 2*x + 1)*exp(2*x))) + ], + }, + + 'separable_14': { + 'eq': f(x).diff(x) - f(x)*log(f(x))/tan(x), + 'sol': [Eq(f(x), exp(C1*sin(x)))], + }, + + 'separable_15': { + 'eq': x*f(x).diff(x) + (1 + f(x)**2)*atan(f(x)), + 'sol': [Eq(f(x), tan(C1/x))], #Two more solutions are f(x)=0 and f(x)=I + 'slow': True, + 'checkodesol_XFAIL': True, + }, + + 'separable_16': { + 'eq': f(x).diff(x) + x*(f(x) + 1), + 'sol': [Eq(f(x), -1 + C1*exp(-x**2/2))], + }, + + 'separable_17': { + 'eq': exp(f(x)**2)*(x**2 + 2*x + 1) + (x*f(x) + f(x))*f(x).diff(x), + 'sol': [ + Eq(f(x), -sqrt(log(1/(C1 + x**2 + 2*x)))), + Eq(f(x), sqrt(log(1/(C1 + x**2 + 2*x)))) + ], + }, + + 'separable_18': { + 'eq': f(x).diff(x) + f(x), + 'sol': [Eq(f(x), C1*exp(-x))], + }, + + 'separable_19': { + 'eq': sin(x)*cos(2*f(x)) + cos(x)*sin(2*f(x))*f(x).diff(x), + 'sol': [Eq(f(x), pi - acos(C1/cos(x)**2)/2), Eq(f(x), acos(C1/cos(x)**2)/2)], + }, + + 'separable_20': { + 'eq': (1 - x)*f(x).diff(x) - x*(f(x) + 1), + 'sol': [Eq(f(x), (C1*exp(-x) - x + 1)/(x - 1))], + }, + + 'separable_21': { + 'eq': f(x)*diff(f(x), x) + x - 3*x*f(x)**2, + 'sol': [Eq(f(x), -sqrt(3)*sqrt(C1*exp(3*x**2) + 1)/3), + Eq(f(x), sqrt(3)*sqrt(C1*exp(3*x**2) + 1)/3)], + }, + + 'separable_22': { + 'eq': f(x).diff(x) - exp(x + f(x)), + 'sol': [Eq(f(x), log(-1/(C1 + exp(x))))], + 'XFAIL': ['lie_group'] #It shows 'NoneType' object is not subscriptable for lie_group. + }, + + # https://github.com/sympy/sympy/issues/7081 + 'separable_23': { + 'eq': x*(f(x).diff(x)) + 1 - f(x)**2, + 'sol': [Eq(f(x), (-C1 - x**2)/(-C1 + x**2))], + }, + + # https://github.com/sympy/sympy/issues/10379 + 'separable_24': { + 'eq': f(t).diff(t)-(1-51.05*y*f(t)), + 'sol': [Eq(f(t), (0.019588638589618023*exp(y*(C1 - 51.049999999999997*t)) + 0.019588638589618023)/y)], + 'func': f(t), + }, + + # https://github.com/sympy/sympy/issues/15999 + 'separable_25': { + 'eq': f(x).diff(x) - C1*f(x), + 'sol': [Eq(f(x), C2*exp(C1*x))], + }, + + 'separable_26': { + 'eq': f1 - k * (v(t) ** 2) - m * Derivative(v(t)), + 'sol': [Eq(v(t), -68.585712797928991/tanh(C1 - 0.14288690166235204*t))], + 'func': v(t), + 'checkodesol_XFAIL': True, + }, + + #https://github.com/sympy/sympy/issues/22155 + 'separable_27': { + 'eq': f(x).diff(x) - exp(f(x) - x), + 'sol': [Eq(f(x), log(-exp(x)/(C1*exp(x) - 1)))], + } + } + } + + +@_add_example_keys +def _get_examples_ode_sol_1st_exact(): + # Type: Exact differential equation, p(x,f) + q(x,f)*f' == 0, + # where dp/df == dq/dx + ''' + Example 7 is an exact equation that fails under the exact engine. It is caught + by first order homogeneous albeit with a much contorted solution. The + exact engine fails because of a poorly simplified integral of q(0,y)dy, + where q is the function multiplying f'. The solutions should be + Eq(sqrt(x**2+f(x)**2)**3+y**3, C1). The equation below is + equivalent, but it is so complex that checkodesol fails, and takes a long + time to do so. + ''' + return { + 'hint': "1st_exact", + 'func': f(x), + 'examples':{ + '1st_exact_01': { + 'eq': sin(x)*cos(f(x)) + cos(x)*sin(f(x))*f(x).diff(x), + 'sol': [Eq(f(x), -acos(C1/cos(x)) + 2*pi), Eq(f(x), acos(C1/cos(x)))], + 'slow': True, + }, + + '1st_exact_02': { + 'eq': (2*x*f(x) + 1)/f(x) + (f(x) - x)/f(x)**2*f(x).diff(x), + 'sol': [Eq(f(x), exp(C1 - x**2 + LambertW(-x*exp(-C1 + x**2))))], + 'XFAIL': ['lie_group'], #It shows dsolve raises an exception: List index out of range for lie_group + 'slow': True, + 'checkodesol_XFAIL':True + }, + + '1st_exact_03': { + 'eq': 2*x + f(x)*cos(x) + (2*f(x) + sin(x) - sin(f(x)))*f(x).diff(x), + 'sol': [Eq(f(x)*sin(x) + cos(f(x)) + x**2 + f(x)**2, C1)], + 'XFAIL': ['lie_group'], #It goes into infinite loop for lie_group. + 'slow': True, + }, + + '1st_exact_04': { + 'eq': cos(f(x)) - (x*sin(f(x)) - f(x)**2)*f(x).diff(x), + 'sol': [Eq(x*cos(f(x)) + f(x)**3/3, C1)], + 'slow': True, + }, + + '1st_exact_05': { + 'eq': 2*x*f(x) + (x**2 + f(x)**2)*f(x).diff(x), + 'sol': [Eq(x**2*f(x) + f(x)**3/3, C1)], + 'slow': True, + 'simplify_flag':False + }, + + # This was from issue: https://github.com/sympy/sympy/issues/11290 + '1st_exact_06': { + 'eq': cos(f(x)) - (x*sin(f(x)) - f(x)**2)*f(x).diff(x), + 'sol': [Eq(x*cos(f(x)) + f(x)**3/3, C1)], + 'simplify_flag':False + }, + + '1st_exact_07': { + 'eq': x*sqrt(x**2 + f(x)**2) - (x**2*f(x)/(f(x) - sqrt(x**2 + f(x)**2)))*f(x).diff(x), + 'sol': [Eq(log(x), + C1 - 9*sqrt(1 + f(x)**2/x**2)*asinh(f(x)/x)/(-27*f(x)/x + + 27*sqrt(1 + f(x)**2/x**2)) - 9*sqrt(1 + f(x)**2/x**2)* + log(1 - sqrt(1 + f(x)**2/x**2)*f(x)/x + 2*f(x)**2/x**2)/ + (-27*f(x)/x + 27*sqrt(1 + f(x)**2/x**2)) + + 9*asinh(f(x)/x)*f(x)/(x*(-27*f(x)/x + 27*sqrt(1 + f(x)**2/x**2))) + + 9*f(x)*log(1 - sqrt(1 + f(x)**2/x**2)*f(x)/x + 2*f(x)**2/x**2)/ + (x*(-27*f(x)/x + 27*sqrt(1 + f(x)**2/x**2))))], + 'slow': True, + 'dsolve_too_slow':True + }, + + # Type: a(x)f'(x)+b(x)*f(x)+c(x)=0 + '1st_exact_08': { + 'eq': Eq(x**2*f(x).diff(x) + 3*x*f(x) - sin(x)/x, 0), + 'sol': [Eq(f(x), (C1 - cos(x))/x**3)], + }, + + # these examples are from test_exact_enhancement + '1st_exact_09': { + 'eq': f(x)/x**2 + ((f(x)*x - 1)/x)*f(x).diff(x), + 'sol': [Eq(f(x), (i*sqrt(C1*x**2 + 1) + 1)/x) for i in (-1, 1)], + }, + + '1st_exact_10': { + 'eq': (x*f(x) - 1) + f(x).diff(x)*(x**2 - x*f(x)), + 'sol': [Eq(f(x), x - sqrt(C1 + x**2 - 2*log(x))), Eq(f(x), x + sqrt(C1 + x**2 - 2*log(x)))], + }, + + '1st_exact_11': { + 'eq': (x + 2)*sin(f(x)) + f(x).diff(x)*x*cos(f(x)), + 'sol': [Eq(f(x), -asin(C1*exp(-x)/x**2) + pi), Eq(f(x), asin(C1*exp(-x)/x**2))], + }, + } + } + + +@_add_example_keys +def _get_examples_ode_sol_nth_linear_var_of_parameters(): + g = exp(-x) + f2 = f(x).diff(x, 2) + c = 3*f(x).diff(x, 3) + 5*f2 + f(x).diff(x) - f(x) - x + return { + 'hint': "nth_linear_constant_coeff_variation_of_parameters", + 'func': f(x), + 'examples':{ + 'var_of_parameters_01': { + 'eq': c - x*g, + 'sol': [Eq(f(x), C3*exp(x/3) - x + (C1 + x*(C2 - x**2/24 - 3*x/32))*exp(-x) - 1)], + 'slow': True, + }, + + 'var_of_parameters_02': { + 'eq': c - g, + 'sol': [Eq(f(x), C3*exp(x/3) - x + (C1 + x*(C2 - x/8))*exp(-x) - 1)], + 'slow': True, + }, + + 'var_of_parameters_03': { + 'eq': f(x).diff(x) - 1, + 'sol': [Eq(f(x), C1 + x)], + 'slow': True, + }, + + 'var_of_parameters_04': { + 'eq': f2 + 3*f(x).diff(x) + 2*f(x) - 4, + 'sol': [Eq(f(x), C1*exp(-2*x) + C2*exp(-x) + 2)], + 'slow': True, + }, + + 'var_of_parameters_05': { + 'eq': f2 + 3*f(x).diff(x) + 2*f(x) - 12*exp(x), + 'sol': [Eq(f(x), C1*exp(-2*x) + C2*exp(-x) + 2*exp(x))], + 'slow': True, + }, + + 'var_of_parameters_06': { + 'eq': f2 - 2*f(x).diff(x) - 8*f(x) - 9*x*exp(x) - 10*exp(-x), + 'sol': [Eq(f(x), -x*exp(x) - 2*exp(-x) + C1*exp(-2*x) + C2*exp(4*x))], + 'slow': True, + }, + + 'var_of_parameters_07': { + 'eq': f2 + 2*f(x).diff(x) + f(x) - x**2*exp(-x), + 'sol': [Eq(f(x), (C1 + x*(C2 + x**3/12))*exp(-x))], + 'slow': True, + }, + + 'var_of_parameters_08': { + 'eq': f2 - 3*f(x).diff(x) + 2*f(x) - x*exp(-x), + 'sol': [Eq(f(x), C1*exp(x) + C2*exp(2*x) + (6*x + 5)*exp(-x)/36)], + 'slow': True, + }, + + 'var_of_parameters_09': { + 'eq': f(x).diff(x, 3) - 3*f2 + 3*f(x).diff(x) - f(x) - exp(x), + 'sol': [Eq(f(x), (C1 + x*(C2 + x*(C3 + x/6)))*exp(x))], + 'slow': True, + }, + + 'var_of_parameters_10': { + 'eq': f2 + 2*f(x).diff(x) + f(x) - exp(-x)/x, + 'sol': [Eq(f(x), (C1 + x*(C2 + log(x)))*exp(-x))], + 'slow': True, + }, + + 'var_of_parameters_11': { + 'eq': f2 + f(x) - 1/sin(x)*1/cos(x), + 'sol': [Eq(f(x), (C1 + log(sin(x) - 1)/2 - log(sin(x) + 1)/2 + )*cos(x) + (C2 + log(cos(x) - 1)/2 - log(cos(x) + 1)/2)*sin(x))], + 'slow': True, + }, + + 'var_of_parameters_12': { + 'eq': f(x).diff(x, 4) - 1/x, + 'sol': [Eq(f(x), C1 + C2*x + C3*x**2 + x**3*(C4 + log(x)/6))], + 'slow': True, + }, + + # These were from issue: https://github.com/sympy/sympy/issues/15996 + 'var_of_parameters_13': { + 'eq': f(x).diff(x, 5) + 2*f(x).diff(x, 3) + f(x).diff(x) - 2*x - exp(I*x), + 'sol': [Eq(f(x), C1 + x**2 + (C2 + x*(C3 - x/8 + 3*exp(I*x)/2 + 3*exp(-I*x)/2) + 5*exp(2*I*x)/16 + 2*I*exp(I*x) - 2*I*exp(-I*x))*sin(x) + (C4 + x*(C5 + I*x/8 + 3*I*exp(I*x)/2 - 3*I*exp(-I*x)/2) + + 5*I*exp(2*I*x)/16 - 2*exp(I*x) - 2*exp(-I*x))*cos(x) - I*exp(I*x))], + }, + + 'var_of_parameters_14': { + 'eq': f(x).diff(x, 5) + 2*f(x).diff(x, 3) + f(x).diff(x) - exp(I*x), + 'sol': [Eq(f(x), C1 + (C2 + x*(C3 - x/8) + 5*exp(2*I*x)/16)*sin(x) + (C4 + x*(C5 + I*x/8) + 5*I*exp(2*I*x)/16)*cos(x) - I*exp(I*x))], + }, + + # https://github.com/sympy/sympy/issues/14395 + 'var_of_parameters_15': { + 'eq': Derivative(f(x), x, x) + 9*f(x) - sec(x), + 'sol': [Eq(f(x), (C1 - x/3 + sin(2*x)/3)*sin(3*x) + (C2 + log(cos(x)) + - 2*log(cos(x)**2)/3 + 2*cos(x)**2/3)*cos(3*x))], + 'slow': True, + }, + } + } + + +@_add_example_keys +def _get_examples_ode_sol_2nd_linear_bessel(): + return { + 'hint': "2nd_linear_bessel", + 'func': f(x), + 'examples':{ + '2nd_lin_bessel_01': { + 'eq': x**2*(f(x).diff(x, 2)) + x*(f(x).diff(x)) + (x**2 - 4)*f(x), + 'sol': [Eq(f(x), C1*besselj(2, x) + C2*bessely(2, x))], + }, + + '2nd_lin_bessel_02': { + 'eq': x**2*(f(x).diff(x, 2)) + x*(f(x).diff(x)) + (x**2 +25)*f(x), + 'sol': [Eq(f(x), C1*besselj(5*I, x) + C2*bessely(5*I, x))], + }, + + '2nd_lin_bessel_03': { + 'eq': x**2*(f(x).diff(x, 2)) + x*(f(x).diff(x)) + (x**2)*f(x), + 'sol': [Eq(f(x), C1*besselj(0, x) + C2*bessely(0, x))], + }, + + '2nd_lin_bessel_04': { + 'eq': x**2*(f(x).diff(x, 2)) + x*(f(x).diff(x)) + (81*x**2 -S(1)/9)*f(x), + 'sol': [Eq(f(x), C1*besselj(S(1)/3, 9*x) + C2*bessely(S(1)/3, 9*x))], + }, + + '2nd_lin_bessel_05': { + 'eq': x**2*(f(x).diff(x, 2)) + x*(f(x).diff(x)) + (x**4 - 4)*f(x), + 'sol': [Eq(f(x), C1*besselj(1, x**2/2) + C2*bessely(1, x**2/2))], + }, + + '2nd_lin_bessel_06': { + 'eq': x**2*(f(x).diff(x, 2)) + 2*x*(f(x).diff(x)) + (x**4 - 4)*f(x), + 'sol': [Eq(f(x), (C1*besselj(sqrt(17)/4, x**2/2) + C2*bessely(sqrt(17)/4, x**2/2))/sqrt(x))], + }, + + '2nd_lin_bessel_07': { + 'eq': x**2*(f(x).diff(x, 2)) + x*(f(x).diff(x)) + (x**2 - S(1)/4)*f(x), + 'sol': [Eq(f(x), C1*besselj(S(1)/2, x) + C2*bessely(S(1)/2, x))], + }, + + '2nd_lin_bessel_08': { + 'eq': x**2*(f(x).diff(x, 2)) - 3*x*(f(x).diff(x)) + (4*x + 4)*f(x), + 'sol': [Eq(f(x), x**2*(C1*besselj(0, 4*sqrt(x)) + C2*bessely(0, 4*sqrt(x))))], + }, + + '2nd_lin_bessel_09': { + 'eq': x*(f(x).diff(x, 2)) - f(x).diff(x) + 4*x**3*f(x), + 'sol': [Eq(f(x), x*(C1*besselj(S(1)/2, x**2) + C2*bessely(S(1)/2, x**2)))], + }, + + '2nd_lin_bessel_10': { + 'eq': (x-2)**2*(f(x).diff(x, 2)) - (x-2)*f(x).diff(x) + 4*(x-2)**2*f(x), + 'sol': [Eq(f(x), (x - 2)*(C1*besselj(1, 2*x - 4) + C2*bessely(1, 2*x - 4)))], + }, + + # https://github.com/sympy/sympy/issues/4414 + '2nd_lin_bessel_11': { + 'eq': f(x).diff(x, x) + 2/x*f(x).diff(x) + f(x), + 'sol': [Eq(f(x), (C1*besselj(S(1)/2, x) + C2*bessely(S(1)/2, x))/sqrt(x))], + }, + '2nd_lin_bessel_12': { + 'eq': x**2*f(x).diff(x, 2) + x*f(x).diff(x) + (a**2*x**2/c**2 - b**2)*f(x), + 'sol': [Eq(f(x), C1*besselj(sqrt(b**2), x*sqrt(a**2/c**2)) + C2*bessely(sqrt(b**2), x*sqrt(a**2/c**2)))], + }, + } + } + + +@_add_example_keys +def _get_examples_ode_sol_2nd_2F1_hypergeometric(): + return { + 'hint': "2nd_hypergeometric", + 'func': f(x), + 'examples':{ + '2nd_2F1_hyper_01': { + 'eq': x*(x-1)*f(x).diff(x, 2) + (S(3)/2 -2*x)*f(x).diff(x) + 2*f(x), + 'sol': [Eq(f(x), C1*x**(S(5)/2)*hyper((S(3)/2, S(1)/2), (S(7)/2,), x) + C2*hyper((-1, -2), (-S(3)/2,), x))], + }, + + '2nd_2F1_hyper_02': { + 'eq': x*(x-1)*f(x).diff(x, 2) + (S(7)/2*x)*f(x).diff(x) + f(x), + 'sol': [Eq(f(x), (C1*(1 - x)**(S(5)/2)*hyper((S(1)/2, 2), (S(7)/2,), 1 - x) + + C2*hyper((-S(1)/2, -2), (-S(3)/2,), 1 - x))/(x - 1)**(S(5)/2))], + }, + + '2nd_2F1_hyper_03': { + 'eq': x*(x-1)*f(x).diff(x, 2) + (S(3)+ S(7)/2*x)*f(x).diff(x) + f(x), + 'sol': [Eq(f(x), (C1*(1 - x)**(S(11)/2)*hyper((S(1)/2, 2), (S(13)/2,), 1 - x) + + C2*hyper((-S(7)/2, -5), (-S(9)/2,), 1 - x))/(x - 1)**(S(11)/2))], + }, + + '2nd_2F1_hyper_04': { + 'eq': -x**(S(5)/7)*(-416*x**(S(9)/7)/9 - 2385*x**(S(5)/7)/49 + S(298)*x/3)*f(x)/(196*(-x**(S(6)/7) + + x)**2*(x**(S(6)/7) + x)**2) + Derivative(f(x), (x, 2)), + 'sol': [Eq(f(x), x**(S(45)/98)*(C1*x**(S(4)/49)*hyper((S(1)/3, -S(1)/2), (S(9)/7,), x**(S(2)/7)) + + C2*hyper((S(1)/21, -S(11)/14), (S(5)/7,), x**(S(2)/7)))/(x**(S(2)/7) - 1)**(S(19)/84))], + 'checkodesol_XFAIL':True, + }, + } + } + +@_add_example_keys +def _get_examples_ode_sol_2nd_nonlinear_autonomous_conserved(): + return { + 'hint': "2nd_nonlinear_autonomous_conserved", + 'func': f(x), + 'examples': { + '2nd_nonlinear_autonomous_conserved_01': { + 'eq': f(x).diff(x, 2) + exp(f(x)) + log(f(x)), + 'sol': [ + Eq(Integral(1/sqrt(C1 - 2*_u*log(_u) + 2*_u - 2*exp(_u)), (_u, f(x))), C2 + x), + Eq(Integral(1/sqrt(C1 - 2*_u*log(_u) + 2*_u - 2*exp(_u)), (_u, f(x))), C2 - x) + ], + 'simplify_flag': False, + }, + '2nd_nonlinear_autonomous_conserved_02': { + 'eq': f(x).diff(x, 2) + cbrt(f(x)) + 1/f(x), + 'sol': [ + Eq(sqrt(2)*Integral(1/sqrt(2*C1 - 3*_u**Rational(4, 3) - 4*log(_u)), (_u, f(x))), C2 + x), + Eq(sqrt(2)*Integral(1/sqrt(2*C1 - 3*_u**Rational(4, 3) - 4*log(_u)), (_u, f(x))), C2 - x) + ], + 'simplify_flag': False, + }, + '2nd_nonlinear_autonomous_conserved_03': { + 'eq': f(x).diff(x, 2) + sin(f(x)), + 'sol': [ + Eq(Integral(1/sqrt(C1 + 2*cos(_u)), (_u, f(x))), C2 + x), + Eq(Integral(1/sqrt(C1 + 2*cos(_u)), (_u, f(x))), C2 - x) + ], + 'simplify_flag': False, + }, + '2nd_nonlinear_autonomous_conserved_04': { + 'eq': f(x).diff(x, 2) + cosh(f(x)), + 'sol': [ + Eq(Integral(1/sqrt(C1 - 2*sinh(_u)), (_u, f(x))), C2 + x), + Eq(Integral(1/sqrt(C1 - 2*sinh(_u)), (_u, f(x))), C2 - x) + ], + 'simplify_flag': False, + }, + '2nd_nonlinear_autonomous_conserved_05': { + 'eq': f(x).diff(x, 2) + asin(f(x)), + 'sol': [ + Eq(Integral(1/sqrt(C1 - 2*_u*asin(_u) - 2*sqrt(1 - _u**2)), (_u, f(x))), C2 + x), + Eq(Integral(1/sqrt(C1 - 2*_u*asin(_u) - 2*sqrt(1 - _u**2)), (_u, f(x))), C2 - x) + ], + 'simplify_flag': False, + 'XFAIL': ['2nd_nonlinear_autonomous_conserved_Integral'] + } + } + } + + +@_add_example_keys +def _get_examples_ode_sol_separable_reduced(): + df = f(x).diff(x) + return { + 'hint': "separable_reduced", + 'func': f(x), + 'examples':{ + 'separable_reduced_01': { + 'eq': x* df + f(x)* (1 / (x**2*f(x) - 1)), + 'sol': [Eq(log(x**2*f(x))/3 + log(x**2*f(x) - Rational(3, 2))/6, C1 + log(x))], + 'simplify_flag': False, + 'XFAIL': ['lie_group'], #It hangs. + }, + + #Note: 'separable_reduced_02' is referred in 'separable_reduced_11' + 'separable_reduced_02': { + 'eq': f(x).diff(x) + (f(x) / (x**4*f(x) - x)), + 'sol': [Eq(log(x**3*f(x))/4 + log(x**3*f(x) - Rational(4,3))/12, C1 + log(x))], + 'simplify_flag': False, + 'checkodesol_XFAIL':True, #It hangs for this. + }, + + 'separable_reduced_03': { + 'eq': x*df + f(x)*(x**2*f(x)), + 'sol': [Eq(log(x**2*f(x))/2 - log(x**2*f(x) - 2)/2, C1 + log(x))], + 'simplify_flag': False, + }, + + 'separable_reduced_04': { + 'eq': Eq(f(x).diff(x) + f(x)/x * (1 + (x**(S(2)/3)*f(x))**2), 0), + 'sol': [Eq(-3*log(x**(S(2)/3)*f(x)) + 3*log(3*x**(S(4)/3)*f(x)**2 + 1)/2, C1 + log(x))], + 'simplify_flag': False, + }, + + 'separable_reduced_05': { + 'eq': Eq(f(x).diff(x) + f(x)/x * (1 + (x*f(x))**2), 0), + 'sol': [Eq(f(x), -sqrt(2)*sqrt(1/(C1 + log(x)))/(2*x)),\ + Eq(f(x), sqrt(2)*sqrt(1/(C1 + log(x)))/(2*x))], + }, + + 'separable_reduced_06': { + 'eq': Eq(f(x).diff(x) + (x**4*f(x)**2 + x**2*f(x))*f(x)/(x*(x**6*f(x)**3 + x**4*f(x)**2)), 0), + 'sol': [Eq(f(x), C1 + 1/(2*x**2))], + }, + + 'separable_reduced_07': { + 'eq': Eq(f(x).diff(x) + (f(x)**2)*f(x)/(x), 0), + 'sol': [ + Eq(f(x), -sqrt(2)*sqrt(1/(C1 + log(x)))/2), + Eq(f(x), sqrt(2)*sqrt(1/(C1 + log(x)))/2) + ], + }, + + 'separable_reduced_08': { + 'eq': Eq(f(x).diff(x) + (f(x)+3)*f(x)/(x*(f(x)+2)), 0), + 'sol': [Eq(-log(f(x) + 3)/3 - 2*log(f(x))/3, C1 + log(x))], + 'simplify_flag': False, + 'XFAIL': ['lie_group'], #It hangs. + }, + + 'separable_reduced_09': { + 'eq': Eq(f(x).diff(x) + (f(x)+3)*f(x)/x, 0), + 'sol': [Eq(f(x), 3/(C1*x**3 - 1))], + }, + + 'separable_reduced_10': { + 'eq': Eq(f(x).diff(x) + (f(x)**2+f(x))*f(x)/(x), 0), + 'sol': [Eq(- log(x) - log(f(x) + 1) + log(f(x)) + 1/f(x), C1)], + 'XFAIL': ['lie_group'],#No algorithms are implemented to solve equation -C1 + x*(_y + 1)*exp(-1/_y)/_y + + }, + + # Equivalent to example_name 'separable_reduced_02'. Only difference is testing with simplify=True + 'separable_reduced_11': { + 'eq': f(x).diff(x) + (f(x) / (x**4*f(x) - x)), + 'sol': [Eq(f(x), -sqrt(2)*sqrt(3*3**Rational(1,3)*(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) +- 3*3**Rational(2,3)*exp(12*C1)/(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) + 2/x**6)/6 +- sqrt(2)*sqrt(-3*3**Rational(1,3)*(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) ++ 3*3**Rational(2,3)*exp(12*C1)/(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) + 4/x**6 +- 4*sqrt(2)/(x**9*sqrt(3*3**Rational(1,3)*(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) +- 3*3**Rational(2,3)*exp(12*C1)/(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) + 2/x**6)))/6 + 1/(3*x**3)), +Eq(f(x), -sqrt(2)*sqrt(3*3**Rational(1,3)*(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) +- 3*3**Rational(2,3)*exp(12*C1)/(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) + 2/x**6)/6 ++ sqrt(2)*sqrt(-3*3**Rational(1,3)*(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) ++ 3*3**Rational(2,3)*exp(12*C1)/(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) + 4/x**6 +- 4*sqrt(2)/(x**9*sqrt(3*3**Rational(1,3)*(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) +- 3*3**Rational(2,3)*exp(12*C1)/(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) + 2/x**6)))/6 + 1/(3*x**3)), +Eq(f(x), sqrt(2)*sqrt(3*3**Rational(1,3)*(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) +- 3*3**Rational(2,3)*exp(12*C1)/(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) + 2/x**6)/6 +- sqrt(2)*sqrt(-3*3**Rational(1,3)*(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) ++ 3*3**Rational(2,3)*exp(12*C1)/(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) ++ 4/x**6 + 4*sqrt(2)/(x**9*sqrt(3*3**Rational(1,3)*(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) +- 3*3**Rational(2,3)*exp(12*C1)/(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) + 2/x**6)))/6 + 1/(3*x**3)), +Eq(f(x), sqrt(2)*sqrt(3*3**Rational(1,3)*(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) +- 3*3**Rational(2,3)*exp(12*C1)/(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) + 2/x**6)/6 ++ sqrt(2)*sqrt(-3*3**Rational(1,3)*(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) + 3*3**Rational(2,3)*exp(12*C1)/(sqrt((3*exp(12*C1) ++ x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) + 4/x**6 + 4*sqrt(2)/(x**9*sqrt(3*3**Rational(1,3)*(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) +- exp(12*C1)/x**6)**Rational(1,3) - 3*3**Rational(2,3)*exp(12*C1)/(sqrt((3*exp(12*C1) + x**(-12))*exp(24*C1)) - exp(12*C1)/x**6)**Rational(1,3) + 2/x**6)))/6 + 1/(3*x**3))], + 'checkodesol_XFAIL':True, #It hangs for this. + 'slow': True, + }, + + #These were from issue: https://github.com/sympy/sympy/issues/6247 + 'separable_reduced_12': { + 'eq': x**2*f(x)**2 + x*Derivative(f(x), x), + 'sol': [Eq(f(x), 2*C1/(C1*x**2 - 1))], + }, + } + } + + +@_add_example_keys +def _get_examples_ode_sol_lie_group(): + a, b, c = symbols("a b c") + return { + 'hint': "lie_group", + 'func': f(x), + 'examples':{ + #Example 1-4 and 19-20 were from issue: https://github.com/sympy/sympy/issues/17322 + 'lie_group_01': { + 'eq': x*f(x).diff(x)*(f(x)+4) + (f(x)**2) -2*f(x)-2*x, + 'sol': [], + 'dsolve_too_slow': True, + 'checkodesol_too_slow': True, + }, + + 'lie_group_02': { + 'eq': x*f(x).diff(x)*(f(x)+4) + (f(x)**2) -2*f(x)-2*x, + 'sol': [], + 'dsolve_too_slow': True, + }, + + 'lie_group_03': { + 'eq': Eq(x**7*Derivative(f(x), x) + 5*x**3*f(x)**2 - (2*x**2 + 2)*f(x)**3, 0), + 'sol': [], + 'dsolve_too_slow': True, + }, + + 'lie_group_04': { + 'eq': f(x).diff(x) - (f(x) - x*log(x))**2/x**2 + log(x), + 'sol': [], + 'XFAIL': ['lie_group'], + }, + + 'lie_group_05': { + 'eq': f(x).diff(x)**2, + 'sol': [Eq(f(x), C1)], + 'XFAIL': ['factorable'], #It raises Not Implemented error + }, + + 'lie_group_06': { + 'eq': Eq(f(x).diff(x), x**2*f(x)), + 'sol': [Eq(f(x), C1*exp(x**3)**Rational(1, 3))], + }, + + 'lie_group_07': { + 'eq': f(x).diff(x) + a*f(x) - c*exp(b*x), + 'sol': [Eq(f(x), Piecewise(((-C1*(a + b) + c*exp(x*(a + b)))*exp(-a*x)/(a + b),\ + Ne(a, -b)), ((-C1 + c*x)*exp(-a*x), True)))], + }, + + 'lie_group_08': { + 'eq': f(x).diff(x) + 2*x*f(x) - x*exp(-x**2), + 'sol': [Eq(f(x), (C1 + x**2/2)*exp(-x**2))], + }, + + 'lie_group_09': { + 'eq': (1 + 2*x)*(f(x).diff(x)) + 2 - 4*exp(-f(x)), + 'sol': [Eq(f(x), log(C1/(2*x + 1) + 2))], + }, + + 'lie_group_10': { + 'eq': x**2*(f(x).diff(x)) - f(x) + x**2*exp(x - (1/x)), + 'sol': [Eq(f(x), (C1 - exp(x))*exp(-1/x))], + 'XFAIL': ['factorable'], #It raises Recursion Error (maixmum depth exceeded) + }, + + 'lie_group_11': { + 'eq': x**2*f(x)**2 + x*Derivative(f(x), x), + 'sol': [Eq(f(x), 2/(C1 + x**2))], + }, + + 'lie_group_12': { + 'eq': diff(f(x),x) + 2*x*f(x) - x*exp(-x**2), + 'sol': [Eq(f(x), exp(-x**2)*(C1 + x**2/2))], + }, + + 'lie_group_13': { + 'eq': diff(f(x),x) + f(x)*cos(x) - exp(2*x), + 'sol': [Eq(f(x), exp(-sin(x))*(C1 + Integral(exp(2*x)*exp(sin(x)), x)))], + }, + + 'lie_group_14': { + 'eq': diff(f(x),x) + f(x)*cos(x) - sin(2*x)/2, + 'sol': [Eq(f(x), C1*exp(-sin(x)) + sin(x) - 1)], + }, + + 'lie_group_15': { + 'eq': x*diff(f(x),x) + f(x) - x*sin(x), + 'sol': [Eq(f(x), (C1 - x*cos(x) + sin(x))/x)], + }, + + 'lie_group_16': { + 'eq': x*diff(f(x),x) - f(x) - x/log(x), + 'sol': [Eq(f(x), x*(C1 + log(log(x))))], + }, + + 'lie_group_17': { + 'eq': (f(x).diff(x)-f(x)) * (f(x).diff(x)+f(x)), + 'sol': [Eq(f(x), C1*exp(x)), Eq(f(x), C1*exp(-x))], + }, + + 'lie_group_18': { + 'eq': f(x).diff(x) * (f(x).diff(x) - f(x)), + 'sol': [Eq(f(x), C1*exp(x)), Eq(f(x), C1)], + }, + + 'lie_group_19': { + 'eq': (f(x).diff(x)-f(x)) * (f(x).diff(x)+f(x)), + 'sol': [Eq(f(x), C1*exp(-x)), Eq(f(x), C1*exp(x))], + }, + + 'lie_group_20': { + 'eq': f(x).diff(x)*(f(x).diff(x)+f(x)), + 'sol': [Eq(f(x), C1), Eq(f(x), C1*exp(-x))], + }, + } + } + + +@_add_example_keys +def _get_examples_ode_sol_2nd_linear_airy(): + return { + 'hint': "2nd_linear_airy", + 'func': f(x), + 'examples':{ + '2nd_lin_airy_01': { + 'eq': f(x).diff(x, 2) - x*f(x), + 'sol': [Eq(f(x), C1*airyai(x) + C2*airybi(x))], + }, + + '2nd_lin_airy_02': { + 'eq': f(x).diff(x, 2) + 2*x*f(x), + 'sol': [Eq(f(x), C1*airyai(-2**(S(1)/3)*x) + C2*airybi(-2**(S(1)/3)*x))], + }, + } + } + + +@_add_example_keys +def _get_examples_ode_sol_nth_linear_constant_coeff_homogeneous(): + # From Exercise 20, in Ordinary Differential Equations, + # Tenenbaum and Pollard, pg. 220 + a = Symbol('a', positive=True) + k = Symbol('k', real=True) + r1, r2, r3, r4, r5 = [rootof(x**5 + 11*x - 2, n) for n in range(5)] + r6, r7, r8, r9, r10 = [rootof(x**5 - 3*x + 1, n) for n in range(5)] + r11, r12, r13, r14, r15 = [rootof(x**5 - 100*x**3 + 1000*x + 1, n) for n in range(5)] + r16, r17, r18, r19, r20 = [rootof(x**5 - x**4 + 10, n) for n in range(5)] + r21, r22, r23, r24, r25 = [rootof(x**5 - x + 1, n) for n in range(5)] + E = exp(1) + return { + 'hint': "nth_linear_constant_coeff_homogeneous", + 'func': f(x), + 'examples':{ + 'lin_const_coeff_hom_01': { + 'eq': f(x).diff(x, 2) + 2*f(x).diff(x), + 'sol': [Eq(f(x), C1 + C2*exp(-2*x))], + }, + + 'lin_const_coeff_hom_02': { + 'eq': f(x).diff(x, 2) - 3*f(x).diff(x) + 2*f(x), + 'sol': [Eq(f(x), (C1 + C2*exp(x))*exp(x))], + }, + + 'lin_const_coeff_hom_03': { + 'eq': f(x).diff(x, 2) - f(x), + 'sol': [Eq(f(x), C1*exp(-x) + C2*exp(x))], + }, + + 'lin_const_coeff_hom_04': { + 'eq': f(x).diff(x, 3) + f(x).diff(x, 2) - 6*f(x).diff(x), + 'sol': [Eq(f(x), C1 + C2*exp(-3*x) + C3*exp(2*x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_05': { + 'eq': 6*f(x).diff(x, 2) - 11*f(x).diff(x) + 4*f(x), + 'sol': [Eq(f(x), C1*exp(x/2) + C2*exp(x*Rational(4, 3)))], + 'slow': True, + }, + + 'lin_const_coeff_hom_06': { + 'eq': Eq(f(x).diff(x, 2) + 2*f(x).diff(x) - f(x), 0), + 'sol': [Eq(f(x), C1*exp(x*(-1 + sqrt(2))) + C2*exp(-x*(sqrt(2) + 1)))], + 'slow': True, + }, + + 'lin_const_coeff_hom_07': { + 'eq': diff(f(x), x, 3) + diff(f(x), x, 2) - 10*diff(f(x), x) - 6*f(x), + 'sol': [Eq(f(x), C1*exp(3*x) + C3*exp(-x*(2 + sqrt(2))) + C2*exp(x*(-2 + sqrt(2))))], + 'slow': True, + }, + + 'lin_const_coeff_hom_08': { + 'eq': f(x).diff(x, 4) - f(x).diff(x, 3) - 4*f(x).diff(x, 2) + \ + 4*f(x).diff(x), + 'sol': [Eq(f(x), C1 + C2*exp(-2*x) + C3*exp(x) + C4*exp(2*x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_09': { + 'eq': f(x).diff(x, 4) + 4*f(x).diff(x, 3) + f(x).diff(x, 2) - \ + 4*f(x).diff(x) - 2*f(x), + 'sol': [Eq(f(x), C3*exp(-x) + C4*exp(x) + (C1*exp(-sqrt(2)*x) + C2*exp(sqrt(2)*x))*exp(-2*x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_10': { + 'eq': f(x).diff(x, 4) - a**2*f(x), + 'sol': [Eq(f(x), C1*exp(-sqrt(a)*x) + C2*exp(sqrt(a)*x) + C3*sin(sqrt(a)*x) + C4*cos(sqrt(a)*x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_11': { + 'eq': f(x).diff(x, 2) - 2*k*f(x).diff(x) - 2*f(x), + 'sol': [Eq(f(x), C1*exp(x*(k - sqrt(k**2 + 2))) + C2*exp(x*(k + sqrt(k**2 + 2))))], + 'slow': True, + }, + + 'lin_const_coeff_hom_12': { + 'eq': f(x).diff(x, 2) + 4*k*f(x).diff(x) - 12*k**2*f(x), + 'sol': [Eq(f(x), C1*exp(-6*k*x) + C2*exp(2*k*x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_13': { + 'eq': f(x).diff(x, 4), + 'sol': [Eq(f(x), C1 + C2*x + C3*x**2 + C4*x**3)], + 'slow': True, + }, + + 'lin_const_coeff_hom_14': { + 'eq': f(x).diff(x, 2) + 4*f(x).diff(x) + 4*f(x), + 'sol': [Eq(f(x), (C1 + C2*x)*exp(-2*x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_15': { + 'eq': 3*f(x).diff(x, 3) + 5*f(x).diff(x, 2) + f(x).diff(x) - f(x), + 'sol': [Eq(f(x), (C1 + C2*x)*exp(-x) + C3*exp(x/3))], + 'slow': True, + }, + + 'lin_const_coeff_hom_16': { + 'eq': f(x).diff(x, 3) - 6*f(x).diff(x, 2) + 12*f(x).diff(x) - 8*f(x), + 'sol': [Eq(f(x), (C1 + x*(C2 + C3*x))*exp(2*x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_17': { + 'eq': f(x).diff(x, 2) - 2*a*f(x).diff(x) + a**2*f(x), + 'sol': [Eq(f(x), (C1 + C2*x)*exp(a*x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_18': { + 'eq': f(x).diff(x, 4) + 3*f(x).diff(x, 3), + 'sol': [Eq(f(x), C1 + C2*x + C3*x**2 + C4*exp(-3*x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_19': { + 'eq': f(x).diff(x, 4) - 2*f(x).diff(x, 2), + 'sol': [Eq(f(x), C1 + C2*x + C3*exp(-sqrt(2)*x) + C4*exp(sqrt(2)*x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_20': { + 'eq': f(x).diff(x, 4) + 2*f(x).diff(x, 3) - 11*f(x).diff(x, 2) - \ + 12*f(x).diff(x) + 36*f(x), + 'sol': [Eq(f(x), (C1 + C2*x)*exp(-3*x) + (C3 + C4*x)*exp(2*x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_21': { + 'eq': 36*f(x).diff(x, 4) - 37*f(x).diff(x, 2) + 4*f(x).diff(x) + 5*f(x), + 'sol': [Eq(f(x), C1*exp(-x) + C2*exp(-x/3) + C3*exp(x/2) + C4*exp(x*Rational(5, 6)))], + 'slow': True, + }, + + 'lin_const_coeff_hom_22': { + 'eq': f(x).diff(x, 4) - 8*f(x).diff(x, 2) + 16*f(x), + 'sol': [Eq(f(x), (C1 + C2*x)*exp(-2*x) + (C3 + C4*x)*exp(2*x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_23': { + 'eq': f(x).diff(x, 2) - 2*f(x).diff(x) + 5*f(x), + 'sol': [Eq(f(x), (C1*sin(2*x) + C2*cos(2*x))*exp(x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_24': { + 'eq': f(x).diff(x, 2) - f(x).diff(x) + f(x), + 'sol': [Eq(f(x), (C1*sin(x*sqrt(3)/2) + C2*cos(x*sqrt(3)/2))*exp(x/2))], + 'slow': True, + }, + + 'lin_const_coeff_hom_25': { + 'eq': f(x).diff(x, 4) + 5*f(x).diff(x, 2) + 6*f(x), + 'sol': [Eq(f(x), + C1*sin(sqrt(2)*x) + C2*sin(sqrt(3)*x) + C3*cos(sqrt(2)*x) + C4*cos(sqrt(3)*x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_26': { + 'eq': f(x).diff(x, 2) - 4*f(x).diff(x) + 20*f(x), + 'sol': [Eq(f(x), (C1*sin(4*x) + C2*cos(4*x))*exp(2*x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_27': { + 'eq': f(x).diff(x, 4) + 4*f(x).diff(x, 2) + 4*f(x), + 'sol': [Eq(f(x), (C1 + C2*x)*sin(x*sqrt(2)) + (C3 + C4*x)*cos(x*sqrt(2)))], + 'slow': True, + }, + + 'lin_const_coeff_hom_28': { + 'eq': f(x).diff(x, 3) + 8*f(x), + 'sol': [Eq(f(x), (C1*sin(x*sqrt(3)) + C2*cos(x*sqrt(3)))*exp(x) + C3*exp(-2*x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_29': { + 'eq': f(x).diff(x, 4) + 4*f(x).diff(x, 2), + 'sol': [Eq(f(x), C1 + C2*x + C3*sin(2*x) + C4*cos(2*x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_30': { + 'eq': f(x).diff(x, 5) + 2*f(x).diff(x, 3) + f(x).diff(x), + 'sol': [Eq(f(x), C1 + (C2 + C3*x)*sin(x) + (C4 + C5*x)*cos(x))], + 'slow': True, + }, + + 'lin_const_coeff_hom_31': { + 'eq': f(x).diff(x, 4) + f(x).diff(x, 2) + f(x), + 'sol': [Eq(f(x), (C1*sin(sqrt(3)*x/2) + C2*cos(sqrt(3)*x/2))*exp(-x/2) + + (C3*sin(sqrt(3)*x/2) + C4*cos(sqrt(3)*x/2))*exp(x/2))], + 'slow': True, + }, + + 'lin_const_coeff_hom_32': { + 'eq': f(x).diff(x, 4) + 4*f(x).diff(x, 2) + f(x), + 'sol': [Eq(f(x), C1*sin(x*sqrt(-sqrt(3) + 2)) + C2*sin(x*sqrt(sqrt(3) + 2)) + + C3*cos(x*sqrt(-sqrt(3) + 2)) + C4*cos(x*sqrt(sqrt(3) + 2)))], + 'slow': True, + }, + + # One real root, two complex conjugate pairs + 'lin_const_coeff_hom_33': { + 'eq': f(x).diff(x, 5) + 11*f(x).diff(x) - 2*f(x), + 'sol': [Eq(f(x), + C5*exp(r1*x) + exp(re(r2)*x) * (C1*sin(im(r2)*x) + C2*cos(im(r2)*x)) + + exp(re(r4)*x) * (C3*sin(im(r4)*x) + C4*cos(im(r4)*x)))], + 'checkodesol_XFAIL':True, #It Hangs + }, + + # Three real roots, one complex conjugate pair + 'lin_const_coeff_hom_34': { + 'eq': f(x).diff(x,5) - 3*f(x).diff(x) + f(x), + 'sol': [Eq(f(x), + C3*exp(r6*x) + C4*exp(r7*x) + C5*exp(r8*x) + + exp(re(r9)*x) * (C1*sin(im(r9)*x) + C2*cos(im(r9)*x)))], + 'checkodesol_XFAIL':True, #It Hangs + }, + + # Five distinct real roots + 'lin_const_coeff_hom_35': { + 'eq': f(x).diff(x,5) - 100*f(x).diff(x,3) + 1000*f(x).diff(x) + f(x), + 'sol': [Eq(f(x), C1*exp(r11*x) + C2*exp(r12*x) + C3*exp(r13*x) + C4*exp(r14*x) + C5*exp(r15*x))], + 'checkodesol_XFAIL':True, #It Hangs + }, + + # Rational root and unsolvable quintic + 'lin_const_coeff_hom_36': { + 'eq': f(x).diff(x, 6) - 6*f(x).diff(x, 5) + 5*f(x).diff(x, 4) + 10*f(x).diff(x) - 50 * f(x), + 'sol': [Eq(f(x), + C5*exp(5*x) + + C6*exp(x*r16) + + exp(re(r17)*x) * (C1*sin(im(r17)*x) + C2*cos(im(r17)*x)) + + exp(re(r19)*x) * (C3*sin(im(r19)*x) + C4*cos(im(r19)*x)))], + 'checkodesol_XFAIL':True, #It Hangs + }, + + # Five double roots (this is (x**5 - x + 1)**2) + 'lin_const_coeff_hom_37': { + 'eq': f(x).diff(x, 10) - 2*f(x).diff(x, 6) + 2*f(x).diff(x, 5) + + f(x).diff(x, 2) - 2*f(x).diff(x, 1) + f(x), + 'sol': [Eq(f(x), (C1 + C2*x)*exp(x*r21) + (-((C3 + C4*x)*sin(x*im(r22))) + + (C5 + C6*x)*cos(x*im(r22)))*exp(x*re(r22)) + (-((C7 + C8*x)*sin(x*im(r24))) + + (C10*x + C9)*cos(x*im(r24)))*exp(x*re(r24)))], + 'checkodesol_XFAIL':True, #It Hangs + }, + + 'lin_const_coeff_hom_38': { + 'eq': Eq(sqrt(2) * f(x).diff(x,x,x) + f(x).diff(x), 0), + 'sol': [Eq(f(x), C1 + C2*sin(2**Rational(3, 4)*x/2) + C3*cos(2**Rational(3, 4)*x/2))], + }, + + 'lin_const_coeff_hom_39': { + 'eq': Eq(E * f(x).diff(x,x,x) + f(x).diff(x), 0), + 'sol': [Eq(f(x), C1 + C2*sin(x/sqrt(E)) + C3*cos(x/sqrt(E)))], + }, + + 'lin_const_coeff_hom_40': { + 'eq': Eq(pi * f(x).diff(x,x,x) + f(x).diff(x), 0), + 'sol': [Eq(f(x), C1 + C2*sin(x/sqrt(pi)) + C3*cos(x/sqrt(pi)))], + }, + + 'lin_const_coeff_hom_41': { + 'eq': Eq(I * f(x).diff(x,x,x) + f(x).diff(x), 0), + 'sol': [Eq(f(x), C1 + C2*exp(-sqrt(I)*x) + C3*exp(sqrt(I)*x))], + }, + + 'lin_const_coeff_hom_42': { + 'eq': f(x).diff(x, x) + y*f(x), + 'sol': [Eq(f(x), C1*exp(-x*sqrt(-y)) + C2*exp(x*sqrt(-y)))], + }, + + 'lin_const_coeff_hom_43': { + 'eq': Eq(9*f(x).diff(x, x) + f(x), 0), + 'sol': [Eq(f(x), C1*sin(x/3) + C2*cos(x/3))], + }, + + 'lin_const_coeff_hom_44': { + 'eq': Eq(9*f(x).diff(x, x), f(x)), + 'sol': [Eq(f(x), C1*exp(-x/3) + C2*exp(x/3))], + }, + + 'lin_const_coeff_hom_45': { + 'eq': Eq(f(x).diff(x, x) - 3*diff(f(x), x) + 2*f(x), 0), + 'sol': [Eq(f(x), (C1 + C2*exp(x))*exp(x))], + }, + + 'lin_const_coeff_hom_46': { + 'eq': Eq(f(x).diff(x, x) - 4*diff(f(x), x) + 4*f(x), 0), + 'sol': [Eq(f(x), (C1 + C2*x)*exp(2*x))], + }, + + # Type: 2nd order, constant coefficients (two real equal roots) + 'lin_const_coeff_hom_47': { + 'eq': Eq(f(x).diff(x, x) + 2*diff(f(x), x) + 3*f(x), 0), + 'sol': [Eq(f(x), (C1*sin(x*sqrt(2)) + C2*cos(x*sqrt(2)))*exp(-x))], + }, + + #These were from issue: https://github.com/sympy/sympy/issues/6247 + 'lin_const_coeff_hom_48': { + 'eq': f(x).diff(x, x) + 4*f(x), + 'sol': [Eq(f(x), C1*sin(2*x) + C2*cos(2*x))], + }, + } + } + + +@_add_example_keys +def _get_examples_ode_sol_1st_homogeneous_coeff_subs_dep_div_indep(): + return { + 'hint': "1st_homogeneous_coeff_subs_dep_div_indep", + 'func': f(x), + 'examples':{ + 'dep_div_indep_01': { + 'eq': f(x)/x*cos(f(x)/x) - (x/f(x)*sin(f(x)/x) + cos(f(x)/x))*f(x).diff(x), + 'sol': [Eq(log(x), C1 - log(f(x)*sin(f(x)/x)/x))], + 'slow': True + }, + + #indep_div_dep actually has a simpler solution for example 2 but it runs too slow. + 'dep_div_indep_02': { + 'eq': x*f(x).diff(x) - f(x) - x*sin(f(x)/x), + 'sol': [Eq(log(x), log(C1) + log(cos(f(x)/x) - 1)/2 - log(cos(f(x)/x) + 1)/2)], + 'simplify_flag':False, + }, + + 'dep_div_indep_03': { + 'eq': x*exp(f(x)/x) - f(x)*sin(f(x)/x) + x*sin(f(x)/x)*f(x).diff(x), + 'sol': [Eq(log(x), C1 + exp(-f(x)/x)*sin(f(x)/x)/2 + exp(-f(x)/x)*cos(f(x)/x)/2)], + 'slow': True + }, + + 'dep_div_indep_04': { + 'eq': f(x).diff(x) - f(x)/x + 1/sin(f(x)/x), + 'sol': [Eq(f(x), x*(-acos(C1 + log(x)) + 2*pi)), Eq(f(x), x*acos(C1 + log(x)))], + 'slow': True + }, + + # previous code was testing with these other solution: + # example5_solb = Eq(f(x), log(log(C1/x)**(-x))) + 'dep_div_indep_05': { + 'eq': x*exp(f(x)/x) + f(x) - x*f(x).diff(x), + 'sol': [Eq(f(x), log((1/(C1 - log(x)))**x))], + 'checkodesol_XFAIL':True, #(because of **x?) + }, + } + } + +@_add_example_keys +def _get_examples_ode_sol_linear_coefficients(): + return { + 'hint': "linear_coefficients", + 'func': f(x), + 'examples':{ + 'linear_coeff_01': { + 'eq': f(x).diff(x) + (3 + 2*f(x))/(x + 3), + 'sol': [Eq(f(x), C1/(x**2 + 6*x + 9) - Rational(3, 2))], + }, + } + } + +@_add_example_keys +def _get_examples_ode_sol_1st_homogeneous_coeff_best(): + return { + 'hint': "1st_homogeneous_coeff_best", + 'func': f(x), + 'examples':{ + # previous code was testing this with other solution: + # example1_solb = Eq(-f(x)/(1 + log(x/f(x))), C1) + '1st_homogeneous_coeff_best_01': { + 'eq': f(x) + (x*log(f(x)/x) - 2*x)*diff(f(x), x), + 'sol': [Eq(f(x), -exp(C1)*LambertW(-x*exp(-C1 + 1)))], + 'checkodesol_XFAIL':True, #(because of LambertW?) + }, + + '1st_homogeneous_coeff_best_02': { + 'eq': 2*f(x)*exp(x/f(x)) + f(x)*f(x).diff(x) - 2*x*exp(x/f(x))*f(x).diff(x), + 'sol': [Eq(log(f(x)), C1 - 2*exp(x/f(x)))], + }, + + # previous code was testing this with other solution: + # example3_solb = Eq(log(C1*x*sqrt(1/x)*sqrt(f(x))) + x**2/(2*f(x)**2), 0) + '1st_homogeneous_coeff_best_03': { + 'eq': 2*x**2*f(x) + f(x)**3 + (x*f(x)**2 - 2*x**3)*f(x).diff(x), + 'sol': [Eq(f(x), exp(2*C1 + LambertW(-2*x**4*exp(-4*C1))/2)/x)], + 'checkodesol_XFAIL':True, #(because of LambertW?) + }, + + '1st_homogeneous_coeff_best_04': { + 'eq': (x + sqrt(f(x)**2 - x*f(x)))*f(x).diff(x) - f(x), + 'sol': [Eq(log(f(x)), C1 - 2*sqrt(-x/f(x) + 1))], + 'slow': True, + }, + + '1st_homogeneous_coeff_best_05': { + 'eq': x + f(x) - (x - f(x))*f(x).diff(x), + 'sol': [Eq(log(x), C1 - log(sqrt(1 + f(x)**2/x**2)) + atan(f(x)/x))], + }, + + '1st_homogeneous_coeff_best_06': { + 'eq': x*f(x).diff(x) - f(x) - x*sin(f(x)/x), + 'sol': [Eq(f(x), 2*x*atan(C1*x))], + }, + + '1st_homogeneous_coeff_best_07': { + 'eq': x**2 + f(x)**2 - 2*x*f(x)*f(x).diff(x), + 'sol': [Eq(f(x), -sqrt(x*(C1 + x))), Eq(f(x), sqrt(x*(C1 + x)))], + }, + + '1st_homogeneous_coeff_best_08': { + 'eq': f(x)**2 + (x*sqrt(f(x)**2 - x**2) - x*f(x))*f(x).diff(x), + 'sol': [Eq(f(x), -C1*sqrt(-x/(x - 2*C1))), Eq(f(x), C1*sqrt(-x/(x - 2*C1)))], + 'checkodesol_XFAIL': True # solutions are valid in a range + }, + } + } + + +def _get_all_examples(): + all_examples = _get_examples_ode_sol_euler_homogeneous + \ + _get_examples_ode_sol_euler_undetermined_coeff + \ + _get_examples_ode_sol_euler_var_para + \ + _get_examples_ode_sol_factorable + \ + _get_examples_ode_sol_bernoulli + \ + _get_examples_ode_sol_nth_algebraic + \ + _get_examples_ode_sol_riccati + \ + _get_examples_ode_sol_1st_linear + \ + _get_examples_ode_sol_1st_exact + \ + _get_examples_ode_sol_almost_linear + \ + _get_examples_ode_sol_nth_order_reducible + \ + _get_examples_ode_sol_nth_linear_undetermined_coefficients + \ + _get_examples_ode_sol_liouville + \ + _get_examples_ode_sol_separable + \ + _get_examples_ode_sol_1st_rational_riccati + \ + _get_examples_ode_sol_nth_linear_var_of_parameters + \ + _get_examples_ode_sol_2nd_linear_bessel + \ + _get_examples_ode_sol_2nd_2F1_hypergeometric + \ + _get_examples_ode_sol_2nd_nonlinear_autonomous_conserved + \ + _get_examples_ode_sol_separable_reduced + \ + _get_examples_ode_sol_lie_group + \ + _get_examples_ode_sol_2nd_linear_airy + \ + _get_examples_ode_sol_nth_linear_constant_coeff_homogeneous +\ + _get_examples_ode_sol_1st_homogeneous_coeff_best +\ + _get_examples_ode_sol_1st_homogeneous_coeff_subs_dep_div_indep +\ + _get_examples_ode_sol_linear_coefficients + + return all_examples diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_subscheck.py b/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_subscheck.py new file mode 100644 index 0000000000000000000000000000000000000000..799c2854e878208721b600767de350cda08cd7e5 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_subscheck.py @@ -0,0 +1,203 @@ +from sympy.core.function import (Derivative, Function, diff) +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.relational import Eq +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.functions.special.error_functions import (Ei, erf, erfi) +from sympy.integrals.integrals import Integral + +from sympy.solvers.ode.subscheck import checkodesol, checksysodesol + +from sympy.functions import besselj, bessely + +from sympy.testing.pytest import raises, slow + + +C0, C1, C2, C3, C4 = symbols('C0:5') +u, x, y, z = symbols('u,x:z', real=True) +f = Function('f') +g = Function('g') +h = Function('h') + + +@slow +def test_checkodesol(): + # For the most part, checkodesol is well tested in the tests below. + # These tests only handle cases not checked below. + raises(ValueError, lambda: checkodesol(f(x, y).diff(x), Eq(f(x, y), x))) + raises(ValueError, lambda: checkodesol(f(x).diff(x), Eq(f(x, y), + x), f(x, y))) + assert checkodesol(f(x).diff(x), Eq(f(x, y), x)) == \ + (False, -f(x).diff(x) + f(x, y).diff(x) - 1) + assert checkodesol(f(x).diff(x), Eq(f(x), x)) is not True + assert checkodesol(f(x).diff(x), Eq(f(x), x)) == (False, 1) + sol1 = Eq(f(x)**5 + 11*f(x) - 2*f(x) + x, 0) + assert checkodesol(diff(sol1.lhs, x), sol1) == (True, 0) + assert checkodesol(diff(sol1.lhs, x)*exp(f(x)), sol1) == (True, 0) + assert checkodesol(diff(sol1.lhs, x, 2), sol1) == (True, 0) + assert checkodesol(diff(sol1.lhs, x, 2)*exp(f(x)), sol1) == (True, 0) + assert checkodesol(diff(sol1.lhs, x, 3), sol1) == (True, 0) + assert checkodesol(diff(sol1.lhs, x, 3)*exp(f(x)), sol1) == (True, 0) + assert checkodesol(diff(sol1.lhs, x, 3), Eq(f(x), x*log(x))) == \ + (False, 60*x**4*((log(x) + 1)**2 + log(x))*( + log(x) + 1)*log(x)**2 - 5*x**4*log(x)**4 - 9) + assert checkodesol(diff(exp(f(x)) + x, x)*x, Eq(exp(f(x)) + x, 0)) == \ + (True, 0) + assert checkodesol(diff(exp(f(x)) + x, x)*x, Eq(exp(f(x)) + x, 0), + solve_for_func=False) == (True, 0) + assert checkodesol(f(x).diff(x, 2), [Eq(f(x), C1 + C2*x), + Eq(f(x), C2 + C1*x), Eq(f(x), C1*x + C2*x**2)]) == \ + [(True, 0), (True, 0), (False, C2)] + assert checkodesol(f(x).diff(x, 2), {Eq(f(x), C1 + C2*x), + Eq(f(x), C2 + C1*x), Eq(f(x), C1*x + C2*x**2)}) == \ + {(True, 0), (True, 0), (False, C2)} + assert checkodesol(f(x).diff(x) - 1/f(x)/2, Eq(f(x)**2, x)) == \ + [(True, 0), (True, 0)] + assert checkodesol(f(x).diff(x) - f(x), Eq(C1*exp(x), f(x))) == (True, 0) + # Based on test_1st_homogeneous_coeff_ode2_eq3sol. Make sure that + # checkodesol tries back substituting f(x) when it can. + eq3 = x*exp(f(x)/x) + f(x) - x*f(x).diff(x) + sol3 = Eq(f(x), log(log(C1/x)**(-x))) + assert not checkodesol(eq3, sol3)[1].has(f(x)) + # This case was failing intermittently depending on hash-seed: + eqn = Eq(Derivative(x*Derivative(f(x), x), x)/x, exp(x)) + sol = Eq(f(x), C1 + C2*log(x) + exp(x) - Ei(x)) + assert checkodesol(eqn, sol, order=2, solve_for_func=False)[0] + eq = x**2*(f(x).diff(x, 2)) + x*(f(x).diff(x)) + (2*x**2 +25)*f(x) + sol = Eq(f(x), C1*besselj(5*I, sqrt(2)*x) + C2*bessely(5*I, sqrt(2)*x)) + assert checkodesol(eq, sol) == (True, 0) + + eqs = [Eq(f(x).diff(x), f(x) + g(x)), Eq(g(x).diff(x), f(x) + g(x))] + sol = [Eq(f(x), -C1 + C2*exp(2*x)), Eq(g(x), C1 + C2*exp(2*x))] + assert checkodesol(eqs, sol) == (True, [0, 0]) + + +def test_checksysodesol(): + x, y, z = symbols('x, y, z', cls=Function) + t = Symbol('t') + eq = (Eq(diff(x(t),t), 9*y(t)), Eq(diff(y(t),t), 12*x(t))) + sol = [Eq(x(t), 9*C1*exp(-6*sqrt(3)*t) + 9*C2*exp(6*sqrt(3)*t)), \ + Eq(y(t), -6*sqrt(3)*C1*exp(-6*sqrt(3)*t) + 6*sqrt(3)*C2*exp(6*sqrt(3)*t))] + assert checksysodesol(eq, sol) == (True, [0, 0]) + + eq = (Eq(diff(x(t),t), 2*x(t) + 4*y(t)), Eq(diff(y(t),t), 12*x(t) + 41*y(t))) + sol = [Eq(x(t), 4*C1*exp(t*(-sqrt(1713)/2 + Rational(43, 2))) + 4*C2*exp(t*(sqrt(1713)/2 + \ + Rational(43, 2)))), Eq(y(t), C1*(-sqrt(1713)/2 + Rational(39, 2))*exp(t*(-sqrt(1713)/2 + \ + Rational(43, 2))) + C2*(Rational(39, 2) + sqrt(1713)/2)*exp(t*(sqrt(1713)/2 + Rational(43, 2))))] + assert checksysodesol(eq, sol) == (True, [0, 0]) + + eq = (Eq(diff(x(t),t), x(t) + y(t)), Eq(diff(y(t),t), -2*x(t) + 2*y(t))) + sol = [Eq(x(t), (C1*sin(sqrt(7)*t/2) + C2*cos(sqrt(7)*t/2))*exp(t*Rational(3, 2))), \ + Eq(y(t), ((C1/2 - sqrt(7)*C2/2)*sin(sqrt(7)*t/2) + (sqrt(7)*C1/2 + \ + C2/2)*cos(sqrt(7)*t/2))*exp(t*Rational(3, 2)))] + assert checksysodesol(eq, sol) == (True, [0, 0]) + + eq = (Eq(diff(x(t),t), x(t) + y(t) + 9), Eq(diff(y(t),t), 2*x(t) + 5*y(t) + 23)) + sol = [Eq(x(t), C1*exp(t*(-sqrt(6) + 3)) + C2*exp(t*(sqrt(6) + 3)) - \ + Rational(22, 3)), Eq(y(t), C1*(-sqrt(6) + 2)*exp(t*(-sqrt(6) + 3)) + C2*(2 + \ + sqrt(6))*exp(t*(sqrt(6) + 3)) - Rational(5, 3))] + assert checksysodesol(eq, sol) == (True, [0, 0]) + + eq = (Eq(diff(x(t),t), x(t) + y(t) + 81), Eq(diff(y(t),t), -2*x(t) + y(t) + 23)) + sol = [Eq(x(t), (C1*sin(sqrt(2)*t) + C2*cos(sqrt(2)*t))*exp(t) - Rational(58, 3)), \ + Eq(y(t), (sqrt(2)*C1*cos(sqrt(2)*t) - sqrt(2)*C2*sin(sqrt(2)*t))*exp(t) - Rational(185, 3))] + assert checksysodesol(eq, sol) == (True, [0, 0]) + + eq = (Eq(diff(x(t),t), 5*t*x(t) + 2*y(t)), Eq(diff(y(t),t), 2*x(t) + 5*t*y(t))) + sol = [Eq(x(t), (C1*exp(Integral(2, t).doit()) + C2*exp(-(Integral(2, t)).doit()))*\ + exp((Integral(5*t, t)).doit())), Eq(y(t), (C1*exp((Integral(2, t)).doit()) - \ + C2*exp(-(Integral(2, t)).doit()))*exp((Integral(5*t, t)).doit()))] + assert checksysodesol(eq, sol) == (True, [0, 0]) + + eq = (Eq(diff(x(t),t), 5*t*x(t) + t**2*y(t)), Eq(diff(y(t),t), -t**2*x(t) + 5*t*y(t))) + sol = [Eq(x(t), (C1*cos((Integral(t**2, t)).doit()) + C2*sin((Integral(t**2, t)).doit()))*\ + exp((Integral(5*t, t)).doit())), Eq(y(t), (-C1*sin((Integral(t**2, t)).doit()) + \ + C2*cos((Integral(t**2, t)).doit()))*exp((Integral(5*t, t)).doit()))] + assert checksysodesol(eq, sol) == (True, [0, 0]) + + eq = (Eq(diff(x(t),t), 5*t*x(t) + t**2*y(t)), Eq(diff(y(t),t), -t**2*x(t) + (5*t+9*t**2)*y(t))) + sol = [Eq(x(t), (C1*exp((-sqrt(77)/2 + Rational(9, 2))*(Integral(t**2, t)).doit()) + \ + C2*exp((sqrt(77)/2 + Rational(9, 2))*(Integral(t**2, t)).doit()))*exp((Integral(5*t, t)).doit())), \ + Eq(y(t), (C1*(-sqrt(77)/2 + Rational(9, 2))*exp((-sqrt(77)/2 + Rational(9, 2))*(Integral(t**2, t)).doit()) + \ + C2*(sqrt(77)/2 + Rational(9, 2))*exp((sqrt(77)/2 + Rational(9, 2))*(Integral(t**2, t)).doit()))*exp((Integral(5*t, t)).doit()))] + assert checksysodesol(eq, sol) == (True, [0, 0]) + + eq = (Eq(diff(x(t),t,t), 5*x(t) + 43*y(t)), Eq(diff(y(t),t,t), x(t) + 9*y(t))) + root0 = -sqrt(-sqrt(47) + 7) + root1 = sqrt(-sqrt(47) + 7) + root2 = -sqrt(sqrt(47) + 7) + root3 = sqrt(sqrt(47) + 7) + sol = [Eq(x(t), 43*C1*exp(t*root0) + 43*C2*exp(t*root1) + 43*C3*exp(t*root2) + 43*C4*exp(t*root3)), \ + Eq(y(t), C1*(root0**2 - 5)*exp(t*root0) + C2*(root1**2 - 5)*exp(t*root1) + \ + C3*(root2**2 - 5)*exp(t*root2) + C4*(root3**2 - 5)*exp(t*root3))] + assert checksysodesol(eq, sol) == (True, [0, 0]) + + eq = (Eq(diff(x(t),t,t), 8*x(t)+3*y(t)+31), Eq(diff(y(t),t,t), 9*x(t)+7*y(t)+12)) + root0 = -sqrt(-sqrt(109)/2 + Rational(15, 2)) + root1 = sqrt(-sqrt(109)/2 + Rational(15, 2)) + root2 = -sqrt(sqrt(109)/2 + Rational(15, 2)) + root3 = sqrt(sqrt(109)/2 + Rational(15, 2)) + sol = [Eq(x(t), 3*C1*exp(t*root0) + 3*C2*exp(t*root1) + 3*C3*exp(t*root2) + 3*C4*exp(t*root3) - Rational(181, 29)), \ + Eq(y(t), C1*(root0**2 - 8)*exp(t*root0) + C2*(root1**2 - 8)*exp(t*root1) + \ + C3*(root2**2 - 8)*exp(t*root2) + C4*(root3**2 - 8)*exp(t*root3) + Rational(183, 29))] + assert checksysodesol(eq, sol) == (True, [0, 0]) + + eq = (Eq(diff(x(t),t,t) - 9*diff(y(t),t) + 7*x(t),0), Eq(diff(y(t),t,t) + 9*diff(x(t),t) + 7*y(t),0)) + sol = [Eq(x(t), C1*cos(t*(Rational(9, 2) + sqrt(109)/2)) + C2*sin(t*(Rational(9, 2) + sqrt(109)/2)) + \ + C3*cos(t*(-sqrt(109)/2 + Rational(9, 2))) + C4*sin(t*(-sqrt(109)/2 + Rational(9, 2)))), Eq(y(t), -C1*sin(t*(Rational(9, 2) + sqrt(109)/2)) \ + + C2*cos(t*(Rational(9, 2) + sqrt(109)/2)) - C3*sin(t*(-sqrt(109)/2 + Rational(9, 2))) + C4*cos(t*(-sqrt(109)/2 + Rational(9, 2))))] + assert checksysodesol(eq, sol) == (True, [0, 0]) + + eq = (Eq(diff(x(t),t,t), 9*t*diff(y(t),t)-9*y(t)), Eq(diff(y(t),t,t),7*t*diff(x(t),t)-7*x(t))) + I1 = sqrt(6)*7**Rational(1, 4)*sqrt(pi)*erfi(sqrt(6)*7**Rational(1, 4)*t/2)/2 - exp(3*sqrt(7)*t**2/2)/t + I2 = -sqrt(6)*7**Rational(1, 4)*sqrt(pi)*erf(sqrt(6)*7**Rational(1, 4)*t/2)/2 - exp(-3*sqrt(7)*t**2/2)/t + sol = [Eq(x(t), C3*t + t*(9*C1*I1 + 9*C2*I2)), Eq(y(t), C4*t + t*(3*sqrt(7)*C1*I1 - 3*sqrt(7)*C2*I2))] + assert checksysodesol(eq, sol) == (True, [0, 0]) + + eq = (Eq(diff(x(t),t), 21*x(t)), Eq(diff(y(t),t), 17*x(t)+3*y(t)), Eq(diff(z(t),t), 5*x(t)+7*y(t)+9*z(t))) + sol = [Eq(x(t), C1*exp(21*t)), Eq(y(t), 17*C1*exp(21*t)/18 + C2*exp(3*t)), \ + Eq(z(t), 209*C1*exp(21*t)/216 - 7*C2*exp(3*t)/6 + C3*exp(9*t))] + assert checksysodesol(eq, sol) == (True, [0, 0, 0]) + + eq = (Eq(diff(x(t),t),3*y(t)-11*z(t)),Eq(diff(y(t),t),7*z(t)-3*x(t)),Eq(diff(z(t),t),11*x(t)-7*y(t))) + sol = [Eq(x(t), 7*C0 + sqrt(179)*C1*cos(sqrt(179)*t) + (77*C1/3 + 130*C2/3)*sin(sqrt(179)*t)), \ + Eq(y(t), 11*C0 + sqrt(179)*C2*cos(sqrt(179)*t) + (-58*C1/3 - 77*C2/3)*sin(sqrt(179)*t)), \ + Eq(z(t), 3*C0 + sqrt(179)*(-7*C1/3 - 11*C2/3)*cos(sqrt(179)*t) + (11*C1 - 7*C2)*sin(sqrt(179)*t))] + assert checksysodesol(eq, sol) == (True, [0, 0, 0]) + + eq = (Eq(3*diff(x(t),t),4*5*(y(t)-z(t))),Eq(4*diff(y(t),t),3*5*(z(t)-x(t))),Eq(5*diff(z(t),t),3*4*(x(t)-y(t)))) + sol = [Eq(x(t), C0 + 5*sqrt(2)*C1*cos(5*sqrt(2)*t) + (12*C1/5 + 164*C2/15)*sin(5*sqrt(2)*t)), \ + Eq(y(t), C0 + 5*sqrt(2)*C2*cos(5*sqrt(2)*t) + (-51*C1/10 - 12*C2/5)*sin(5*sqrt(2)*t)), \ + Eq(z(t), C0 + 5*sqrt(2)*(-9*C1/25 - 16*C2/25)*cos(5*sqrt(2)*t) + (12*C1/5 - 12*C2/5)*sin(5*sqrt(2)*t))] + assert checksysodesol(eq, sol) == (True, [0, 0, 0]) + + eq = (Eq(diff(x(t),t),4*x(t) - z(t)),Eq(diff(y(t),t),2*x(t)+2*y(t)-z(t)),Eq(diff(z(t),t),3*x(t)+y(t))) + sol = [Eq(x(t), C1*exp(2*t) + C2*t*exp(2*t) + C2*exp(2*t) + C3*t**2*exp(2*t)/2 + C3*t*exp(2*t) + C3*exp(2*t)), \ + Eq(y(t), C1*exp(2*t) + C2*t*exp(2*t) + C2*exp(2*t) + C3*t**2*exp(2*t)/2 + C3*t*exp(2*t)), \ + Eq(z(t), 2*C1*exp(2*t) + 2*C2*t*exp(2*t) + C2*exp(2*t) + C3*t**2*exp(2*t) + C3*t*exp(2*t) + C3*exp(2*t))] + assert checksysodesol(eq, sol) == (True, [0, 0, 0]) + + eq = (Eq(diff(x(t),t),4*x(t) - y(t) - 2*z(t)),Eq(diff(y(t),t),2*x(t) + y(t)- 2*z(t)),Eq(diff(z(t),t),5*x(t)-3*z(t))) + sol = [Eq(x(t), C1*exp(2*t) + C2*(-sin(t) + 3*cos(t)) + C3*(3*sin(t) + cos(t))), \ + Eq(y(t), C2*(-sin(t) + 3*cos(t)) + C3*(3*sin(t) + cos(t))), Eq(z(t), C1*exp(2*t) + 5*C2*cos(t) + 5*C3*sin(t))] + assert checksysodesol(eq, sol) == (True, [0, 0, 0]) + + eq = (Eq(diff(x(t),t),x(t)*y(t)**3), Eq(diff(y(t),t),y(t)**5)) + sol = [Eq(x(t), C1*exp((-1/(4*C2 + 4*t))**(Rational(-1, 4)))), Eq(y(t), -(-1/(4*C2 + 4*t))**Rational(1, 4)), \ + Eq(x(t), C1*exp(-1/(-1/(4*C2 + 4*t))**Rational(1, 4))), Eq(y(t), (-1/(4*C2 + 4*t))**Rational(1, 4)), \ + Eq(x(t), C1*exp(-I/(-1/(4*C2 + 4*t))**Rational(1, 4))), Eq(y(t), -I*(-1/(4*C2 + 4*t))**Rational(1, 4)), \ + Eq(x(t), C1*exp(I/(-1/(4*C2 + 4*t))**Rational(1, 4))), Eq(y(t), I*(-1/(4*C2 + 4*t))**Rational(1, 4))] + assert checksysodesol(eq, sol) == (True, [0, 0]) + + eq = (Eq(diff(x(t),t), exp(3*x(t))*y(t)**3),Eq(diff(y(t),t), y(t)**5)) + sol = [Eq(x(t), -log(C1 - 3/(-1/(4*C2 + 4*t))**Rational(1, 4))/3), Eq(y(t), -(-1/(4*C2 + 4*t))**Rational(1, 4)), \ + Eq(x(t), -log(C1 + 3/(-1/(4*C2 + 4*t))**Rational(1, 4))/3), Eq(y(t), (-1/(4*C2 + 4*t))**Rational(1, 4)), \ + Eq(x(t), -log(C1 + 3*I/(-1/(4*C2 + 4*t))**Rational(1, 4))/3), Eq(y(t), -I*(-1/(4*C2 + 4*t))**Rational(1, 4)), \ + Eq(x(t), -log(C1 - 3*I/(-1/(4*C2 + 4*t))**Rational(1, 4))/3), Eq(y(t), I*(-1/(4*C2 + 4*t))**Rational(1, 4))] + assert checksysodesol(eq, sol) == (True, [0, 0]) + + eq = (Eq(x(t),t*diff(x(t),t)+diff(x(t),t)*diff(y(t),t)), Eq(y(t),t*diff(y(t),t)+diff(y(t),t)**2)) + sol = {Eq(x(t), C1*C2 + C1*t), Eq(y(t), C2**2 + C2*t)} + assert checksysodesol(eq, sol) == (True, [0, 0]) diff --git a/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_systems.py b/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_systems.py new file mode 100644 index 0000000000000000000000000000000000000000..9d206129dfcf38c7b8c2e0ab42bd875003253f35 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/ode/tests/test_systems.py @@ -0,0 +1,2544 @@ +from sympy.core.function import (Derivative, Function, diff) +from sympy.core.mul import Mul +from sympy.core.numbers import (I, Rational, pi) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.hyperbolic import sinh +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.matrices.dense import Matrix +from sympy.core.containers import Tuple +from sympy.functions import exp, cos, sin, log, Ci, Si, erf, erfi +from sympy.matrices import dotprodsimp, NonSquareMatrixError +from sympy.solvers.ode import dsolve +from sympy.solvers.ode.ode import constant_renumber +from sympy.solvers.ode.subscheck import checksysodesol +from sympy.solvers.ode.systems import (_classify_linear_system, linear_ode_to_matrix, + ODEOrderError, ODENonlinearError, _simpsol, + _is_commutative_anti_derivative, linodesolve, + canonical_odes, dsolve_system, _component_division, + _eqs2dict, _dict2graph) +from sympy.functions import airyai, airybi +from sympy.integrals.integrals import Integral +from sympy.simplify.ratsimp import ratsimp +from sympy.testing.pytest import raises, slow, tooslow, XFAIL + + +C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10 = symbols('C0:11') +x = symbols('x') +f = Function('f') +g = Function('g') +h = Function('h') + + +def test_linear_ode_to_matrix(): + f, g, h = symbols("f, g, h", cls=Function) + t = Symbol("t") + funcs = [f(t), g(t), h(t)] + f1 = f(t).diff(t) + g1 = g(t).diff(t) + h1 = h(t).diff(t) + f2 = f(t).diff(t, 2) + g2 = g(t).diff(t, 2) + h2 = h(t).diff(t, 2) + + eqs_1 = [Eq(f1, g(t)), Eq(g1, f(t))] + sol_1 = ([Matrix([[1, 0], [0, 1]]), Matrix([[ 0, 1], [1, 0]])], Matrix([[0],[0]])) + assert linear_ode_to_matrix(eqs_1, funcs[:-1], t, 1) == sol_1 + + eqs_2 = [Eq(f1, f(t) + 2*g(t)), Eq(g1, h(t)), Eq(h1, g(t) + h(t) + f(t))] + sol_2 = ([Matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]), Matrix([[1, 2, 0], [ 0, 0, 1], [1, 1, 1]])], + Matrix([[0], [0], [0]])) + assert linear_ode_to_matrix(eqs_2, funcs, t, 1) == sol_2 + + eqs_3 = [Eq(2*f1 + 3*h1, f(t) + g(t)), Eq(4*h1 + 5*g1, f(t) + h(t)), Eq(5*f1 + 4*g1, g(t) + h(t))] + sol_3 = ([Matrix([[2, 0, 3], [0, 5, 4], [5, 4, 0]]), Matrix([[1, 1, 0], [1, 0, 1], [0, 1, 1]])], + Matrix([[0], [0], [0]])) + assert linear_ode_to_matrix(eqs_3, funcs, t, 1) == sol_3 + + eqs_4 = [Eq(f2 + h(t), f1 + g(t)), Eq(2*h2 + g2 + g1 + g(t), 0), Eq(3*h1, 4)] + sol_4 = ([Matrix([[1, 0, 0], [0, 1, 2], [0, 0, 0]]), Matrix([[1, 0, 0], [0, -1, 0], [0, 0, -3]]), + Matrix([[0, 1, -1], [0, -1, 0], [0, 0, 0]])], Matrix([[0], [0], [4]])) + assert linear_ode_to_matrix(eqs_4, funcs, t, 2) == sol_4 + + eqs_5 = [Eq(f2, g(t)), Eq(f1 + g1, f(t))] + raises(ODEOrderError, lambda: linear_ode_to_matrix(eqs_5, funcs[:-1], t, 1)) + + eqs_6 = [Eq(f1, f(t)**2), Eq(g1, f(t) + g(t))] + raises(ODENonlinearError, lambda: linear_ode_to_matrix(eqs_6, funcs[:-1], t, 1)) + + +def test__classify_linear_system(): + x, y, z, w = symbols('x, y, z, w', cls=Function) + t, k, l = symbols('t k l') + x1 = diff(x(t), t) + y1 = diff(y(t), t) + z1 = diff(z(t), t) + w1 = diff(w(t), t) + x2 = diff(x(t), t, t) + y2 = diff(y(t), t, t) + funcs = [x(t), y(t)] + funcs_2 = funcs + [z(t), w(t)] + + eqs_1 = (5 * x1 + 12 * x(t) - 6 * (y(t)), (2 * y1 - 11 * t * x(t) + 3 * y(t) + t)) + assert _classify_linear_system(eqs_1, funcs, t) is None + + eqs_2 = (5 * (x1**2) + 12 * x(t) - 6 * (y(t)), (2 * y1 - 11 * t * x(t) + 3 * y(t) + t)) + sol2 = {'is_implicit': True, + 'canon_eqs': [[Eq(Derivative(x(t), t), -sqrt(-12*x(t)/5 + 6*y(t)/5)), + Eq(Derivative(y(t), t), 11*t*x(t)/2 - t/2 - 3*y(t)/2)], + [Eq(Derivative(x(t), t), sqrt(-12*x(t)/5 + 6*y(t)/5)), + Eq(Derivative(y(t), t), 11*t*x(t)/2 - t/2 - 3*y(t)/2)]]} + assert _classify_linear_system(eqs_2, funcs, t) == sol2 + + eqs_2_1 = [Eq(Derivative(x(t), t), -sqrt(-12*x(t)/5 + 6*y(t)/5)), + Eq(Derivative(y(t), t), 11*t*x(t)/2 - t/2 - 3*y(t)/2)] + assert _classify_linear_system(eqs_2_1, funcs, t) is None + + eqs_2_2 = [Eq(Derivative(x(t), t), sqrt(-12*x(t)/5 + 6*y(t)/5)), + Eq(Derivative(y(t), t), 11*t*x(t)/2 - t/2 - 3*y(t)/2)] + assert _classify_linear_system(eqs_2_2, funcs, t) is None + + eqs_3 = (5 * x1 + 12 * x(t) - 6 * (y(t)), (2 * y1 - 11 * x(t) + 3 * y(t)), (5 * w1 + z(t)), (z1 + w(t))) + answer_3 = {'no_of_equation': 4, + 'eq': (12*x(t) - 6*y(t) + 5*Derivative(x(t), t), + -11*x(t) + 3*y(t) + 2*Derivative(y(t), t), + z(t) + 5*Derivative(w(t), t), + w(t) + Derivative(z(t), t)), + 'func': [x(t), y(t), z(t), w(t)], + 'order': {x(t): 1, y(t): 1, z(t): 1, w(t): 1}, + 'is_linear': True, + 'is_constant': True, + 'is_homogeneous': True, + 'func_coeff': -Matrix([ + [Rational(12, 5), Rational(-6, 5), 0, 0], + [Rational(-11, 2), Rational(3, 2), 0, 0], + [0, 0, 0, 1], + [0, 0, Rational(1, 5), 0]]), + 'type_of_equation': 'type1', + 'is_general': True} + assert _classify_linear_system(eqs_3, funcs_2, t) == answer_3 + + eqs_4 = (5 * x1 + 12 * x(t) - 6 * (y(t)), (2 * y1 - 11 * x(t) + 3 * y(t)), (z1 - w(t)), (w1 - z(t))) + answer_4 = {'no_of_equation': 4, + 'eq': (12 * x(t) - 6 * y(t) + 5 * Derivative(x(t), t), + -11 * x(t) + 3 * y(t) + 2 * Derivative(y(t), t), + -w(t) + Derivative(z(t), t), + -z(t) + Derivative(w(t), t)), + 'func': [x(t), y(t), z(t), w(t)], + 'order': {x(t): 1, y(t): 1, z(t): 1, w(t): 1}, + 'is_linear': True, + 'is_constant': True, + 'is_homogeneous': True, + 'func_coeff': -Matrix([ + [Rational(12, 5), Rational(-6, 5), 0, 0], + [Rational(-11, 2), Rational(3, 2), 0, 0], + [0, 0, 0, -1], + [0, 0, -1, 0]]), + 'type_of_equation': 'type1', + 'is_general': True} + assert _classify_linear_system(eqs_4, funcs_2, t) == answer_4 + + eqs_5 = (5*x1 + 12*x(t) - 6*(y(t)) + x2, (2*y1 - 11*x(t) + 3*y(t)), (z1 - w(t)), (w1 - z(t))) + answer_5 = {'no_of_equation': 4, 'eq': (12*x(t) - 6*y(t) + 5*Derivative(x(t), t) + Derivative(x(t), (t, 2)), + -11*x(t) + 3*y(t) + 2*Derivative(y(t), t), -w(t) + Derivative(z(t), t), -z(t) + Derivative(w(t), + t)), 'func': [x(t), y(t), z(t), w(t)], 'order': {x(t): 2, y(t): 1, z(t): 1, w(t): 1}, 'is_linear': + True, 'is_homogeneous': True, 'is_general': True, 'type_of_equation': 'type0', 'is_higher_order': True} + assert _classify_linear_system(eqs_5, funcs_2, t) == answer_5 + + eqs_6 = (Eq(x1, 3*y(t) - 11*z(t)), Eq(y1, 7*z(t) - 3*x(t)), Eq(z1, 11*x(t) - 7*y(t))) + answer_6 = {'no_of_equation': 3, 'eq': (Eq(Derivative(x(t), t), 3*y(t) - 11*z(t)), Eq(Derivative(y(t), t), -3*x(t) + 7*z(t)), + Eq(Derivative(z(t), t), 11*x(t) - 7*y(t))), 'func': [x(t), y(t), z(t)], 'order': {x(t): 1, y(t): 1, z(t): 1}, + 'is_linear': True, 'is_constant': True, 'is_homogeneous': True, + 'func_coeff': -Matrix([ + [ 0, -3, 11], + [ 3, 0, -7], + [-11, 7, 0]]), + 'type_of_equation': 'type1', 'is_general': True} + + assert _classify_linear_system(eqs_6, funcs_2[:-1], t) == answer_6 + + eqs_7 = (Eq(x1, y(t)), Eq(y1, x(t))) + answer_7 = {'no_of_equation': 2, 'eq': (Eq(Derivative(x(t), t), y(t)), Eq(Derivative(y(t), t), x(t))), + 'func': [x(t), y(t)], 'order': {x(t): 1, y(t): 1}, 'is_linear': True, 'is_constant': True, + 'is_homogeneous': True, 'func_coeff': -Matrix([ + [ 0, -1], + [-1, 0]]), + 'type_of_equation': 'type1', 'is_general': True} + assert _classify_linear_system(eqs_7, funcs, t) == answer_7 + + eqs_8 = (Eq(x1, 21*x(t)), Eq(y1, 17*x(t) + 3*y(t)), Eq(z1, 5*x(t) + 7*y(t) + 9*z(t))) + answer_8 = {'no_of_equation': 3, 'eq': (Eq(Derivative(x(t), t), 21*x(t)), Eq(Derivative(y(t), t), 17*x(t) + 3*y(t)), + Eq(Derivative(z(t), t), 5*x(t) + 7*y(t) + 9*z(t))), 'func': [x(t), y(t), z(t)], 'order': {x(t): 1, y(t): 1, z(t): 1}, + 'is_linear': True, 'is_constant': True, 'is_homogeneous': True, + 'func_coeff': -Matrix([ + [-21, 0, 0], + [-17, -3, 0], + [ -5, -7, -9]]), + 'type_of_equation': 'type1', 'is_general': True} + + assert _classify_linear_system(eqs_8, funcs_2[:-1], t) == answer_8 + + eqs_9 = (Eq(x1, 4*x(t) + 5*y(t) + 2*z(t)), Eq(y1, x(t) + 13*y(t) + 9*z(t)), Eq(z1, 32*x(t) + 41*y(t) + 11*z(t))) + answer_9 = {'no_of_equation': 3, 'eq': (Eq(Derivative(x(t), t), 4*x(t) + 5*y(t) + 2*z(t)), + Eq(Derivative(y(t), t), x(t) + 13*y(t) + 9*z(t)), Eq(Derivative(z(t), t), 32*x(t) + 41*y(t) + 11*z(t))), + 'func': [x(t), y(t), z(t)], 'order': {x(t): 1, y(t): 1, z(t): 1}, 'is_linear': True, + 'is_constant': True, 'is_homogeneous': True, + 'func_coeff': -Matrix([ + [ -4, -5, -2], + [ -1, -13, -9], + [-32, -41, -11]]), + 'type_of_equation': 'type1', 'is_general': True} + assert _classify_linear_system(eqs_9, funcs_2[:-1], t) == answer_9 + + eqs_10 = (Eq(3*x1, 4*5*(y(t) - z(t))), Eq(4*y1, 3*5*(z(t) - x(t))), Eq(5*z1, 3*4*(x(t) - y(t)))) + answer_10 = {'no_of_equation': 3, 'eq': (Eq(3*Derivative(x(t), t), 20*y(t) - 20*z(t)), + Eq(4*Derivative(y(t), t), -15*x(t) + 15*z(t)), Eq(5*Derivative(z(t), t), 12*x(t) - 12*y(t))), + 'func': [x(t), y(t), z(t)], 'order': {x(t): 1, y(t): 1, z(t): 1}, 'is_linear': True, + 'is_constant': True, 'is_homogeneous': True, + 'func_coeff': -Matrix([ + [ 0, Rational(-20, 3), Rational(20, 3)], + [Rational(15, 4), 0, Rational(-15, 4)], + [Rational(-12, 5), Rational(12, 5), 0]]), + 'type_of_equation': 'type1', 'is_general': True} + assert _classify_linear_system(eqs_10, funcs_2[:-1], t) == answer_10 + + eq11 = (Eq(x1, 3*y(t) - 11*z(t)), Eq(y1, 7*z(t) - 3*x(t)), Eq(z1, 11*x(t) - 7*y(t))) + sol11 = {'no_of_equation': 3, 'eq': (Eq(Derivative(x(t), t), 3*y(t) - 11*z(t)), Eq(Derivative(y(t), t), -3*x(t) + 7*z(t)), + Eq(Derivative(z(t), t), 11*x(t) - 7*y(t))), 'func': [x(t), y(t), z(t)], 'order': {x(t): 1, y(t): 1, z(t): 1}, + 'is_linear': True, 'is_constant': True, 'is_homogeneous': True, 'func_coeff': -Matrix([ + [ 0, -3, 11], [ 3, 0, -7], [-11, 7, 0]]), 'type_of_equation': 'type1', 'is_general': True} + assert _classify_linear_system(eq11, funcs_2[:-1], t) == sol11 + + eq12 = (Eq(Derivative(x(t), t), y(t)), Eq(Derivative(y(t), t), x(t))) + sol12 = {'no_of_equation': 2, 'eq': (Eq(Derivative(x(t), t), y(t)), Eq(Derivative(y(t), t), x(t))), + 'func': [x(t), y(t)], 'order': {x(t): 1, y(t): 1}, 'is_linear': True, 'is_constant': True, + 'is_homogeneous': True, 'func_coeff': -Matrix([ + [0, -1], + [-1, 0]]), 'type_of_equation': 'type1', 'is_general': True} + assert _classify_linear_system(eq12, [x(t), y(t)], t) == sol12 + + eq13 = (Eq(Derivative(x(t), t), 21*x(t)), Eq(Derivative(y(t), t), 17*x(t) + 3*y(t)), + Eq(Derivative(z(t), t), 5*x(t) + 7*y(t) + 9*z(t))) + sol13 = {'no_of_equation': 3, 'eq': ( + Eq(Derivative(x(t), t), 21 * x(t)), Eq(Derivative(y(t), t), 17 * x(t) + 3 * y(t)), + Eq(Derivative(z(t), t), 5 * x(t) + 7 * y(t) + 9 * z(t))), 'func': [x(t), y(t), z(t)], + 'order': {x(t): 1, y(t): 1, z(t): 1}, 'is_linear': True, 'is_constant': True, 'is_homogeneous': True, + 'func_coeff': -Matrix([ + [-21, 0, 0], + [-17, -3, 0], + [-5, -7, -9]]), 'type_of_equation': 'type1', 'is_general': True} + assert _classify_linear_system(eq13, [x(t), y(t), z(t)], t) == sol13 + + eq14 = ( + Eq(Derivative(x(t), t), 4*x(t) + 5*y(t) + 2*z(t)), Eq(Derivative(y(t), t), x(t) + 13*y(t) + 9*z(t)), + Eq(Derivative(z(t), t), 32*x(t) + 41*y(t) + 11*z(t))) + sol14 = {'no_of_equation': 3, 'eq': ( + Eq(Derivative(x(t), t), 4 * x(t) + 5 * y(t) + 2 * z(t)), Eq(Derivative(y(t), t), x(t) + 13 * y(t) + 9 * z(t)), + Eq(Derivative(z(t), t), 32 * x(t) + 41 * y(t) + 11 * z(t))), 'func': [x(t), y(t), z(t)], + 'order': {x(t): 1, y(t): 1, z(t): 1}, 'is_linear': True, 'is_constant': True, 'is_homogeneous': True, + 'func_coeff': -Matrix([ + [-4, -5, -2], + [-1, -13, -9], + [-32, -41, -11]]), 'type_of_equation': 'type1', 'is_general': True} + assert _classify_linear_system(eq14, [x(t), y(t), z(t)], t) == sol14 + + eq15 = (Eq(3*Derivative(x(t), t), 20*y(t) - 20*z(t)), Eq(4*Derivative(y(t), t), -15*x(t) + 15*z(t)), + Eq(5*Derivative(z(t), t), 12*x(t) - 12*y(t))) + sol15 = {'no_of_equation': 3, 'eq': ( + Eq(3 * Derivative(x(t), t), 20 * y(t) - 20 * z(t)), Eq(4 * Derivative(y(t), t), -15 * x(t) + 15 * z(t)), + Eq(5 * Derivative(z(t), t), 12 * x(t) - 12 * y(t))), 'func': [x(t), y(t), z(t)], + 'order': {x(t): 1, y(t): 1, z(t): 1}, 'is_linear': True, 'is_constant': True, 'is_homogeneous': True, + 'func_coeff': -Matrix([ + [0, Rational(-20, 3), Rational(20, 3)], + [Rational(15, 4), 0, Rational(-15, 4)], + [Rational(-12, 5), Rational(12, 5), 0]]), 'type_of_equation': 'type1', 'is_general': True} + assert _classify_linear_system(eq15, [x(t), y(t), z(t)], t) == sol15 + + # Constant coefficient homogeneous ODEs + eq1 = (Eq(diff(x(t), t), x(t) + y(t) + 9), Eq(diff(y(t), t), 2*x(t) + 5*y(t) + 23)) + sol1 = {'no_of_equation': 2, 'eq': (Eq(Derivative(x(t), t), x(t) + y(t) + 9), + Eq(Derivative(y(t), t), 2*x(t) + 5*y(t) + 23)), 'func': [x(t), y(t)], + 'order': {x(t): 1, y(t): 1}, 'is_linear': True, 'is_constant': True, 'is_homogeneous': False, 'is_general': True, + 'func_coeff': -Matrix([[-1, -1], [-2, -5]]), 'rhs': Matrix([[ 9], [23]]), 'type_of_equation': 'type2'} + assert _classify_linear_system(eq1, funcs, t) == sol1 + + # Non constant coefficient homogeneous ODEs + eq1 = (Eq(diff(x(t), t), 5*t*x(t) + 2*y(t)), Eq(diff(y(t), t), 2*x(t) + 5*t*y(t))) + sol1 = {'no_of_equation': 2, 'eq': (Eq(Derivative(x(t), t), 5*t*x(t) + 2*y(t)), Eq(Derivative(y(t), t), 5*t*y(t) + 2*x(t))), + 'func': [x(t), y(t)], 'order': {x(t): 1, y(t): 1}, 'is_linear': True, 'is_constant': False, + 'is_homogeneous': True, 'func_coeff': -Matrix([ [-5*t, -2], [ -2, -5*t]]), 'commutative_antiderivative': Matrix([ + [5*t**2/2, 2*t], [ 2*t, 5*t**2/2]]), 'type_of_equation': 'type3', 'is_general': True} + assert _classify_linear_system(eq1, funcs, t) == sol1 + + # Non constant coefficient non-homogeneous ODEs + eq1 = [Eq(x1, x(t) + t*y(t) + t), Eq(y1, t*x(t) + y(t))] + sol1 = {'no_of_equation': 2, 'eq': [Eq(Derivative(x(t), t), t*y(t) + t + x(t)), Eq(Derivative(y(t), t), + t*x(t) + y(t))], 'func': [x(t), y(t)], 'order': {x(t): 1, y(t): 1}, 'is_linear': True, + 'is_constant': False, 'is_homogeneous': False, 'is_general': True, 'func_coeff': -Matrix([ [-1, -t], + [-t, -1]]), 'commutative_antiderivative': Matrix([ [ t, t**2/2], [t**2/2, t]]), 'rhs': + Matrix([ [t], [0]]), 'type_of_equation': 'type4'} + assert _classify_linear_system(eq1, funcs, t) == sol1 + + eq2 = [Eq(x1, t*x(t) + t*y(t) + t), Eq(y1, t*x(t) + t*y(t) + cos(t))] + sol2 = {'no_of_equation': 2, 'eq': [Eq(Derivative(x(t), t), t*x(t) + t*y(t) + t), Eq(Derivative(y(t), t), + t*x(t) + t*y(t) + cos(t))], 'func': [x(t), y(t)], 'order': {x(t): 1, y(t): 1}, 'is_linear': True, + 'is_homogeneous': False, 'is_general': True, 'rhs': Matrix([ [ t], [cos(t)]]), 'func_coeff': + Matrix([ [t, t], [t, t]]), 'is_constant': False, 'type_of_equation': 'type4', + 'commutative_antiderivative': Matrix([ [t**2/2, t**2/2], [t**2/2, t**2/2]])} + assert _classify_linear_system(eq2, funcs, t) == sol2 + + eq3 = [Eq(x1, t*(x(t) + y(t) + z(t) + 1)), Eq(y1, t*(x(t) + y(t) + z(t))), Eq(z1, t*(x(t) + y(t) + z(t)))] + sol3 = {'no_of_equation': 3, 'eq': [Eq(Derivative(x(t), t), t*(x(t) + y(t) + z(t) + 1)), + Eq(Derivative(y(t), t), t*(x(t) + y(t) + z(t))), Eq(Derivative(z(t), t), t*(x(t) + y(t) + z(t)))], + 'func': [x(t), y(t), z(t)], 'order': {x(t): 1, y(t): 1, z(t): 1}, 'is_linear': True, 'is_constant': + False, 'is_homogeneous': False, 'is_general': True, 'func_coeff': -Matrix([ [-t, -t, -t], [-t, -t, + -t], [-t, -t, -t]]), 'commutative_antiderivative': Matrix([ [t**2/2, t**2/2, t**2/2], [t**2/2, + t**2/2, t**2/2], [t**2/2, t**2/2, t**2/2]]), 'rhs': Matrix([ [t], [0], [0]]), 'type_of_equation': + 'type4'} + assert _classify_linear_system(eq3, funcs_2[:-1], t) == sol3 + + eq4 = [Eq(x1, x(t) + y(t) + t*z(t) + 1), Eq(y1, x(t) + t*y(t) + z(t) + 10), Eq(z1, t*x(t) + y(t) + z(t) + t)] + sol4 = {'no_of_equation': 3, 'eq': [Eq(Derivative(x(t), t), t*z(t) + x(t) + y(t) + 1), Eq(Derivative(y(t), + t), t*y(t) + x(t) + z(t) + 10), Eq(Derivative(z(t), t), t*x(t) + t + y(t) + z(t))], 'func': [x(t), + y(t), z(t)], 'order': {x(t): 1, y(t): 1, z(t): 1}, 'is_linear': True, 'is_constant': False, + 'is_homogeneous': False, 'is_general': True, 'func_coeff': -Matrix([ [-1, -1, -t], [-1, -t, -1], [-t, + -1, -1]]), 'commutative_antiderivative': Matrix([ [ t, t, t**2/2], [ t, t**2/2, + t], [t**2/2, t, t]]), 'rhs': Matrix([ [ 1], [10], [ t]]), 'type_of_equation': 'type4'} + assert _classify_linear_system(eq4, funcs_2[:-1], t) == sol4 + + sum_terms = t*(x(t) + y(t) + z(t) + w(t)) + eq5 = [Eq(x1, sum_terms), Eq(y1, sum_terms), Eq(z1, sum_terms + 1), Eq(w1, sum_terms)] + sol5 = {'no_of_equation': 4, 'eq': [Eq(Derivative(x(t), t), t*(w(t) + x(t) + y(t) + z(t))), + Eq(Derivative(y(t), t), t*(w(t) + x(t) + y(t) + z(t))), Eq(Derivative(z(t), t), t*(w(t) + x(t) + + y(t) + z(t)) + 1), Eq(Derivative(w(t), t), t*(w(t) + x(t) + y(t) + z(t)))], 'func': [x(t), y(t), + z(t), w(t)], 'order': {x(t): 1, y(t): 1, z(t): 1, w(t): 1}, 'is_linear': True, 'is_constant': False, + 'is_homogeneous': False, 'is_general': True, 'func_coeff': -Matrix([ [-t, -t, -t, -t], [-t, -t, -t, + -t], [-t, -t, -t, -t], [-t, -t, -t, -t]]), 'commutative_antiderivative': Matrix([ [t**2/2, t**2/2, + t**2/2, t**2/2], [t**2/2, t**2/2, t**2/2, t**2/2], [t**2/2, t**2/2, t**2/2, t**2/2], [t**2/2, + t**2/2, t**2/2, t**2/2]]), 'rhs': Matrix([ [0], [0], [1], [0]]), 'type_of_equation': 'type4'} + assert _classify_linear_system(eq5, funcs_2, t) == sol5 + + # Second Order + t_ = symbols("t_") + + eq1 = (Eq(9*x(t) + 7*y(t) + 4*Derivative(x(t), t) + Derivative(x(t), (t, 2)) + 3*Derivative(y(t), t), 11*exp(I*t)), + Eq(3*x(t) + 12*y(t) + 5*Derivative(x(t), t) + 8*Derivative(y(t), t) + Derivative(y(t), (t, 2)), 2*exp(I*t))) + sol1 = {'no_of_equation': 2, 'eq': (Eq(9*x(t) + 7*y(t) + 4*Derivative(x(t), t) + Derivative(x(t), (t, 2)) + + 3*Derivative(y(t), t), 11*exp(I*t)), Eq(3*x(t) + 12*y(t) + 5*Derivative(x(t), t) + + 8*Derivative(y(t), t) + Derivative(y(t), (t, 2)), 2*exp(I*t))), 'func': [x(t), y(t)], 'order': + {x(t): 2, y(t): 2}, 'is_linear': True, 'is_homogeneous': False, 'is_general': True, 'rhs': Matrix([ + [11*exp(I*t)], [ 2*exp(I*t)]]), 'type_of_equation': 'type0', 'is_second_order': True, + 'is_higher_order': True} + assert _classify_linear_system(eq1, funcs, t) == sol1 + + eq2 = (Eq((4*t**2 + 7*t + 1)**2*Derivative(x(t), (t, 2)), 5*x(t) + 35*y(t)), + Eq((4*t**2 + 7*t + 1)**2*Derivative(y(t), (t, 2)), x(t) + 9*y(t))) + sol2 = {'no_of_equation': 2, 'eq': (Eq((4*t**2 + 7*t + 1)**2*Derivative(x(t), (t, 2)), 5*x(t) + 35*y(t)), + Eq((4*t**2 + 7*t + 1)**2*Derivative(y(t), (t, 2)), x(t) + 9*y(t))), 'func': [x(t), y(t)], 'order': + {x(t): 2, y(t): 2}, 'is_linear': True, 'is_homogeneous': True, 'is_general': True, + 'type_of_equation': 'type2', 'A0': Matrix([ [Rational(53, 4), 35], [ 1, Rational(69, 4)]]), 'g(t)': sqrt(4*t**2 + 7*t + + 1), 'tau': sqrt(33)*log(t - sqrt(33)/8 + Rational(7, 8))/33 - sqrt(33)*log(t + sqrt(33)/8 + Rational(7, 8))/33, + 'is_transformed': True, 't_': t_, 'is_second_order': True, 'is_higher_order': True} + assert _classify_linear_system(eq2, funcs, t) == sol2 + + eq3 = ((t*Derivative(x(t), t) - x(t))*log(t) + (t*Derivative(y(t), t) - y(t))*exp(t) + Derivative(x(t), (t, 2)), + t**2*(t*Derivative(x(t), t) - x(t)) + t*(t*Derivative(y(t), t) - y(t)) + Derivative(y(t), (t, 2))) + sol3 = {'no_of_equation': 2, 'eq': ((t*Derivative(x(t), t) - x(t))*log(t) + (t*Derivative(y(t), t) - + y(t))*exp(t) + Derivative(x(t), (t, 2)), t**2*(t*Derivative(x(t), t) - x(t)) + t*(t*Derivative(y(t), + t) - y(t)) + Derivative(y(t), (t, 2))), 'func': [x(t), y(t)], 'order': {x(t): 2, y(t): 2}, + 'is_linear': True, 'is_homogeneous': True, 'is_general': True, 'type_of_equation': 'type1', 'A1': + Matrix([ [-t*log(t), -t*exp(t)], [ -t**3, -t**2]]), 'is_second_order': True, + 'is_higher_order': True} + assert _classify_linear_system(eq3, funcs, t) == sol3 + + eq4 = (Eq(x2, k*x(t) - l*y1), Eq(y2, l*x1 + k*y(t))) + sol4 = {'no_of_equation': 2, 'eq': (Eq(Derivative(x(t), (t, 2)), k*x(t) - l*Derivative(y(t), t)), + Eq(Derivative(y(t), (t, 2)), k*y(t) + l*Derivative(x(t), t))), 'func': [x(t), y(t)], 'order': {x(t): + 2, y(t): 2}, 'is_linear': True, 'is_homogeneous': True, 'is_general': True, 'type_of_equation': + 'type0', 'is_second_order': True, 'is_higher_order': True} + assert _classify_linear_system(eq4, funcs, t) == sol4 + + + # Multiple matches + + f, g = symbols("f g", cls=Function) + y, t_ = symbols("y t_") + funcs = [f(t), g(t)] + + eq1 = [Eq(Derivative(f(t), t)**2 - 2*Derivative(f(t), t) + 1, 4), + Eq(-y*f(t) + Derivative(g(t), t), 0)] + sol1 = {'is_implicit': True, + 'canon_eqs': [[Eq(Derivative(f(t), t), -1), Eq(Derivative(g(t), t), y*f(t))], + [Eq(Derivative(f(t), t), 3), Eq(Derivative(g(t), t), y*f(t))]]} + assert _classify_linear_system(eq1, funcs, t) == sol1 + + raises(ValueError, lambda: _classify_linear_system(eq1, funcs[:1], t)) + + eq2 = [Eq(Derivative(f(t), t), (2*f(t) + g(t) + 1)/t), Eq(Derivative(g(t), t), (f(t) + 2*g(t))/t)] + sol2 = {'no_of_equation': 2, 'eq': [Eq(Derivative(f(t), t), (2*f(t) + g(t) + 1)/t), Eq(Derivative(g(t), t), + (f(t) + 2*g(t))/t)], 'func': [f(t), g(t)], 'order': {f(t): 1, g(t): 1}, 'is_linear': True, + 'is_homogeneous': False, 'is_general': True, 'rhs': Matrix([ [1], [0]]), 'func_coeff': Matrix([ [2, + 1], [1, 2]]), 'is_constant': False, 'type_of_equation': 'type6', 't_': t_, 'tau': log(t), + 'commutative_antiderivative': Matrix([ [2*log(t), log(t)], [ log(t), 2*log(t)]])} + assert _classify_linear_system(eq2, funcs, t) == sol2 + + eq3 = [Eq(Derivative(f(t), t), (2*f(t) + g(t))/t), Eq(Derivative(g(t), t), (f(t) + 2*g(t))/t)] + sol3 = {'no_of_equation': 2, 'eq': [Eq(Derivative(f(t), t), (2*f(t) + g(t))/t), Eq(Derivative(g(t), t), + (f(t) + 2*g(t))/t)], 'func': [f(t), g(t)], 'order': {f(t): 1, g(t): 1}, 'is_linear': True, + 'is_homogeneous': True, 'is_general': True, 'func_coeff': Matrix([ [2, 1], [1, 2]]), 'is_constant': + False, 'type_of_equation': 'type5', 't_': t_, 'rhs': Matrix([ [0], [0]]), 'tau': log(t), + 'commutative_antiderivative': Matrix([ [2*log(t), log(t)], [ log(t), 2*log(t)]])} + assert _classify_linear_system(eq3, funcs, t) == sol3 + + +def test_matrix_exp(): + from sympy.matrices.dense import Matrix, eye, zeros + from sympy.solvers.ode.systems import matrix_exp + t = Symbol('t') + + for n in range(1, 6+1): + assert matrix_exp(zeros(n), t) == eye(n) + + for n in range(1, 6+1): + A = eye(n) + expAt = exp(t) * eye(n) + assert matrix_exp(A, t) == expAt + + for n in range(1, 6+1): + A = Matrix(n, n, lambda i,j: i+1 if i==j else 0) + expAt = Matrix(n, n, lambda i,j: exp((i+1)*t) if i==j else 0) + assert matrix_exp(A, t) == expAt + + A = Matrix([[0, 1], [-1, 0]]) + expAt = Matrix([[cos(t), sin(t)], [-sin(t), cos(t)]]) + assert matrix_exp(A, t) == expAt + + A = Matrix([[2, -5], [2, -4]]) + expAt = Matrix([ + [3*exp(-t)*sin(t) + exp(-t)*cos(t), -5*exp(-t)*sin(t)], + [2*exp(-t)*sin(t), -3*exp(-t)*sin(t) + exp(-t)*cos(t)] + ]) + assert matrix_exp(A, t) == expAt + + A = Matrix([[21, 17, 6], [-5, -1, -6], [4, 4, 16]]) + # TO update this. + # expAt = Matrix([ + # [(8*t*exp(12*t) + 5*exp(12*t) - 1)*exp(4*t)/4, + # (8*t*exp(12*t) + 5*exp(12*t) - 5)*exp(4*t)/4, + # (exp(12*t) - 1)*exp(4*t)/2], + # [(-8*t*exp(12*t) - exp(12*t) + 1)*exp(4*t)/4, + # (-8*t*exp(12*t) - exp(12*t) + 5)*exp(4*t)/4, + # (-exp(12*t) + 1)*exp(4*t)/2], + # [4*t*exp(16*t), 4*t*exp(16*t), exp(16*t)]]) + expAt = Matrix([ + [2*t*exp(16*t) + 5*exp(16*t)/4 - exp(4*t)/4, 2*t*exp(16*t) + 5*exp(16*t)/4 - 5*exp(4*t)/4, exp(16*t)/2 - exp(4*t)/2], + [ -2*t*exp(16*t) - exp(16*t)/4 + exp(4*t)/4, -2*t*exp(16*t) - exp(16*t)/4 + 5*exp(4*t)/4, -exp(16*t)/2 + exp(4*t)/2], + [ 4*t*exp(16*t), 4*t*exp(16*t), exp(16*t)] + ]) + assert matrix_exp(A, t) == expAt + + A = Matrix([[1, 1, 0, 0], + [0, 1, 1, 0], + [0, 0, 1, -S(1)/8], + [0, 0, S(1)/2, S(1)/2]]) + expAt = Matrix([ + [exp(t), t*exp(t), 4*t*exp(3*t/4) + 8*t*exp(t) + 48*exp(3*t/4) - 48*exp(t), + -2*t*exp(3*t/4) - 2*t*exp(t) - 16*exp(3*t/4) + 16*exp(t)], + [0, exp(t), -t*exp(3*t/4) - 8*exp(3*t/4) + 8*exp(t), t*exp(3*t/4)/2 + 2*exp(3*t/4) - 2*exp(t)], + [0, 0, t*exp(3*t/4)/4 + exp(3*t/4), -t*exp(3*t/4)/8], + [0, 0, t*exp(3*t/4)/2, -t*exp(3*t/4)/4 + exp(3*t/4)] + ]) + assert matrix_exp(A, t) == expAt + + A = Matrix([ + [ 0, 1, 0, 0], + [-1, 0, 0, 0], + [ 0, 0, 0, 1], + [ 0, 0, -1, 0]]) + + expAt = Matrix([ + [ cos(t), sin(t), 0, 0], + [-sin(t), cos(t), 0, 0], + [ 0, 0, cos(t), sin(t)], + [ 0, 0, -sin(t), cos(t)]]) + assert matrix_exp(A, t) == expAt + + A = Matrix([ + [ 0, 1, 1, 0], + [-1, 0, 0, 1], + [ 0, 0, 0, 1], + [ 0, 0, -1, 0]]) + + expAt = Matrix([ + [ cos(t), sin(t), t*cos(t), t*sin(t)], + [-sin(t), cos(t), -t*sin(t), t*cos(t)], + [ 0, 0, cos(t), sin(t)], + [ 0, 0, -sin(t), cos(t)]]) + assert matrix_exp(A, t) == expAt + + # This case is unacceptably slow right now but should be solvable... + #a, b, c, d, e, f = symbols('a b c d e f') + #A = Matrix([ + #[-a, b, c, d], + #[ a, -b, e, 0], + #[ 0, 0, -c - e - f, 0], + #[ 0, 0, f, -d]]) + + A = Matrix([[0, I], [I, 0]]) + expAt = Matrix([ + [exp(I*t)/2 + exp(-I*t)/2, exp(I*t)/2 - exp(-I*t)/2], + [exp(I*t)/2 - exp(-I*t)/2, exp(I*t)/2 + exp(-I*t)/2]]) + assert matrix_exp(A, t) == expAt + + # Testing Errors + M = Matrix([[1, 2, 3], [4, 5, 6], [7, 7, 7]]) + M1 = Matrix([[t, 1], [1, 1]]) + + raises(ValueError, lambda: matrix_exp(M[:, :2], t)) + raises(ValueError, lambda: matrix_exp(M[:2, :], t)) + raises(ValueError, lambda: matrix_exp(M1, t)) + raises(ValueError, lambda: matrix_exp(M1[:1, :1], t)) + + +def test_canonical_odes(): + f, g, h = symbols('f g h', cls=Function) + x = symbols('x') + funcs = [f(x), g(x), h(x)] + + eqs1 = [Eq(f(x).diff(x, x), f(x) + 2*g(x)), Eq(g(x) + 1, g(x).diff(x) + f(x))] + sol1 = [[Eq(Derivative(f(x), (x, 2)), f(x) + 2*g(x)), Eq(Derivative(g(x), x), -f(x) + g(x) + 1)]] + assert canonical_odes(eqs1, funcs[:2], x) == sol1 + + eqs2 = [Eq(f(x).diff(x), h(x).diff(x) + f(x)), Eq(g(x).diff(x)**2, f(x) + h(x)), Eq(h(x).diff(x), f(x))] + sol2 = [[Eq(Derivative(f(x), x), 2*f(x)), Eq(Derivative(g(x), x), -sqrt(f(x) + h(x))), Eq(Derivative(h(x), x), f(x))], + [Eq(Derivative(f(x), x), 2*f(x)), Eq(Derivative(g(x), x), sqrt(f(x) + h(x))), Eq(Derivative(h(x), x), f(x))]] + assert canonical_odes(eqs2, funcs, x) == sol2 + + +def test_sysode_linear_neq_order1_type1(): + + f, g, x, y, h = symbols('f g x y h', cls=Function) + a, b, c, t = symbols('a b c t') + + eqs1 = [Eq(Derivative(x(t), t), x(t)), + Eq(Derivative(y(t), t), y(t))] + sol1 = [Eq(x(t), C1*exp(t)), + Eq(y(t), C2*exp(t))] + assert dsolve(eqs1) == sol1 + assert checksysodesol(eqs1, sol1) == (True, [0, 0]) + + eqs2 = [Eq(Derivative(x(t), t), 2*x(t)), + Eq(Derivative(y(t), t), 3*y(t))] + sol2 = [Eq(x(t), C1*exp(2*t)), + Eq(y(t), C2*exp(3*t))] + assert dsolve(eqs2) == sol2 + assert checksysodesol(eqs2, sol2) == (True, [0, 0]) + + eqs3 = [Eq(Derivative(x(t), t), a*x(t)), + Eq(Derivative(y(t), t), a*y(t))] + sol3 = [Eq(x(t), C1*exp(a*t)), + Eq(y(t), C2*exp(a*t))] + assert dsolve(eqs3) == sol3 + assert checksysodesol(eqs3, sol3) == (True, [0, 0]) + + # Regression test case for issue #15474 + # https://github.com/sympy/sympy/issues/15474 + eqs4 = [Eq(Derivative(x(t), t), a*x(t)), + Eq(Derivative(y(t), t), b*y(t))] + sol4 = [Eq(x(t), C1*exp(a*t)), + Eq(y(t), C2*exp(b*t))] + assert dsolve(eqs4) == sol4 + assert checksysodesol(eqs4, sol4) == (True, [0, 0]) + + eqs5 = [Eq(Derivative(x(t), t), -y(t)), + Eq(Derivative(y(t), t), x(t))] + sol5 = [Eq(x(t), -C1*sin(t) - C2*cos(t)), + Eq(y(t), C1*cos(t) - C2*sin(t))] + assert dsolve(eqs5) == sol5 + assert checksysodesol(eqs5, sol5) == (True, [0, 0]) + + eqs6 = [Eq(Derivative(x(t), t), -2*y(t)), + Eq(Derivative(y(t), t), 2*x(t))] + sol6 = [Eq(x(t), -C1*sin(2*t) - C2*cos(2*t)), + Eq(y(t), C1*cos(2*t) - C2*sin(2*t))] + assert dsolve(eqs6) == sol6 + assert checksysodesol(eqs6, sol6) == (True, [0, 0]) + + eqs7 = [Eq(Derivative(x(t), t), I*y(t)), + Eq(Derivative(y(t), t), I*x(t))] + sol7 = [Eq(x(t), -C1*exp(-I*t) + C2*exp(I*t)), + Eq(y(t), C1*exp(-I*t) + C2*exp(I*t))] + assert dsolve(eqs7) == sol7 + assert checksysodesol(eqs7, sol7) == (True, [0, 0]) + + eqs8 = [Eq(Derivative(x(t), t), -a*y(t)), + Eq(Derivative(y(t), t), a*x(t))] + sol8 = [Eq(x(t), -I*C1*exp(-I*a*t) + I*C2*exp(I*a*t)), + Eq(y(t), C1*exp(-I*a*t) + C2*exp(I*a*t))] + assert dsolve(eqs8) == sol8 + assert checksysodesol(eqs8, sol8) == (True, [0, 0]) + + eqs9 = [Eq(Derivative(x(t), t), x(t) + y(t)), + Eq(Derivative(y(t), t), x(t) - y(t))] + sol9 = [Eq(x(t), C1*(1 - sqrt(2))*exp(-sqrt(2)*t) + C2*(1 + sqrt(2))*exp(sqrt(2)*t)), + Eq(y(t), C1*exp(-sqrt(2)*t) + C2*exp(sqrt(2)*t))] + assert dsolve(eqs9) == sol9 + assert checksysodesol(eqs9, sol9) == (True, [0, 0]) + + eqs10 = [Eq(Derivative(x(t), t), x(t) + y(t)), + Eq(Derivative(y(t), t), x(t) + y(t))] + sol10 = [Eq(x(t), -C1 + C2*exp(2*t)), + Eq(y(t), C1 + C2*exp(2*t))] + assert dsolve(eqs10) == sol10 + assert checksysodesol(eqs10, sol10) == (True, [0, 0]) + + eqs11 = [Eq(Derivative(x(t), t), 2*x(t) + y(t)), + Eq(Derivative(y(t), t), -x(t) + 2*y(t))] + sol11 = [Eq(x(t), C1*exp(2*t)*sin(t) + C2*exp(2*t)*cos(t)), + Eq(y(t), C1*exp(2*t)*cos(t) - C2*exp(2*t)*sin(t))] + assert dsolve(eqs11) == sol11 + assert checksysodesol(eqs11, sol11) == (True, [0, 0]) + + eqs12 = [Eq(Derivative(x(t), t), x(t) + 2*y(t)), + Eq(Derivative(y(t), t), 2*x(t) + y(t))] + sol12 = [Eq(x(t), -C1*exp(-t) + C2*exp(3*t)), + Eq(y(t), C1*exp(-t) + C2*exp(3*t))] + assert dsolve(eqs12) == sol12 + assert checksysodesol(eqs12, sol12) == (True, [0, 0]) + + eqs13 = [Eq(Derivative(x(t), t), 4*x(t) + y(t)), + Eq(Derivative(y(t), t), -x(t) + 2*y(t))] + sol13 = [Eq(x(t), C2*t*exp(3*t) + (C1 + C2)*exp(3*t)), + Eq(y(t), -C1*exp(3*t) - C2*t*exp(3*t))] + assert dsolve(eqs13) == sol13 + assert checksysodesol(eqs13, sol13) == (True, [0, 0]) + + eqs14 = [Eq(Derivative(x(t), t), a*y(t)), + Eq(Derivative(y(t), t), a*x(t))] + sol14 = [Eq(x(t), -C1*exp(-a*t) + C2*exp(a*t)), + Eq(y(t), C1*exp(-a*t) + C2*exp(a*t))] + assert dsolve(eqs14) == sol14 + assert checksysodesol(eqs14, sol14) == (True, [0, 0]) + + eqs15 = [Eq(Derivative(x(t), t), a*y(t)), + Eq(Derivative(y(t), t), b*x(t))] + sol15 = [Eq(x(t), -C1*a*exp(-t*sqrt(a*b))/sqrt(a*b) + C2*a*exp(t*sqrt(a*b))/sqrt(a*b)), + Eq(y(t), C1*exp(-t*sqrt(a*b)) + C2*exp(t*sqrt(a*b)))] + assert dsolve(eqs15) == sol15 + assert checksysodesol(eqs15, sol15) == (True, [0, 0]) + + eqs16 = [Eq(Derivative(x(t), t), a*x(t) + b*y(t)), + Eq(Derivative(y(t), t), c*x(t))] + sol16 = [Eq(x(t), -2*C1*b*exp(t*(a + sqrt(a**2 + 4*b*c))/2)/(a - sqrt(a**2 + 4*b*c)) - 2*C2*b*exp(t*(a - + sqrt(a**2 + 4*b*c))/2)/(a + sqrt(a**2 + 4*b*c))), + Eq(y(t), C1*exp(t*(a + sqrt(a**2 + 4*b*c))/2) + C2*exp(t*(a - sqrt(a**2 + 4*b*c))/2))] + assert dsolve(eqs16) == sol16 + assert checksysodesol(eqs16, sol16) == (True, [0, 0]) + + # Regression test case for issue #18562 + # https://github.com/sympy/sympy/issues/18562 + eqs17 = [Eq(Derivative(x(t), t), a*y(t) + x(t)), + Eq(Derivative(y(t), t), a*x(t) - y(t))] + sol17 = [Eq(x(t), C1*a*exp(t*sqrt(a**2 + 1))/(sqrt(a**2 + 1) - 1) - C2*a*exp(-t*sqrt(a**2 + 1))/(sqrt(a**2 + + 1) + 1)), + Eq(y(t), C1*exp(t*sqrt(a**2 + 1)) + C2*exp(-t*sqrt(a**2 + 1)))] + assert dsolve(eqs17) == sol17 + assert checksysodesol(eqs17, sol17) == (True, [0, 0]) + + eqs18 = [Eq(Derivative(x(t), t), 0), + Eq(Derivative(y(t), t), 0)] + sol18 = [Eq(x(t), C1), + Eq(y(t), C2)] + assert dsolve(eqs18) == sol18 + assert checksysodesol(eqs18, sol18) == (True, [0, 0]) + + eqs19 = [Eq(Derivative(x(t), t), 2*x(t) - y(t)), + Eq(Derivative(y(t), t), x(t))] + sol19 = [Eq(x(t), C2*t*exp(t) + (C1 + C2)*exp(t)), + Eq(y(t), C1*exp(t) + C2*t*exp(t))] + assert dsolve(eqs19) == sol19 + assert checksysodesol(eqs19, sol19) == (True, [0, 0]) + + eqs20 = [Eq(Derivative(x(t), t), x(t)), + Eq(Derivative(y(t), t), x(t) + y(t))] + sol20 = [Eq(x(t), C1*exp(t)), + Eq(y(t), C1*t*exp(t) + C2*exp(t))] + assert dsolve(eqs20) == sol20 + assert checksysodesol(eqs20, sol20) == (True, [0, 0]) + + eqs21 = [Eq(Derivative(x(t), t), 3*x(t)), + Eq(Derivative(y(t), t), x(t) + y(t))] + sol21 = [Eq(x(t), 2*C1*exp(3*t)), + Eq(y(t), C1*exp(3*t) + C2*exp(t))] + assert dsolve(eqs21) == sol21 + assert checksysodesol(eqs21, sol21) == (True, [0, 0]) + + eqs22 = [Eq(Derivative(x(t), t), 3*x(t)), + Eq(Derivative(y(t), t), y(t))] + sol22 = [Eq(x(t), C1*exp(3*t)), + Eq(y(t), C2*exp(t))] + assert dsolve(eqs22) == sol22 + assert checksysodesol(eqs22, sol22) == (True, [0, 0]) + + +@slow +def test_sysode_linear_neq_order1_type1_slow(): + + t = Symbol('t') + Z0 = Function('Z0') + Z1 = Function('Z1') + Z2 = Function('Z2') + Z3 = Function('Z3') + + k01, k10, k20, k21, k23, k30 = symbols('k01 k10 k20 k21 k23 k30') + + eqs1 = [Eq(Derivative(Z0(t), t), -k01*Z0(t) + k10*Z1(t) + k20*Z2(t) + k30*Z3(t)), + Eq(Derivative(Z1(t), t), k01*Z0(t) - k10*Z1(t) + k21*Z2(t)), + Eq(Derivative(Z2(t), t), (-k20 - k21 - k23)*Z2(t)), + Eq(Derivative(Z3(t), t), k23*Z2(t) - k30*Z3(t))] + sol1 = [Eq(Z0(t), C1*k10/k01 - C2*(k10 - k30)*exp(-k30*t)/(k01 + k10 - k30) - C3*(k10*(k20 + k21 - k30) - + k20**2 - k20*(k21 + k23 - k30) + k23*k30)*exp(-t*(k20 + k21 + k23))/(k23*(-k01 - k10 + k20 + k21 + + k23)) - C4*exp(-t*(k01 + k10))), + Eq(Z1(t), C1 - C2*k01*exp(-k30*t)/(k01 + k10 - k30) + C3*(-k01*(k20 + k21 - k30) + k20*k21 + k21**2 + + k21*(k23 - k30))*exp(-t*(k20 + k21 + k23))/(k23*(-k01 - k10 + k20 + k21 + k23)) + C4*exp(-t*(k01 + + k10))), + Eq(Z2(t), -C3*(k20 + k21 + k23 - k30)*exp(-t*(k20 + k21 + k23))/k23), + Eq(Z3(t), C2*exp(-k30*t) + C3*exp(-t*(k20 + k21 + k23)))] + assert dsolve(eqs1) == sol1 + assert checksysodesol(eqs1, sol1) == (True, [0, 0, 0, 0]) + + x, y, z, u, v, w = symbols('x y z u v w', cls=Function) + k2, k3 = symbols('k2 k3') + a_b, a_c = symbols('a_b a_c', real=True) + + eqs2 = [Eq(Derivative(z(t), t), k2*y(t)), + Eq(Derivative(x(t), t), k3*y(t)), + Eq(Derivative(y(t), t), (-k2 - k3)*y(t))] + sol2 = [Eq(z(t), C1 - C2*k2*exp(-t*(k2 + k3))/(k2 + k3)), + Eq(x(t), -C2*k3*exp(-t*(k2 + k3))/(k2 + k3) + C3), + Eq(y(t), C2*exp(-t*(k2 + k3)))] + assert dsolve(eqs2) == sol2 + assert checksysodesol(eqs2, sol2) == (True, [0, 0, 0]) + + eqs3 = [4*u(t) - v(t) - 2*w(t) + Derivative(u(t), t), + 2*u(t) + v(t) - 2*w(t) + Derivative(v(t), t), + 5*u(t) + v(t) - 3*w(t) + Derivative(w(t), t)] + sol3 = [Eq(u(t), C3*exp(-2*t) + (C1/2 + sqrt(3)*C2/6)*cos(sqrt(3)*t) + sin(sqrt(3)*t)*(sqrt(3)*C1/6 + + C2*Rational(-1, 2))), + Eq(v(t), (C1/2 + sqrt(3)*C2/6)*cos(sqrt(3)*t) + sin(sqrt(3)*t)*(sqrt(3)*C1/6 + C2*Rational(-1, 2))), + Eq(w(t), C1*cos(sqrt(3)*t) - C2*sin(sqrt(3)*t) + C3*exp(-2*t))] + assert dsolve(eqs3) == sol3 + assert checksysodesol(eqs3, sol3) == (True, [0, 0, 0]) + + eqs4 = [Eq(Derivative(x(t), t), w(t)*Rational(-2, 9) + 2*x(t) + y(t) + z(t)*Rational(-8, 9)), + Eq(Derivative(y(t), t), w(t)*Rational(4, 9) + 2*y(t) + z(t)*Rational(16, 9)), + Eq(Derivative(z(t), t), w(t)*Rational(-2, 9) + z(t)*Rational(37, 9)), + Eq(Derivative(w(t), t), w(t)*Rational(44, 9) + z(t)*Rational(-4, 9))] + sol4 = [Eq(x(t), C1*exp(2*t) + C2*t*exp(2*t)), + Eq(y(t), C2*exp(2*t) + 2*C3*exp(4*t)), + Eq(z(t), 2*C3*exp(4*t) + C4*exp(5*t)*Rational(-1, 4)), + Eq(w(t), C3*exp(4*t) + C4*exp(5*t))] + assert dsolve(eqs4) == sol4 + assert checksysodesol(eqs4, sol4) == (True, [0, 0, 0, 0]) + + # Regression test case for issue #15574 + # https://github.com/sympy/sympy/issues/15574 + eq5 = [Eq(x(t).diff(t), x(t)), Eq(y(t).diff(t), y(t)), Eq(z(t).diff(t), z(t)), Eq(w(t).diff(t), w(t))] + sol5 = [Eq(x(t), C1*exp(t)), Eq(y(t), C2*exp(t)), Eq(z(t), C3*exp(t)), Eq(w(t), C4*exp(t))] + assert dsolve(eq5) == sol5 + assert checksysodesol(eq5, sol5) == (True, [0, 0, 0, 0]) + + eqs6 = [Eq(Derivative(x(t), t), x(t) + y(t)), + Eq(Derivative(y(t), t), y(t) + z(t)), + Eq(Derivative(z(t), t), w(t)*Rational(-1, 8) + z(t)), + Eq(Derivative(w(t), t), w(t)/2 + z(t)/2)] + sol6 = [Eq(x(t), C1*exp(t) + C2*t*exp(t) + 4*C4*t*exp(t*Rational(3, 4)) + (4*C3 + 48*C4)*exp(t*Rational(3, + 4))), + Eq(y(t), C2*exp(t) - C4*t*exp(t*Rational(3, 4)) - (C3 + 8*C4)*exp(t*Rational(3, 4))), + Eq(z(t), C4*t*exp(t*Rational(3, 4))/4 + (C3/4 + C4)*exp(t*Rational(3, 4))), + Eq(w(t), C3*exp(t*Rational(3, 4))/2 + C4*t*exp(t*Rational(3, 4))/2)] + assert dsolve(eqs6) == sol6 + assert checksysodesol(eqs6, sol6) == (True, [0, 0, 0, 0]) + + # Regression test case for issue #15574 + # https://github.com/sympy/sympy/issues/15574 + eq7 = [Eq(Derivative(x(t), t), x(t)), Eq(Derivative(y(t), t), y(t)), Eq(Derivative(z(t), t), z(t)), + Eq(Derivative(w(t), t), w(t)), Eq(Derivative(u(t), t), u(t))] + sol7 = [Eq(x(t), C1*exp(t)), Eq(y(t), C2*exp(t)), Eq(z(t), C3*exp(t)), Eq(w(t), C4*exp(t)), + Eq(u(t), C5*exp(t))] + assert dsolve(eq7) == sol7 + assert checksysodesol(eq7, sol7) == (True, [0, 0, 0, 0, 0]) + + eqs8 = [Eq(Derivative(x(t), t), 2*x(t) + y(t)), + Eq(Derivative(y(t), t), 2*y(t)), + Eq(Derivative(z(t), t), 4*z(t)), + Eq(Derivative(w(t), t), u(t) + 5*w(t)), + Eq(Derivative(u(t), t), 5*u(t))] + sol8 = [Eq(x(t), C1*exp(2*t) + C2*t*exp(2*t)), + Eq(y(t), C2*exp(2*t)), + Eq(z(t), C3*exp(4*t)), + Eq(w(t), C4*exp(5*t) + C5*t*exp(5*t)), + Eq(u(t), C5*exp(5*t))] + assert dsolve(eqs8) == sol8 + assert checksysodesol(eqs8, sol8) == (True, [0, 0, 0, 0, 0]) + + # Regression test case for issue #15574 + # https://github.com/sympy/sympy/issues/15574 + eq9 = [Eq(Derivative(x(t), t), x(t)), Eq(Derivative(y(t), t), y(t)), Eq(Derivative(z(t), t), z(t))] + sol9 = [Eq(x(t), C1*exp(t)), Eq(y(t), C2*exp(t)), Eq(z(t), C3*exp(t))] + assert dsolve(eq9) == sol9 + assert checksysodesol(eq9, sol9) == (True, [0, 0, 0]) + + # Regression test case for issue #15407 + # https://github.com/sympy/sympy/issues/15407 + eqs10 = [Eq(Derivative(x(t), t), (-a_b - a_c)*x(t)), + Eq(Derivative(y(t), t), a_b*y(t)), + Eq(Derivative(z(t), t), a_c*x(t))] + sol10 = [Eq(x(t), -C1*(a_b + a_c)*exp(-t*(a_b + a_c))/a_c), + Eq(y(t), C2*exp(a_b*t)), + Eq(z(t), C1*exp(-t*(a_b + a_c)) + C3)] + assert dsolve(eqs10) == sol10 + assert checksysodesol(eqs10, sol10) == (True, [0, 0, 0]) + + # Regression test case for issue #14312 + # https://github.com/sympy/sympy/issues/14312 + eqs11 = [Eq(Derivative(x(t), t), k3*y(t)), + Eq(Derivative(y(t), t), (-k2 - k3)*y(t)), + Eq(Derivative(z(t), t), k2*y(t))] + sol11 = [Eq(x(t), C1 + C2*k3*exp(-t*(k2 + k3))/k2), + Eq(y(t), -C2*(k2 + k3)*exp(-t*(k2 + k3))/k2), + Eq(z(t), C2*exp(-t*(k2 + k3)) + C3)] + assert dsolve(eqs11) == sol11 + assert checksysodesol(eqs11, sol11) == (True, [0, 0, 0]) + + # Regression test case for issue #14312 + # https://github.com/sympy/sympy/issues/14312 + eqs12 = [Eq(Derivative(z(t), t), k2*y(t)), + Eq(Derivative(x(t), t), k3*y(t)), + Eq(Derivative(y(t), t), (-k2 - k3)*y(t))] + sol12 = [Eq(z(t), C1 - C2*k2*exp(-t*(k2 + k3))/(k2 + k3)), + Eq(x(t), -C2*k3*exp(-t*(k2 + k3))/(k2 + k3) + C3), + Eq(y(t), C2*exp(-t*(k2 + k3)))] + assert dsolve(eqs12) == sol12 + assert checksysodesol(eqs12, sol12) == (True, [0, 0, 0]) + + f, g, h = symbols('f, g, h', cls=Function) + a, b, c = symbols('a, b, c') + + # Regression test case for issue #15474 + # https://github.com/sympy/sympy/issues/15474 + eqs13 = [Eq(Derivative(f(t), t), 2*f(t) + g(t)), + Eq(Derivative(g(t), t), a*f(t))] + sol13 = [Eq(f(t), C1*exp(t*(sqrt(a + 1) + 1))/(sqrt(a + 1) - 1) - C2*exp(-t*(sqrt(a + 1) - 1))/(sqrt(a + 1) + + 1)), + Eq(g(t), C1*exp(t*(sqrt(a + 1) + 1)) + C2*exp(-t*(sqrt(a + 1) - 1)))] + assert dsolve(eqs13) == sol13 + assert checksysodesol(eqs13, sol13) == (True, [0, 0]) + + eqs14 = [Eq(Derivative(f(t), t), 2*g(t) - 3*h(t)), + Eq(Derivative(g(t), t), -2*f(t) + 4*h(t)), + Eq(Derivative(h(t), t), 3*f(t) - 4*g(t))] + sol14 = [Eq(f(t), 2*C1 - sin(sqrt(29)*t)*(sqrt(29)*C2*Rational(3, 25) + C3*Rational(-8, 25)) - + cos(sqrt(29)*t)*(C2*Rational(8, 25) + sqrt(29)*C3*Rational(3, 25))), + Eq(g(t), C1*Rational(3, 2) + sin(sqrt(29)*t)*(sqrt(29)*C2*Rational(4, 25) + C3*Rational(6, 25)) - + cos(sqrt(29)*t)*(C2*Rational(6, 25) + sqrt(29)*C3*Rational(-4, 25))), + Eq(h(t), C1 + C2*cos(sqrt(29)*t) - C3*sin(sqrt(29)*t))] + assert dsolve(eqs14) == sol14 + assert checksysodesol(eqs14, sol14) == (True, [0, 0, 0]) + + eqs15 = [Eq(2*Derivative(f(t), t), 12*g(t) - 12*h(t)), + Eq(3*Derivative(g(t), t), -8*f(t) + 8*h(t)), + Eq(4*Derivative(h(t), t), 6*f(t) - 6*g(t))] + sol15 = [Eq(f(t), C1 - sin(sqrt(29)*t)*(sqrt(29)*C2*Rational(6, 13) + C3*Rational(-16, 13)) - + cos(sqrt(29)*t)*(C2*Rational(16, 13) + sqrt(29)*C3*Rational(6, 13))), + Eq(g(t), C1 + sin(sqrt(29)*t)*(sqrt(29)*C2*Rational(8, 39) + C3*Rational(16, 13)) - + cos(sqrt(29)*t)*(C2*Rational(16, 13) + sqrt(29)*C3*Rational(-8, 39))), + Eq(h(t), C1 + C2*cos(sqrt(29)*t) - C3*sin(sqrt(29)*t))] + assert dsolve(eqs15) == sol15 + assert checksysodesol(eqs15, sol15) == (True, [0, 0, 0]) + + eq16 = (Eq(diff(x(t), t), 21*x(t)), Eq(diff(y(t), t), 17*x(t) + 3*y(t)), + Eq(diff(z(t), t), 5*x(t) + 7*y(t) + 9*z(t))) + sol16 = [Eq(x(t), 216*C1*exp(21*t)/209), + Eq(y(t), 204*C1*exp(21*t)/209 - 6*C2*exp(3*t)/7), + Eq(z(t), C1*exp(21*t) + C2*exp(3*t) + C3*exp(9*t))] + assert dsolve(eq16) == sol16 + assert checksysodesol(eq16, sol16) == (True, [0, 0, 0]) + + eqs17 = [Eq(Derivative(x(t), t), 3*y(t) - 11*z(t)), + Eq(Derivative(y(t), t), -3*x(t) + 7*z(t)), + Eq(Derivative(z(t), t), 11*x(t) - 7*y(t))] + sol17 = [Eq(x(t), C1*Rational(7, 3) - sin(sqrt(179)*t)*(sqrt(179)*C2*Rational(11, 170) + C3*Rational(-21, + 170)) - cos(sqrt(179)*t)*(C2*Rational(21, 170) + sqrt(179)*C3*Rational(11, 170))), + Eq(y(t), C1*Rational(11, 3) + sin(sqrt(179)*t)*(sqrt(179)*C2*Rational(7, 170) + C3*Rational(33, + 170)) - cos(sqrt(179)*t)*(C2*Rational(33, 170) + sqrt(179)*C3*Rational(-7, 170))), + Eq(z(t), C1 + C2*cos(sqrt(179)*t) - C3*sin(sqrt(179)*t))] + assert dsolve(eqs17) == sol17 + assert checksysodesol(eqs17, sol17) == (True, [0, 0, 0]) + + eqs18 = [Eq(3*Derivative(x(t), t), 20*y(t) - 20*z(t)), + Eq(4*Derivative(y(t), t), -15*x(t) + 15*z(t)), + Eq(5*Derivative(z(t), t), 12*x(t) - 12*y(t))] + sol18 = [Eq(x(t), C1 - sin(5*sqrt(2)*t)*(sqrt(2)*C2*Rational(4, 3) - C3) - cos(5*sqrt(2)*t)*(C2 + + sqrt(2)*C3*Rational(4, 3))), + Eq(y(t), C1 + sin(5*sqrt(2)*t)*(sqrt(2)*C2*Rational(3, 4) + C3) - cos(5*sqrt(2)*t)*(C2 + + sqrt(2)*C3*Rational(-3, 4))), + Eq(z(t), C1 + C2*cos(5*sqrt(2)*t) - C3*sin(5*sqrt(2)*t))] + assert dsolve(eqs18) == sol18 + assert checksysodesol(eqs18, sol18) == (True, [0, 0, 0]) + + eqs19 = [Eq(Derivative(x(t), t), 4*x(t) - z(t)), + Eq(Derivative(y(t), t), 2*x(t) + 2*y(t) - z(t)), + Eq(Derivative(z(t), t), 3*x(t) + y(t))] + sol19 = [Eq(x(t), C2*t**2*exp(2*t)/2 + t*(2*C2 + C3)*exp(2*t) + (C1 + C2 + 2*C3)*exp(2*t)), + Eq(y(t), C2*t**2*exp(2*t)/2 + t*(2*C2 + C3)*exp(2*t) + (C1 + 2*C3)*exp(2*t)), + Eq(z(t), C2*t**2*exp(2*t) + t*(3*C2 + 2*C3)*exp(2*t) + (2*C1 + 3*C3)*exp(2*t))] + assert dsolve(eqs19) == sol19 + assert checksysodesol(eqs19, sol19) == (True, [0, 0, 0]) + + eqs20 = [Eq(Derivative(x(t), t), 4*x(t) - y(t) - 2*z(t)), + Eq(Derivative(y(t), t), 2*x(t) + y(t) - 2*z(t)), + Eq(Derivative(z(t), t), 5*x(t) - 3*z(t))] + sol20 = [Eq(x(t), C1*exp(2*t) - sin(t)*(C2*Rational(3, 5) + C3/5) - cos(t)*(C2/5 + C3*Rational(-3, 5))), + Eq(y(t), -sin(t)*(C2*Rational(3, 5) + C3/5) - cos(t)*(C2/5 + C3*Rational(-3, 5))), + Eq(z(t), C1*exp(2*t) - C2*sin(t) + C3*cos(t))] + assert dsolve(eqs20) == sol20 + assert checksysodesol(eqs20, sol20) == (True, [0, 0, 0]) + + eq21 = (Eq(diff(x(t), t), 9*y(t)), Eq(diff(y(t), t), 12*x(t))) + sol21 = [Eq(x(t), -sqrt(3)*C1*exp(-6*sqrt(3)*t)/2 + sqrt(3)*C2*exp(6*sqrt(3)*t)/2), + Eq(y(t), C1*exp(-6*sqrt(3)*t) + C2*exp(6*sqrt(3)*t))] + + assert dsolve(eq21) == sol21 + assert checksysodesol(eq21, sol21) == (True, [0, 0]) + + eqs22 = [Eq(Derivative(x(t), t), 2*x(t) + 4*y(t)), + Eq(Derivative(y(t), t), 12*x(t) + 41*y(t))] + sol22 = [Eq(x(t), C1*(39 - sqrt(1713))*exp(t*(sqrt(1713) + 43)/2)*Rational(-1, 24) + C2*(39 + + sqrt(1713))*exp(t*(43 - sqrt(1713))/2)*Rational(-1, 24)), + Eq(y(t), C1*exp(t*(sqrt(1713) + 43)/2) + C2*exp(t*(43 - sqrt(1713))/2))] + assert dsolve(eqs22) == sol22 + assert checksysodesol(eqs22, sol22) == (True, [0, 0]) + + eqs23 = [Eq(Derivative(x(t), t), x(t) + y(t)), + Eq(Derivative(y(t), t), -2*x(t) + 2*y(t))] + sol23 = [Eq(x(t), (C1/4 + sqrt(7)*C2/4)*cos(sqrt(7)*t/2)*exp(t*Rational(3, 2)) + + sin(sqrt(7)*t/2)*(sqrt(7)*C1/4 + C2*Rational(-1, 4))*exp(t*Rational(3, 2))), + Eq(y(t), C1*cos(sqrt(7)*t/2)*exp(t*Rational(3, 2)) - C2*sin(sqrt(7)*t/2)*exp(t*Rational(3, 2)))] + assert dsolve(eqs23) == sol23 + assert checksysodesol(eqs23, sol23) == (True, [0, 0]) + + # Regression test case for issue #15474 + # https://github.com/sympy/sympy/issues/15474 + a = Symbol("a", real=True) + eq24 = [x(t).diff(t) - a*y(t), y(t).diff(t) + a*x(t)] + sol24 = [Eq(x(t), C1*sin(a*t) + C2*cos(a*t)), Eq(y(t), C1*cos(a*t) - C2*sin(a*t))] + assert dsolve(eq24) == sol24 + assert checksysodesol(eq24, sol24) == (True, [0, 0]) + + # Regression test case for issue #19150 + # https://github.com/sympy/sympy/issues/19150 + eqs25 = [Eq(Derivative(f(t), t), 0), + Eq(Derivative(g(t), t), (f(t) - 2*g(t) + x(t))/(b*c)), + Eq(Derivative(x(t), t), (g(t) - 2*x(t) + y(t))/(b*c)), + Eq(Derivative(y(t), t), (h(t) + x(t) - 2*y(t))/(b*c)), + Eq(Derivative(h(t), t), 0)] + sol25 = [Eq(f(t), -3*C1 + 4*C2), + Eq(g(t), -2*C1 + 3*C2 - C3*exp(-2*t/(b*c)) + C4*exp(-t*(sqrt(2) + 2)/(b*c)) + C5*exp(-t*(2 - + sqrt(2))/(b*c))), + Eq(x(t), -C1 + 2*C2 - sqrt(2)*C4*exp(-t*(sqrt(2) + 2)/(b*c)) + sqrt(2)*C5*exp(-t*(2 - + sqrt(2))/(b*c))), + Eq(y(t), C2 + C3*exp(-2*t/(b*c)) + C4*exp(-t*(sqrt(2) + 2)/(b*c)) + C5*exp(-t*(2 - sqrt(2))/(b*c))), + Eq(h(t), C1)] + assert dsolve(eqs25) == sol25 + assert checksysodesol(eqs25, sol25) == (True, [0, 0, 0, 0, 0]) + + eq26 = [Eq(Derivative(f(t), t), 2*f(t)), Eq(Derivative(g(t), t), 3*f(t) + 7*g(t))] + sol26 = [Eq(f(t), -5*C1*exp(2*t)/3), Eq(g(t), C1*exp(2*t) + C2*exp(7*t))] + assert dsolve(eq26) == sol26 + assert checksysodesol(eq26, sol26) == (True, [0, 0]) + + eq27 = [Eq(Derivative(f(t), t), -9*I*f(t) - 4*g(t)), Eq(Derivative(g(t), t), -4*I*g(t))] + sol27 = [Eq(f(t), 4*I*C1*exp(-4*I*t)/5 + C2*exp(-9*I*t)), Eq(g(t), C1*exp(-4*I*t))] + assert dsolve(eq27) == sol27 + assert checksysodesol(eq27, sol27) == (True, [0, 0]) + + eq28 = [Eq(Derivative(f(t), t), -9*I*f(t)), Eq(Derivative(g(t), t), -4*I*g(t))] + sol28 = [Eq(f(t), C1*exp(-9*I*t)), Eq(g(t), C2*exp(-4*I*t))] + assert dsolve(eq28) == sol28 + assert checksysodesol(eq28, sol28) == (True, [0, 0]) + + eq29 = [Eq(Derivative(f(t), t), 0), Eq(Derivative(g(t), t), 0)] + sol29 = [Eq(f(t), C1), Eq(g(t), C2)] + assert dsolve(eq29) == sol29 + assert checksysodesol(eq29, sol29) == (True, [0, 0]) + + eq30 = [Eq(Derivative(f(t), t), f(t)), Eq(Derivative(g(t), t), 0)] + sol30 = [Eq(f(t), C1*exp(t)), Eq(g(t), C2)] + assert dsolve(eq30) == sol30 + assert checksysodesol(eq30, sol30) == (True, [0, 0]) + + eq31 = [Eq(Derivative(f(t), t), g(t)), Eq(Derivative(g(t), t), 0)] + sol31 = [Eq(f(t), C1 + C2*t), Eq(g(t), C2)] + assert dsolve(eq31) == sol31 + assert checksysodesol(eq31, sol31) == (True, [0, 0]) + + eq32 = [Eq(Derivative(f(t), t), 0), Eq(Derivative(g(t), t), f(t))] + sol32 = [Eq(f(t), C1), Eq(g(t), C1*t + C2)] + assert dsolve(eq32) == sol32 + assert checksysodesol(eq32, sol32) == (True, [0, 0]) + + eq33 = [Eq(Derivative(f(t), t), 0), Eq(Derivative(g(t), t), g(t))] + sol33 = [Eq(f(t), C1), Eq(g(t), C2*exp(t))] + assert dsolve(eq33) == sol33 + assert checksysodesol(eq33, sol33) == (True, [0, 0]) + + eq34 = [Eq(Derivative(f(t), t), f(t)), Eq(Derivative(g(t), t), I*g(t))] + sol34 = [Eq(f(t), C1*exp(t)), Eq(g(t), C2*exp(I*t))] + assert dsolve(eq34) == sol34 + assert checksysodesol(eq34, sol34) == (True, [0, 0]) + + eq35 = [Eq(Derivative(f(t), t), I*f(t)), Eq(Derivative(g(t), t), -I*g(t))] + sol35 = [Eq(f(t), C1*exp(I*t)), Eq(g(t), C2*exp(-I*t))] + assert dsolve(eq35) == sol35 + assert checksysodesol(eq35, sol35) == (True, [0, 0]) + + eq36 = [Eq(Derivative(f(t), t), I*g(t)), Eq(Derivative(g(t), t), 0)] + sol36 = [Eq(f(t), I*C1 + I*C2*t), Eq(g(t), C2)] + assert dsolve(eq36) == sol36 + assert checksysodesol(eq36, sol36) == (True, [0, 0]) + + eq37 = [Eq(Derivative(f(t), t), I*g(t)), Eq(Derivative(g(t), t), I*f(t))] + sol37 = [Eq(f(t), -C1*exp(-I*t) + C2*exp(I*t)), Eq(g(t), C1*exp(-I*t) + C2*exp(I*t))] + assert dsolve(eq37) == sol37 + assert checksysodesol(eq37, sol37) == (True, [0, 0]) + + # Multiple systems + eq1 = [Eq(Derivative(f(t), t)**2, g(t)**2), Eq(-f(t) + Derivative(g(t), t), 0)] + sol1 = [[Eq(f(t), -C1*sin(t) - C2*cos(t)), + Eq(g(t), C1*cos(t) - C2*sin(t))], + [Eq(f(t), -C1*exp(-t) + C2*exp(t)), + Eq(g(t), C1*exp(-t) + C2*exp(t))]] + assert dsolve(eq1) == sol1 + for sol in sol1: + assert checksysodesol(eq1, sol) == (True, [0, 0]) + + +def test_sysode_linear_neq_order1_type2(): + + f, g, h, k = symbols('f g h k', cls=Function) + x, t, a, b, c, d, y = symbols('x t a b c d y') + k1, k2 = symbols('k1 k2') + + + eqs1 = [Eq(Derivative(f(x), x), f(x) + g(x) + 5), + Eq(Derivative(g(x), x), -f(x) - g(x) + 7)] + sol1 = [Eq(f(x), C1 + C2 + 6*x**2 + x*(C2 + 5)), + Eq(g(x), -C1 - 6*x**2 - x*(C2 - 7))] + assert dsolve(eqs1) == sol1 + assert checksysodesol(eqs1, sol1) == (True, [0, 0]) + + eqs2 = [Eq(Derivative(f(x), x), f(x) + g(x) + 5), + Eq(Derivative(g(x), x), f(x) + g(x) + 7)] + sol2 = [Eq(f(x), -C1 + C2*exp(2*x) - x - 3), + Eq(g(x), C1 + C2*exp(2*x) + x - 3)] + assert dsolve(eqs2) == sol2 + assert checksysodesol(eqs2, sol2) == (True, [0, 0]) + + eqs3 = [Eq(Derivative(f(x), x), f(x) + 5), + Eq(Derivative(g(x), x), f(x) + 7)] + sol3 = [Eq(f(x), C1*exp(x) - 5), + Eq(g(x), C1*exp(x) + C2 + 2*x - 5)] + assert dsolve(eqs3) == sol3 + assert checksysodesol(eqs3, sol3) == (True, [0, 0]) + + eqs4 = [Eq(Derivative(f(x), x), f(x) + exp(x)), + Eq(Derivative(g(x), x), x*exp(x) + f(x) + g(x))] + sol4 = [Eq(f(x), C1*exp(x) + x*exp(x)), + Eq(g(x), C1*x*exp(x) + C2*exp(x) + x**2*exp(x))] + assert dsolve(eqs4) == sol4 + assert checksysodesol(eqs4, sol4) == (True, [0, 0]) + + eqs5 = [Eq(Derivative(f(x), x), 5*x + f(x) + g(x)), + Eq(Derivative(g(x), x), f(x) - g(x))] + sol5 = [Eq(f(x), C1*(1 + sqrt(2))*exp(sqrt(2)*x) + C2*(1 - sqrt(2))*exp(-sqrt(2)*x) + x*Rational(-5, 2) + + Rational(-5, 2)), + Eq(g(x), C1*exp(sqrt(2)*x) + C2*exp(-sqrt(2)*x) + x*Rational(-5, 2))] + assert dsolve(eqs5) == sol5 + assert checksysodesol(eqs5, sol5) == (True, [0, 0]) + + eqs6 = [Eq(Derivative(f(x), x), -9*f(x) - 4*g(x)), + Eq(Derivative(g(x), x), -4*g(x)), + Eq(Derivative(h(x), x), h(x) + exp(x))] + sol6 = [Eq(f(x), C2*exp(-4*x)*Rational(-4, 5) + C1*exp(-9*x)), + Eq(g(x), C2*exp(-4*x)), + Eq(h(x), C3*exp(x) + x*exp(x))] + assert dsolve(eqs6) == sol6 + assert checksysodesol(eqs6, sol6) == (True, [0, 0, 0]) + + # Regression test case for issue #8859 + # https://github.com/sympy/sympy/issues/8859 + eqs7 = [Eq(Derivative(f(t), t), 3*t + f(t)), + Eq(Derivative(g(t), t), g(t))] + sol7 = [Eq(f(t), C1*exp(t) - 3*t - 3), + Eq(g(t), C2*exp(t))] + assert dsolve(eqs7) == sol7 + assert checksysodesol(eqs7, sol7) == (True, [0, 0]) + + # Regression test case for issue #8567 + # https://github.com/sympy/sympy/issues/8567 + eqs8 = [Eq(Derivative(f(t), t), f(t) + 2*g(t)), + Eq(Derivative(g(t), t), -2*f(t) + g(t) + 2*exp(t))] + sol8 = [Eq(f(t), C1*exp(t)*sin(2*t) + C2*exp(t)*cos(2*t) + + exp(t)*sin(2*t)**2 + exp(t)*cos(2*t)**2), + Eq(g(t), C1*exp(t)*cos(2*t) - C2*exp(t)*sin(2*t))] + assert dsolve(eqs8) == sol8 + assert checksysodesol(eqs8, sol8) == (True, [0, 0]) + + # Regression test case for issue #19150 + # https://github.com/sympy/sympy/issues/19150 + eqs9 = [Eq(Derivative(f(t), t), (c - 2*f(t) + g(t))/(a*b)), + Eq(Derivative(g(t), t), (f(t) - 2*g(t) + h(t))/(a*b)), + Eq(Derivative(h(t), t), (d + g(t) - 2*h(t))/(a*b))] + sol9 = [Eq(f(t), -C1*exp(-2*t/(a*b)) + C2*exp(-t*(sqrt(2) + 2)/(a*b)) + C3*exp(-t*(2 - sqrt(2))/(a*b)) + + Mul(Rational(1, 4), 3*c + d, evaluate=False)), + Eq(g(t), -sqrt(2)*C2*exp(-t*(sqrt(2) + 2)/(a*b)) + sqrt(2)*C3*exp(-t*(2 - sqrt(2))/(a*b)) + + Mul(Rational(1, 2), c + d, evaluate=False)), + Eq(h(t), C1*exp(-2*t/(a*b)) + C2*exp(-t*(sqrt(2) + 2)/(a*b)) + C3*exp(-t*(2 - sqrt(2))/(a*b)) + + Mul(Rational(1, 4), c + 3*d, evaluate=False))] + assert dsolve(eqs9) == sol9 + assert checksysodesol(eqs9, sol9) == (True, [0, 0, 0]) + + # Regression test case for issue #16635 + # https://github.com/sympy/sympy/issues/16635 + eqs10 = [Eq(Derivative(f(t), t), 15*t + f(t) - g(t) - 10), + Eq(Derivative(g(t), t), -15*t + f(t) - g(t) - 5)] + sol10 = [Eq(f(t), C1 + C2 + 5*t**3 + 5*t**2 + t*(C2 - 10)), + Eq(g(t), C1 + 5*t**3 - 10*t**2 + t*(C2 - 5))] + assert dsolve(eqs10) == sol10 + assert checksysodesol(eqs10, sol10) == (True, [0, 0]) + + # Multiple solutions + eqs11 = [Eq(Derivative(f(t), t)**2 - 2*Derivative(f(t), t) + 1, 4), + Eq(-y*f(t) + Derivative(g(t), t), 0)] + sol11 = [[Eq(f(t), C1 - t), Eq(g(t), C1*t*y + C2*y + t**2*y*Rational(-1, 2))], + [Eq(f(t), C1 + 3*t), Eq(g(t), C1*t*y + C2*y + t**2*y*Rational(3, 2))]] + assert dsolve(eqs11) == sol11 + for s11 in sol11: + assert checksysodesol(eqs11, s11) == (True, [0, 0]) + + # test case for issue #19831 + # https://github.com/sympy/sympy/issues/19831 + n = symbols('n', positive=True) + x0 = symbols('x_0') + t0 = symbols('t_0') + x_0 = symbols('x_0') + t_0 = symbols('t_0') + t = symbols('t') + x = Function('x') + y = Function('y') + T = symbols('T') + + eqs12 = [Eq(Derivative(y(t), t), x(t)), + Eq(Derivative(x(t), t), n*(y(t) + 1))] + sol12 = [Eq(y(t), C1*exp(sqrt(n)*t)*n**Rational(-1, 2) - C2*exp(-sqrt(n)*t)*n**Rational(-1, 2) - 1), + Eq(x(t), C1*exp(sqrt(n)*t) + C2*exp(-sqrt(n)*t))] + assert dsolve(eqs12) == sol12 + assert checksysodesol(eqs12, sol12) == (True, [0, 0]) + + sol12b = [ + Eq(y(t), (T*exp(-sqrt(n)*t_0)/2 + exp(-sqrt(n)*t_0)/2 + + x_0*exp(-sqrt(n)*t_0)/(2*sqrt(n)))*exp(sqrt(n)*t) + + (T*exp(sqrt(n)*t_0)/2 + exp(sqrt(n)*t_0)/2 - + x_0*exp(sqrt(n)*t_0)/(2*sqrt(n)))*exp(-sqrt(n)*t) - 1), + Eq(x(t), (T*sqrt(n)*exp(-sqrt(n)*t_0)/2 + sqrt(n)*exp(-sqrt(n)*t_0)/2 + + x_0*exp(-sqrt(n)*t_0)/2)*exp(sqrt(n)*t) + - (T*sqrt(n)*exp(sqrt(n)*t_0)/2 + sqrt(n)*exp(sqrt(n)*t_0)/2 - + x_0*exp(sqrt(n)*t_0)/2)*exp(-sqrt(n)*t)) + ] + assert dsolve(eqs12, ics={y(t0): T, x(t0): x0}) == sol12b + assert checksysodesol(eqs12, sol12b) == (True, [0, 0]) + + #Test cases added for the issue 19763 + #https://github.com/sympy/sympy/issues/19763 + + eq13 = [Eq(Derivative(f(t), t), f(t) + g(t) + 9), + Eq(Derivative(g(t), t), 2*f(t) + 5*g(t) + 23)] + sol13 = [Eq(f(t), -C1*(2 + sqrt(6))*exp(t*(3 - sqrt(6)))/2 - C2*(2 - sqrt(6))*exp(t*(sqrt(6) + 3))/2 - + Rational(22,3)), + Eq(g(t), C1*exp(t*(3 - sqrt(6))) + C2*exp(t*(sqrt(6) + 3)) - Rational(5,3))] + assert dsolve(eq13) == sol13 + assert checksysodesol(eq13, sol13) == (True, [0, 0]) + + eq14 = [Eq(Derivative(f(t), t), f(t) + g(t) + 81), + Eq(Derivative(g(t), t), -2*f(t) + g(t) + 23)] + sol14 = [Eq(f(t), sqrt(2)*C1*exp(t)*sin(sqrt(2)*t)/2 + + sqrt(2)*C2*exp(t)*cos(sqrt(2)*t)/2 + - 58*sin(sqrt(2)*t)**2/3 - 58*cos(sqrt(2)*t)**2/3), + Eq(g(t), C1*exp(t)*cos(sqrt(2)*t) - C2*exp(t)*sin(sqrt(2)*t) + - 185*sin(sqrt(2)*t)**2/3 - 185*cos(sqrt(2)*t)**2/3)] + assert dsolve(eq14) == sol14 + assert checksysodesol(eq14, sol14) == (True, [0,0]) + + eq15 = [Eq(Derivative(f(t), t), f(t) + 2*g(t) + k1), + Eq(Derivative(g(t), t), 3*f(t) + 4*g(t) + k2)] + sol15 = [Eq(f(t), -C1*(3 - sqrt(33))*exp(t*(5 + sqrt(33))/2)/6 - + C2*(3 + sqrt(33))*exp(t*(5 - sqrt(33))/2)/6 + 2*k1 - k2), + Eq(g(t), C1*exp(t*(5 + sqrt(33))/2) + C2*exp(t*(5 - sqrt(33))/2) - + Mul(Rational(1,2), 3*k1 - k2, evaluate = False))] + assert dsolve(eq15) == sol15 + assert checksysodesol(eq15, sol15) == (True, [0,0]) + + eq16 = [Eq(Derivative(f(t), t), k1), + Eq(Derivative(g(t), t), k2)] + sol16 = [Eq(f(t), C1 + k1*t), + Eq(g(t), C2 + k2*t)] + assert dsolve(eq16) == sol16 + assert checksysodesol(eq16, sol16) == (True, [0,0]) + + eq17 = [Eq(Derivative(f(t), t), 0), + Eq(Derivative(g(t), t), c*f(t) + k2)] + sol17 = [Eq(f(t), C1), + Eq(g(t), C2*c + t*(C1*c + k2))] + assert dsolve(eq17) == sol17 + assert checksysodesol(eq17 , sol17) == (True , [0,0]) + + eq18 = [Eq(Derivative(f(t), t), k1), + Eq(Derivative(g(t), t), f(t) + k2)] + sol18 = [Eq(f(t), C1 + k1*t), + Eq(g(t), C2 + k1*t**2/2 + t*(C1 + k2))] + assert dsolve(eq18) == sol18 + assert checksysodesol(eq18 , sol18) == (True , [0,0]) + + eq19 = [Eq(Derivative(f(t), t), k1), + Eq(Derivative(g(t), t), f(t) + 2*g(t) + k2)] + sol19 = [Eq(f(t), -2*C1 + k1*t), + Eq(g(t), C1 + C2*exp(2*t) - k1*t/2 - Mul(Rational(1,4), k1 + 2*k2 , evaluate = False))] + assert dsolve(eq19) == sol19 + assert checksysodesol(eq19 , sol19) == (True , [0,0]) + + eq20 = [Eq(diff(f(t), t), f(t) + k1), + Eq(diff(g(t), t), k2)] + sol20 = [Eq(f(t), C1*exp(t) - k1), + Eq(g(t), C2 + k2*t)] + assert dsolve(eq20) == sol20 + assert checksysodesol(eq20 , sol20) == (True , [0,0]) + + eq21 = [Eq(diff(f(t), t), g(t) + k1), + Eq(diff(g(t), t), 0)] + sol21 = [Eq(f(t), C1 + t*(C2 + k1)), + Eq(g(t), C2)] + assert dsolve(eq21) == sol21 + assert checksysodesol(eq21 , sol21) == (True , [0,0]) + + eq22 = [Eq(Derivative(f(t), t), f(t) + 2*g(t) + k1), + Eq(Derivative(g(t), t), k2)] + sol22 = [Eq(f(t), -2*C1 + C2*exp(t) - k1 - 2*k2*t - 2*k2), + Eq(g(t), C1 + k2*t)] + assert dsolve(eq22) == sol22 + assert checksysodesol(eq22 , sol22) == (True , [0,0]) + + eq23 = [Eq(Derivative(f(t), t), g(t) + k1), + Eq(Derivative(g(t), t), 2*g(t) + k2)] + sol23 = [Eq(f(t), C1 + C2*exp(2*t)/2 - k2/4 + t*(2*k1 - k2)/2), + Eq(g(t), C2*exp(2*t) - k2/2)] + assert dsolve(eq23) == sol23 + assert checksysodesol(eq23 , sol23) == (True , [0,0]) + + eq24 = [Eq(Derivative(f(t), t), f(t) + k1), + Eq(Derivative(g(t), t), 2*f(t) + k2)] + sol24 = [Eq(f(t), C1*exp(t)/2 - k1), + Eq(g(t), C1*exp(t) + C2 - 2*k1 - t*(2*k1 - k2))] + assert dsolve(eq24) == sol24 + assert checksysodesol(eq24 , sol24) == (True , [0,0]) + + eq25 = [Eq(Derivative(f(t), t), f(t) + 2*g(t) + k1), + Eq(Derivative(g(t), t), 3*f(t) + 6*g(t) + k2)] + sol25 = [Eq(f(t), -2*C1 + C2*exp(7*t)/3 + 2*t*(3*k1 - k2)/7 - + Mul(Rational(1,49), k1 + 2*k2 , evaluate = False)), + Eq(g(t), C1 + C2*exp(7*t) - t*(3*k1 - k2)/7 - + Mul(Rational(3,49), k1 + 2*k2 , evaluate = False))] + assert dsolve(eq25) == sol25 + assert checksysodesol(eq25 , sol25) == (True , [0,0]) + + eq26 = [Eq(Derivative(f(t), t), 2*f(t) - g(t) + k1), + Eq(Derivative(g(t), t), 4*f(t) - 2*g(t) + 2*k1)] + sol26 = [Eq(f(t), C1 + 2*C2 + t*(2*C1 + k1)), + Eq(g(t), 4*C2 + t*(4*C1 + 2*k1))] + assert dsolve(eq26) == sol26 + assert checksysodesol(eq26 , sol26) == (True , [0,0]) + + # Test Case added for issue #22715 + # https://github.com/sympy/sympy/issues/22715 + + eq27 = [Eq(diff(x(t),t),-1*y(t)+10), Eq(diff(y(t),t),5*x(t)-2*y(t)+3)] + sol27 = [Eq(x(t), (C1/5 - 2*C2/5)*exp(-t)*cos(2*t) + - (2*C1/5 + C2/5)*exp(-t)*sin(2*t) + + 17*sin(2*t)**2/5 + 17*cos(2*t)**2/5), + Eq(y(t), C1*exp(-t)*cos(2*t) - C2*exp(-t)*sin(2*t) + + 10*sin(2*t)**2 + 10*cos(2*t)**2)] + assert dsolve(eq27) == sol27 + assert checksysodesol(eq27 , sol27) == (True , [0,0]) + + +def test_sysode_linear_neq_order1_type3(): + + f, g, h, k, x0 , y0 = symbols('f g h k x0 y0', cls=Function) + x, t, a = symbols('x t a') + r = symbols('r', real=True) + + eqs1 = [Eq(Derivative(f(r), r), r*g(r) + f(r)), + Eq(Derivative(g(r), r), -r*f(r) + g(r))] + sol1 = [Eq(f(r), C1*exp(r)*sin(r**2/2) + C2*exp(r)*cos(r**2/2)), + Eq(g(r), C1*exp(r)*cos(r**2/2) - C2*exp(r)*sin(r**2/2))] + assert dsolve(eqs1) == sol1 + assert checksysodesol(eqs1, sol1) == (True, [0, 0]) + + eqs2 = [Eq(Derivative(f(x), x), x**2*g(x) + x*f(x)), + Eq(Derivative(g(x), x), 2*x**2*f(x) + (3*x**2 + x)*g(x))] + sol2 = [Eq(f(x), (sqrt(17)*C1/17 + C2*(17 - 3*sqrt(17))/34)*exp(x**3*(3 + sqrt(17))/6 + x**2/2) - + exp(x**3*(3 - sqrt(17))/6 + x**2/2)*(sqrt(17)*C1/17 + C2*(3*sqrt(17) + 17)*Rational(-1, 34))), + Eq(g(x), exp(x**3*(3 - sqrt(17))/6 + x**2/2)*(C1*(17 - 3*sqrt(17))/34 + sqrt(17)*C2*Rational(-2, + 17)) + exp(x**3*(3 + sqrt(17))/6 + x**2/2)*(C1*(3*sqrt(17) + 17)/34 + sqrt(17)*C2*Rational(2, 17)))] + assert dsolve(eqs2) == sol2 + assert checksysodesol(eqs2, sol2) == (True, [0, 0]) + + eqs3 = [Eq(f(x).diff(x), x*f(x) + g(x)), + Eq(g(x).diff(x), -f(x) + x*g(x))] + sol3 = [Eq(f(x), (C1/2 + I*C2/2)*exp(x**2/2 - I*x) + exp(x**2/2 + I*x)*(C1/2 + I*C2*Rational(-1, 2))), + Eq(g(x), (I*C1/2 + C2/2)*exp(x**2/2 + I*x) - exp(x**2/2 - I*x)*(I*C1/2 + C2*Rational(-1, 2)))] + assert dsolve(eqs3) == sol3 + assert checksysodesol(eqs3, sol3) == (True, [0, 0]) + + eqs4 = [Eq(f(x).diff(x), x*(f(x) + g(x) + h(x))), Eq(g(x).diff(x), x*(f(x) + g(x) + h(x))), + Eq(h(x).diff(x), x*(f(x) + g(x) + h(x)))] + sol4 = [Eq(f(x), -C1/3 - C2/3 + 2*C3/3 + (C1/3 + C2/3 + C3/3)*exp(3*x**2/2)), + Eq(g(x), 2*C1/3 - C2/3 - C3/3 + (C1/3 + C2/3 + C3/3)*exp(3*x**2/2)), + Eq(h(x), -C1/3 + 2*C2/3 - C3/3 + (C1/3 + C2/3 + C3/3)*exp(3*x**2/2))] + assert dsolve(eqs4) == sol4 + assert checksysodesol(eqs4, sol4) == (True, [0, 0, 0]) + + eqs5 = [Eq(f(x).diff(x), x**2*(f(x) + g(x) + h(x))), Eq(g(x).diff(x), x**2*(f(x) + g(x) + h(x))), + Eq(h(x).diff(x), x**2*(f(x) + g(x) + h(x)))] + sol5 = [Eq(f(x), -C1/3 - C2/3 + 2*C3/3 + (C1/3 + C2/3 + C3/3)*exp(x**3)), + Eq(g(x), 2*C1/3 - C2/3 - C3/3 + (C1/3 + C2/3 + C3/3)*exp(x**3)), + Eq(h(x), -C1/3 + 2*C2/3 - C3/3 + (C1/3 + C2/3 + C3/3)*exp(x**3))] + assert dsolve(eqs5) == sol5 + assert checksysodesol(eqs5, sol5) == (True, [0, 0, 0]) + + eqs6 = [Eq(Derivative(f(x), x), x*(f(x) + g(x) + h(x) + k(x))), + Eq(Derivative(g(x), x), x*(f(x) + g(x) + h(x) + k(x))), + Eq(Derivative(h(x), x), x*(f(x) + g(x) + h(x) + k(x))), + Eq(Derivative(k(x), x), x*(f(x) + g(x) + h(x) + k(x)))] + sol6 = [Eq(f(x), -C1/4 - C2/4 - C3/4 + 3*C4/4 + (C1/4 + C2/4 + C3/4 + C4/4)*exp(2*x**2)), + Eq(g(x), 3*C1/4 - C2/4 - C3/4 - C4/4 + (C1/4 + C2/4 + C3/4 + C4/4)*exp(2*x**2)), + Eq(h(x), -C1/4 + 3*C2/4 - C3/4 - C4/4 + (C1/4 + C2/4 + C3/4 + C4/4)*exp(2*x**2)), + Eq(k(x), -C1/4 - C2/4 + 3*C3/4 - C4/4 + (C1/4 + C2/4 + C3/4 + C4/4)*exp(2*x**2))] + assert dsolve(eqs6) == sol6 + assert checksysodesol(eqs6, sol6) == (True, [0, 0, 0, 0]) + + y = symbols("y", real=True) + + eqs7 = [Eq(Derivative(f(y), y), y*f(y) + g(y)), + Eq(Derivative(g(y), y), y*g(y) - f(y))] + sol7 = [Eq(f(y), C1*exp(y**2/2)*sin(y) + C2*exp(y**2/2)*cos(y)), + Eq(g(y), C1*exp(y**2/2)*cos(y) - C2*exp(y**2/2)*sin(y))] + assert dsolve(eqs7) == sol7 + assert checksysodesol(eqs7, sol7) == (True, [0, 0]) + + #Test cases added for the issue 19763 + #https://github.com/sympy/sympy/issues/19763 + + eqs8 = [Eq(Derivative(f(t), t), 5*t*f(t) + 2*h(t)), + Eq(Derivative(h(t), t), 2*f(t) + 5*t*h(t))] + sol8 = [Eq(f(t), Mul(-1, (C1/2 - C2/2), evaluate = False)*exp(5*t**2/2 - 2*t) + (C1/2 + C2/2)*exp(5*t**2/2 + 2*t)), + Eq(h(t), (C1/2 - C2/2)*exp(5*t**2/2 - 2*t) + (C1/2 + C2/2)*exp(5*t**2/2 + 2*t))] + assert dsolve(eqs8) == sol8 + assert checksysodesol(eqs8, sol8) == (True, [0, 0]) + + eqs9 = [Eq(diff(f(t), t), 5*t*f(t) + t**2*g(t)), + Eq(diff(g(t), t), -t**2*f(t) + 5*t*g(t))] + sol9 = [Eq(f(t), (C1/2 - I*C2/2)*exp(I*t**3/3 + 5*t**2/2) + (C1/2 + I*C2/2)*exp(-I*t**3/3 + 5*t**2/2)), + Eq(g(t), Mul(-1, (I*C1/2 - C2/2) , evaluate = False)*exp(-I*t**3/3 + 5*t**2/2) + (I*C1/2 + C2/2)*exp(I*t**3/3 + 5*t**2/2))] + assert dsolve(eqs9) == sol9 + assert checksysodesol(eqs9 , sol9) == (True , [0,0]) + + eqs10 = [Eq(diff(f(t), t), t**2*g(t) + 5*t*f(t)), + Eq(diff(g(t), t), -t**2*f(t) + (9*t**2 + 5*t)*g(t))] + sol10 = [Eq(f(t), (C1*(77 - 9*sqrt(77))/154 + sqrt(77)*C2/77)*exp(t**3*(sqrt(77) + 9)/6 + 5*t**2/2) + (C1*(77 + 9*sqrt(77))/154 - sqrt(77)*C2/77)*exp(t**3*(9 - sqrt(77))/6 + 5*t**2/2)), + Eq(g(t), (sqrt(77)*C1/77 + C2*(77 - 9*sqrt(77))/154)*exp(t**3*(9 - sqrt(77))/6 + 5*t**2/2) - (sqrt(77)*C1/77 - C2*(77 + 9*sqrt(77))/154)*exp(t**3*(sqrt(77) + 9)/6 + 5*t**2/2))] + assert dsolve(eqs10) == sol10 + assert checksysodesol(eqs10 , sol10) == (True , [0,0]) + + eqs11 = [Eq(diff(f(t), t), 5*t*f(t) + t**2*g(t)), + Eq(diff(g(t), t), (1-t**2)*f(t) + (5*t + 9*t**2)*g(t))] + sol11 = [Eq(f(t), C1*x0(t) + C2*x0(t)*Integral(t**2*exp(Integral(5*t, t))*exp(Integral(9*t**2 + 5*t, t))/x0(t)**2, t)), + Eq(g(t), C1*y0(t) + C2*(y0(t)*Integral(t**2*exp(Integral(5*t, t))*exp(Integral(9*t**2 + 5*t, t))/x0(t)**2, t) + exp(Integral(5*t, t))*exp(Integral(9*t**2 + 5*t, t))/x0(t)))] + assert dsolve(eqs11) == sol11 + +@slow +def test_sysode_linear_neq_order1_type4(): + + f, g, h, k = symbols('f g h k', cls=Function) + x, t, a = symbols('x t a') + r = symbols('r', real=True) + + eqs1 = [Eq(diff(f(r), r), f(r) + r*g(r) + r**2), Eq(diff(g(r), r), -r*f(r) + g(r) + r)] + sol1 = [Eq(f(r), C1*exp(r)*sin(r**2/2) + C2*exp(r)*cos(r**2/2) + exp(r)*sin(r**2/2)*Integral(r**2*exp(-r)*sin(r**2/2) + + r*exp(-r)*cos(r**2/2), r) + exp(r)*cos(r**2/2)*Integral(r**2*exp(-r)*cos(r**2/2) - r*exp(-r)*sin(r**2/2), r)), + Eq(g(r), C1*exp(r)*cos(r**2/2) - C2*exp(r)*sin(r**2/2) - exp(r)*sin(r**2/2)*Integral(r**2*exp(-r)*cos(r**2/2) - + r*exp(-r)*sin(r**2/2), r) + exp(r)*cos(r**2/2)*Integral(r**2*exp(-r)*sin(r**2/2) + r*exp(-r)*cos(r**2/2), r))] + assert dsolve(eqs1) == sol1 + assert checksysodesol(eqs1, sol1) == (True, [0, 0]) + + eqs2 = [Eq(diff(f(r), r), f(r) + r*g(r) + r), Eq(diff(g(r), r), -r*f(r) + g(r) + log(r))] + sol2 = [Eq(f(r), C1*exp(r)*sin(r**2/2) + C2*exp(r)*cos(r**2/2) + exp(r)*sin(r**2/2)*Integral(r*exp(-r)*sin(r**2/2) + + exp(-r)*log(r)*cos(r**2/2), r) + exp(r)*cos(r**2/2)*Integral(r*exp(-r)*cos(r**2/2) - exp(-r)*log(r)*sin( + r**2/2), r)), + Eq(g(r), C1*exp(r)*cos(r**2/2) - C2*exp(r)*sin(r**2/2) - exp(r)*sin(r**2/2)*Integral(r*exp(-r)*cos(r**2/2) - + exp(-r)*log(r)*sin(r**2/2), r) + exp(r)*cos(r**2/2)*Integral(r*exp(-r)*sin(r**2/2) + exp(-r)*log(r)*cos( + r**2/2), r))] + # XXX: dsolve hangs for this in integration + assert dsolve_system(eqs2, simplify=False, doit=False) == [sol2] + assert checksysodesol(eqs2, sol2) == (True, [0, 0]) + + eqs3 = [Eq(Derivative(f(x), x), x*(f(x) + g(x) + h(x)) + x), + Eq(Derivative(g(x), x), x*(f(x) + g(x) + h(x)) + x), + Eq(Derivative(h(x), x), x*(f(x) + g(x) + h(x)) + 1)] + sol3 = [Eq(f(x), C1*Rational(-1, 3) + C2*Rational(-1, 3) + C3*Rational(2, 3) + x**2/6 + x*Rational(-1, 3) + + (C1/3 + C2/3 + C3/3)*exp(x**2*Rational(3, 2)) + + sqrt(6)*sqrt(pi)*erf(sqrt(6)*x/2)*exp(x**2*Rational(3, 2))/18 + Rational(-2, 9)), + Eq(g(x), C1*Rational(2, 3) + C2*Rational(-1, 3) + C3*Rational(-1, 3) + x**2/6 + x*Rational(-1, 3) + + (C1/3 + C2/3 + C3/3)*exp(x**2*Rational(3, 2)) + + sqrt(6)*sqrt(pi)*erf(sqrt(6)*x/2)*exp(x**2*Rational(3, 2))/18 + Rational(-2, 9)), + Eq(h(x), C1*Rational(-1, 3) + C2*Rational(2, 3) + C3*Rational(-1, 3) + x**2*Rational(-1, 3) + + x*Rational(2, 3) + (C1/3 + C2/3 + C3/3)*exp(x**2*Rational(3, 2)) + + sqrt(6)*sqrt(pi)*erf(sqrt(6)*x/2)*exp(x**2*Rational(3, 2))/18 + Rational(-2, 9))] + assert dsolve(eqs3) == sol3 + assert checksysodesol(eqs3, sol3) == (True, [0, 0, 0]) + + eqs4 = [Eq(Derivative(f(x), x), x*(f(x) + g(x) + h(x)) + sin(x)), + Eq(Derivative(g(x), x), x*(f(x) + g(x) + h(x)) + sin(x)), + Eq(Derivative(h(x), x), x*(f(x) + g(x) + h(x)) + sin(x))] + sol4 = [Eq(f(x), C1*Rational(-1, 3) + C2*Rational(-1, 3) + C3*Rational(2, 3) + (C1/3 + C2/3 + + C3/3)*exp(x**2*Rational(3, 2)) + Integral(sin(x)*exp(x**2*Rational(-3, 2)), x)*exp(x**2*Rational(3, + 2))), + Eq(g(x), C1*Rational(2, 3) + C2*Rational(-1, 3) + C3*Rational(-1, 3) + (C1/3 + C2/3 + + C3/3)*exp(x**2*Rational(3, 2)) + Integral(sin(x)*exp(x**2*Rational(-3, 2)), x)*exp(x**2*Rational(3, + 2))), + Eq(h(x), C1*Rational(-1, 3) + C2*Rational(2, 3) + C3*Rational(-1, 3) + (C1/3 + C2/3 + + C3/3)*exp(x**2*Rational(3, 2)) + Integral(sin(x)*exp(x**2*Rational(-3, 2)), x)*exp(x**2*Rational(3, + 2)))] + assert dsolve(eqs4) == sol4 + assert checksysodesol(eqs4, sol4) == (True, [0, 0, 0]) + + eqs5 = [Eq(Derivative(f(x), x), x*(f(x) + g(x) + h(x) + k(x) + 1)), + Eq(Derivative(g(x), x), x*(f(x) + g(x) + h(x) + k(x) + 1)), + Eq(Derivative(h(x), x), x*(f(x) + g(x) + h(x) + k(x) + 1)), + Eq(Derivative(k(x), x), x*(f(x) + g(x) + h(x) + k(x) + 1))] + sol5 = [Eq(f(x), C1*Rational(-1, 4) + C2*Rational(-1, 4) + C3*Rational(-1, 4) + C4*Rational(3, 4) + (C1/4 + + C2/4 + C3/4 + C4/4)*exp(2*x**2) + Rational(-1, 4)), + Eq(g(x), C1*Rational(3, 4) + C2*Rational(-1, 4) + C3*Rational(-1, 4) + C4*Rational(-1, 4) + (C1/4 + + C2/4 + C3/4 + C4/4)*exp(2*x**2) + Rational(-1, 4)), + Eq(h(x), C1*Rational(-1, 4) + C2*Rational(3, 4) + C3*Rational(-1, 4) + C4*Rational(-1, 4) + (C1/4 + + C2/4 + C3/4 + C4/4)*exp(2*x**2) + Rational(-1, 4)), + Eq(k(x), C1*Rational(-1, 4) + C2*Rational(-1, 4) + C3*Rational(3, 4) + C4*Rational(-1, 4) + (C1/4 + + C2/4 + C3/4 + C4/4)*exp(2*x**2) + Rational(-1, 4))] + assert dsolve(eqs5) == sol5 + assert checksysodesol(eqs5, sol5) == (True, [0, 0, 0, 0]) + + eqs6 = [Eq(Derivative(f(x), x), x**2*(f(x) + g(x) + h(x) + k(x) + 1)), + Eq(Derivative(g(x), x), x**2*(f(x) + g(x) + h(x) + k(x) + 1)), + Eq(Derivative(h(x), x), x**2*(f(x) + g(x) + h(x) + k(x) + 1)), + Eq(Derivative(k(x), x), x**2*(f(x) + g(x) + h(x) + k(x) + 1))] + sol6 = [Eq(f(x), C1*Rational(-1, 4) + C2*Rational(-1, 4) + C3*Rational(-1, 4) + C4*Rational(3, 4) + (C1/4 + + C2/4 + C3/4 + C4/4)*exp(x**3*Rational(4, 3)) + Rational(-1, 4)), + Eq(g(x), C1*Rational(3, 4) + C2*Rational(-1, 4) + C3*Rational(-1, 4) + C4*Rational(-1, 4) + (C1/4 + + C2/4 + C3/4 + C4/4)*exp(x**3*Rational(4, 3)) + Rational(-1, 4)), + Eq(h(x), C1*Rational(-1, 4) + C2*Rational(3, 4) + C3*Rational(-1, 4) + C4*Rational(-1, 4) + (C1/4 + + C2/4 + C3/4 + C4/4)*exp(x**3*Rational(4, 3)) + Rational(-1, 4)), + Eq(k(x), C1*Rational(-1, 4) + C2*Rational(-1, 4) + C3*Rational(3, 4) + C4*Rational(-1, 4) + (C1/4 + + C2/4 + C3/4 + C4/4)*exp(x**3*Rational(4, 3)) + Rational(-1, 4))] + assert dsolve(eqs6) == sol6 + assert checksysodesol(eqs6, sol6) == (True, [0, 0, 0, 0]) + + eqs7 = [Eq(Derivative(f(x), x), (f(x) + g(x) + h(x))*log(x) + sin(x)), Eq(Derivative(g(x), x), (f(x) + g(x) + + h(x))*log(x) + sin(x)), Eq(Derivative(h(x), x), (f(x) + g(x) + h(x))*log(x) + sin(x))] + sol7 = [Eq(f(x), -C1/3 - C2/3 + 2*C3/3 + (C1/3 + C2/3 + + C3/3)*exp(x*(3*log(x) - 3)) + exp(x*(3*log(x) - + 3))*Integral(exp(3*x)*exp(-3*x*log(x))*sin(x), x)), + Eq(g(x), 2*C1/3 - C2/3 - C3/3 + (C1/3 + C2/3 + + C3/3)*exp(x*(3*log(x) - 3)) + exp(x*(3*log(x) - + 3))*Integral(exp(3*x)*exp(-3*x*log(x))*sin(x), x)), + Eq(h(x), -C1/3 + 2*C2/3 - C3/3 + (C1/3 + C2/3 + + C3/3)*exp(x*(3*log(x) - 3)) + exp(x*(3*log(x) - + 3))*Integral(exp(3*x)*exp(-3*x*log(x))*sin(x), x))] + with dotprodsimp(True): + assert dsolve(eqs7, simplify=False, doit=False) == sol7 + assert checksysodesol(eqs7, sol7) == (True, [0, 0, 0]) + + eqs8 = [Eq(Derivative(f(x), x), (f(x) + g(x) + h(x) + k(x))*log(x) + sin(x)), Eq(Derivative(g(x), x), (f(x) + + g(x) + h(x) + k(x))*log(x) + sin(x)), Eq(Derivative(h(x), x), (f(x) + g(x) + h(x) + k(x))*log(x) + + sin(x)), Eq(Derivative(k(x), x), (f(x) + g(x) + h(x) + k(x))*log(x) + sin(x))] + sol8 = [Eq(f(x), -C1/4 - C2/4 - C3/4 + 3*C4/4 + (C1/4 + C2/4 + C3/4 + + C4/4)*exp(x*(4*log(x) - 4)) + exp(x*(4*log(x) - + 4))*Integral(exp(4*x)*exp(-4*x*log(x))*sin(x), x)), + Eq(g(x), 3*C1/4 - C2/4 - C3/4 - C4/4 + (C1/4 + C2/4 + C3/4 + + C4/4)*exp(x*(4*log(x) - 4)) + exp(x*(4*log(x) - + 4))*Integral(exp(4*x)*exp(-4*x*log(x))*sin(x), x)), + Eq(h(x), -C1/4 + 3*C2/4 - C3/4 - C4/4 + (C1/4 + C2/4 + C3/4 + + C4/4)*exp(x*(4*log(x) - 4)) + exp(x*(4*log(x) - + 4))*Integral(exp(4*x)*exp(-4*x*log(x))*sin(x), x)), + Eq(k(x), -C1/4 - C2/4 + 3*C3/4 - C4/4 + (C1/4 + C2/4 + C3/4 + + C4/4)*exp(x*(4*log(x) - 4)) + exp(x*(4*log(x) - + 4))*Integral(exp(4*x)*exp(-4*x*log(x))*sin(x), x))] + with dotprodsimp(True): + assert dsolve(eqs8) == sol8 + assert checksysodesol(eqs8, sol8) == (True, [0, 0, 0, 0]) + + +def test_sysode_linear_neq_order1_type5_type6(): + f, g = symbols("f g", cls=Function) + x, x_ = symbols("x x_") + + # Type 5 + eqs1 = [Eq(Derivative(f(x), x), (2*f(x) + g(x))/x), Eq(Derivative(g(x), x), (f(x) + 2*g(x))/x)] + sol1 = [Eq(f(x), -C1*x + C2*x**3), Eq(g(x), C1*x + C2*x**3)] + assert dsolve(eqs1) == sol1 + assert checksysodesol(eqs1, sol1) == (True, [0, 0]) + + # Type 6 + eqs2 = [Eq(Derivative(f(x), x), (2*f(x) + g(x) + 1)/x), + Eq(Derivative(g(x), x), (x + f(x) + 2*g(x))/x)] + sol2 = [Eq(f(x), C2*x**3 - x*(C1 + Rational(1, 4)) + x*log(x)*Rational(-1, 2) + Rational(-2, 3)), + Eq(g(x), C2*x**3 + x*log(x)/2 + x*(C1 + Rational(-1, 4)) + Rational(1, 3))] + assert dsolve(eqs2) == sol2 + assert checksysodesol(eqs2, sol2) == (True, [0, 0]) + + +def test_higher_order_to_first_order(): + f, g = symbols('f g', cls=Function) + x = symbols('x') + + eqs1 = [Eq(Derivative(f(x), (x, 2)), 2*f(x) + g(x)), + Eq(Derivative(g(x), (x, 2)), -f(x))] + sol1 = [Eq(f(x), -C2*x*exp(-x) + C3*x*exp(x) - (C1 - C2)*exp(-x) + (C3 + C4)*exp(x)), + Eq(g(x), C2*x*exp(-x) - C3*x*exp(x) + (C1 + C2)*exp(-x) + (C3 - C4)*exp(x))] + assert dsolve(eqs1) == sol1 + assert checksysodesol(eqs1, sol1) == (True, [0, 0]) + + eqs2 = [Eq(f(x).diff(x, 2), 0), Eq(g(x).diff(x, 2), f(x))] + sol2 = [Eq(f(x), C1 + C2*x), Eq(g(x), C1*x**2/2 + C2*x**3/6 + C3 + C4*x)] + assert dsolve(eqs2) == sol2 + assert checksysodesol(eqs2, sol2) == (True, [0, 0]) + + eqs3 = [Eq(Derivative(f(x), (x, 2)), 2*f(x)), + Eq(Derivative(g(x), (x, 2)), -f(x) + 2*g(x))] + sol3 = [Eq(f(x), 4*C1*exp(-sqrt(2)*x) + 4*C2*exp(sqrt(2)*x)), + Eq(g(x), sqrt(2)*C1*x*exp(-sqrt(2)*x) - sqrt(2)*C2*x*exp(sqrt(2)*x) + (C1 + + sqrt(2)*C4)*exp(-sqrt(2)*x) + (C2 - sqrt(2)*C3)*exp(sqrt(2)*x))] + assert dsolve(eqs3) == sol3 + assert checksysodesol(eqs3, sol3) == (True, [0, 0]) + + eqs4 = [Eq(Derivative(f(x), (x, 2)), 2*f(x) + g(x)), + Eq(Derivative(g(x), (x, 2)), 2*g(x))] + sol4 = [Eq(f(x), C1*x*exp(sqrt(2)*x)/4 + C3*x*exp(-sqrt(2)*x)/4 + (C2/4 + sqrt(2)*C3/8)*exp(-sqrt(2)*x) - + exp(sqrt(2)*x)*(sqrt(2)*C1/8 + C4*Rational(-1, 4))), + Eq(g(x), sqrt(2)*C1*exp(sqrt(2)*x)/2 + sqrt(2)*C3*exp(-sqrt(2)*x)*Rational(-1, 2))] + assert dsolve(eqs4) == sol4 + assert checksysodesol(eqs4, sol4) == (True, [0, 0]) + + eqs5 = [Eq(f(x).diff(x, 2), f(x)), Eq(g(x).diff(x, 2), f(x))] + sol5 = [Eq(f(x), -C1*exp(-x) + C2*exp(x)), Eq(g(x), -C1*exp(-x) + C2*exp(x) + C3 + C4*x)] + assert dsolve(eqs5) == sol5 + assert checksysodesol(eqs5, sol5) == (True, [0, 0]) + + eqs6 = [Eq(Derivative(f(x), (x, 2)), f(x) + g(x)), + Eq(Derivative(g(x), (x, 2)), -f(x) - g(x))] + sol6 = [Eq(f(x), C1 + C2*x**2/2 + C2 + C4*x**3/6 + x*(C3 + C4)), + Eq(g(x), -C1 + C2*x**2*Rational(-1, 2) - C3*x + C4*x**3*Rational(-1, 6))] + assert dsolve(eqs6) == sol6 + assert checksysodesol(eqs6, sol6) == (True, [0, 0]) + + eqs7 = [Eq(Derivative(f(x), (x, 2)), f(x) + g(x) + 1), + Eq(Derivative(g(x), (x, 2)), f(x) + g(x) + 1)] + sol7 = [Eq(f(x), -C1 - C2*x + sqrt(2)*C3*exp(sqrt(2)*x)/2 + sqrt(2)*C4*exp(-sqrt(2)*x)*Rational(-1, 2) + + Rational(-1, 2)), + Eq(g(x), C1 + C2*x + sqrt(2)*C3*exp(sqrt(2)*x)/2 + sqrt(2)*C4*exp(-sqrt(2)*x)*Rational(-1, 2) + + Rational(-1, 2))] + assert dsolve(eqs7) == sol7 + assert checksysodesol(eqs7, sol7) == (True, [0, 0]) + + eqs8 = [Eq(Derivative(f(x), (x, 2)), f(x) + g(x) + 1), + Eq(Derivative(g(x), (x, 2)), -f(x) - g(x) + 1)] + sol8 = [Eq(f(x), C1 + C2 + C4*x**3/6 + x**4/12 + x**2*(C2/2 + Rational(1, 2)) + x*(C3 + C4)), + Eq(g(x), -C1 - C3*x + C4*x**3*Rational(-1, 6) + x**4*Rational(-1, 12) - x**2*(C2/2 + Rational(-1, + 2)))] + assert dsolve(eqs8) == sol8 + assert checksysodesol(eqs8, sol8) == (True, [0, 0]) + + x, y = symbols('x, y', cls=Function) + t, l = symbols('t, l') + + eqs10 = [Eq(Derivative(x(t), (t, 2)), 5*x(t) + 43*y(t)), + Eq(Derivative(y(t), (t, 2)), x(t) + 9*y(t))] + sol10 = [Eq(x(t), C1*(61 - 9*sqrt(47))*sqrt(sqrt(47) + 7)*exp(-t*sqrt(sqrt(47) + 7))/2 + C2*sqrt(7 - + sqrt(47))*(61 + 9*sqrt(47))*exp(-t*sqrt(7 - sqrt(47)))/2 + C3*(61 - 9*sqrt(47))*sqrt(sqrt(47) + + 7)*exp(t*sqrt(sqrt(47) + 7))*Rational(-1, 2) + C4*sqrt(7 - sqrt(47))*(61 + 9*sqrt(47))*exp(t*sqrt(7 + - sqrt(47)))*Rational(-1, 2)), + Eq(y(t), C1*(7 - sqrt(47))*sqrt(sqrt(47) + 7)*exp(-t*sqrt(sqrt(47) + 7))*Rational(-1, 2) + C2*sqrt(7 + - sqrt(47))*(sqrt(47) + 7)*exp(-t*sqrt(7 - sqrt(47)))*Rational(-1, 2) + C3*(7 - + sqrt(47))*sqrt(sqrt(47) + 7)*exp(t*sqrt(sqrt(47) + 7))/2 + C4*sqrt(7 - sqrt(47))*(sqrt(47) + + 7)*exp(t*sqrt(7 - sqrt(47)))/2)] + assert dsolve(eqs10) == sol10 + assert checksysodesol(eqs10, sol10) == (True, [0, 0]) + + eqs11 = [Eq(7*x(t) + Derivative(x(t), (t, 2)) - 9*Derivative(y(t), t), 0), + Eq(7*y(t) + 9*Derivative(x(t), t) + Derivative(y(t), (t, 2)), 0)] + sol11 = [Eq(y(t), C1*(9 - sqrt(109))*sin(sqrt(2)*t*sqrt(9*sqrt(109) + 95)/2)/14 + C2*(9 - + sqrt(109))*cos(sqrt(2)*t*sqrt(9*sqrt(109) + 95)/2)*Rational(-1, 14) + C3*(9 + + sqrt(109))*sin(sqrt(2)*t*sqrt(95 - 9*sqrt(109))/2)/14 + C4*(9 + sqrt(109))*cos(sqrt(2)*t*sqrt(95 - + 9*sqrt(109))/2)*Rational(-1, 14)), + Eq(x(t), C1*(9 - sqrt(109))*cos(sqrt(2)*t*sqrt(9*sqrt(109) + 95)/2)*Rational(-1, 14) + C2*(9 - + sqrt(109))*sin(sqrt(2)*t*sqrt(9*sqrt(109) + 95)/2)*Rational(-1, 14) + C3*(9 + + sqrt(109))*cos(sqrt(2)*t*sqrt(95 - 9*sqrt(109))/2)/14 + C4*(9 + sqrt(109))*sin(sqrt(2)*t*sqrt(95 - + 9*sqrt(109))/2)/14)] + assert dsolve(eqs11) == sol11 + assert checksysodesol(eqs11, sol11) == (True, [0, 0]) + + # Euler Systems + # Note: To add examples of euler systems solver with non-homogeneous term. + eqs13 = [Eq(Derivative(f(t), (t, 2)), Derivative(f(t), t)/t + f(t)/t**2 + g(t)/t**2), + Eq(Derivative(g(t), (t, 2)), g(t)/t**2)] + sol13 = [Eq(f(t), C1*(sqrt(5) + 3)*Rational(-1, 2)*t**(Rational(1, 2) + + sqrt(5)*Rational(-1, 2)) + C2*t**(Rational(1, 2) + + sqrt(5)/2)*(3 - sqrt(5))*Rational(-1, 2) - C3*t**(1 - + sqrt(2))*(1 + sqrt(2)) - C4*t**(1 + sqrt(2))*(1 - sqrt(2))), + Eq(g(t), C1*(1 + sqrt(5))*Rational(-1, 2)*t**(Rational(1, 2) + + sqrt(5)*Rational(-1, 2)) + C2*t**(Rational(1, 2) + + sqrt(5)/2)*(1 - sqrt(5))*Rational(-1, 2))] + assert dsolve(eqs13) == sol13 + assert checksysodesol(eqs13, sol13) == (True, [0, 0]) + + # Solving systems using dsolve separately + eqs14 = [Eq(Derivative(f(t), (t, 2)), t*f(t)), + Eq(Derivative(g(t), (t, 2)), t*g(t))] + sol14 = [Eq(f(t), C1*airyai(t) + C2*airybi(t)), + Eq(g(t), C3*airyai(t) + C4*airybi(t))] + assert dsolve(eqs14) == sol14 + assert checksysodesol(eqs14, sol14) == (True, [0, 0]) + + + eqs15 = [Eq(Derivative(x(t), (t, 2)), t*(4*Derivative(x(t), t) + 8*Derivative(y(t), t))), + Eq(Derivative(y(t), (t, 2)), t*(12*Derivative(x(t), t) - 6*Derivative(y(t), t)))] + sol15 = [Eq(x(t), C1 - erf(sqrt(6)*t)*(sqrt(6)*sqrt(pi)*C2/33 + sqrt(6)*sqrt(pi)*C3*Rational(-1, 44)) + + erfi(sqrt(5)*t)*(sqrt(5)*sqrt(pi)*C2*Rational(2, 55) + sqrt(5)*sqrt(pi)*C3*Rational(4, 55))), + Eq(y(t), C4 + erf(sqrt(6)*t)*(sqrt(6)*sqrt(pi)*C2*Rational(2, 33) + sqrt(6)*sqrt(pi)*C3*Rational(-1, + 22)) + erfi(sqrt(5)*t)*(sqrt(5)*sqrt(pi)*C2*Rational(3, 110) + sqrt(5)*sqrt(pi)*C3*Rational(3, 55)))] + assert dsolve(eqs15) == sol15 + assert checksysodesol(eqs15, sol15) == (True, [0, 0]) + + +@slow +def test_higher_order_to_first_order_9(): + f, g = symbols('f g', cls=Function) + x = symbols('x') + + eqs9 = [f(x) + g(x) - 2*exp(I*x) + 2*Derivative(f(x), x) + Derivative(f(x), (x, 2)), + f(x) + g(x) - 2*exp(I*x) + 2*Derivative(g(x), x) + Derivative(g(x), (x, 2))] + sol9 = [Eq(f(x), -C1 + C4*exp(-2*x)/2 - (C2/2 - C3/2)*exp(-x)*cos(x) + + (C2/2 + C3/2)*exp(-x)*sin(x) + 2*((1 - 2*I)*exp(I*x)*sin(x)**2/5) + + 2*((1 - 2*I)*exp(I*x)*cos(x)**2/5)), + Eq(g(x), C1 - C4*exp(-2*x)/2 - (C2/2 - C3/2)*exp(-x)*cos(x) + + (C2/2 + C3/2)*exp(-x)*sin(x) + 2*((1 - 2*I)*exp(I*x)*sin(x)**2/5) + + 2*((1 - 2*I)*exp(I*x)*cos(x)**2/5))] + assert dsolve(eqs9) == sol9 + assert checksysodesol(eqs9, sol9) == (True, [0, 0]) + + +def test_higher_order_to_first_order_12(): + f, g = symbols('f g', cls=Function) + x = symbols('x') + + x, y = symbols('x, y', cls=Function) + t, l = symbols('t, l') + + eqs12 = [Eq(4*x(t) + Derivative(x(t), (t, 2)) + 8*Derivative(y(t), t), 0), + Eq(4*y(t) - 8*Derivative(x(t), t) + Derivative(y(t), (t, 2)), 0)] + sol12 = [Eq(y(t), C1*(2 - sqrt(5))*sin(2*t*sqrt(4*sqrt(5) + 9))*Rational(-1, 2) + C2*(2 - + sqrt(5))*cos(2*t*sqrt(4*sqrt(5) + 9))/2 + C3*(2 + sqrt(5))*sin(2*t*sqrt(9 - 4*sqrt(5)))*Rational(-1, + 2) + C4*(2 + sqrt(5))*cos(2*t*sqrt(9 - 4*sqrt(5)))/2), + Eq(x(t), C1*(2 - sqrt(5))*cos(2*t*sqrt(4*sqrt(5) + 9))*Rational(-1, 2) + C2*(2 - + sqrt(5))*sin(2*t*sqrt(4*sqrt(5) + 9))*Rational(-1, 2) + C3*(2 + sqrt(5))*cos(2*t*sqrt(9 - + 4*sqrt(5)))/2 + C4*(2 + sqrt(5))*sin(2*t*sqrt(9 - 4*sqrt(5)))/2)] + assert dsolve(eqs12) == sol12 + assert checksysodesol(eqs12, sol12) == (True, [0, 0]) + + +def test_second_order_to_first_order_2(): + f, g = symbols("f g", cls=Function) + x, t, x_, t_, d, a, m = symbols("x t x_ t_ d a m") + + eqs2 = [Eq(f(x).diff(x, 2), 2*(x*g(x).diff(x) - g(x))), + Eq(g(x).diff(x, 2),-2*(x*f(x).diff(x) - f(x)))] + sol2 = [Eq(f(x), C1*x + x*Integral(C2*exp(-x_)*exp(I*exp(2*x_))/2 + C2*exp(-x_)*exp(-I*exp(2*x_))/2 - + I*C3*exp(-x_)*exp(I*exp(2*x_))/2 + I*C3*exp(-x_)*exp(-I*exp(2*x_))/2, (x_, log(x)))), + Eq(g(x), C4*x + x*Integral(I*C2*exp(-x_)*exp(I*exp(2*x_))/2 - I*C2*exp(-x_)*exp(-I*exp(2*x_))/2 + + C3*exp(-x_)*exp(I*exp(2*x_))/2 + C3*exp(-x_)*exp(-I*exp(2*x_))/2, (x_, log(x))))] + # XXX: dsolve hangs for this in integration + assert dsolve_system(eqs2, simplify=False, doit=False) == [sol2] + assert checksysodesol(eqs2, sol2) == (True, [0, 0]) + + eqs3 = (Eq(diff(f(t),t,t), 9*t*diff(g(t),t)-9*g(t)), Eq(diff(g(t),t,t),7*t*diff(f(t),t)-7*f(t))) + sol3 = [Eq(f(t), C1*t + t*Integral(C2*exp(-t_)*exp(3*sqrt(7)*exp(2*t_)/2)/2 + C2*exp(-t_)* + exp(-3*sqrt(7)*exp(2*t_)/2)/2 + 3*sqrt(7)*C3*exp(-t_)*exp(3*sqrt(7)*exp(2*t_)/2)/14 - + 3*sqrt(7)*C3*exp(-t_)*exp(-3*sqrt(7)*exp(2*t_)/2)/14, (t_, log(t)))), + Eq(g(t), C4*t + t*Integral(sqrt(7)*C2*exp(-t_)*exp(3*sqrt(7)*exp(2*t_)/2)/6 - sqrt(7)*C2*exp(-t_)* + exp(-3*sqrt(7)*exp(2*t_)/2)/6 + C3*exp(-t_)*exp(3*sqrt(7)*exp(2*t_)/2)/2 + C3*exp(-t_)*exp(-3*sqrt(7)* + exp(2*t_)/2)/2, (t_, log(t))))] + # XXX: dsolve hangs for this in integration + assert dsolve_system(eqs3, simplify=False, doit=False) == [sol3] + assert checksysodesol(eqs3, sol3) == (True, [0, 0]) + + # Regression Test case for sympy#19238 + # https://github.com/sympy/sympy/issues/19238 + # Note: When the doit method is removed, these particular types of systems + # can be divided first so that we have lesser number of big matrices. + eqs5 = [Eq(Derivative(g(t), (t, 2)), a*m), + Eq(Derivative(f(t), (t, 2)), 0)] + sol5 = [Eq(g(t), C1 + C2*t + a*m*t**2/2), + Eq(f(t), C3 + C4*t)] + assert dsolve(eqs5) == sol5 + assert checksysodesol(eqs5, sol5) == (True, [0, 0]) + + # Type 2 + eqs6 = [Eq(Derivative(f(t), (t, 2)), f(t)/t**4), + Eq(Derivative(g(t), (t, 2)), d*g(t)/t**4)] + sol6 = [Eq(f(t), C1*sqrt(t**2)*exp(-1/t) - C2*sqrt(t**2)*exp(1/t)), + Eq(g(t), C3*sqrt(t**2)*exp(-sqrt(d)/t)*d**Rational(-1, 2) - + C4*sqrt(t**2)*exp(sqrt(d)/t)*d**Rational(-1, 2))] + assert dsolve(eqs6) == sol6 + assert checksysodesol(eqs6, sol6) == (True, [0, 0]) + + +@slow +def test_second_order_to_first_order_slow1(): + f, g = symbols("f g", cls=Function) + x, t, x_, t_, d, a, m = symbols("x t x_ t_ d a m") + + # Type 1 + + eqs1 = [Eq(f(x).diff(x, 2), 2/x *(x*g(x).diff(x) - g(x))), + Eq(g(x).diff(x, 2),-2/x *(x*f(x).diff(x) - f(x)))] + sol1 = [Eq(f(x), C1*x + 2*C2*x*Ci(2*x) - C2*sin(2*x) - 2*C3*x*Si(2*x) - C3*cos(2*x)), + Eq(g(x), -2*C2*x*Si(2*x) - C2*cos(2*x) - 2*C3*x*Ci(2*x) + C3*sin(2*x) + C4*x)] + assert dsolve(eqs1) == sol1 + assert checksysodesol(eqs1, sol1) == (True, [0, 0]) + + +def test_second_order_to_first_order_slow4(): + f, g = symbols("f g", cls=Function) + x, t, x_, t_, d, a, m = symbols("x t x_ t_ d a m") + + eqs4 = [Eq(Derivative(f(t), (t, 2)), t*sin(t)*Derivative(g(t), t) - g(t)*sin(t)), + Eq(Derivative(g(t), (t, 2)), t*sin(t)*Derivative(f(t), t) - f(t)*sin(t))] + sol4 = [Eq(f(t), C1*t + t*Integral(C2*exp(-t_)*exp(exp(t_)*cos(exp(t_)))*exp(-sin(exp(t_)))/2 + + C2*exp(-t_)*exp(-exp(t_)*cos(exp(t_)))*exp(sin(exp(t_)))/2 - C3*exp(-t_)*exp(exp(t_)*cos(exp(t_)))* + exp(-sin(exp(t_)))/2 + + C3*exp(-t_)*exp(-exp(t_)*cos(exp(t_)))*exp(sin(exp(t_)))/2, (t_, log(t)))), + Eq(g(t), C4*t + t*Integral(-C2*exp(-t_)*exp(exp(t_)*cos(exp(t_)))*exp(-sin(exp(t_)))/2 + + C2*exp(-t_)*exp(-exp(t_)*cos(exp(t_)))*exp(sin(exp(t_)))/2 + C3*exp(-t_)*exp(exp(t_)*cos(exp(t_)))* + exp(-sin(exp(t_)))/2 + C3*exp(-t_)*exp(-exp(t_)*cos(exp(t_)))*exp(sin(exp(t_)))/2, (t_, log(t))))] + # XXX: dsolve hangs for this in integration + assert dsolve_system(eqs4, simplify=False, doit=False) == [sol4] + assert checksysodesol(eqs4, sol4) == (True, [0, 0]) + + +def test_component_division(): + f, g, h, k = symbols('f g h k', cls=Function) + x = symbols("x") + funcs = [f(x), g(x), h(x), k(x)] + + eqs1 = [Eq(Derivative(f(x), x), 2*f(x)), + Eq(Derivative(g(x), x), f(x)), + Eq(Derivative(h(x), x), h(x)), + Eq(Derivative(k(x), x), h(x)**4 + k(x))] + sol1 = [Eq(f(x), 2*C1*exp(2*x)), + Eq(g(x), C1*exp(2*x) + C2), + Eq(h(x), C3*exp(x)), + Eq(k(x), C3**4*exp(4*x)/3 + C4*exp(x))] + assert dsolve(eqs1) == sol1 + assert checksysodesol(eqs1, sol1) == (True, [0, 0, 0, 0]) + + components1 = {((Eq(Derivative(f(x), x), 2*f(x)),), (Eq(Derivative(g(x), x), f(x)),)), + ((Eq(Derivative(h(x), x), h(x)),), (Eq(Derivative(k(x), x), h(x)**4 + k(x)),))} + eqsdict1 = ({f(x): set(), g(x): {f(x)}, h(x): set(), k(x): {h(x)}}, + {f(x): Eq(Derivative(f(x), x), 2*f(x)), + g(x): Eq(Derivative(g(x), x), f(x)), + h(x): Eq(Derivative(h(x), x), h(x)), + k(x): Eq(Derivative(k(x), x), h(x)**4 + k(x))}) + graph1 = [{f(x), g(x), h(x), k(x)}, {(g(x), f(x)), (k(x), h(x))}] + assert {tuple(tuple(scc) for scc in wcc) for wcc in _component_division(eqs1, funcs, x)} == components1 + assert _eqs2dict(eqs1, funcs) == eqsdict1 + assert [set(element) for element in _dict2graph(eqsdict1[0])] == graph1 + + eqs2 = [Eq(Derivative(f(x), x), 2*f(x)), + Eq(Derivative(g(x), x), f(x)), + Eq(Derivative(h(x), x), h(x)), + Eq(Derivative(k(x), x), f(x)**4 + k(x))] + sol2 = [Eq(f(x), C1*exp(2*x)), + Eq(g(x), C1*exp(2*x)/2 + C2), + Eq(h(x), C3*exp(x)), + Eq(k(x), C1**4*exp(8*x)/7 + C4*exp(x))] + assert dsolve(eqs2) == sol2 + assert checksysodesol(eqs2, sol2) == (True, [0, 0, 0, 0]) + + components2 = {frozenset([(Eq(Derivative(f(x), x), 2*f(x)),), + (Eq(Derivative(g(x), x), f(x)),), + (Eq(Derivative(k(x), x), f(x)**4 + k(x)),)]), + frozenset([(Eq(Derivative(h(x), x), h(x)),)])} + eqsdict2 = ({f(x): set(), g(x): {f(x)}, h(x): set(), k(x): {f(x)}}, + {f(x): Eq(Derivative(f(x), x), 2*f(x)), + g(x): Eq(Derivative(g(x), x), f(x)), + h(x): Eq(Derivative(h(x), x), h(x)), + k(x): Eq(Derivative(k(x), x), f(x)**4 + k(x))}) + graph2 = [{f(x), g(x), h(x), k(x)}, {(g(x), f(x)), (k(x), f(x))}] + assert {frozenset(tuple(scc) for scc in wcc) for wcc in _component_division(eqs2, funcs, x)} == components2 + assert _eqs2dict(eqs2, funcs) == eqsdict2 + assert [set(element) for element in _dict2graph(eqsdict2[0])] == graph2 + + eqs3 = [Eq(Derivative(f(x), x), 2*f(x)), + Eq(Derivative(g(x), x), x + f(x)), + Eq(Derivative(h(x), x), h(x)), + Eq(Derivative(k(x), x), f(x)**4 + k(x))] + sol3 = [Eq(f(x), C1*exp(2*x)), + Eq(g(x), C1*exp(2*x)/2 + C2 + x**2/2), + Eq(h(x), C3*exp(x)), + Eq(k(x), C1**4*exp(8*x)/7 + C4*exp(x))] + assert dsolve(eqs3) == sol3 + assert checksysodesol(eqs3, sol3) == (True, [0, 0, 0, 0]) + + components3 = {frozenset([(Eq(Derivative(f(x), x), 2*f(x)),), + (Eq(Derivative(g(x), x), x + f(x)),), + (Eq(Derivative(k(x), x), f(x)**4 + k(x)),)]), + frozenset([(Eq(Derivative(h(x), x), h(x)),),])} + eqsdict3 = ({f(x): set(), g(x): {f(x)}, h(x): set(), k(x): {f(x)}}, + {f(x): Eq(Derivative(f(x), x), 2*f(x)), + g(x): Eq(Derivative(g(x), x), x + f(x)), + h(x): Eq(Derivative(h(x), x), h(x)), + k(x): Eq(Derivative(k(x), x), f(x)**4 + k(x))}) + graph3 = [{f(x), g(x), h(x), k(x)}, {(g(x), f(x)), (k(x), f(x))}] + assert {frozenset(tuple(scc) for scc in wcc) for wcc in _component_division(eqs3, funcs, x)} == components3 + assert _eqs2dict(eqs3, funcs) == eqsdict3 + assert [set(l) for l in _dict2graph(eqsdict3[0])] == graph3 + + # Note: To be uncommented when the default option to call dsolve first for + # single ODE system can be rearranged. This can be done after the doit + # option in dsolve is made False by default. + + eqs4 = [Eq(Derivative(f(x), x), x*f(x) + 2*g(x)), + Eq(Derivative(g(x), x), f(x) + x*g(x) + x), + Eq(Derivative(h(x), x), h(x)), + Eq(Derivative(k(x), x), f(x)**4 + k(x))] + sol4 = [Eq(f(x), (C1/2 - sqrt(2)*C2/2 - sqrt(2)*Integral(x*exp(-x**2/2 - sqrt(2)*x)/2 + x*exp(-x**2/2 +\ + sqrt(2)*x)/2, x)/2 + Integral(sqrt(2)*x*exp(-x**2/2 - sqrt(2)*x)/2 - sqrt(2)*x*exp(-x**2/2 +\ + sqrt(2)*x)/2, x)/2)*exp(x**2/2 - sqrt(2)*x) + (C1/2 + sqrt(2)*C2/2 + sqrt(2)*Integral(x*exp(-x**2/2 + - sqrt(2)*x)/2 + x*exp(-x**2/2 + sqrt(2)*x)/2, x)/2 + Integral(sqrt(2)*x*exp(-x**2/2 - sqrt(2)*x)/2 + - sqrt(2)*x*exp(-x**2/2 + sqrt(2)*x)/2, x)/2)*exp(x**2/2 + sqrt(2)*x)), + Eq(g(x), (-sqrt(2)*C1/4 + C2/2 + Integral(x*exp(-x**2/2 - sqrt(2)*x)/2 + x*exp(-x**2/2 + sqrt(2)*x)/2, x)/2 -\ + sqrt(2)*Integral(sqrt(2)*x*exp(-x**2/2 - sqrt(2)*x)/2 - sqrt(2)*x*exp(-x**2/2 + sqrt(2)*x)/2, + x)/4)*exp(x**2/2 - sqrt(2)*x) + (sqrt(2)*C1/4 + C2/2 + Integral(x*exp(-x**2/2 - sqrt(2)*x)/2 + + x*exp(-x**2/2 + sqrt(2)*x)/2, x)/2 + sqrt(2)*Integral(sqrt(2)*x*exp(-x**2/2 - sqrt(2)*x)/2 - + sqrt(2)*x*exp(-x**2/2 + sqrt(2)*x)/2, x)/4)*exp(x**2/2 + sqrt(2)*x)), + Eq(h(x), C3*exp(x)), + Eq(k(x), C4*exp(x) + exp(x)*Integral((C1*exp(x**2/2 - sqrt(2)*x)/2 + C1*exp(x**2/2 + sqrt(2)*x)/2 - + sqrt(2)*C2*exp(x**2/2 - sqrt(2)*x)/2 + sqrt(2)*C2*exp(x**2/2 + sqrt(2)*x)/2 - sqrt(2)*exp(x**2/2 - + sqrt(2)*x)*Integral(x*exp(-x**2/2 - sqrt(2)*x)/2 + x*exp(-x**2/2 + sqrt(2)*x)/2, x)/2 + exp(x**2/2 - + sqrt(2)*x)*Integral(sqrt(2)*x*exp(-x**2/2 - sqrt(2)*x)/2 - sqrt(2)*x*exp(-x**2/2 + sqrt(2)*x)/2, + x)/2 + sqrt(2)*exp(x**2/2 + sqrt(2)*x)*Integral(x*exp(-x**2/2 - sqrt(2)*x)/2 + x*exp(-x**2/2 + + sqrt(2)*x)/2, x)/2 + exp(x**2/2 + sqrt(2)*x)*Integral(sqrt(2)*x*exp(-x**2/2 - sqrt(2)*x)/2 - + sqrt(2)*x*exp(-x**2/2 + sqrt(2)*x)/2, x)/2)**4*exp(-x), x))] + components4 = {(frozenset([Eq(Derivative(f(x), x), x*f(x) + 2*g(x)), + Eq(Derivative(g(x), x), x*g(x) + x + f(x))]), + frozenset([Eq(Derivative(k(x), x), f(x)**4 + k(x)),])), + (frozenset([Eq(Derivative(h(x), x), h(x)),]),)} + eqsdict4 = ({f(x): {g(x)}, g(x): {f(x)}, h(x): set(), k(x): {f(x)}}, + {f(x): Eq(Derivative(f(x), x), x*f(x) + 2*g(x)), + g(x): Eq(Derivative(g(x), x), x*g(x) + x + f(x)), + h(x): Eq(Derivative(h(x), x), h(x)), + k(x): Eq(Derivative(k(x), x), f(x)**4 + k(x))}) + graph4 = [{f(x), g(x), h(x), k(x)}, {(f(x), g(x)), (g(x), f(x)), (k(x), f(x))}] + assert {tuple(frozenset(scc) for scc in wcc) for wcc in _component_division(eqs4, funcs, x)} == components4 + assert _eqs2dict(eqs4, funcs) == eqsdict4 + assert [set(element) for element in _dict2graph(eqsdict4[0])] == graph4 + # XXX: dsolve hangs in integration here: + assert dsolve_system(eqs4, simplify=False, doit=False) == [sol4] + assert checksysodesol(eqs4, sol4) == (True, [0, 0, 0, 0]) + + eqs5 = [Eq(Derivative(f(x), x), x*f(x) + 2*g(x)), + Eq(Derivative(g(x), x), x*g(x) + f(x)), + Eq(Derivative(h(x), x), h(x)), + Eq(Derivative(k(x), x), f(x)**4 + k(x))] + sol5 = [Eq(f(x), (C1/2 - sqrt(2)*C2/2)*exp(x**2/2 - sqrt(2)*x) + (C1/2 + sqrt(2)*C2/2)*exp(x**2/2 + sqrt(2)*x)), + Eq(g(x), (-sqrt(2)*C1/4 + C2/2)*exp(x**2/2 - sqrt(2)*x) + (sqrt(2)*C1/4 + C2/2)*exp(x**2/2 + sqrt(2)*x)), + Eq(h(x), C3*exp(x)), + Eq(k(x), C4*exp(x) + exp(x)*Integral((C1*exp(x**2/2 - sqrt(2)*x)/2 + C1*exp(x**2/2 + sqrt(2)*x)/2 - + sqrt(2)*C2*exp(x**2/2 - sqrt(2)*x)/2 + sqrt(2)*C2*exp(x**2/2 + sqrt(2)*x)/2)**4*exp(-x), x))] + components5 = {(frozenset([Eq(Derivative(f(x), x), x*f(x) + 2*g(x)), + Eq(Derivative(g(x), x), x*g(x) + f(x))]), + frozenset([Eq(Derivative(k(x), x), f(x)**4 + k(x)),])), + (frozenset([Eq(Derivative(h(x), x), h(x)),]),)} + eqsdict5 = ({f(x): {g(x)}, g(x): {f(x)}, h(x): set(), k(x): {f(x)}}, + {f(x): Eq(Derivative(f(x), x), x*f(x) + 2*g(x)), + g(x): Eq(Derivative(g(x), x), x*g(x) + f(x)), + h(x): Eq(Derivative(h(x), x), h(x)), + k(x): Eq(Derivative(k(x), x), f(x)**4 + k(x))}) + graph5 = [{f(x), g(x), h(x), k(x)}, {(f(x), g(x)), (g(x), f(x)), (k(x), f(x))}] + assert {tuple(frozenset(scc) for scc in wcc) for wcc in _component_division(eqs5, funcs, x)} == components5 + assert _eqs2dict(eqs5, funcs) == eqsdict5 + assert [set(element) for element in _dict2graph(eqsdict5[0])] == graph5 + # XXX: dsolve hangs in integration here: + assert dsolve_system(eqs5, simplify=False, doit=False) == [sol5] + assert checksysodesol(eqs5, sol5) == (True, [0, 0, 0, 0]) + + +def test_linodesolve(): + t, x, a = symbols("t x a") + f, g, h = symbols("f g h", cls=Function) + + # Testing the Errors + raises(ValueError, lambda: linodesolve(1, t)) + raises(ValueError, lambda: linodesolve(a, t)) + + A1 = Matrix([[1, 2], [2, 4], [4, 6]]) + raises(NonSquareMatrixError, lambda: linodesolve(A1, t)) + + A2 = Matrix([[1, 2, 1], [3, 1, 2]]) + raises(NonSquareMatrixError, lambda: linodesolve(A2, t)) + + # Testing auto functionality + func = [f(t), g(t)] + eq = [Eq(f(t).diff(t) + g(t).diff(t), g(t)), Eq(g(t).diff(t), f(t))] + ceq = canonical_odes(eq, func, t) + (A1, A0), b = linear_ode_to_matrix(ceq[0], func, t, 1) + A = A0 + sol = [C1*(-Rational(1, 2) + sqrt(5)/2)*exp(t*(-Rational(1, 2) + sqrt(5)/2)) + C2*(-sqrt(5)/2 - Rational(1, 2))* + exp(t*(-sqrt(5)/2 - Rational(1, 2))), + C1*exp(t*(-Rational(1, 2) + sqrt(5)/2)) + C2*exp(t*(-sqrt(5)/2 - Rational(1, 2)))] + assert constant_renumber(linodesolve(A, t), variables=Tuple(*eq).free_symbols) == sol + + # Testing the Errors + raises(ValueError, lambda: linodesolve(1, t, b=Matrix([t+1]))) + raises(ValueError, lambda: linodesolve(a, t, b=Matrix([log(t) + sin(t)]))) + + raises(ValueError, lambda: linodesolve(Matrix([7]), t, b=t**2)) + raises(ValueError, lambda: linodesolve(Matrix([a+10]), t, b=log(t)*cos(t))) + + raises(ValueError, lambda: linodesolve(7, t, b=t**2)) + raises(ValueError, lambda: linodesolve(a, t, b=log(t) + sin(t))) + + A1 = Matrix([[1, 2], [2, 4], [4, 6]]) + b1 = Matrix([t, 1, t**2]) + raises(NonSquareMatrixError, lambda: linodesolve(A1, t, b=b1)) + + A2 = Matrix([[1, 2, 1], [3, 1, 2]]) + b2 = Matrix([t, t**2]) + raises(NonSquareMatrixError, lambda: linodesolve(A2, t, b=b2)) + + raises(ValueError, lambda: linodesolve(A1[:2, :], t, b=b1)) + raises(ValueError, lambda: linodesolve(A1[:2, :], t, b=b1[:1])) + + # DOIT check + A1 = Matrix([[1, -1], [1, -1]]) + b1 = Matrix([15*t - 10, -15*t - 5]) + sol1 = [C1 + C2*t + C2 - 10*t**3 + 10*t**2 + t*(15*t**2 - 5*t) - 10*t, + C1 + C2*t - 10*t**3 - 5*t**2 + t*(15*t**2 - 5*t) - 5*t] + assert constant_renumber(linodesolve(A1, t, b=b1, type="type2", doit=True), + variables=[t]) == sol1 + + # Testing auto functionality + func = [f(t), g(t)] + eq = [Eq(f(t).diff(t) + g(t).diff(t), g(t) + t), Eq(g(t).diff(t), f(t))] + ceq = canonical_odes(eq, func, t) + (A1, A0), b = linear_ode_to_matrix(ceq[0], func, t, 1) + A = A0 + sol = [-C1*exp(-t/2 + sqrt(5)*t/2)/2 + sqrt(5)*C1*exp(-t/2 + sqrt(5)*t/2)/2 - sqrt(5)*C2*exp(-sqrt(5)*t/2 - + t/2)/2 - C2*exp(-sqrt(5)*t/2 - t/2)/2 - exp(-t/2 + sqrt(5)*t/2)*Integral(t*exp(-sqrt(5)*t/2 + + t/2)/(-5 + sqrt(5)) - sqrt(5)*t*exp(-sqrt(5)*t/2 + t/2)/(-5 + sqrt(5)), t)/2 + sqrt(5)*exp(-t/2 + + sqrt(5)*t/2)*Integral(t*exp(-sqrt(5)*t/2 + t/2)/(-5 + sqrt(5)) - sqrt(5)*t*exp(-sqrt(5)*t/2 + + t/2)/(-5 + sqrt(5)), t)/2 - sqrt(5)*exp(-sqrt(5)*t/2 - t/2)*Integral(-sqrt(5)*t*exp(t/2 + + sqrt(5)*t/2)/5, t)/2 - exp(-sqrt(5)*t/2 - t/2)*Integral(-sqrt(5)*t*exp(t/2 + sqrt(5)*t/2)/5, t)/2, + C1*exp(-t/2 + sqrt(5)*t/2) + C2*exp(-sqrt(5)*t/2 - t/2) + exp(-t/2 + + sqrt(5)*t/2)*Integral(t*exp(-sqrt(5)*t/2 + t/2)/(-5 + sqrt(5)) - sqrt(5)*t*exp(-sqrt(5)*t/2 + + t/2)/(-5 + sqrt(5)), t) + exp(-sqrt(5)*t/2 - + t/2)*Integral(-sqrt(5)*t*exp(t/2 + sqrt(5)*t/2)/5, t)] + assert constant_renumber(linodesolve(A, t, b=b), variables=[t]) == sol + + # non-homogeneous term assumed to be 0 + sol1 = [-C1*exp(-t/2 + sqrt(5)*t/2)/2 + sqrt(5)*C1*exp(-t/2 + sqrt(5)*t/2)/2 - sqrt(5)*C2*exp(-sqrt(5)*t/2 + - t/2)/2 - C2*exp(-sqrt(5)*t/2 - t/2)/2, + C1*exp(-t/2 + sqrt(5)*t/2) + C2*exp(-sqrt(5)*t/2 - t/2)] + assert constant_renumber(linodesolve(A, t, type="type2"), variables=[t]) == sol1 + + # Testing the Errors + raises(ValueError, lambda: linodesolve(t+10, t)) + raises(ValueError, lambda: linodesolve(a*t, t)) + + A1 = Matrix([[1, t], [-t, 1]]) + B1, _ = _is_commutative_anti_derivative(A1, t) + raises(NonSquareMatrixError, lambda: linodesolve(A1[:, :1], t, B=B1)) + raises(ValueError, lambda: linodesolve(A1, t, B=1)) + + A2 = Matrix([[t, t, t], [t, t, t], [t, t, t]]) + B2, _ = _is_commutative_anti_derivative(A2, t) + raises(NonSquareMatrixError, lambda: linodesolve(A2, t, B=B2[:2, :])) + raises(ValueError, lambda: linodesolve(A2, t, B=2)) + raises(ValueError, lambda: linodesolve(A2, t, B=B2, type="type31")) + + raises(ValueError, lambda: linodesolve(A1, t, B=B2)) + raises(ValueError, lambda: linodesolve(A2, t, B=B1)) + + # Testing auto functionality + func = [f(t), g(t)] + eq = [Eq(f(t).diff(t), f(t) + t*g(t)), Eq(g(t).diff(t), -t*f(t) + g(t))] + ceq = canonical_odes(eq, func, t) + (A1, A0), b = linear_ode_to_matrix(ceq[0], func, t, 1) + A = A0 + sol = [(C1/2 - I*C2/2)*exp(I*t**2/2 + t) + (C1/2 + I*C2/2)*exp(-I*t**2/2 + t), + (-I*C1/2 + C2/2)*exp(-I*t**2/2 + t) + (I*C1/2 + C2/2)*exp(I*t**2/2 + t)] + assert constant_renumber(linodesolve(A, t), variables=Tuple(*eq).free_symbols) == sol + assert constant_renumber(linodesolve(A, t, type="type3"), variables=Tuple(*eq).free_symbols) == sol + + A1 = Matrix([[t, 1], [t, -1]]) + raises(NotImplementedError, lambda: linodesolve(A1, t)) + + # Testing the Errors + raises(ValueError, lambda: linodesolve(t+10, t, b=Matrix([t+1]))) + raises(ValueError, lambda: linodesolve(a*t, t, b=Matrix([log(t) + sin(t)]))) + + raises(ValueError, lambda: linodesolve(Matrix([7*t]), t, b=t**2)) + raises(ValueError, lambda: linodesolve(Matrix([a + 10*log(t)]), t, b=log(t)*cos(t))) + + raises(ValueError, lambda: linodesolve(7*t, t, b=t**2)) + raises(ValueError, lambda: linodesolve(a*t**2, t, b=log(t) + sin(t))) + + A1 = Matrix([[1, t], [-t, 1]]) + b1 = Matrix([t, t ** 2]) + B1, _ = _is_commutative_anti_derivative(A1, t) + raises(NonSquareMatrixError, lambda: linodesolve(A1[:, :1], t, b=b1)) + + A2 = Matrix([[t, t, t], [t, t, t], [t, t, t]]) + b2 = Matrix([t, 1, t**2]) + B2, _ = _is_commutative_anti_derivative(A2, t) + raises(NonSquareMatrixError, lambda: linodesolve(A2[:2, :], t, b=b2)) + + raises(ValueError, lambda: linodesolve(A1, t, b=b2)) + raises(ValueError, lambda: linodesolve(A2, t, b=b1)) + + raises(ValueError, lambda: linodesolve(A1, t, b=b1, B=B2)) + raises(ValueError, lambda: linodesolve(A2, t, b=b2, B=B1)) + + # Testing auto functionality + func = [f(x), g(x), h(x)] + eq = [Eq(f(x).diff(x), x*(f(x) + g(x) + h(x)) + x), + Eq(g(x).diff(x), x*(f(x) + g(x) + h(x)) + x), + Eq(h(x).diff(x), x*(f(x) + g(x) + h(x)) + 1)] + ceq = canonical_odes(eq, func, x) + (A1, A0), b = linear_ode_to_matrix(ceq[0], func, x, 1) + A = A0 + _x1 = exp(-3*x**2/2) + _x2 = exp(3*x**2/2) + _x3 = Integral(2*_x1*x/3 + _x1/3 + x/3 - Rational(1, 3), x) + _x4 = 2*_x2*_x3/3 + _x5 = Integral(2*_x1*x/3 + _x1/3 - 2*x/3 + Rational(2, 3), x) + sol = [ + C1*_x2/3 - C1/3 + C2*_x2/3 - C2/3 + C3*_x2/3 + 2*C3/3 + _x2*_x5/3 + _x3/3 + _x4 - _x5/3, + C1*_x2/3 + 2*C1/3 + C2*_x2/3 - C2/3 + C3*_x2/3 - C3/3 + _x2*_x5/3 + _x3/3 + _x4 - _x5/3, + C1*_x2/3 - C1/3 + C2*_x2/3 + 2*C2/3 + C3*_x2/3 - C3/3 + _x2*_x5/3 - 2*_x3/3 + _x4 + 2*_x5/3, + ] + assert constant_renumber(linodesolve(A, x, b=b), variables=Tuple(*eq).free_symbols) == sol + assert constant_renumber(linodesolve(A, x, b=b, type="type4"), + variables=Tuple(*eq).free_symbols) == sol + + A1 = Matrix([[t, 1], [t, -1]]) + raises(NotImplementedError, lambda: linodesolve(A1, t, b=b1)) + + # non-homogeneous term not passed + sol1 = [-C1/3 - C2/3 + 2*C3/3 + (C1/3 + C2/3 + C3/3)*exp(3*x**2/2), 2*C1/3 - C2/3 - C3/3 + (C1/3 + C2/3 + C3/3)*exp(3*x**2/2), + -C1/3 + 2*C2/3 - C3/3 + (C1/3 + C2/3 + C3/3)*exp(3*x**2/2)] + assert constant_renumber(linodesolve(A, x, type="type4", doit=True), variables=Tuple(*eq).free_symbols) == sol1 + + +@slow +def test_linear_3eq_order1_type4_slow(): + x, y, z = symbols('x, y, z', cls=Function) + t = Symbol('t') + + f = t ** 3 + log(t) + g = t ** 2 + sin(t) + eq1 = (Eq(diff(x(t), t), (4 * f + g) * x(t) - f * y(t) - 2 * f * z(t)), + Eq(diff(y(t), t), 2 * f * x(t) + (f + g) * y(t) - 2 * f * z(t)), Eq(diff(z(t), t), 5 * f * x(t) + f * y( + t) + (-3 * f + g) * z(t))) + with dotprodsimp(True): + dsolve(eq1) + + +@slow +def test_linear_neq_order1_type2_slow1(): + i, r1, c1, r2, c2, t = symbols('i, r1, c1, r2, c2, t') + x1 = Function('x1') + x2 = Function('x2') + + eq1 = r1*c1*Derivative(x1(t), t) + x1(t) - x2(t) - r1*i + eq2 = r2*c1*Derivative(x1(t), t) + r2*c2*Derivative(x2(t), t) + x2(t) - r2*i + eq = [eq1, eq2] + + # XXX: Solution is too complicated + [sol] = dsolve_system(eq, simplify=False, doit=False) + assert checksysodesol(eq, sol) == (True, [0, 0]) + + +# Regression test case for issue #9204 +# https://github.com/sympy/sympy/issues/9204 +@tooslow +def test_linear_new_order1_type2_de_lorentz_slow_check(): + m = Symbol("m", real=True) + q = Symbol("q", real=True) + t = Symbol("t", real=True) + + e1, e2, e3 = symbols("e1:4", real=True) + b1, b2, b3 = symbols("b1:4", real=True) + v1, v2, v3 = symbols("v1:4", cls=Function, real=True) + + eqs = [ + -e1*q + m*Derivative(v1(t), t) - q*(-b2*v3(t) + b3*v2(t)), + -e2*q + m*Derivative(v2(t), t) - q*(b1*v3(t) - b3*v1(t)), + -e3*q + m*Derivative(v3(t), t) - q*(-b1*v2(t) + b2*v1(t)) + ] + sol = dsolve(eqs) + assert checksysodesol(eqs, sol) == (True, [0, 0, 0]) + + +# Regression test case for issue #14001 +# https://github.com/sympy/sympy/issues/14001 +@slow +def test_linear_neq_order1_type2_slow_check(): + RC, t, C, Vs, L, R1, V0, I0 = symbols("RC t C Vs L R1 V0 I0") + V = Function("V") + I = Function("I") + system = [Eq(V(t).diff(t), -1/RC*V(t) + I(t)/C), Eq(I(t).diff(t), -R1/L*I(t) - 1/L*V(t) + Vs/L)] + [sol] = dsolve_system(system, simplify=False, doit=False) + + assert checksysodesol(system, sol) == (True, [0, 0]) + + +def _linear_3eq_order1_type4_long(): + x, y, z = symbols('x, y, z', cls=Function) + t = Symbol('t') + + f = t ** 3 + log(t) + g = t ** 2 + sin(t) + + eq1 = (Eq(diff(x(t), t), (4*f + g)*x(t) - f*y(t) - 2*f*z(t)), + Eq(diff(y(t), t), 2*f*x(t) + (f + g)*y(t) - 2*f*z(t)), Eq(diff(z(t), t), 5*f*x(t) + f*y( + t) + (-3*f + g)*z(t))) + + dsolve_sol = dsolve(eq1) + dsolve_sol1 = [_simpsol(sol) for sol in dsolve_sol] + + x_1 = sqrt(-t**6 - 8*t**3*log(t) + 8*t**3 - 16*log(t)**2 + 32*log(t) - 16) + x_2 = sqrt(3) + x_3 = 8324372644*C1*x_1*x_2 + 4162186322*C2*x_1*x_2 - 8324372644*C3*x_1*x_2 + x_4 = 1 / (1903457163*t**3 + 3825881643*x_1*x_2 + 7613828652*log(t) - 7613828652) + x_5 = exp(t**3/3 + t*x_1*x_2/4 - cos(t)) + x_6 = exp(t**3/3 - t*x_1*x_2/4 - cos(t)) + x_7 = exp(t**4/2 + t**3/3 + 2*t*log(t) - 2*t - cos(t)) + x_8 = 91238*C1*x_1*x_2 + 91238*C2*x_1*x_2 - 91238*C3*x_1*x_2 + x_9 = 1 / (66049*t**3 - 50629*x_1*x_2 + 264196*log(t) - 264196) + x_10 = 50629 * C1 / 25189 + 37909*C2/25189 - 50629*C3/25189 - x_3*x_4 + x_11 = -50629*C1/25189 - 12720*C2/25189 + 50629*C3/25189 + x_3*x_4 + sol = [Eq(x(t), x_10*x_5 + x_11*x_6 + x_7*(C1 - C2)), Eq(y(t), x_10*x_5 + x_11*x_6), Eq(z(t), x_5*( + -424*C1/257 - 167*C2/257 + 424*C3/257 - x_8*x_9) + x_6*(167*C1/257 + 424*C2/257 - + 167*C3/257 + x_8*x_9) + x_7*(C1 - C2))] + + assert dsolve_sol1 == sol + assert checksysodesol(eq1, dsolve_sol1) == (True, [0, 0, 0]) + + +@slow +def test_neq_order1_type4_slow_check1(): + f, g = symbols("f g", cls=Function) + x = symbols("x") + + eqs = [Eq(diff(f(x), x), x*f(x) + x**2*g(x) + x), + Eq(diff(g(x), x), 2*x**2*f(x) + (x + 3*x**2)*g(x) + 1)] + sol = dsolve(eqs) + assert checksysodesol(eqs, sol) == (True, [0, 0]) + + +@slow +def test_neq_order1_type4_slow_check2(): + f, g, h = symbols("f, g, h", cls=Function) + x = Symbol("x") + + eqs = [ + Eq(Derivative(f(x), x), x*h(x) + f(x) + g(x) + 1), + Eq(Derivative(g(x), x), x*g(x) + f(x) + h(x) + 10), + Eq(Derivative(h(x), x), x*f(x) + x + g(x) + h(x)) + ] + with dotprodsimp(True): + sol = dsolve(eqs) + assert checksysodesol(eqs, sol) == (True, [0, 0, 0]) + + +def _neq_order1_type4_slow3(): + f, g = symbols("f g", cls=Function) + x = symbols("x") + + eqs = [ + Eq(Derivative(f(x), x), x*f(x) + g(x) + sin(x)), + Eq(Derivative(g(x), x), x**2 + x*g(x) - f(x)) + ] + sol = [ + Eq(f(x), (C1/2 - I*C2/2 - I*Integral(x**2*exp(-x**2/2 - I*x)/2 + + x**2*exp(-x**2/2 + I*x)/2 + I*exp(-x**2/2 - I*x)*sin(x)/2 - + I*exp(-x**2/2 + I*x)*sin(x)/2, x)/2 + Integral(-I*x**2*exp(-x**2/2 + - I*x)/2 + I*x**2*exp(-x**2/2 + I*x)/2 + exp(-x**2/2 - + I*x)*sin(x)/2 + exp(-x**2/2 + I*x)*sin(x)/2, x)/2)*exp(x**2/2 + + I*x) + (C1/2 + I*C2/2 + I*Integral(x**2*exp(-x**2/2 - I*x)/2 + + x**2*exp(-x**2/2 + I*x)/2 + I*exp(-x**2/2 - I*x)*sin(x)/2 - + I*exp(-x**2/2 + I*x)*sin(x)/2, x)/2 + Integral(-I*x**2*exp(-x**2/2 + - I*x)/2 + I*x**2*exp(-x**2/2 + I*x)/2 + exp(-x**2/2 - + I*x)*sin(x)/2 + exp(-x**2/2 + I*x)*sin(x)/2, x)/2)*exp(x**2/2 - + I*x)), + Eq(g(x), (-I*C1/2 + C2/2 + Integral(x**2*exp(-x**2/2 - I*x)/2 + + x**2*exp(-x**2/2 + I*x)/2 + I*exp(-x**2/2 - I*x)*sin(x)/2 - + I*exp(-x**2/2 + I*x)*sin(x)/2, x)/2 - + I*Integral(-I*x**2*exp(-x**2/2 - I*x)/2 + I*x**2*exp(-x**2/2 + + I*x)/2 + exp(-x**2/2 - I*x)*sin(x)/2 + exp(-x**2/2 + + I*x)*sin(x)/2, x)/2)*exp(x**2/2 - I*x) + (I*C1/2 + C2/2 + + Integral(x**2*exp(-x**2/2 - I*x)/2 + x**2*exp(-x**2/2 + I*x)/2 + + I*exp(-x**2/2 - I*x)*sin(x)/2 - I*exp(-x**2/2 + I*x)*sin(x)/2, + x)/2 + I*Integral(-I*x**2*exp(-x**2/2 - I*x)/2 + + I*x**2*exp(-x**2/2 + I*x)/2 + exp(-x**2/2 - I*x)*sin(x)/2 + + exp(-x**2/2 + I*x)*sin(x)/2, x)/2)*exp(x**2/2 + I*x)) + ] + + return eqs, sol + + +def test_neq_order1_type4_slow3(): + eqs, sol = _neq_order1_type4_slow3() + assert dsolve_system(eqs, simplify=False, doit=False) == [sol] + # XXX: dsolve gives an error in integration: + # assert dsolve(eqs) == sol + # https://github.com/sympy/sympy/issues/20155 + + +@slow +def test_neq_order1_type4_slow_check3(): + eqs, sol = _neq_order1_type4_slow3() + assert checksysodesol(eqs, sol) == (True, [0, 0]) + + +@tooslow +@XFAIL +def test_linear_3eq_order1_type4_long_dsolve_slow_xfail(): + eq, sol = _linear_3eq_order1_type4_long() + + dsolve_sol = dsolve(eq) + dsolve_sol1 = [_simpsol(sol) for sol in dsolve_sol] + + assert dsolve_sol1 == sol + + +@tooslow +def test_linear_3eq_order1_type4_long_dsolve_dotprodsimp(): + eq, sol = _linear_3eq_order1_type4_long() + + # XXX: Only works with dotprodsimp see + # test_linear_3eq_order1_type4_long_dsolve_slow_xfail which is too slow + with dotprodsimp(True): + dsolve_sol = dsolve(eq) + + dsolve_sol1 = [_simpsol(sol) for sol in dsolve_sol] + assert dsolve_sol1 == sol + + +@tooslow +def test_linear_3eq_order1_type4_long_check(): + eq, sol = _linear_3eq_order1_type4_long() + assert checksysodesol(eq, sol) == (True, [0, 0, 0]) + + +def test_dsolve_system(): + f, g = symbols("f g", cls=Function) + x = symbols("x") + eqs = [Eq(f(x).diff(x), f(x) + g(x)), Eq(g(x).diff(x), f(x) + g(x))] + funcs = [f(x), g(x)] + + sol = [[Eq(f(x), -C1 + C2*exp(2*x)), Eq(g(x), C1 + C2*exp(2*x))]] + assert dsolve_system(eqs, funcs=funcs, t=x, doit=True) == sol + + raises(ValueError, lambda: dsolve_system(1)) + raises(ValueError, lambda: dsolve_system(eqs, 1)) + raises(ValueError, lambda: dsolve_system(eqs, funcs, 1)) + raises(ValueError, lambda: dsolve_system(eqs, funcs[:1], x)) + + eq = (Eq(f(x).diff(x), 12 * f(x) - 6 * g(x)), Eq(g(x).diff(x) ** 2, 11 * f(x) + 3 * g(x))) + raises(NotImplementedError, lambda: dsolve_system(eq) == ([], [])) + + raises(NotImplementedError, lambda: dsolve_system(eq, funcs=[f(x), g(x)]) == ([], [])) + raises(NotImplementedError, lambda: dsolve_system(eq, funcs=[f(x), g(x)], t=x) == ([], [])) + raises(NotImplementedError, lambda: dsolve_system(eq, funcs=[f(x), g(x)], t=x, ics={f(0): 1, g(0): 1}) == ([], [])) + raises(NotImplementedError, lambda: dsolve_system(eq, t=x, ics={f(0): 1, g(0): 1}) == ([], [])) + raises(NotImplementedError, lambda: dsolve_system(eq, ics={f(0): 1, g(0): 1}) == ([], [])) + raises(NotImplementedError, lambda: dsolve_system(eq, funcs=[f(x), g(x)], ics={f(0): 1, g(0): 1}) == ([], [])) + +def test_dsolve(): + + f, g = symbols('f g', cls=Function) + x, y = symbols('x y') + + eqs = [f(x).diff(x) - x, f(x).diff(x) + x] + with raises(ValueError): + dsolve(eqs) + + eqs = [f(x, y).diff(x)] + with raises(ValueError): + dsolve(eqs) + + eqs = [f(x, y).diff(x)+g(x).diff(x), g(x).diff(x)] + with raises(ValueError): + dsolve(eqs) + + +@slow +def test_higher_order1_slow1(): + x, y = symbols("x y", cls=Function) + t = symbols("t") + + eq = [ + Eq(diff(x(t),t,t), (log(t)+t**2)*diff(x(t),t)+(log(t)+t**2)*3*diff(y(t),t)), + Eq(diff(y(t),t,t), (log(t)+t**2)*2*diff(x(t),t)+(log(t)+t**2)*9*diff(y(t),t)) + ] + sol, = dsolve_system(eq, simplify=False, doit=False) + # The solution is too long to write out explicitly and checkodesol is too + # slow so we test for particular values of t: + for e in eq: + res = (e.lhs - e.rhs).subs({sol[0].lhs:sol[0].rhs, sol[1].lhs:sol[1].rhs}) + res = res.subs({d: d.doit(deep=False) for d in res.atoms(Derivative)}) + assert ratsimp(res.subs(t, 1)) == 0 + + +def test_second_order_type2_slow1(): + x, y, z = symbols('x, y, z', cls=Function) + t, l = symbols('t, l') + + eqs1 = [Eq(Derivative(x(t), (t, 2)), t*(2*x(t) + y(t))), + Eq(Derivative(y(t), (t, 2)), t*(-x(t) + 2*y(t)))] + sol1 = [Eq(x(t), I*C1*airyai(t*(2 - I)**(S(1)/3)) + I*C2*airybi(t*(2 - I)**(S(1)/3)) - I*C3*airyai(t*(2 + + I)**(S(1)/3)) - I*C4*airybi(t*(2 + I)**(S(1)/3))), + Eq(y(t), C1*airyai(t*(2 - I)**(S(1)/3)) + C2*airybi(t*(2 - I)**(S(1)/3)) + C3*airyai(t*(2 + I)**(S(1)/3)) + + C4*airybi(t*(2 + I)**(S(1)/3)))] + assert dsolve(eqs1) == sol1 + assert checksysodesol(eqs1, sol1) == (True, [0, 0]) + + +@tooslow +@XFAIL +def test_nonlinear_3eq_order1_type1(): + a, b, c = symbols('a b c') + + eqs = [ + a * f(x).diff(x) - (b - c) * g(x) * h(x), + b * g(x).diff(x) - (c - a) * h(x) * f(x), + c * h(x).diff(x) - (a - b) * f(x) * g(x), + ] + + assert dsolve(eqs) # NotImplementedError + + +@XFAIL +def test_nonlinear_3eq_order1_type4(): + eqs = [ + Eq(f(x).diff(x), (2*h(x)*g(x) - 3*g(x)*h(x))), + Eq(g(x).diff(x), (4*f(x)*h(x) - 2*h(x)*f(x))), + Eq(h(x).diff(x), (3*g(x)*f(x) - 4*f(x)*g(x))), + ] + dsolve(eqs) # KeyError when matching + # sol = ? + # assert dsolve_sol == sol + # assert checksysodesol(eqs, dsolve_sol) == (True, [0, 0, 0]) + + +@tooslow +@XFAIL +def test_nonlinear_3eq_order1_type3(): + eqs = [ + Eq(f(x).diff(x), (2*f(x)**2 - 3 )), + Eq(g(x).diff(x), (4 - 2*h(x) )), + Eq(h(x).diff(x), (3*h(x) - 4*f(x)**2)), + ] + dsolve(eqs) # Not sure if this finishes... + # sol = ? + # assert dsolve_sol == sol + # assert checksysodesol(eqs, dsolve_sol) == (True, [0, 0, 0]) + + +@XFAIL +def test_nonlinear_3eq_order1_type5(): + eqs = [ + Eq(f(x).diff(x), f(x)*(2*f(x) - 3*g(x))), + Eq(g(x).diff(x), g(x)*(4*g(x) - 2*h(x))), + Eq(h(x).diff(x), h(x)*(3*h(x) - 4*f(x))), + ] + dsolve(eqs) # KeyError + # sol = ? + # assert dsolve_sol == sol + # assert checksysodesol(eqs, dsolve_sol) == (True, [0, 0, 0]) + + +def test_linear_2eq_order1(): + x, y, z = symbols('x, y, z', cls=Function) + k, l, m, n = symbols('k, l, m, n', Integer=True) + t = Symbol('t') + x0, y0 = symbols('x0, y0', cls=Function) + + eq1 = (Eq(diff(x(t),t), x(t) + y(t) + 9), Eq(diff(y(t),t), 2*x(t) + 5*y(t) + 23)) + sol1 = [Eq(x(t), C1*exp(t*(sqrt(6) + 3)) + C2*exp(t*(-sqrt(6) + 3)) - Rational(22, 3)), \ + Eq(y(t), C1*(2 + sqrt(6))*exp(t*(sqrt(6) + 3)) + C2*(-sqrt(6) + 2)*exp(t*(-sqrt(6) + 3)) - Rational(5, 3))] + assert checksysodesol(eq1, sol1) == (True, [0, 0]) + + eq2 = (Eq(diff(x(t),t), x(t) + y(t) + 81), Eq(diff(y(t),t), -2*x(t) + y(t) + 23)) + sol2 = [Eq(x(t), (C1*cos(sqrt(2)*t) + C2*sin(sqrt(2)*t))*exp(t) - Rational(58, 3)), \ + Eq(y(t), (-sqrt(2)*C1*sin(sqrt(2)*t) + sqrt(2)*C2*cos(sqrt(2)*t))*exp(t) - Rational(185, 3))] + assert checksysodesol(eq2, sol2) == (True, [0, 0]) + + eq3 = (Eq(diff(x(t),t), 5*t*x(t) + 2*y(t)), Eq(diff(y(t),t), 2*x(t) + 5*t*y(t))) + sol3 = [Eq(x(t), (C1*exp(2*t) + C2*exp(-2*t))*exp(Rational(5, 2)*t**2)), \ + Eq(y(t), (C1*exp(2*t) - C2*exp(-2*t))*exp(Rational(5, 2)*t**2))] + assert checksysodesol(eq3, sol3) == (True, [0, 0]) + + eq4 = (Eq(diff(x(t),t), 5*t*x(t) + t**2*y(t)), Eq(diff(y(t),t), -t**2*x(t) + 5*t*y(t))) + sol4 = [Eq(x(t), (C1*cos((t**3)/3) + C2*sin((t**3)/3))*exp(Rational(5, 2)*t**2)), \ + Eq(y(t), (-C1*sin((t**3)/3) + C2*cos((t**3)/3))*exp(Rational(5, 2)*t**2))] + assert checksysodesol(eq4, sol4) == (True, [0, 0]) + + eq5 = (Eq(diff(x(t),t), 5*t*x(t) + t**2*y(t)), Eq(diff(y(t),t), -t**2*x(t) + (5*t+9*t**2)*y(t))) + sol5 = [Eq(x(t), (C1*exp((sqrt(77)/2 + Rational(9, 2))*(t**3)/3) + \ + C2*exp((-sqrt(77)/2 + Rational(9, 2))*(t**3)/3))*exp(Rational(5, 2)*t**2)), \ + Eq(y(t), (C1*(sqrt(77)/2 + Rational(9, 2))*exp((sqrt(77)/2 + Rational(9, 2))*(t**3)/3) + \ + C2*(-sqrt(77)/2 + Rational(9, 2))*exp((-sqrt(77)/2 + Rational(9, 2))*(t**3)/3))*exp(Rational(5, 2)*t**2))] + assert checksysodesol(eq5, sol5) == (True, [0, 0]) + + eq6 = (Eq(diff(x(t),t), 5*t*x(t) + t**2*y(t)), Eq(diff(y(t),t), (1-t**2)*x(t) + (5*t+9*t**2)*y(t))) + sol6 = [Eq(x(t), C1*x0(t) + C2*x0(t)*Integral(t**2*exp(Integral(5*t, t))*exp(Integral(9*t**2 + 5*t, t))/x0(t)**2, t)), \ + Eq(y(t), C1*y0(t) + C2*(y0(t)*Integral(t**2*exp(Integral(5*t, t))*exp(Integral(9*t**2 + 5*t, t))/x0(t)**2, t) + \ + exp(Integral(5*t, t))*exp(Integral(9*t**2 + 5*t, t))/x0(t)))] + s = dsolve(eq6) + assert s == sol6 # too complicated to test with subs and simplify + # assert checksysodesol(eq10, sol10) == (True, [0, 0]) # this one fails + + +def test_nonlinear_2eq_order1(): + x, y, z = symbols('x, y, z', cls=Function) + t = Symbol('t') + eq1 = (Eq(diff(x(t),t),x(t)*y(t)**3), Eq(diff(y(t),t),y(t)**5)) + sol1 = [ + Eq(x(t), C1*exp((-1/(4*C2 + 4*t))**(Rational(-1, 4)))), + Eq(y(t), -(-1/(4*C2 + 4*t))**Rational(1, 4)), + Eq(x(t), C1*exp(-1/(-1/(4*C2 + 4*t))**Rational(1, 4))), + Eq(y(t), (-1/(4*C2 + 4*t))**Rational(1, 4)), + Eq(x(t), C1*exp(-I/(-1/(4*C2 + 4*t))**Rational(1, 4))), + Eq(y(t), -I*(-1/(4*C2 + 4*t))**Rational(1, 4)), + Eq(x(t), C1*exp(I/(-1/(4*C2 + 4*t))**Rational(1, 4))), + Eq(y(t), I*(-1/(4*C2 + 4*t))**Rational(1, 4))] + assert dsolve(eq1) == sol1 + assert checksysodesol(eq1, sol1) == (True, [0, 0]) + + eq2 = (Eq(diff(x(t),t), exp(3*x(t))*y(t)**3),Eq(diff(y(t),t), y(t)**5)) + sol2 = [ + Eq(x(t), -log(C1 - 3/(-1/(4*C2 + 4*t))**Rational(1, 4))/3), + Eq(y(t), -(-1/(4*C2 + 4*t))**Rational(1, 4)), + Eq(x(t), -log(C1 + 3/(-1/(4*C2 + 4*t))**Rational(1, 4))/3), + Eq(y(t), (-1/(4*C2 + 4*t))**Rational(1, 4)), + Eq(x(t), -log(C1 + 3*I/(-1/(4*C2 + 4*t))**Rational(1, 4))/3), + Eq(y(t), -I*(-1/(4*C2 + 4*t))**Rational(1, 4)), + Eq(x(t), -log(C1 - 3*I/(-1/(4*C2 + 4*t))**Rational(1, 4))/3), + Eq(y(t), I*(-1/(4*C2 + 4*t))**Rational(1, 4))] + assert dsolve(eq2) == sol2 + assert checksysodesol(eq2, sol2) == (True, [0, 0]) + + eq3 = (Eq(diff(x(t),t), y(t)*x(t)), Eq(diff(y(t),t), x(t)**3)) + tt = Rational(2, 3) + sol3 = [ + Eq(x(t), 6**tt/(6*(-sinh(sqrt(C1)*(C2 + t)/2)/sqrt(C1))**tt)), + Eq(y(t), sqrt(C1 + C1/sinh(sqrt(C1)*(C2 + t)/2)**2)/3)] + assert dsolve(eq3) == sol3 + # FIXME: assert checksysodesol(eq3, sol3) == (True, [0, 0]) + + eq4 = (Eq(diff(x(t),t),x(t)*y(t)*sin(t)**2), Eq(diff(y(t),t),y(t)**2*sin(t)**2)) + sol4 = {Eq(x(t), -2*exp(C1)/(C2*exp(C1) + t - sin(2*t)/2)), Eq(y(t), -2/(C1 + t - sin(2*t)/2))} + assert dsolve(eq4) == sol4 + # FIXME: assert checksysodesol(eq4, sol4) == (True, [0, 0]) + + eq5 = (Eq(x(t),t*diff(x(t),t)+diff(x(t),t)*diff(y(t),t)), Eq(y(t),t*diff(y(t),t)+diff(y(t),t)**2)) + sol5 = {Eq(x(t), C1*C2 + C1*t), Eq(y(t), C2**2 + C2*t)} + assert dsolve(eq5) == sol5 + assert checksysodesol(eq5, sol5) == (True, [0, 0]) + + eq6 = (Eq(diff(x(t),t),x(t)**2*y(t)**3), Eq(diff(y(t),t),y(t)**5)) + sol6 = [ + Eq(x(t), 1/(C1 - 1/(-1/(4*C2 + 4*t))**Rational(1, 4))), + Eq(y(t), -(-1/(4*C2 + 4*t))**Rational(1, 4)), + Eq(x(t), 1/(C1 + (-1/(4*C2 + 4*t))**(Rational(-1, 4)))), + Eq(y(t), (-1/(4*C2 + 4*t))**Rational(1, 4)), + Eq(x(t), 1/(C1 + I/(-1/(4*C2 + 4*t))**Rational(1, 4))), + Eq(y(t), -I*(-1/(4*C2 + 4*t))**Rational(1, 4)), + Eq(x(t), 1/(C1 - I/(-1/(4*C2 + 4*t))**Rational(1, 4))), + Eq(y(t), I*(-1/(4*C2 + 4*t))**Rational(1, 4))] + assert dsolve(eq6) == sol6 + assert checksysodesol(eq6, sol6) == (True, [0, 0]) + + +@slow +def test_nonlinear_3eq_order1(): + x, y, z = symbols('x, y, z', cls=Function) + t, u = symbols('t u') + eq1 = (4*diff(x(t),t) + 2*y(t)*z(t), 3*diff(y(t),t) - z(t)*x(t), 5*diff(z(t),t) - x(t)*y(t)) + sol1 = [Eq(4*Integral(1/(sqrt(-4*u**2 - 3*C1 + C2)*sqrt(-4*u**2 + 5*C1 - C2)), (u, x(t))), + C3 - sqrt(15)*t/15), Eq(3*Integral(1/(sqrt(-6*u**2 - C1 + 5*C2)*sqrt(3*u**2 + C1 - 4*C2)), + (u, y(t))), C3 + sqrt(5)*t/10), Eq(5*Integral(1/(sqrt(-10*u**2 - 3*C1 + C2)* + sqrt(5*u**2 + 4*C1 - C2)), (u, z(t))), C3 + sqrt(3)*t/6)] + assert [i.dummy_eq(j) for i, j in zip(dsolve(eq1), sol1)] + # FIXME: assert checksysodesol(eq1, sol1) == (True, [0, 0, 0]) + + eq2 = (4*diff(x(t),t) + 2*y(t)*z(t)*sin(t), 3*diff(y(t),t) - z(t)*x(t)*sin(t), 5*diff(z(t),t) - x(t)*y(t)*sin(t)) + sol2 = [Eq(3*Integral(1/(sqrt(-6*u**2 - C1 + 5*C2)*sqrt(3*u**2 + C1 - 4*C2)), (u, x(t))), C3 + + sqrt(5)*cos(t)/10), Eq(4*Integral(1/(sqrt(-4*u**2 - 3*C1 + C2)*sqrt(-4*u**2 + 5*C1 - C2)), + (u, y(t))), C3 - sqrt(15)*cos(t)/15), Eq(5*Integral(1/(sqrt(-10*u**2 - 3*C1 + C2)* + sqrt(5*u**2 + 4*C1 - C2)), (u, z(t))), C3 + sqrt(3)*cos(t)/6)] + assert [i.dummy_eq(j) for i, j in zip(dsolve(eq2), sol2)] + # FIXME: assert checksysodesol(eq2, sol2) == (True, [0, 0, 0]) + + +def test_C1_function_9239(): + t = Symbol('t') + C1 = Function('C1') + C2 = Function('C2') + C3 = Symbol('C3') + C4 = Symbol('C4') + eq = (Eq(diff(C1(t), t), 9*C2(t)), Eq(diff(C2(t), t), 12*C1(t))) + sol = [Eq(C1(t), 9*C3*exp(6*sqrt(3)*t) + 9*C4*exp(-6*sqrt(3)*t)), + Eq(C2(t), 6*sqrt(3)*C3*exp(6*sqrt(3)*t) - 6*sqrt(3)*C4*exp(-6*sqrt(3)*t))] + assert checksysodesol(eq, sol) == (True, [0, 0]) + + +def test_dsolve_linsystem_symbol(): + eps = Symbol('epsilon', positive=True) + eq1 = (Eq(diff(f(x), x), -eps*g(x)), Eq(diff(g(x), x), eps*f(x))) + sol1 = [Eq(f(x), -C1*eps*cos(eps*x) - C2*eps*sin(eps*x)), + Eq(g(x), -C1*eps*sin(eps*x) + C2*eps*cos(eps*x))] + assert checksysodesol(eq1, sol1) == (True, [0, 0]) diff --git a/lib/python3.12/site-packages/sympy/solvers/solveset.py b/lib/python3.12/site-packages/sympy/solvers/solveset.py new file mode 100644 index 0000000000000000000000000000000000000000..4d1bd62439f816f304fb1c812ab54555144a2b2a --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/solveset.py @@ -0,0 +1,4124 @@ +""" +This module contains functions to: + + - solve a single equation for a single variable, in any domain either real or complex. + + - solve a single transcendental equation for a single variable in any domain either real or complex. + (currently supports solving in real domain only) + + - solve a system of linear equations with N variables and M equations. + + - solve a system of Non Linear Equations with N variables and M equations +""" +from sympy.core.sympify import sympify +from sympy.core import (S, Pow, Dummy, pi, Expr, Wild, Mul, + Add, Basic) +from sympy.core.containers import Tuple +from sympy.core.function import (Lambda, expand_complex, AppliedUndef, + expand_log, _mexpand, expand_trig, nfloat) +from sympy.core.mod import Mod +from sympy.core.numbers import I, Number, Rational, oo +from sympy.core.intfunc import integer_log +from sympy.core.relational import Eq, Ne, Relational +from sympy.core.sorting import default_sort_key, ordered +from sympy.core.symbol import Symbol, _uniquely_named_symbol +from sympy.core.sympify import _sympify +from sympy.core.traversal import preorder_traversal +from sympy.external.gmpy import gcd as number_gcd, lcm as number_lcm +from sympy.polys.matrices.linsolve import _linear_eq_to_dict +from sympy.polys.polyroots import UnsolvableFactorError +from sympy.simplify.simplify import simplify, fraction, trigsimp, nsimplify +from sympy.simplify import powdenest, logcombine +from sympy.functions import (log, tan, cot, sin, cos, sec, csc, exp, + acos, asin, atan, acot, acsc, asec, + piecewise_fold, Piecewise) +from sympy.functions.combinatorial.numbers import totient +from sympy.functions.elementary.complexes import Abs, arg, re, im +from sympy.functions.elementary.hyperbolic import (HyperbolicFunction, + sinh, cosh, tanh, coth, sech, csch, + asinh, acosh, atanh, acoth, asech, acsch) +from sympy.functions.elementary.miscellaneous import real_root +from sympy.functions.elementary.trigonometric import TrigonometricFunction +from sympy.logic.boolalg import And, BooleanTrue +from sympy.sets import (FiniteSet, imageset, Interval, Intersection, + Union, ConditionSet, ImageSet, Complement, Contains) +from sympy.sets.sets import Set, ProductSet +from sympy.matrices import zeros, Matrix, MatrixBase +from sympy.ntheory.factor_ import divisors +from sympy.ntheory.residue_ntheory import discrete_log, nthroot_mod +from sympy.polys import (roots, Poly, degree, together, PolynomialError, + RootOf, factor, lcm, gcd) +from sympy.polys.polyerrors import CoercionFailed +from sympy.polys.polytools import invert, groebner, poly +from sympy.polys.solvers import (sympy_eqs_to_ring, solve_lin_sys, + PolyNonlinearError) +from sympy.polys.matrices.linsolve import _linsolve +from sympy.solvers.solvers import (checksol, denoms, unrad, + _simple_dens, recast_to_symbols) +from sympy.solvers.polysys import solve_poly_system +from sympy.utilities import filldedent +from sympy.utilities.iterables import (numbered_symbols, has_dups, + is_sequence, iterable) +from sympy.calculus.util import periodicity, continuous_domain, function_range + + +from types import GeneratorType + + +class NonlinearError(ValueError): + """Raised when unexpectedly encountering nonlinear equations""" + pass + + +def _masked(f, *atoms): + """Return ``f``, with all objects given by ``atoms`` replaced with + Dummy symbols, ``d``, and the list of replacements, ``(d, e)``, + where ``e`` is an object of type given by ``atoms`` in which + any other instances of atoms have been recursively replaced with + Dummy symbols, too. The tuples are ordered so that if they are + applied in sequence, the origin ``f`` will be restored. + + Examples + ======== + + >>> from sympy import cos + >>> from sympy.abc import x + >>> from sympy.solvers.solveset import _masked + + >>> f = cos(cos(x) + 1) + >>> f, reps = _masked(cos(1 + cos(x)), cos) + >>> f + _a1 + >>> reps + [(_a1, cos(_a0 + 1)), (_a0, cos(x))] + >>> for d, e in reps: + ... f = f.xreplace({d: e}) + >>> f + cos(cos(x) + 1) + """ + sym = numbered_symbols('a', cls=Dummy, real=True) + mask = [] + for a in ordered(f.atoms(*atoms)): + for i in mask: + a = a.replace(*i) + mask.append((a, next(sym))) + for i, (o, n) in enumerate(mask): + f = f.replace(o, n) + mask[i] = (n, o) + mask = list(reversed(mask)) + return f, mask + + +def _invert(f_x, y, x, domain=S.Complexes): + r""" + Reduce the complex valued equation $f(x) = y$ to a set of equations + + $$\left\{g(x) = h_1(y),\ g(x) = h_2(y),\ \dots,\ g(x) = h_n(y) \right\}$$ + + where $g(x)$ is a simpler function than $f(x)$. The return value is a tuple + $(g(x), \mathrm{set}_h)$, where $g(x)$ is a function of $x$ and $\mathrm{set}_h$ is + the set of function $\left\{h_1(y), h_2(y), \dots, h_n(y)\right\}$. + Here, $y$ is not necessarily a symbol. + + $\mathrm{set}_h$ contains the functions, along with the information + about the domain in which they are valid, through set + operations. For instance, if :math:`y = |x| - n` is inverted + in the real domain, then $\mathrm{set}_h$ is not simply + $\{-n, n\}$ as the nature of `n` is unknown; rather, it is: + + $$ \left(\left[0, \infty\right) \cap \left\{n\right\}\right) \cup + \left(\left(-\infty, 0\right] \cap \left\{- n\right\}\right)$$ + + By default, the complex domain is used which means that inverting even + seemingly simple functions like $\exp(x)$ will give very different + results from those obtained in the real domain. + (In the case of $\exp(x)$, the inversion via $\log$ is multi-valued + in the complex domain, having infinitely many branches.) + + If you are working with real values only (or you are not sure which + function to use) you should probably set the domain to + ``S.Reals`` (or use ``invert_real`` which does that automatically). + + + Examples + ======== + + >>> from sympy.solvers.solveset import invert_complex, invert_real + >>> from sympy.abc import x, y + >>> from sympy import exp + + When does exp(x) == y? + + >>> invert_complex(exp(x), y, x) + (x, ImageSet(Lambda(_n, I*(2*_n*pi + arg(y)) + log(Abs(y))), Integers)) + >>> invert_real(exp(x), y, x) + (x, Intersection({log(y)}, Reals)) + + When does exp(x) == 1? + + >>> invert_complex(exp(x), 1, x) + (x, ImageSet(Lambda(_n, 2*_n*I*pi), Integers)) + >>> invert_real(exp(x), 1, x) + (x, {0}) + + See Also + ======== + invert_real, invert_complex + """ + x = sympify(x) + if not x.is_Symbol: + raise ValueError("x must be a symbol") + f_x = sympify(f_x) + if x not in f_x.free_symbols: + raise ValueError("Inverse of constant function doesn't exist") + y = sympify(y) + if x in y.free_symbols: + raise ValueError("y should be independent of x ") + + if domain.is_subset(S.Reals): + x1, s = _invert_real(f_x, FiniteSet(y), x) + else: + x1, s = _invert_complex(f_x, FiniteSet(y), x) + + # f couldn't be inverted completely; return unmodified. + if x1 != x: + return x1, s + + # Avoid adding gratuitous intersections with S.Complexes. Actual + # conditions should be handled by the respective inverters. + if domain is S.Complexes: + return x1, s + + if isinstance(s, FiniteSet): + return x1, s.intersect(domain) + + # "Fancier" solution sets like those obtained by inversion of trigonometric + # functions already include general validity conditions (i.e. conditions on + # the domain of the respective inverse functions), so we should avoid adding + # blanket intesections with S.Reals. But subsets of R (or C) must still be + # accounted for. + if domain is S.Reals: + return x1, s + else: + return x1, s.intersect(domain) + + +invert_complex = _invert + + +def invert_real(f_x, y, x): + """ + Inverts a real-valued function. Same as :func:`invert_complex`, but sets + the domain to ``S.Reals`` before inverting. + """ + return _invert(f_x, y, x, S.Reals) + + +def _invert_real(f, g_ys, symbol): + """Helper function for _invert.""" + + if f == symbol or g_ys is S.EmptySet: + return (symbol, g_ys) + + n = Dummy('n', real=True) + + if isinstance(f, exp) or (f.is_Pow and f.base == S.Exp1): + return _invert_real(f.exp, + imageset(Lambda(n, log(n)), g_ys), + symbol) + + if hasattr(f, 'inverse') and f.inverse() is not None and not isinstance(f, ( + TrigonometricFunction, + HyperbolicFunction, + )): + if len(f.args) > 1: + raise ValueError("Only functions with one argument are supported.") + return _invert_real(f.args[0], + imageset(Lambda(n, f.inverse()(n)), g_ys), + symbol) + + if isinstance(f, Abs): + return _invert_abs(f.args[0], g_ys, symbol) + + if f.is_Add: + # f = g + h + g, h = f.as_independent(symbol) + if g is not S.Zero: + return _invert_real(h, imageset(Lambda(n, n - g), g_ys), symbol) + + if f.is_Mul: + # f = g*h + g, h = f.as_independent(symbol) + + if g is not S.One: + return _invert_real(h, imageset(Lambda(n, n/g), g_ys), symbol) + + if f.is_Pow: + base, expo = f.args + base_has_sym = base.has(symbol) + expo_has_sym = expo.has(symbol) + + if not expo_has_sym: + + if expo.is_rational: + num, den = expo.as_numer_denom() + + if den % 2 == 0 and num % 2 == 1 and den.is_zero is False: + # Here we have f(x)**(num/den) = y + # where den is nonzero and even and y is an element + # of the set g_ys. + # den is even, so we are only interested in the cases + # where both f(x) and y are positive. + # Restricting y to be positive (using the set g_ys_pos) + # means that y**(den/num) is always positive. + # Therefore it isn't necessary to also constrain f(x) + # to be positive because we are only going to + # find solutions of f(x) = y**(d/n) + # where the rhs is already required to be positive. + root = Lambda(n, real_root(n, expo)) + g_ys_pos = g_ys & Interval(0, oo) + res = imageset(root, g_ys_pos) + _inv, _set = _invert_real(base, res, symbol) + return (_inv, _set) + + if den % 2 == 1: + root = Lambda(n, real_root(n, expo)) + res = imageset(root, g_ys) + if num % 2 == 0: + neg_res = imageset(Lambda(n, -n), res) + return _invert_real(base, res + neg_res, symbol) + if num % 2 == 1: + return _invert_real(base, res, symbol) + + elif expo.is_irrational: + root = Lambda(n, real_root(n, expo)) + g_ys_pos = g_ys & Interval(0, oo) + res = imageset(root, g_ys_pos) + return _invert_real(base, res, symbol) + + else: + # indeterminate exponent, e.g. Float or parity of + # num, den of rational could not be determined + pass # use default return + + if not base_has_sym: + rhs = g_ys.args[0] + if base.is_positive: + return _invert_real(expo, + imageset(Lambda(n, log(n, base, evaluate=False)), g_ys), symbol) + elif base.is_negative: + s, b = integer_log(rhs, base) + if b: + return _invert_real(expo, FiniteSet(s), symbol) + else: + return (expo, S.EmptySet) + elif base.is_zero: + one = Eq(rhs, 1) + if one == S.true: + # special case: 0**x - 1 + return _invert_real(expo, FiniteSet(0), symbol) + elif one == S.false: + return (expo, S.EmptySet) + + if isinstance(f, (TrigonometricFunction, HyperbolicFunction)): + return _invert_trig_hyp_real(f, g_ys, symbol) + + return (f, g_ys) + + +# Dictionaries of inverses will be cached after first use. +_trig_inverses = None +_hyp_inverses = None + +def _invert_trig_hyp_real(f, g_ys, symbol): + """Helper function for inverting trigonometric and hyperbolic functions. + + This helper only handles inversion over the reals. + + For trigonometric functions only finite `g_ys` sets are implemented. + + For hyperbolic functions the set `g_ys` is checked against the domain of the + respective inverse functions. Infinite `g_ys` sets are also supported. + """ + + if isinstance(f, HyperbolicFunction): + n = Dummy('n', real=True) + + if isinstance(f, sinh): + # asinh is defined over R. + return _invert_real(f.args[0], imageset(n, asinh(n), g_ys), symbol) + + if isinstance(f, cosh): + g_ys_dom = g_ys.intersect(Interval(1, oo)) + if isinstance(g_ys_dom, Intersection): + # could not properly resolve domain check + if isinstance(g_ys, FiniteSet): + # If g_ys is a `FiniteSet`` it should be sufficient to just + # let the calling `_invert_real()` add an intersection with + # `S.Reals` (or a subset `domain`) to ensure that only valid + # (real) solutions are returned. + # This avoids adding "too many" Intersections or + # ConditionSets in the returned set. + g_ys_dom = g_ys + else: + return (f, g_ys) + return _invert_real(f.args[0], Union( + imageset(n, acosh(n), g_ys_dom), + imageset(n, -acosh(n), g_ys_dom)), symbol) + + if isinstance(f, sech): + g_ys_dom = g_ys.intersect(Interval.Lopen(0, 1)) + if isinstance(g_ys_dom, Intersection): + if isinstance(g_ys, FiniteSet): + g_ys_dom = g_ys + else: + return (f, g_ys) + return _invert_real(f.args[0], Union( + imageset(n, asech(n), g_ys_dom), + imageset(n, -asech(n), g_ys_dom)), symbol) + + if isinstance(f, tanh): + g_ys_dom = g_ys.intersect(Interval.open(-1, 1)) + if isinstance(g_ys_dom, Intersection): + if isinstance(g_ys, FiniteSet): + g_ys_dom = g_ys + else: + return (f, g_ys) + return _invert_real(f.args[0], + imageset(n, atanh(n), g_ys_dom), symbol) + + if isinstance(f, coth): + g_ys_dom = g_ys - Interval(-1, 1) + if isinstance(g_ys_dom, Complement): + if isinstance(g_ys, FiniteSet): + g_ys_dom = g_ys + else: + return (f, g_ys) + return _invert_real(f.args[0], + imageset(n, acoth(n), g_ys_dom), symbol) + + if isinstance(f, csch): + g_ys_dom = g_ys - FiniteSet(0) + if isinstance(g_ys_dom, Complement): + if isinstance(g_ys, FiniteSet): + g_ys_dom = g_ys + else: + return (f, g_ys) + return _invert_real(f.args[0], + imageset(n, acsch(n), g_ys_dom), symbol) + + elif isinstance(f, TrigonometricFunction) and isinstance(g_ys, FiniteSet): + def _get_trig_inverses(func): + global _trig_inverses + if _trig_inverses is None: + _trig_inverses = { + sin : ((asin, lambda y: pi-asin(y)), 2*pi, Interval(-1, 1)), + cos : ((acos, lambda y: -acos(y)), 2*pi, Interval(-1, 1)), + tan : ((atan,), pi, S.Reals), + cot : ((acot,), pi, S.Reals), + sec : ((asec, lambda y: -asec(y)), 2*pi, + Union(Interval(-oo, -1), Interval(1, oo))), + csc : ((acsc, lambda y: pi-acsc(y)), 2*pi, + Union(Interval(-oo, -1), Interval(1, oo)))} + return _trig_inverses[func] + + invs, period, rng = _get_trig_inverses(f.func) + n = Dummy('n', integer=True) + def create_return_set(g): + # returns ConditionSet that will be part of the final (x, set) tuple + invsimg = Union(*[ + imageset(n, period*n + inv(g), S.Integers) for inv in invs]) + inv_f, inv_g_ys = _invert_real(f.args[0], invsimg, symbol) + if inv_f == symbol: # inversion successful + conds = rng.contains(g) + return ConditionSet(symbol, conds, inv_g_ys) + else: + return ConditionSet(symbol, Eq(f, g), S.Reals) + + retset = Union(*[create_return_set(g) for g in g_ys]) + return (symbol, retset) + + else: + return (f, g_ys) + + +def _invert_trig_hyp_complex(f, g_ys, symbol): + """Helper function for inverting trigonometric and hyperbolic functions. + + This helper only handles inversion over the complex numbers. + Only finite `g_ys` sets are implemented. + + Handling of singularities is only implemented for hyperbolic equations. + In case of a symbolic element g in g_ys a ConditionSet may be returned. + """ + + if isinstance(f, TrigonometricFunction) and isinstance(g_ys, FiniteSet): + def inv(trig): + if isinstance(trig, (sin, csc)): + F = asin if isinstance(trig, sin) else acsc + return ( + lambda a: 2*n*pi + F(a), + lambda a: 2*n*pi + pi - F(a)) + if isinstance(trig, (cos, sec)): + F = acos if isinstance(trig, cos) else asec + return ( + lambda a: 2*n*pi + F(a), + lambda a: 2*n*pi - F(a)) + if isinstance(trig, (tan, cot)): + return (lambda a: n*pi + trig.inverse()(a),) + + n = Dummy('n', integer=True) + invs = S.EmptySet + for L in inv(f): + invs += Union(*[imageset(Lambda(n, L(g)), S.Integers) for g in g_ys]) + return _invert_complex(f.args[0], invs, symbol) + + elif isinstance(f, HyperbolicFunction) and isinstance(g_ys, FiniteSet): + # There are two main options regarding singularities / domain checking + # for symbolic elements in g_ys: + # 1. Add a "catch-all" intersection with S.Complexes. + # 2. ConditionSets. + # At present ConditionSets seem to work better and have the additional + # benefit of representing the precise conditions that must be satisfied. + # The conditions are also rather straightforward. (At most two isolated + # points.) + def _get_hyp_inverses(func): + global _hyp_inverses + if _hyp_inverses is None: + _hyp_inverses = { + sinh : ((asinh, lambda y: I*pi-asinh(y)), 2*I*pi, ()), + cosh : ((acosh, lambda y: -acosh(y)), 2*I*pi, ()), + tanh : ((atanh,), I*pi, (-1, 1)), + coth : ((acoth,), I*pi, (-1, 1)), + sech : ((asech, lambda y: -asech(y)), 2*I*pi, (0, )), + csch : ((acsch, lambda y: I*pi-acsch(y)), 2*I*pi, (0, ))} + return _hyp_inverses[func] + + # invs: iterable of main inverses, e.g. (acosh, -acosh). + # excl: iterable of singularities to be checked for. + invs, period, excl = _get_hyp_inverses(f.func) + n = Dummy('n', integer=True) + def create_return_set(g): + # returns ConditionSet that will be part of the final (x, set) tuple + invsimg = Union(*[ + imageset(n, period*n + inv(g), S.Integers) for inv in invs]) + inv_f, inv_g_ys = _invert_complex(f.args[0], invsimg, symbol) + if inv_f == symbol: # inversion successful + conds = And(*[Ne(g, e) for e in excl]) + return ConditionSet(symbol, conds, inv_g_ys) + else: + return ConditionSet(symbol, Eq(f, g), S.Complexes) + + retset = Union(*[create_return_set(g) for g in g_ys]) + return (symbol, retset) + + else: + return (f, g_ys) + + +def _invert_complex(f, g_ys, symbol): + """Helper function for _invert.""" + + if f == symbol or g_ys is S.EmptySet: + return (symbol, g_ys) + + n = Dummy('n') + + if f.is_Add: + # f = g + h + g, h = f.as_independent(symbol) + if g is not S.Zero: + return _invert_complex(h, imageset(Lambda(n, n - g), g_ys), symbol) + + if f.is_Mul: + # f = g*h + g, h = f.as_independent(symbol) + + if g is not S.One: + if g in {S.NegativeInfinity, S.ComplexInfinity, S.Infinity}: + return (h, S.EmptySet) + return _invert_complex(h, imageset(Lambda(n, n/g), g_ys), symbol) + + if f.is_Pow: + base, expo = f.args + # special case: g**r = 0 + # Could be improved like `_invert_real` to handle more general cases. + if expo.is_Rational and g_ys == FiniteSet(0): + if expo.is_positive: + return _invert_complex(base, g_ys, symbol) + + if hasattr(f, 'inverse') and f.inverse() is not None and \ + not isinstance(f, TrigonometricFunction) and \ + not isinstance(f, HyperbolicFunction) and \ + not isinstance(f, exp): + if len(f.args) > 1: + raise ValueError("Only functions with one argument are supported.") + return _invert_complex(f.args[0], + imageset(Lambda(n, f.inverse()(n)), g_ys), symbol) + + if isinstance(f, exp) or (f.is_Pow and f.base == S.Exp1): + if isinstance(g_ys, ImageSet): + # can solve up to `(d*exp(exp(...(exp(a*x + b))...) + c)` format. + # Further can be improved to `(d*exp(exp(...(exp(a*x**n + b*x**(n-1) + ... + f))...) + c)`. + g_ys_expr = g_ys.lamda.expr + g_ys_vars = g_ys.lamda.variables + k = Dummy('k{}'.format(len(g_ys_vars))) + g_ys_vars_1 = (k,) + g_ys_vars + exp_invs = Union(*[imageset(Lambda((g_ys_vars_1,), (I*(2*k*pi + arg(g_ys_expr)) + + log(Abs(g_ys_expr)))), S.Integers**(len(g_ys_vars_1)))]) + return _invert_complex(f.exp, exp_invs, symbol) + + elif isinstance(g_ys, FiniteSet): + exp_invs = Union(*[imageset(Lambda(n, I*(2*n*pi + arg(g_y)) + + log(Abs(g_y))), S.Integers) + for g_y in g_ys if g_y != 0]) + return _invert_complex(f.exp, exp_invs, symbol) + + if isinstance(f, (TrigonometricFunction, HyperbolicFunction)): + return _invert_trig_hyp_complex(f, g_ys, symbol) + + return (f, g_ys) + + +def _invert_abs(f, g_ys, symbol): + """Helper function for inverting absolute value functions. + + Returns the complete result of inverting an absolute value + function along with the conditions which must also be satisfied. + + If it is certain that all these conditions are met, a :class:`~.FiniteSet` + of all possible solutions is returned. If any condition cannot be + satisfied, an :class:`~.EmptySet` is returned. Otherwise, a + :class:`~.ConditionSet` of the solutions, with all the required conditions + specified, is returned. + + """ + if not g_ys.is_FiniteSet: + # this could be used for FiniteSet, but the + # results are more compact if they aren't, e.g. + # ConditionSet(x, Contains(n, Interval(0, oo)), {-n, n}) vs + # Union(Intersection(Interval(0, oo), {n}), Intersection(Interval(-oo, 0), {-n})) + # for the solution of abs(x) - n + pos = Intersection(g_ys, Interval(0, S.Infinity)) + parg = _invert_real(f, pos, symbol) + narg = _invert_real(-f, pos, symbol) + if parg[0] != narg[0]: + raise NotImplementedError + return parg[0], Union(narg[1], parg[1]) + + # check conditions: all these must be true. If any are unknown + # then return them as conditions which must be satisfied + unknown = [] + for a in g_ys.args: + ok = a.is_nonnegative if a.is_Number else a.is_positive + if ok is None: + unknown.append(a) + elif not ok: + return symbol, S.EmptySet + if unknown: + conditions = And(*[Contains(i, Interval(0, oo)) + for i in unknown]) + else: + conditions = True + n = Dummy('n', real=True) + # this is slightly different than above: instead of solving + # +/-f on positive values, here we solve for f on +/- g_ys + g_x, values = _invert_real(f, Union( + imageset(Lambda(n, n), g_ys), + imageset(Lambda(n, -n), g_ys)), symbol) + return g_x, ConditionSet(g_x, conditions, values) + + +def domain_check(f, symbol, p): + """Returns False if point p is infinite or any subexpression of f + is infinite or becomes so after replacing symbol with p. If none of + these conditions is met then True will be returned. + + Examples + ======== + + >>> from sympy import Mul, oo + >>> from sympy.abc import x + >>> from sympy.solvers.solveset import domain_check + >>> g = 1/(1 + (1/(x + 1))**2) + >>> domain_check(g, x, -1) + False + >>> domain_check(x**2, x, 0) + True + >>> domain_check(1/x, x, oo) + False + + * The function relies on the assumption that the original form + of the equation has not been changed by automatic simplification. + + >>> domain_check(x/x, x, 0) # x/x is automatically simplified to 1 + True + + * To deal with automatic evaluations use evaluate=False: + + >>> domain_check(Mul(x, 1/x, evaluate=False), x, 0) + False + """ + f, p = sympify(f), sympify(p) + if p.is_infinite: + return False + return _domain_check(f, symbol, p) + + +def _domain_check(f, symbol, p): + # helper for domain check + if f.is_Atom and f.is_finite: + return True + elif f.subs(symbol, p).is_infinite: + return False + elif isinstance(f, Piecewise): + # Check the cases of the Piecewise in turn. There might be invalid + # expressions in later cases that don't apply e.g. + # solveset(Piecewise((0, Eq(x, 0)), (1/x, True)), x) + for expr, cond in f.args: + condsubs = cond.subs(symbol, p) + if condsubs is S.false: + continue + elif condsubs is S.true: + return _domain_check(expr, symbol, p) + else: + # We don't know which case of the Piecewise holds. On this + # basis we cannot decide whether any solution is in or out of + # the domain. Ideally this function would allow returning a + # symbolic condition for the validity of the solution that + # could be handled in the calling code. In the mean time we'll + # give this particular solution the benefit of the doubt and + # let it pass. + return True + else: + # TODO : We should not blindly recurse through all args of arbitrary expressions like this + return all(_domain_check(g, symbol, p) + for g in f.args) + + +def _is_finite_with_finite_vars(f, domain=S.Complexes): + """ + Return True if the given expression is finite. For symbols that + do not assign a value for `complex` and/or `real`, the domain will + be used to assign a value; symbols that do not assign a value + for `finite` will be made finite. All other assumptions are + left unmodified. + """ + def assumptions(s): + A = s.assumptions0 + A.setdefault('finite', A.get('finite', True)) + if domain.is_subset(S.Reals): + # if this gets set it will make complex=True, too + A.setdefault('real', True) + else: + # don't change 'real' because being complex implies + # nothing about being real + A.setdefault('complex', True) + return A + + reps = {s: Dummy(**assumptions(s)) for s in f.free_symbols} + return f.xreplace(reps).is_finite + + +def _is_function_class_equation(func_class, f, symbol): + """ Tests whether the equation is an equation of the given function class. + + The given equation belongs to the given function class if it is + comprised of functions of the function class which are multiplied by + or added to expressions independent of the symbol. In addition, the + arguments of all such functions must be linear in the symbol as well. + + Examples + ======== + + >>> from sympy.solvers.solveset import _is_function_class_equation + >>> from sympy import tan, sin, tanh, sinh, exp + >>> from sympy.abc import x + >>> from sympy.functions.elementary.trigonometric import TrigonometricFunction + >>> from sympy.functions.elementary.hyperbolic import HyperbolicFunction + >>> _is_function_class_equation(TrigonometricFunction, exp(x) + tan(x), x) + False + >>> _is_function_class_equation(TrigonometricFunction, tan(x) + sin(x), x) + True + >>> _is_function_class_equation(TrigonometricFunction, tan(x**2), x) + False + >>> _is_function_class_equation(TrigonometricFunction, tan(x + 2), x) + True + >>> _is_function_class_equation(HyperbolicFunction, tanh(x) + sinh(x), x) + True + """ + if f.is_Mul or f.is_Add: + return all(_is_function_class_equation(func_class, arg, symbol) + for arg in f.args) + + if f.is_Pow: + if not f.exp.has(symbol): + return _is_function_class_equation(func_class, f.base, symbol) + else: + return False + + if not f.has(symbol): + return True + + if isinstance(f, func_class): + try: + g = Poly(f.args[0], symbol) + return g.degree() <= 1 + except PolynomialError: + return False + else: + return False + + +def _solve_as_rational(f, symbol, domain): + """ solve rational functions""" + f = together(_mexpand(f, recursive=True), deep=True) + g, h = fraction(f) + if not h.has(symbol): + try: + return _solve_as_poly(g, symbol, domain) + except NotImplementedError: + # The polynomial formed from g could end up having + # coefficients in a ring over which finding roots + # isn't implemented yet, e.g. ZZ[a] for some symbol a + return ConditionSet(symbol, Eq(f, 0), domain) + except CoercionFailed: + # contained oo, zoo or nan + return S.EmptySet + else: + valid_solns = _solveset(g, symbol, domain) + invalid_solns = _solveset(h, symbol, domain) + return valid_solns - invalid_solns + + +class _SolveTrig1Error(Exception): + """Raised when _solve_trig1 heuristics do not apply""" + +def _solve_trig(f, symbol, domain): + """Function to call other helpers to solve trigonometric equations """ + # If f is composed of a single trig function (potentially appearing multiple + # times) we should solve by either inverting directly or inverting after a + # suitable change of variable. + # + # _solve_trig is currently only called by _solveset for trig/hyperbolic + # functions of an argument linear in x. Inverting a symbolic argument should + # include a guard against division by zero in order to have a result that is + # consistent with similar processing done by _solve_trig1. + # (Ideally _invert should add these conditions by itself.) + trig_expr, count = None, 0 + for expr in preorder_traversal(f): + if isinstance(expr, (TrigonometricFunction, + HyperbolicFunction)) and expr.has(symbol): + if not trig_expr: + trig_expr, count = expr, 1 + elif expr == trig_expr: + count += 1 + else: + trig_expr, count = False, 0 + break + if count == 1: + # direct inversion + x, sol = _invert(f, 0, symbol, domain) + if x == symbol: + cond = True + if trig_expr.free_symbols - {symbol}: + a, h = trig_expr.args[0].as_independent(symbol, as_Add=True) + m, h = h.as_independent(symbol, as_Add=False) + num, den = m.as_numer_denom() + cond = Ne(num, 0) & Ne(den, 0) + return ConditionSet(symbol, cond, sol) + else: + return ConditionSet(symbol, Eq(f, 0), domain) + elif count: + # solve by change of variable + y = Dummy('y') + f_cov = f.subs(trig_expr, y) + sol_cov = solveset(f_cov, y, domain) + if isinstance(sol_cov, FiniteSet): + return Union( + *[_solve_trig(trig_expr-s, symbol, domain) for s in sol_cov]) + + sol = None + try: + # multiple trig/hyp functions; solve by rewriting to exp + sol = _solve_trig1(f, symbol, domain) + except _SolveTrig1Error: + try: + # multiple trig/hyp functions; solve by rewriting to tan(x/2) + sol = _solve_trig2(f, symbol, domain) + except ValueError: + raise NotImplementedError(filldedent(''' + Solution to this kind of trigonometric equations + is yet to be implemented''')) + return sol + + +def _solve_trig1(f, symbol, domain): + """Primary solver for trigonometric and hyperbolic equations + + Returns either the solution set as a ConditionSet (auto-evaluated to a + union of ImageSets if no variables besides 'symbol' are involved) or + raises _SolveTrig1Error if f == 0 cannot be solved. + + Notes + ===== + Algorithm: + 1. Do a change of variable x -> mu*x in arguments to trigonometric and + hyperbolic functions, in order to reduce them to small integers. (This + step is crucial to keep the degrees of the polynomials of step 4 low.) + 2. Rewrite trigonometric/hyperbolic functions as exponentials. + 3. Proceed to a 2nd change of variable, replacing exp(I*x) or exp(x) by y. + 4. Solve the resulting rational equation. + 5. Use invert_complex or invert_real to return to the original variable. + 6. If the coefficients of 'symbol' were symbolic in nature, add the + necessary consistency conditions in a ConditionSet. + + """ + # Prepare change of variable + x = Dummy('x') + if _is_function_class_equation(HyperbolicFunction, f, symbol): + cov = exp(x) + inverter = invert_real if domain.is_subset(S.Reals) else invert_complex + else: + cov = exp(I*x) + inverter = invert_complex + + f = trigsimp(f) + f_original = f + trig_functions = f.atoms(TrigonometricFunction, HyperbolicFunction) + trig_arguments = [e.args[0] for e in trig_functions] + # trigsimp may have reduced the equation to an expression + # that is independent of 'symbol' (e.g. cos**2+sin**2) + if not any(a.has(symbol) for a in trig_arguments): + return solveset(f_original, symbol, domain) + + denominators = [] + numerators = [] + for ar in trig_arguments: + try: + poly_ar = Poly(ar, symbol) + except PolynomialError: + raise _SolveTrig1Error("trig argument is not a polynomial") + if poly_ar.degree() > 1: # degree >1 still bad + raise _SolveTrig1Error("degree of variable must not exceed one") + if poly_ar.degree() == 0: # degree 0, don't care + continue + c = poly_ar.all_coeffs()[0] # got the coefficient of 'symbol' + numerators.append(fraction(c)[0]) + denominators.append(fraction(c)[1]) + + mu = lcm(denominators)/gcd(numerators) + f = f.subs(symbol, mu*x) + f = f.rewrite(exp) + f = together(f) + g, h = fraction(f) + y = Dummy('y') + g, h = g.expand(), h.expand() + g, h = g.subs(cov, y), h.subs(cov, y) + if g.has(x) or h.has(x): + raise _SolveTrig1Error("change of variable not possible") + + solns = solveset_complex(g, y) - solveset_complex(h, y) + if isinstance(solns, ConditionSet): + raise _SolveTrig1Error("polynomial has ConditionSet solution") + + if isinstance(solns, FiniteSet): + if any(isinstance(s, RootOf) for s in solns): + raise _SolveTrig1Error("polynomial results in RootOf object") + # revert the change of variable + cov = cov.subs(x, symbol/mu) + result = Union(*[inverter(cov, s, symbol)[1] for s in solns]) + # In case of symbolic coefficients, the solution set is only valid + # if numerator and denominator of mu are non-zero. + if mu.has(Symbol): + syms = (mu).atoms(Symbol) + munum, muden = fraction(mu) + condnum = munum.as_independent(*syms, as_Add=False)[1] + condden = muden.as_independent(*syms, as_Add=False)[1] + cond = And(Ne(condnum, 0), Ne(condden, 0)) + else: + cond = True + # Actual conditions are returned as part of the ConditionSet. Adding an + # intersection with C would only complicate some solution sets due to + # current limitations of intersection code. (e.g. #19154) + if domain is S.Complexes: + # This is a slight abuse of ConditionSet. Ideally this should + # be some kind of "PiecewiseSet". (See #19507 discussion) + return ConditionSet(symbol, cond, result) + else: + return ConditionSet(symbol, cond, Intersection(result, domain)) + elif solns is S.EmptySet: + return S.EmptySet + else: + raise _SolveTrig1Error("polynomial solutions must form FiniteSet") + + +def _solve_trig2(f, symbol, domain): + """Secondary helper to solve trigonometric equations, + called when first helper fails """ + f = trigsimp(f) + f_original = f + trig_functions = f.atoms(sin, cos, tan, sec, cot, csc) + trig_arguments = [e.args[0] for e in trig_functions] + denominators = [] + numerators = [] + + # todo: This solver can be extended to hyperbolics if the + # analogous change of variable to tanh (instead of tan) + # is used. + if not trig_functions: + return ConditionSet(symbol, Eq(f_original, 0), domain) + + # todo: The pre-processing below (extraction of numerators, denominators, + # gcd, lcm, mu, etc.) should be updated to the enhanced version in + # _solve_trig1. (See #19507) + for ar in trig_arguments: + try: + poly_ar = Poly(ar, symbol) + except PolynomialError: + raise ValueError("give up, we cannot solve if this is not a polynomial in x") + if poly_ar.degree() > 1: # degree >1 still bad + raise ValueError("degree of variable inside polynomial should not exceed one") + if poly_ar.degree() == 0: # degree 0, don't care + continue + c = poly_ar.all_coeffs()[0] # got the coefficient of 'symbol' + try: + numerators.append(Rational(c).p) + denominators.append(Rational(c).q) + except TypeError: + return ConditionSet(symbol, Eq(f_original, 0), domain) + + x = Dummy('x') + + mu = Rational(2)*number_lcm(*denominators)/number_gcd(*numerators) + f = f.subs(symbol, mu*x) + f = f.rewrite(tan) + f = expand_trig(f) + f = together(f) + + g, h = fraction(f) + y = Dummy('y') + g, h = g.expand(), h.expand() + g, h = g.subs(tan(x), y), h.subs(tan(x), y) + + if g.has(x) or h.has(x): + return ConditionSet(symbol, Eq(f_original, 0), domain) + solns = solveset(g, y, S.Reals) - solveset(h, y, S.Reals) + + if isinstance(solns, FiniteSet): + result = Union(*[invert_real(tan(symbol/mu), s, symbol)[1] + for s in solns]) + dsol = invert_real(tan(symbol/mu), oo, symbol)[1] + if degree(h) > degree(g): # If degree(denom)>degree(num) then there + result = Union(result, dsol) # would be another sol at Lim(denom-->oo) + return Intersection(result, domain) + elif solns is S.EmptySet: + return S.EmptySet + else: + return ConditionSet(symbol, Eq(f_original, 0), S.Reals) + + +def _solve_as_poly(f, symbol, domain=S.Complexes): + """ + Solve the equation using polynomial techniques if it already is a + polynomial equation or, with a change of variables, can be made so. + """ + result = None + if f.is_polynomial(symbol): + solns = roots(f, symbol, cubics=True, quartics=True, + quintics=True, domain='EX') + num_roots = sum(solns.values()) + if degree(f, symbol) <= num_roots: + result = FiniteSet(*solns.keys()) + else: + poly = Poly(f, symbol) + solns = poly.all_roots() + if poly.degree() <= len(solns): + result = FiniteSet(*solns) + else: + result = ConditionSet(symbol, Eq(f, 0), domain) + else: + poly = Poly(f) + if poly is None: + result = ConditionSet(symbol, Eq(f, 0), domain) + gens = [g for g in poly.gens if g.has(symbol)] + + if len(gens) == 1: + poly = Poly(poly, gens[0]) + gen = poly.gen + deg = poly.degree() + poly = Poly(poly.as_expr(), poly.gen, composite=True) + poly_solns = FiniteSet(*roots(poly, cubics=True, quartics=True, + quintics=True).keys()) + + if len(poly_solns) < deg: + result = ConditionSet(symbol, Eq(f, 0), domain) + + if gen != symbol: + y = Dummy('y') + inverter = invert_real if domain.is_subset(S.Reals) else invert_complex + lhs, rhs_s = inverter(gen, y, symbol) + if lhs == symbol: + result = Union(*[rhs_s.subs(y, s) for s in poly_solns]) + if isinstance(result, FiniteSet) and isinstance(gen, Pow + ) and gen.base.is_Rational: + result = FiniteSet(*[expand_log(i) for i in result]) + else: + result = ConditionSet(symbol, Eq(f, 0), domain) + else: + result = ConditionSet(symbol, Eq(f, 0), domain) + + if result is not None: + if isinstance(result, FiniteSet): + # this is to simplify solutions like -sqrt(-I) to sqrt(2)/2 + # - sqrt(2)*I/2. We are not expanding for solution with symbols + # or undefined functions because that makes the solution more complicated. + # For example, expand_complex(a) returns re(a) + I*im(a) + if all(s.atoms(Symbol, AppliedUndef) == set() and not isinstance(s, RootOf) + for s in result): + s = Dummy('s') + result = imageset(Lambda(s, expand_complex(s)), result) + if isinstance(result, FiniteSet) and domain != S.Complexes: + # Avoid adding gratuitous intersections with S.Complexes. Actual + # conditions should be handled elsewhere. + result = result.intersection(domain) + return result + else: + return ConditionSet(symbol, Eq(f, 0), domain) + + +def _solve_radical(f, unradf, symbol, solveset_solver): + """ Helper function to solve equations with radicals """ + res = unradf + eq, cov = res if res else (f, []) + if not cov: + result = solveset_solver(eq, symbol) - \ + Union(*[solveset_solver(g, symbol) for g in denoms(f, symbol)]) + else: + y, yeq = cov + if not solveset_solver(y - I, y): + yreal = Dummy('yreal', real=True) + yeq = yeq.xreplace({y: yreal}) + eq = eq.xreplace({y: yreal}) + y = yreal + g_y_s = solveset_solver(yeq, symbol) + f_y_sols = solveset_solver(eq, y) + result = Union(*[imageset(Lambda(y, g_y), f_y_sols) + for g_y in g_y_s]) + + def check_finiteset(solutions): + f_set = [] # solutions for FiniteSet + c_set = [] # solutions for ConditionSet + for s in solutions: + if checksol(f, symbol, s): + f_set.append(s) + else: + c_set.append(s) + return FiniteSet(*f_set) + ConditionSet(symbol, Eq(f, 0), FiniteSet(*c_set)) + + def check_set(solutions): + if solutions is S.EmptySet: + return solutions + elif isinstance(solutions, ConditionSet): + # XXX: Maybe the base set should be checked? + return solutions + elif isinstance(solutions, FiniteSet): + return check_finiteset(solutions) + elif isinstance(solutions, Complement): + A, B = solutions.args + return Complement(check_set(A), B) + elif isinstance(solutions, Union): + return Union(*[check_set(s) for s in solutions.args]) + else: + # XXX: There should be more cases checked here. The cases above + # are all those that come up in the test suite for now. + return solutions + + solution_set = check_set(result) + + return solution_set + + +def _solve_abs(f, symbol, domain): + """ Helper function to solve equation involving absolute value function """ + if not domain.is_subset(S.Reals): + raise ValueError(filldedent(''' + Absolute values cannot be inverted in the + complex domain.''')) + p, q, r = Wild('p'), Wild('q'), Wild('r') + pattern_match = f.match(p*Abs(q) + r) or {} + f_p, f_q, f_r = [pattern_match.get(i, S.Zero) for i in (p, q, r)] + + if not (f_p.is_zero or f_q.is_zero): + domain = continuous_domain(f_q, symbol, domain) + from .inequalities import solve_univariate_inequality + q_pos_cond = solve_univariate_inequality(f_q >= 0, symbol, + relational=False, domain=domain, continuous=True) + q_neg_cond = q_pos_cond.complement(domain) + + sols_q_pos = solveset_real(f_p*f_q + f_r, + symbol).intersect(q_pos_cond) + sols_q_neg = solveset_real(f_p*(-f_q) + f_r, + symbol).intersect(q_neg_cond) + return Union(sols_q_pos, sols_q_neg) + else: + return ConditionSet(symbol, Eq(f, 0), domain) + + +def solve_decomposition(f, symbol, domain): + """ + Function to solve equations via the principle of "Decomposition + and Rewriting". + + Examples + ======== + >>> from sympy import exp, sin, Symbol, pprint, S + >>> from sympy.solvers.solveset import solve_decomposition as sd + >>> x = Symbol('x') + >>> f1 = exp(2*x) - 3*exp(x) + 2 + >>> sd(f1, x, S.Reals) + {0, log(2)} + >>> f2 = sin(x)**2 + 2*sin(x) + 1 + >>> pprint(sd(f2, x, S.Reals), use_unicode=False) + 3*pi + {2*n*pi + ---- | n in Integers} + 2 + >>> f3 = sin(x + 2) + >>> pprint(sd(f3, x, S.Reals), use_unicode=False) + {2*n*pi - 2 | n in Integers} U {2*n*pi - 2 + pi | n in Integers} + + """ + from sympy.solvers.decompogen import decompogen + # decompose the given function + g_s = decompogen(f, symbol) + # `y_s` represents the set of values for which the function `g` is to be + # solved. + # `solutions` represent the solutions of the equations `g = y_s` or + # `g = 0` depending on the type of `y_s`. + # As we are interested in solving the equation: f = 0 + y_s = FiniteSet(0) + for g in g_s: + frange = function_range(g, symbol, domain) + y_s = Intersection(frange, y_s) + result = S.EmptySet + if isinstance(y_s, FiniteSet): + for y in y_s: + solutions = solveset(Eq(g, y), symbol, domain) + if not isinstance(solutions, ConditionSet): + result += solutions + + else: + if isinstance(y_s, ImageSet): + iter_iset = (y_s,) + + elif isinstance(y_s, Union): + iter_iset = y_s.args + + elif y_s is S.EmptySet: + # y_s is not in the range of g in g_s, so no solution exists + #in the given domain + return S.EmptySet + + for iset in iter_iset: + new_solutions = solveset(Eq(iset.lamda.expr, g), symbol, domain) + dummy_var = tuple(iset.lamda.expr.free_symbols)[0] + (base_set,) = iset.base_sets + if isinstance(new_solutions, FiniteSet): + new_exprs = new_solutions + + elif isinstance(new_solutions, Intersection): + if isinstance(new_solutions.args[1], FiniteSet): + new_exprs = new_solutions.args[1] + + for new_expr in new_exprs: + result += ImageSet(Lambda(dummy_var, new_expr), base_set) + + if result is S.EmptySet: + return ConditionSet(symbol, Eq(f, 0), domain) + + y_s = result + + return y_s + + +def _solveset(f, symbol, domain, _check=False): + """Helper for solveset to return a result from an expression + that has already been sympify'ed and is known to contain the + given symbol.""" + # _check controls whether the answer is checked or not + from sympy.simplify.simplify import signsimp + + if isinstance(f, BooleanTrue): + return domain + + orig_f = f + if f.is_Mul: + coeff, f = f.as_independent(symbol, as_Add=False) + if coeff in {S.ComplexInfinity, S.NegativeInfinity, S.Infinity}: + f = together(orig_f) + elif f.is_Add: + a, h = f.as_independent(symbol) + m, h = h.as_independent(symbol, as_Add=False) + if m not in {S.ComplexInfinity, S.Zero, S.Infinity, + S.NegativeInfinity}: + f = a/m + h # XXX condition `m != 0` should be added to soln + + # assign the solvers to use + solver = lambda f, x, domain=domain: _solveset(f, x, domain) + inverter = lambda f, rhs, symbol: _invert(f, rhs, symbol, domain) + + result = S.EmptySet + + if f.expand().is_zero: + return domain + elif not f.has(symbol): + return S.EmptySet + elif f.is_Mul and all(_is_finite_with_finite_vars(m, domain) + for m in f.args): + # if f(x) and g(x) are both finite we can say that the solution of + # f(x)*g(x) == 0 is same as Union(f(x) == 0, g(x) == 0) is not true in + # general. g(x) can grow to infinitely large for the values where + # f(x) == 0. To be sure that we are not silently allowing any + # wrong solutions we are using this technique only if both f and g are + # finite for a finite input. + result = Union(*[solver(m, symbol) for m in f.args]) + elif (_is_function_class_equation(TrigonometricFunction, f, symbol) or \ + _is_function_class_equation(HyperbolicFunction, f, symbol)): + result = _solve_trig(f, symbol, domain) + elif isinstance(f, arg): + a = f.args[0] + result = Intersection(_solveset(re(a) > 0, symbol, domain), + _solveset(im(a), symbol, domain)) + elif f.is_Piecewise: + expr_set_pairs = f.as_expr_set_pairs(domain) + for (expr, in_set) in expr_set_pairs: + if in_set.is_Relational: + in_set = in_set.as_set() + solns = solver(expr, symbol, in_set) + result += solns + elif isinstance(f, Eq): + result = solver(Add(f.lhs, -f.rhs, evaluate=False), symbol, domain) + + elif f.is_Relational: + from .inequalities import solve_univariate_inequality + try: + result = solve_univariate_inequality( + f, symbol, domain=domain, relational=False) + except NotImplementedError: + result = ConditionSet(symbol, f, domain) + return result + elif _is_modular(f, symbol): + result = _solve_modular(f, symbol, domain) + else: + lhs, rhs_s = inverter(f, 0, symbol) + if lhs == symbol: + # do some very minimal simplification since + # repeated inversion may have left the result + # in a state that other solvers (e.g. poly) + # would have simplified; this is done here + # rather than in the inverter since here it + # is only done once whereas there it would + # be repeated for each step of the inversion + if isinstance(rhs_s, FiniteSet): + rhs_s = FiniteSet(*[Mul(* + signsimp(i).as_content_primitive()) + for i in rhs_s]) + result = rhs_s + + elif isinstance(rhs_s, FiniteSet): + for equation in [lhs - rhs for rhs in rhs_s]: + if equation == f: + u = unrad(f, symbol) + if u: + result += _solve_radical(equation, u, + symbol, + solver) + elif equation.has(Abs): + result += _solve_abs(f, symbol, domain) + else: + result_rational = _solve_as_rational(equation, symbol, domain) + if not isinstance(result_rational, ConditionSet): + result += result_rational + else: + # may be a transcendental type equation + t_result = _transolve(equation, symbol, domain) + if isinstance(t_result, ConditionSet): + # might need factoring; this is expensive so we + # have delayed until now. To avoid recursion + # errors look for a non-trivial factoring into + # a product of symbol dependent terms; I think + # that something that factors as a Pow would + # have already been recognized by now. + factored = equation.factor() + if factored.is_Mul and equation != factored: + _, dep = factored.as_independent(symbol) + if not dep.is_Add: + # non-trivial factoring of equation + # but use form with constants + # in case they need special handling + t_results = [] + for fac in Mul.make_args(factored): + if fac.has(symbol): + t_results.append(solver(fac, symbol)) + t_result = Union(*t_results) + result += t_result + else: + result += solver(equation, symbol) + + elif rhs_s is not S.EmptySet: + result = ConditionSet(symbol, Eq(f, 0), domain) + + if isinstance(result, ConditionSet): + if isinstance(f, Expr): + num, den = f.as_numer_denom() + if den.has(symbol): + _result = _solveset(num, symbol, domain) + if not isinstance(_result, ConditionSet): + singularities = _solveset(den, symbol, domain) + result = _result - singularities + + if _check: + if isinstance(result, ConditionSet): + # it wasn't solved or has enumerated all conditions + # -- leave it alone + return result + + # whittle away all but the symbol-containing core + # to use this for testing + if isinstance(orig_f, Expr): + fx = orig_f.as_independent(symbol, as_Add=True)[1] + fx = fx.as_independent(symbol, as_Add=False)[1] + else: + fx = orig_f + + if isinstance(result, FiniteSet): + # check the result for invalid solutions + result = FiniteSet(*[s for s in result + if isinstance(s, RootOf) + or domain_check(fx, symbol, s)]) + + return result + + +def _is_modular(f, symbol): + """ + Helper function to check below mentioned types of modular equations. + ``A - Mod(B, C) = 0`` + + A -> This can or cannot be a function of symbol. + B -> This is surely a function of symbol. + C -> It is an integer. + + Parameters + ========== + + f : Expr + The equation to be checked. + + symbol : Symbol + The concerned variable for which the equation is to be checked. + + Examples + ======== + + >>> from sympy import symbols, exp, Mod + >>> from sympy.solvers.solveset import _is_modular as check + >>> x, y = symbols('x y') + >>> check(Mod(x, 3) - 1, x) + True + >>> check(Mod(x, 3) - 1, y) + False + >>> check(Mod(x, 3)**2 - 5, x) + False + >>> check(Mod(x, 3)**2 - y, x) + False + >>> check(exp(Mod(x, 3)) - 1, x) + False + >>> check(Mod(3, y) - 1, y) + False + """ + + if not f.has(Mod): + return False + + # extract modterms from f. + modterms = list(f.atoms(Mod)) + + return (len(modterms) == 1 and # only one Mod should be present + modterms[0].args[0].has(symbol) and # B-> function of symbol + modterms[0].args[1].is_integer and # C-> to be an integer. + any(isinstance(term, Mod) + for term in list(_term_factors(f))) # free from other funcs + ) + + +def _invert_modular(modterm, rhs, n, symbol): + """ + Helper function to invert modular equation. + ``Mod(a, m) - rhs = 0`` + + Generally it is inverted as (a, ImageSet(Lambda(n, m*n + rhs), S.Integers)). + More simplified form will be returned if possible. + + If it is not invertible then (modterm, rhs) is returned. + + The following cases arise while inverting equation ``Mod(a, m) - rhs = 0``: + + 1. If a is symbol then m*n + rhs is the required solution. + + 2. If a is an instance of ``Add`` then we try to find two symbol independent + parts of a and the symbol independent part gets transferred to the other + side and again the ``_invert_modular`` is called on the symbol + dependent part. + + 3. If a is an instance of ``Mul`` then same as we done in ``Add`` we separate + out the symbol dependent and symbol independent parts and transfer the + symbol independent part to the rhs with the help of invert and again the + ``_invert_modular`` is called on the symbol dependent part. + + 4. If a is an instance of ``Pow`` then two cases arise as following: + + - If a is of type (symbol_indep)**(symbol_dep) then the remainder is + evaluated with the help of discrete_log function and then the least + period is being found out with the help of totient function. + period*n + remainder is the required solution in this case. + For reference: (https://en.wikipedia.org/wiki/Euler's_theorem) + + - If a is of type (symbol_dep)**(symbol_indep) then we try to find all + primitive solutions list with the help of nthroot_mod function. + m*n + rem is the general solution where rem belongs to solutions list + from nthroot_mod function. + + Parameters + ========== + + modterm, rhs : Expr + The modular equation to be inverted, ``modterm - rhs = 0`` + + symbol : Symbol + The variable in the equation to be inverted. + + n : Dummy + Dummy variable for output g_n. + + Returns + ======= + + A tuple (f_x, g_n) is being returned where f_x is modular independent function + of symbol and g_n being set of values f_x can have. + + Examples + ======== + + >>> from sympy import symbols, exp, Mod, Dummy, S + >>> from sympy.solvers.solveset import _invert_modular as invert_modular + >>> x, y = symbols('x y') + >>> n = Dummy('n') + >>> invert_modular(Mod(exp(x), 7), S(5), n, x) + (Mod(exp(x), 7), 5) + >>> invert_modular(Mod(x, 7), S(5), n, x) + (x, ImageSet(Lambda(_n, 7*_n + 5), Integers)) + >>> invert_modular(Mod(3*x + 8, 7), S(5), n, x) + (x, ImageSet(Lambda(_n, 7*_n + 6), Integers)) + >>> invert_modular(Mod(x**4, 7), S(5), n, x) + (x, EmptySet) + >>> invert_modular(Mod(2**(x**2 + x + 1), 7), S(2), n, x) + (x**2 + x + 1, ImageSet(Lambda(_n, 3*_n + 1), Naturals0)) + + """ + a, m = modterm.args + + if rhs.is_integer is False: + return symbol, S.EmptySet + + if rhs.is_real is False or any(term.is_real is False + for term in list(_term_factors(a))): + # Check for complex arguments + return modterm, rhs + + if abs(rhs) >= abs(m): + # if rhs has value greater than value of m. + return symbol, S.EmptySet + + if a == symbol: + return symbol, ImageSet(Lambda(n, m*n + rhs), S.Integers) + + if a.is_Add: + # g + h = a + g, h = a.as_independent(symbol) + if g is not S.Zero: + x_indep_term = rhs - Mod(g, m) + return _invert_modular(Mod(h, m), Mod(x_indep_term, m), n, symbol) + + if a.is_Mul: + # g*h = a + g, h = a.as_independent(symbol) + if g is not S.One: + x_indep_term = rhs*invert(g, m) + return _invert_modular(Mod(h, m), Mod(x_indep_term, m), n, symbol) + + if a.is_Pow: + # base**expo = a + base, expo = a.args + if expo.has(symbol) and not base.has(symbol): + # remainder -> solution independent of n of equation. + # m, rhs are made coprime by dividing number_gcd(m, rhs) + if not m.is_Integer and rhs.is_Integer and a.base.is_Integer: + return modterm, rhs + + mdiv = m.p // number_gcd(m.p, rhs.p) + try: + remainder = discrete_log(mdiv, rhs.p, a.base.p) + except ValueError: # log does not exist + return modterm, rhs + # period -> coefficient of n in the solution and also referred as + # the least period of expo in which it is repeats itself. + # (a**(totient(m)) - 1) divides m. Here is link of theorem: + # (https://en.wikipedia.org/wiki/Euler's_theorem) + period = totient(m) + for p in divisors(period): + # there might a lesser period exist than totient(m). + if pow(a.base, p, m / number_gcd(m.p, a.base.p)) == 1: + period = p + break + # recursion is not applied here since _invert_modular is currently + # not smart enough to handle infinite rhs as here expo has infinite + # rhs = ImageSet(Lambda(n, period*n + remainder), S.Naturals0). + return expo, ImageSet(Lambda(n, period*n + remainder), S.Naturals0) + elif base.has(symbol) and not expo.has(symbol): + try: + remainder_list = nthroot_mod(rhs, expo, m, all_roots=True) + if remainder_list == []: + return symbol, S.EmptySet + except (ValueError, NotImplementedError): + return modterm, rhs + g_n = S.EmptySet + for rem in remainder_list: + g_n += ImageSet(Lambda(n, m*n + rem), S.Integers) + return base, g_n + + return modterm, rhs + + +def _solve_modular(f, symbol, domain): + r""" + Helper function for solving modular equations of type ``A - Mod(B, C) = 0``, + where A can or cannot be a function of symbol, B is surely a function of + symbol and C is an integer. + + Currently ``_solve_modular`` is only able to solve cases + where A is not a function of symbol. + + Parameters + ========== + + f : Expr + The modular equation to be solved, ``f = 0`` + + symbol : Symbol + The variable in the equation to be solved. + + domain : Set + A set over which the equation is solved. It has to be a subset of + Integers. + + Returns + ======= + + A set of integer solutions satisfying the given modular equation. + A ``ConditionSet`` if the equation is unsolvable. + + Examples + ======== + + >>> from sympy.solvers.solveset import _solve_modular as solve_modulo + >>> from sympy import S, Symbol, sin, Intersection, Interval, Mod + >>> x = Symbol('x') + >>> solve_modulo(Mod(5*x - 8, 7) - 3, x, S.Integers) + ImageSet(Lambda(_n, 7*_n + 5), Integers) + >>> solve_modulo(Mod(5*x - 8, 7) - 3, x, S.Reals) # domain should be subset of integers. + ConditionSet(x, Eq(Mod(5*x + 6, 7) - 3, 0), Reals) + >>> solve_modulo(-7 + Mod(x, 5), x, S.Integers) + EmptySet + >>> solve_modulo(Mod(12**x, 21) - 18, x, S.Integers) + ImageSet(Lambda(_n, 6*_n + 2), Naturals0) + >>> solve_modulo(Mod(sin(x), 7) - 3, x, S.Integers) # not solvable + ConditionSet(x, Eq(Mod(sin(x), 7) - 3, 0), Integers) + >>> solve_modulo(3 - Mod(x, 5), x, Intersection(S.Integers, Interval(0, 100))) + Intersection(ImageSet(Lambda(_n, 5*_n + 3), Integers), Range(0, 101, 1)) + """ + # extract modterm and g_y from f + unsolved_result = ConditionSet(symbol, Eq(f, 0), domain) + modterm = list(f.atoms(Mod))[0] + rhs = -S.One*(f.subs(modterm, S.Zero)) + if f.as_coefficients_dict()[modterm].is_negative: + # checks if coefficient of modterm is negative in main equation. + rhs *= -S.One + + if not domain.is_subset(S.Integers): + return unsolved_result + + if rhs.has(symbol): + # TODO Case: A-> function of symbol, can be extended here + # in future. + return unsolved_result + + n = Dummy('n', integer=True) + f_x, g_n = _invert_modular(modterm, rhs, n, symbol) + + if f_x == modterm and g_n == rhs: + return unsolved_result + + if f_x == symbol: + if domain is not S.Integers: + return domain.intersect(g_n) + return g_n + + if isinstance(g_n, ImageSet): + lamda_expr = g_n.lamda.expr + lamda_vars = g_n.lamda.variables + base_sets = g_n.base_sets + sol_set = _solveset(f_x - lamda_expr, symbol, S.Integers) + if isinstance(sol_set, FiniteSet): + tmp_sol = S.EmptySet + for sol in sol_set: + tmp_sol += ImageSet(Lambda(lamda_vars, sol), *base_sets) + sol_set = tmp_sol + else: + sol_set = ImageSet(Lambda(lamda_vars, sol_set), *base_sets) + return domain.intersect(sol_set) + + return unsolved_result + + +def _term_factors(f): + """ + Iterator to get the factors of all terms present + in the given equation. + + Parameters + ========== + f : Expr + Equation that needs to be addressed + + Returns + ======= + Factors of all terms present in the equation. + + Examples + ======== + + >>> from sympy import symbols + >>> from sympy.solvers.solveset import _term_factors + >>> x = symbols('x') + >>> list(_term_factors(-2 - x**2 + x*(x + 1))) + [-2, -1, x**2, x, x + 1] + """ + for add_arg in Add.make_args(f): + yield from Mul.make_args(add_arg) + + +def _solve_exponential(lhs, rhs, symbol, domain): + r""" + Helper function for solving (supported) exponential equations. + + Exponential equations are the sum of (currently) at most + two terms with one or both of them having a power with a + symbol-dependent exponent. + + For example + + .. math:: 5^{2x + 3} - 5^{3x - 1} + + .. math:: 4^{5 - 9x} - e^{2 - x} + + Parameters + ========== + + lhs, rhs : Expr + The exponential equation to be solved, `lhs = rhs` + + symbol : Symbol + The variable in which the equation is solved + + domain : Set + A set over which the equation is solved. + + Returns + ======= + + A set of solutions satisfying the given equation. + A ``ConditionSet`` if the equation is unsolvable or + if the assumptions are not properly defined, in that case + a different style of ``ConditionSet`` is returned having the + solution(s) of the equation with the desired assumptions. + + Examples + ======== + + >>> from sympy.solvers.solveset import _solve_exponential as solve_expo + >>> from sympy import symbols, S + >>> x = symbols('x', real=True) + >>> a, b = symbols('a b') + >>> solve_expo(2**x + 3**x - 5**x, 0, x, S.Reals) # not solvable + ConditionSet(x, Eq(2**x + 3**x - 5**x, 0), Reals) + >>> solve_expo(a**x - b**x, 0, x, S.Reals) # solvable but incorrect assumptions + ConditionSet(x, (a > 0) & (b > 0), {0}) + >>> solve_expo(3**(2*x) - 2**(x + 3), 0, x, S.Reals) + {-3*log(2)/(-2*log(3) + log(2))} + >>> solve_expo(2**x - 4**x, 0, x, S.Reals) + {0} + + * Proof of correctness of the method + + The logarithm function is the inverse of the exponential function. + The defining relation between exponentiation and logarithm is: + + .. math:: {\log_b x} = y \enspace if \enspace b^y = x + + Therefore if we are given an equation with exponent terms, we can + convert every term to its corresponding logarithmic form. This is + achieved by taking logarithms and expanding the equation using + logarithmic identities so that it can easily be handled by ``solveset``. + + For example: + + .. math:: 3^{2x} = 2^{x + 3} + + Taking log both sides will reduce the equation to + + .. math:: (2x)\log(3) = (x + 3)\log(2) + + This form can be easily handed by ``solveset``. + """ + unsolved_result = ConditionSet(symbol, Eq(lhs - rhs, 0), domain) + newlhs = powdenest(lhs) + if lhs != newlhs: + # it may also be advantageous to factor the new expr + neweq = factor(newlhs - rhs) + if neweq != (lhs - rhs): + return _solveset(neweq, symbol, domain) # try again with _solveset + + if not (isinstance(lhs, Add) and len(lhs.args) == 2): + # solving for the sum of more than two powers is possible + # but not yet implemented + return unsolved_result + + if rhs != 0: + return unsolved_result + + a, b = list(ordered(lhs.args)) + a_term = a.as_independent(symbol)[1] + b_term = b.as_independent(symbol)[1] + + a_base, a_exp = a_term.as_base_exp() + b_base, b_exp = b_term.as_base_exp() + + if domain.is_subset(S.Reals): + conditions = And( + a_base > 0, + b_base > 0, + Eq(im(a_exp), 0), + Eq(im(b_exp), 0)) + else: + conditions = And( + Ne(a_base, 0), + Ne(b_base, 0)) + + L, R = (expand_log(log(i), force=True) for i in (a, -b)) + solutions = _solveset(L - R, symbol, domain) + + return ConditionSet(symbol, conditions, solutions) + + +def _is_exponential(f, symbol): + r""" + Return ``True`` if one or more terms contain ``symbol`` only in + exponents, else ``False``. + + Parameters + ========== + + f : Expr + The equation to be checked + + symbol : Symbol + The variable in which the equation is checked + + Examples + ======== + + >>> from sympy import symbols, cos, exp + >>> from sympy.solvers.solveset import _is_exponential as check + >>> x, y = symbols('x y') + >>> check(y, y) + False + >>> check(x**y - 1, y) + True + >>> check(x**y*2**y - 1, y) + True + >>> check(exp(x + 3) + 3**x, x) + True + >>> check(cos(2**x), x) + False + + * Philosophy behind the helper + + The function extracts each term of the equation and checks if it is + of exponential form w.r.t ``symbol``. + """ + rv = False + for expr_arg in _term_factors(f): + if symbol not in expr_arg.free_symbols: + continue + if (isinstance(expr_arg, Pow) and + symbol not in expr_arg.base.free_symbols or + isinstance(expr_arg, exp)): + rv = True # symbol in exponent + else: + return False # dependent on symbol in non-exponential way + return rv + + +def _solve_logarithm(lhs, rhs, symbol, domain): + r""" + Helper to solve logarithmic equations which are reducible + to a single instance of `\log`. + + Logarithmic equations are (currently) the equations that contains + `\log` terms which can be reduced to a single `\log` term or + a constant using various logarithmic identities. + + For example: + + .. math:: \log(x) + \log(x - 4) + + can be reduced to: + + .. math:: \log(x(x - 4)) + + Parameters + ========== + + lhs, rhs : Expr + The logarithmic equation to be solved, `lhs = rhs` + + symbol : Symbol + The variable in which the equation is solved + + domain : Set + A set over which the equation is solved. + + Returns + ======= + + A set of solutions satisfying the given equation. + A ``ConditionSet`` if the equation is unsolvable. + + Examples + ======== + + >>> from sympy import symbols, log, S + >>> from sympy.solvers.solveset import _solve_logarithm as solve_log + >>> x = symbols('x') + >>> f = log(x - 3) + log(x + 3) + >>> solve_log(f, 0, x, S.Reals) + {-sqrt(10), sqrt(10)} + + * Proof of correctness + + A logarithm is another way to write exponent and is defined by + + .. math:: {\log_b x} = y \enspace if \enspace b^y = x + + When one side of the equation contains a single logarithm, the + equation can be solved by rewriting the equation as an equivalent + exponential equation as defined above. But if one side contains + more than one logarithm, we need to use the properties of logarithm + to condense it into a single logarithm. + + Take for example + + .. math:: \log(2x) - 15 = 0 + + contains single logarithm, therefore we can directly rewrite it to + exponential form as + + .. math:: x = \frac{e^{15}}{2} + + But if the equation has more than one logarithm as + + .. math:: \log(x - 3) + \log(x + 3) = 0 + + we use logarithmic identities to convert it into a reduced form + + Using, + + .. math:: \log(a) + \log(b) = \log(ab) + + the equation becomes, + + .. math:: \log((x - 3)(x + 3)) + + This equation contains one logarithm and can be solved by rewriting + to exponents. + """ + new_lhs = logcombine(lhs, force=True) + new_f = new_lhs - rhs + + return _solveset(new_f, symbol, domain) + + +def _is_logarithmic(f, symbol): + r""" + Return ``True`` if the equation is in the form + `a\log(f(x)) + b\log(g(x)) + ... + c` else ``False``. + + Parameters + ========== + + f : Expr + The equation to be checked + + symbol : Symbol + The variable in which the equation is checked + + Returns + ======= + + ``True`` if the equation is logarithmic otherwise ``False``. + + Examples + ======== + + >>> from sympy import symbols, tan, log + >>> from sympy.solvers.solveset import _is_logarithmic as check + >>> x, y = symbols('x y') + >>> check(log(x + 2) - log(x + 3), x) + True + >>> check(tan(log(2*x)), x) + False + >>> check(x*log(x), x) + False + >>> check(x + log(x), x) + False + >>> check(y + log(x), x) + True + + * Philosophy behind the helper + + The function extracts each term and checks whether it is + logarithmic w.r.t ``symbol``. + """ + rv = False + for term in Add.make_args(f): + saw_log = False + for term_arg in Mul.make_args(term): + if symbol not in term_arg.free_symbols: + continue + if isinstance(term_arg, log): + if saw_log: + return False # more than one log in term + saw_log = True + else: + return False # dependent on symbol in non-log way + if saw_log: + rv = True + return rv + + +def _is_lambert(f, symbol): + r""" + If this returns ``False`` then the Lambert solver (``_solve_lambert``) will not be called. + + Explanation + =========== + + Quick check for cases that the Lambert solver might be able to handle. + + 1. Equations containing more than two operands and `symbol`s involving any of + `Pow`, `exp`, `HyperbolicFunction`,`TrigonometricFunction`, `log` terms. + + 2. In `Pow`, `exp` the exponent should have `symbol` whereas for + `HyperbolicFunction`,`TrigonometricFunction`, `log` should contain `symbol`. + + 3. For `HyperbolicFunction`,`TrigonometricFunction` the number of trigonometric functions in + equation should be less than number of symbols. (since `A*cos(x) + B*sin(x) - c` + is not the Lambert type). + + Some forms of lambert equations are: + 1. X**X = C + 2. X*(B*log(X) + D)**A = C + 3. A*log(B*X + A) + d*X = C + 4. (B*X + A)*exp(d*X + g) = C + 5. g*exp(B*X + h) - B*X = C + 6. A*D**(E*X + g) - B*X = C + 7. A*cos(X) + B*sin(X) - D*X = C + 8. A*cosh(X) + B*sinh(X) - D*X = C + + Where X is any variable, + A, B, C, D, E are any constants, + g, h are linear functions or log terms. + + Parameters + ========== + + f : Expr + The equation to be checked + + symbol : Symbol + The variable in which the equation is checked + + Returns + ======= + + If this returns ``False`` then the Lambert solver (``_solve_lambert``) will not be called. + + Examples + ======== + + >>> from sympy.solvers.solveset import _is_lambert + >>> from sympy import symbols, cosh, sinh, log + >>> x = symbols('x') + + >>> _is_lambert(3*log(x) - x*log(3), x) + True + >>> _is_lambert(log(log(x - 3)) + log(x-3), x) + True + >>> _is_lambert(cosh(x) - sinh(x), x) + False + >>> _is_lambert((x**2 - 2*x + 1).subs(x, (log(x) + 3*x)**2 - 1), x) + True + + See Also + ======== + + _solve_lambert + + """ + term_factors = list(_term_factors(f.expand())) + + # total number of symbols in equation + no_of_symbols = len([arg for arg in term_factors if arg.has(symbol)]) + # total number of trigonometric terms in equation + no_of_trig = len([arg for arg in term_factors \ + if arg.has(HyperbolicFunction, TrigonometricFunction)]) + + if f.is_Add and no_of_symbols >= 2: + # `log`, `HyperbolicFunction`, `TrigonometricFunction` should have symbols + # and no_of_trig < no_of_symbols + lambert_funcs = (log, HyperbolicFunction, TrigonometricFunction) + if any(isinstance(arg, lambert_funcs)\ + for arg in term_factors if arg.has(symbol)): + if no_of_trig < no_of_symbols: + return True + # here, `Pow`, `exp` exponent should have symbols + elif any(isinstance(arg, (Pow, exp)) \ + for arg in term_factors if (arg.as_base_exp()[1]).has(symbol)): + return True + return False + + +def _transolve(f, symbol, domain): + r""" + Function to solve transcendental equations. It is a helper to + ``solveset`` and should be used internally. ``_transolve`` + currently supports the following class of equations: + + - Exponential equations + - Logarithmic equations + + Parameters + ========== + + f : Any transcendental equation that needs to be solved. + This needs to be an expression, which is assumed + to be equal to ``0``. + + symbol : The variable for which the equation is solved. + This needs to be of class ``Symbol``. + + domain : A set over which the equation is solved. + This needs to be of class ``Set``. + + Returns + ======= + + Set + A set of values for ``symbol`` for which ``f`` is equal to + zero. An ``EmptySet`` is returned if ``f`` does not have solutions + in respective domain. A ``ConditionSet`` is returned as unsolved + object if algorithms to evaluate complete solution are not + yet implemented. + + How to use ``_transolve`` + ========================= + + ``_transolve`` should not be used as an independent function, because + it assumes that the equation (``f``) and the ``symbol`` comes from + ``solveset`` and might have undergone a few modification(s). + To use ``_transolve`` as an independent function the equation (``f``) + and the ``symbol`` should be passed as they would have been by + ``solveset``. + + Examples + ======== + + >>> from sympy.solvers.solveset import _transolve as transolve + >>> from sympy.solvers.solvers import _tsolve as tsolve + >>> from sympy import symbols, S, pprint + >>> x = symbols('x', real=True) # assumption added + >>> transolve(5**(x - 3) - 3**(2*x + 1), x, S.Reals) + {-(log(3) + 3*log(5))/(-log(5) + 2*log(3))} + + How ``_transolve`` works + ======================== + + ``_transolve`` uses two types of helper functions to solve equations + of a particular class: + + Identifying helpers: To determine whether a given equation + belongs to a certain class of equation or not. Returns either + ``True`` or ``False``. + + Solving helpers: Once an equation is identified, a corresponding + helper either solves the equation or returns a form of the equation + that ``solveset`` might better be able to handle. + + * Philosophy behind the module + + The purpose of ``_transolve`` is to take equations which are not + already polynomial in their generator(s) and to either recast them + as such through a valid transformation or to solve them outright. + A pair of helper functions for each class of supported + transcendental functions are employed for this purpose. One + identifies the transcendental form of an equation and the other + either solves it or recasts it into a tractable form that can be + solved by ``solveset``. + For example, an equation in the form `ab^{f(x)} - cd^{g(x)} = 0` + can be transformed to + `\log(a) + f(x)\log(b) - \log(c) - g(x)\log(d) = 0` + (under certain assumptions) and this can be solved with ``solveset`` + if `f(x)` and `g(x)` are in polynomial form. + + How ``_transolve`` is better than ``_tsolve`` + ============================================= + + 1) Better output + + ``_transolve`` provides expressions in a more simplified form. + + Consider a simple exponential equation + + >>> f = 3**(2*x) - 2**(x + 3) + >>> pprint(transolve(f, x, S.Reals), use_unicode=False) + -3*log(2) + {------------------} + -2*log(3) + log(2) + >>> pprint(tsolve(f, x), use_unicode=False) + / 3 \ + | --------| + | log(2/9)| + [-log\2 /] + + 2) Extensible + + The API of ``_transolve`` is designed such that it is easily + extensible, i.e. the code that solves a given class of + equations is encapsulated in a helper and not mixed in with + the code of ``_transolve`` itself. + + 3) Modular + + ``_transolve`` is designed to be modular i.e, for every class of + equation a separate helper for identification and solving is + implemented. This makes it easy to change or modify any of the + method implemented directly in the helpers without interfering + with the actual structure of the API. + + 4) Faster Computation + + Solving equation via ``_transolve`` is much faster as compared to + ``_tsolve``. In ``solve``, attempts are made computing every possibility + to get the solutions. This series of attempts makes solving a bit + slow. In ``_transolve``, computation begins only after a particular + type of equation is identified. + + How to add new class of equations + ================================= + + Adding a new class of equation solver is a three-step procedure: + + - Identify the type of the equations + + Determine the type of the class of equations to which they belong: + it could be of ``Add``, ``Pow``, etc. types. Separate internal functions + are used for each type. Write identification and solving helpers + and use them from within the routine for the given type of equation + (after adding it, if necessary). Something like: + + .. code-block:: python + + def add_type(lhs, rhs, x): + .... + if _is_exponential(lhs, x): + new_eq = _solve_exponential(lhs, rhs, x) + .... + rhs, lhs = eq.as_independent(x) + if lhs.is_Add: + result = add_type(lhs, rhs, x) + + - Define the identification helper. + + - Define the solving helper. + + Apart from this, a few other things needs to be taken care while + adding an equation solver: + + - Naming conventions: + Name of the identification helper should be as + ``_is_class`` where class will be the name or abbreviation + of the class of equation. The solving helper will be named as + ``_solve_class``. + For example: for exponential equations it becomes + ``_is_exponential`` and ``_solve_expo``. + - The identifying helpers should take two input parameters, + the equation to be checked and the variable for which a solution + is being sought, while solving helpers would require an additional + domain parameter. + - Be sure to consider corner cases. + - Add tests for each helper. + - Add a docstring to your helper that describes the method + implemented. + The documentation of the helpers should identify: + + - the purpose of the helper, + - the method used to identify and solve the equation, + - a proof of correctness + - the return values of the helpers + """ + + def add_type(lhs, rhs, symbol, domain): + """ + Helper for ``_transolve`` to handle equations of + ``Add`` type, i.e. equations taking the form as + ``a*f(x) + b*g(x) + .... = c``. + For example: 4**x + 8**x = 0 + """ + result = ConditionSet(symbol, Eq(lhs - rhs, 0), domain) + + # check if it is exponential type equation + if _is_exponential(lhs, symbol): + result = _solve_exponential(lhs, rhs, symbol, domain) + # check if it is logarithmic type equation + elif _is_logarithmic(lhs, symbol): + result = _solve_logarithm(lhs, rhs, symbol, domain) + + return result + + result = ConditionSet(symbol, Eq(f, 0), domain) + + # invert_complex handles the call to the desired inverter based + # on the domain specified. + lhs, rhs_s = invert_complex(f, 0, symbol, domain) + + if isinstance(rhs_s, FiniteSet): + assert (len(rhs_s.args)) == 1 + rhs = rhs_s.args[0] + + if lhs.is_Add: + result = add_type(lhs, rhs, symbol, domain) + else: + result = rhs_s + + return result + + +def solveset(f, symbol=None, domain=S.Complexes): + r"""Solves a given inequality or equation with set as output + + Parameters + ========== + + f : Expr or a relational. + The target equation or inequality + symbol : Symbol + The variable for which the equation is solved + domain : Set + The domain over which the equation is solved + + Returns + ======= + + Set + A set of values for `symbol` for which `f` is True or is equal to + zero. An :class:`~.EmptySet` is returned if `f` is False or nonzero. + A :class:`~.ConditionSet` is returned as unsolved object if algorithms + to evaluate complete solution are not yet implemented. + + ``solveset`` claims to be complete in the solution set that it returns. + + Raises + ====== + + NotImplementedError + The algorithms to solve inequalities in complex domain are + not yet implemented. + ValueError + The input is not valid. + RuntimeError + It is a bug, please report to the github issue tracker. + + + Notes + ===== + + Python interprets 0 and 1 as False and True, respectively, but + in this function they refer to solutions of an expression. So 0 and 1 + return the domain and EmptySet, respectively, while True and False + return the opposite (as they are assumed to be solutions of relational + expressions). + + + See Also + ======== + + solveset_real: solver for real domain + solveset_complex: solver for complex domain + + Examples + ======== + + >>> from sympy import exp, sin, Symbol, pprint, S, Eq + >>> from sympy.solvers.solveset import solveset, solveset_real + + * The default domain is complex. Not specifying a domain will lead + to the solving of the equation in the complex domain (and this + is not affected by the assumptions on the symbol): + + >>> x = Symbol('x') + >>> pprint(solveset(exp(x) - 1, x), use_unicode=False) + {2*n*I*pi | n in Integers} + + >>> x = Symbol('x', real=True) + >>> pprint(solveset(exp(x) - 1, x), use_unicode=False) + {2*n*I*pi | n in Integers} + + * If you want to use ``solveset`` to solve the equation in the + real domain, provide a real domain. (Using ``solveset_real`` + does this automatically.) + + >>> R = S.Reals + >>> x = Symbol('x') + >>> solveset(exp(x) - 1, x, R) + {0} + >>> solveset_real(exp(x) - 1, x) + {0} + + The solution is unaffected by assumptions on the symbol: + + >>> p = Symbol('p', positive=True) + >>> pprint(solveset(p**2 - 4)) + {-2, 2} + + When a :class:`~.ConditionSet` is returned, symbols with assumptions that + would alter the set are replaced with more generic symbols: + + >>> i = Symbol('i', imaginary=True) + >>> solveset(Eq(i**2 + i*sin(i), 1), i, domain=S.Reals) + ConditionSet(_R, Eq(_R**2 + _R*sin(_R) - 1, 0), Reals) + + * Inequalities can be solved over the real domain only. Use of a complex + domain leads to a NotImplementedError. + + >>> solveset(exp(x) > 1, x, R) + Interval.open(0, oo) + + """ + f = sympify(f) + symbol = sympify(symbol) + + if f is S.true: + return domain + + if f is S.false: + return S.EmptySet + + if not isinstance(f, (Expr, Relational, Number)): + raise ValueError("%s is not a valid SymPy expression" % f) + + if not isinstance(symbol, (Expr, Relational)) and symbol is not None: + raise ValueError("%s is not a valid SymPy symbol" % (symbol,)) + + if not isinstance(domain, Set): + raise ValueError("%s is not a valid domain" %(domain)) + + free_symbols = f.free_symbols + + if f.has(Piecewise): + f = piecewise_fold(f) + + if symbol is None and not free_symbols: + b = Eq(f, 0) + if b is S.true: + return domain + elif b is S.false: + return S.EmptySet + else: + raise NotImplementedError(filldedent(''' + relationship between value and 0 is unknown: %s''' % b)) + + if symbol is None: + if len(free_symbols) == 1: + symbol = free_symbols.pop() + elif free_symbols: + raise ValueError(filldedent(''' + The independent variable must be specified for a + multivariate equation.''')) + elif not isinstance(symbol, Symbol): + f, s, swap = recast_to_symbols([f], [symbol]) + # the xreplace will be needed if a ConditionSet is returned + return solveset(f[0], s[0], domain).xreplace(swap) + + # solveset should ignore assumptions on symbols + newsym = None + if domain.is_subset(S.Reals): + if symbol._assumptions_orig != {'real': True}: + newsym = Dummy('R', real=True) + elif domain.is_subset(S.Complexes): + if symbol._assumptions_orig != {'complex': True}: + newsym = Dummy('C', complex=True) + + if newsym is not None: + rv = solveset(f.xreplace({symbol: newsym}), newsym, domain) + # try to use the original symbol if possible + try: + _rv = rv.xreplace({newsym: symbol}) + except TypeError: + _rv = rv + if rv.dummy_eq(_rv): + rv = _rv + return rv + + # Abs has its own handling method which avoids the + # rewriting property that the first piece of abs(x) + # is for x >= 0 and the 2nd piece for x < 0 -- solutions + # can look better if the 2nd condition is x <= 0. Since + # the solution is a set, duplication of results is not + # an issue, e.g. {y, -y} when y is 0 will be {0} + f, mask = _masked(f, Abs) + f = f.rewrite(Piecewise) # everything that's not an Abs + for d, e in mask: + # everything *in* an Abs + e = e.func(e.args[0].rewrite(Piecewise)) + f = f.xreplace({d: e}) + f = piecewise_fold(f) + + return _solveset(f, symbol, domain, _check=True) + + +def solveset_real(f, symbol): + return solveset(f, symbol, S.Reals) + + +def solveset_complex(f, symbol): + return solveset(f, symbol, S.Complexes) + + +def _solveset_multi(eqs, syms, domains): + '''Basic implementation of a multivariate solveset. + + For internal use (not ready for public consumption)''' + + rep = {} + for sym, dom in zip(syms, domains): + if dom is S.Reals: + rep[sym] = Symbol(sym.name, real=True) + eqs = [eq.subs(rep) for eq in eqs] + syms = [sym.subs(rep) for sym in syms] + + syms = tuple(syms) + + if len(eqs) == 0: + return ProductSet(*domains) + + if len(syms) == 1: + sym = syms[0] + domain = domains[0] + solsets = [solveset(eq, sym, domain) for eq in eqs] + solset = Intersection(*solsets) + return ImageSet(Lambda((sym,), (sym,)), solset).doit() + + eqs = sorted(eqs, key=lambda eq: len(eq.free_symbols & set(syms))) + + for n, eq in enumerate(eqs): + sols = [] + all_handled = True + for sym in syms: + if sym not in eq.free_symbols: + continue + sol = solveset(eq, sym, domains[syms.index(sym)]) + + if isinstance(sol, FiniteSet): + i = syms.index(sym) + symsp = syms[:i] + syms[i+1:] + domainsp = domains[:i] + domains[i+1:] + eqsp = eqs[:n] + eqs[n+1:] + for s in sol: + eqsp_sub = [eq.subs(sym, s) for eq in eqsp] + sol_others = _solveset_multi(eqsp_sub, symsp, domainsp) + fun = Lambda((symsp,), symsp[:i] + (s,) + symsp[i:]) + sols.append(ImageSet(fun, sol_others).doit()) + else: + all_handled = False + if all_handled: + return Union(*sols) + + +def solvify(f, symbol, domain): + """Solves an equation using solveset and returns the solution in accordance + with the `solve` output API. + + Returns + ======= + + We classify the output based on the type of solution returned by `solveset`. + + Solution | Output + ---------------------------------------- + FiniteSet | list + + ImageSet, | list (if `f` is periodic) + Union | + + Union | list (with FiniteSet) + + EmptySet | empty list + + Others | None + + + Raises + ====== + + NotImplementedError + A ConditionSet is the input. + + Examples + ======== + + >>> from sympy.solvers.solveset import solvify + >>> from sympy.abc import x + >>> from sympy import S, tan, sin, exp + >>> solvify(x**2 - 9, x, S.Reals) + [-3, 3] + >>> solvify(sin(x) - 1, x, S.Reals) + [pi/2] + >>> solvify(tan(x), x, S.Reals) + [0] + >>> solvify(exp(x) - 1, x, S.Complexes) + + >>> solvify(exp(x) - 1, x, S.Reals) + [0] + + """ + solution_set = solveset(f, symbol, domain) + result = None + if solution_set is S.EmptySet: + result = [] + + elif isinstance(solution_set, ConditionSet): + raise NotImplementedError('solveset is unable to solve this equation.') + + elif isinstance(solution_set, FiniteSet): + result = list(solution_set) + + else: + period = periodicity(f, symbol) + if period is not None: + solutions = S.EmptySet + iter_solutions = () + if isinstance(solution_set, ImageSet): + iter_solutions = (solution_set,) + elif isinstance(solution_set, Union): + if all(isinstance(i, ImageSet) for i in solution_set.args): + iter_solutions = solution_set.args + + for solution in iter_solutions: + solutions += solution.intersect(Interval(0, period, False, True)) + + if isinstance(solutions, FiniteSet): + result = list(solutions) + + else: + solution = solution_set.intersect(domain) + if isinstance(solution, Union): + # concerned about only FiniteSet with Union but not about ImageSet + # if required could be extend + if any(isinstance(i, FiniteSet) for i in solution.args): + result = [sol for soln in solution.args \ + for sol in soln.args if isinstance(soln,FiniteSet)] + else: + return None + + elif isinstance(solution, FiniteSet): + result += solution + + return result + + +############################################################################### +################################ LINSOLVE ##################################### +############################################################################### + + +def linear_coeffs(eq, *syms, dict=False): + """Return a list whose elements are the coefficients of the + corresponding symbols in the sum of terms in ``eq``. + The additive constant is returned as the last element of the + list. + + Raises + ====== + + NonlinearError + The equation contains a nonlinear term + ValueError + duplicate or unordered symbols are passed + + Parameters + ========== + + dict - (default False) when True, return coefficients as a + dictionary with coefficients keyed to syms that were present; + key 1 gives the constant term + + Examples + ======== + + >>> from sympy.solvers.solveset import linear_coeffs + >>> from sympy.abc import x, y, z + >>> linear_coeffs(3*x + 2*y - 1, x, y) + [3, 2, -1] + + It is not necessary to expand the expression: + + >>> linear_coeffs(x + y*(z*(x*3 + 2) + 3), x) + [3*y*z + 1, y*(2*z + 3)] + + When nonlinear is detected, an error will be raised: + + * even if they would cancel after expansion (so the + situation does not pass silently past the caller's + attention) + + >>> eq = 1/x*(x - 1) + 1/x + >>> linear_coeffs(eq.expand(), x) + [0, 1] + >>> linear_coeffs(eq, x) + Traceback (most recent call last): + ... + NonlinearError: + nonlinear in given generators + + * when there are cross terms + + >>> linear_coeffs(x*(y + 1), x, y) + Traceback (most recent call last): + ... + NonlinearError: + symbol-dependent cross-terms encountered + + * when there are terms that contain an expression + dependent on the symbols that is not linear + + >>> linear_coeffs(x**2, x) + Traceback (most recent call last): + ... + NonlinearError: + nonlinear in given generators + """ + eq = _sympify(eq) + if len(syms) == 1 and iterable(syms[0]) and not isinstance(syms[0], Basic): + raise ValueError('expecting unpacked symbols, *syms') + symset = set(syms) + if len(symset) != len(syms): + raise ValueError('duplicate symbols given') + try: + d, c = _linear_eq_to_dict([eq], symset) + d = d[0] + c = c[0] + except PolyNonlinearError as err: + raise NonlinearError(str(err)) + if dict: + if c: + d[S.One] = c + return d + rv = [S.Zero]*(len(syms) + 1) + rv[-1] = c + for i, k in enumerate(syms): + if k not in d: + continue + rv[i] = d[k] + return rv + + +def linear_eq_to_matrix(equations, *symbols): + r""" + Converts a given System of Equations into Matrix form. + Here `equations` must be a linear system of equations in + `symbols`. Element ``M[i, j]`` corresponds to the coefficient + of the jth symbol in the ith equation. + + The Matrix form corresponds to the augmented matrix form. + For example: + + .. math:: 4x + 2y + 3z = 1 + .. math:: 3x + y + z = -6 + .. math:: 2x + 4y + 9z = 2 + + This system will return $A$ and $b$ as: + + $$ A = \left[\begin{array}{ccc} + 4 & 2 & 3 \\ + 3 & 1 & 1 \\ + 2 & 4 & 9 + \end{array}\right] \ \ b = \left[\begin{array}{c} + 1 \\ -6 \\ 2 + \end{array}\right] $$ + + The only simplification performed is to convert + ``Eq(a, b)`` $\Rightarrow a - b$. + + Raises + ====== + + NonlinearError + The equations contain a nonlinear term. + ValueError + The symbols are not given or are not unique. + + Examples + ======== + + >>> from sympy import linear_eq_to_matrix, symbols + >>> c, x, y, z = symbols('c, x, y, z') + + The coefficients (numerical or symbolic) of the symbols will + be returned as matrices: + + >>> eqns = [c*x + z - 1 - c, y + z, x - y] + >>> A, b = linear_eq_to_matrix(eqns, [x, y, z]) + >>> A + Matrix([ + [c, 0, 1], + [0, 1, 1], + [1, -1, 0]]) + >>> b + Matrix([ + [c + 1], + [ 0], + [ 0]]) + + This routine does not simplify expressions and will raise an error + if nonlinearity is encountered: + + >>> eqns = [ + ... (x**2 - 3*x)/(x - 3) - 3, + ... y**2 - 3*y - y*(y - 4) + x - 4] + >>> linear_eq_to_matrix(eqns, [x, y]) + Traceback (most recent call last): + ... + NonlinearError: + symbol-dependent term can be ignored using `strict=False` + + Simplifying these equations will discard the removable singularity + in the first and reveal the linear structure of the second: + + >>> [e.simplify() for e in eqns] + [x - 3, x + y - 4] + + Any such simplification needed to eliminate nonlinear terms must + be done *before* calling this routine. + """ + if not symbols: + raise ValueError(filldedent(''' + Symbols must be given, for which coefficients + are to be found. + ''')) + + # Check if 'symbols' is a set and raise an error if it is + if isinstance(symbols[0], set): + raise TypeError( + "Unordered 'set' type is not supported as input for symbols.") + + if hasattr(symbols[0], '__iter__'): + symbols = symbols[0] + + if has_dups(symbols): + raise ValueError('Symbols must be unique') + + equations = sympify(equations) + if isinstance(equations, MatrixBase): + equations = list(equations) + elif isinstance(equations, (Expr, Eq)): + equations = [equations] + elif not is_sequence(equations): + raise ValueError(filldedent(''' + Equation(s) must be given as a sequence, Expr, + Eq or Matrix. + ''')) + + # construct the dictionaries + try: + eq, c = _linear_eq_to_dict(equations, symbols) + except PolyNonlinearError as err: + raise NonlinearError(str(err)) + # prepare output matrices + n, m = shape = len(eq), len(symbols) + ix = dict(zip(symbols, range(m))) + A = zeros(*shape) + for row, d in enumerate(eq): + for k in d: + col = ix[k] + A[row, col] = d[k] + b = Matrix(n, 1, [-i for i in c]) + return A, b + + +def linsolve(system, *symbols): + r""" + Solve system of $N$ linear equations with $M$ variables; both + underdetermined and overdetermined systems are supported. + The possible number of solutions is zero, one or infinite. + Zero solutions throws a ValueError, whereas infinite + solutions are represented parametrically in terms of the given + symbols. For unique solution a :class:`~.FiniteSet` of ordered tuples + is returned. + + All standard input formats are supported: + For the given set of equations, the respective input types + are given below: + + .. math:: 3x + 2y - z = 1 + .. math:: 2x - 2y + 4z = -2 + .. math:: 2x - y + 2z = 0 + + * Augmented matrix form, ``system`` given below: + + $$ \text{system} = \left[{array}{cccc} + 3 & 2 & -1 & 1\\ + 2 & -2 & 4 & -2\\ + 2 & -1 & 2 & 0 + \end{array}\right] $$ + + :: + + system = Matrix([[3, 2, -1, 1], [2, -2, 4, -2], [2, -1, 2, 0]]) + + * List of equations form + + :: + + system = [3x + 2y - z - 1, 2x - 2y + 4z + 2, 2x - y + 2z] + + * Input $A$ and $b$ in matrix form (from $Ax = b$) are given as: + + $$ A = \left[\begin{array}{ccc} + 3 & 2 & -1 \\ + 2 & -2 & 4 \\ + 2 & -1 & 2 + \end{array}\right] \ \ b = \left[\begin{array}{c} + 1 \\ -2 \\ 0 + \end{array}\right] $$ + + :: + + A = Matrix([[3, 2, -1], [2, -2, 4], [2, -1, 2]]) + b = Matrix([[1], [-2], [0]]) + system = (A, b) + + Symbols can always be passed but are actually only needed + when 1) a system of equations is being passed and 2) the + system is passed as an underdetermined matrix and one wants + to control the name of the free variables in the result. + An error is raised if no symbols are used for case 1, but if + no symbols are provided for case 2, internally generated symbols + will be provided. When providing symbols for case 2, there should + be at least as many symbols are there are columns in matrix A. + + The algorithm used here is Gauss-Jordan elimination, which + results, after elimination, in a row echelon form matrix. + + Returns + ======= + + A FiniteSet containing an ordered tuple of values for the + unknowns for which the `system` has a solution. (Wrapping + the tuple in FiniteSet is used to maintain a consistent + output format throughout solveset.) + + Returns EmptySet, if the linear system is inconsistent. + + Raises + ====== + + ValueError + The input is not valid. + The symbols are not given. + + Examples + ======== + + >>> from sympy import Matrix, linsolve, symbols + >>> x, y, z = symbols("x, y, z") + >>> A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 10]]) + >>> b = Matrix([3, 6, 9]) + >>> A + Matrix([ + [1, 2, 3], + [4, 5, 6], + [7, 8, 10]]) + >>> b + Matrix([ + [3], + [6], + [9]]) + >>> linsolve((A, b), [x, y, z]) + {(-1, 2, 0)} + + * Parametric Solution: In case the system is underdetermined, the + function will return a parametric solution in terms of the given + symbols. Those that are free will be returned unchanged. e.g. in + the system below, `z` is returned as the solution for variable z; + it can take on any value. + + >>> A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + >>> b = Matrix([3, 6, 9]) + >>> linsolve((A, b), x, y, z) + {(z - 1, 2 - 2*z, z)} + + If no symbols are given, internally generated symbols will be used. + The ``tau0`` in the third position indicates (as before) that the third + variable -- whatever it is named -- can take on any value: + + >>> linsolve((A, b)) + {(tau0 - 1, 2 - 2*tau0, tau0)} + + * List of equations as input + + >>> Eqns = [3*x + 2*y - z - 1, 2*x - 2*y + 4*z + 2, - x + y/2 - z] + >>> linsolve(Eqns, x, y, z) + {(1, -2, -2)} + + * Augmented matrix as input + + >>> aug = Matrix([[2, 1, 3, 1], [2, 6, 8, 3], [6, 8, 18, 5]]) + >>> aug + Matrix([ + [2, 1, 3, 1], + [2, 6, 8, 3], + [6, 8, 18, 5]]) + >>> linsolve(aug, x, y, z) + {(3/10, 2/5, 0)} + + * Solve for symbolic coefficients + + >>> a, b, c, d, e, f = symbols('a, b, c, d, e, f') + >>> eqns = [a*x + b*y - c, d*x + e*y - f] + >>> linsolve(eqns, x, y) + {((-b*f + c*e)/(a*e - b*d), (a*f - c*d)/(a*e - b*d))} + + * A degenerate system returns solution as set of given + symbols. + + >>> system = Matrix(([0, 0, 0], [0, 0, 0], [0, 0, 0])) + >>> linsolve(system, x, y) + {(x, y)} + + * For an empty system linsolve returns empty set + + >>> linsolve([], x) + EmptySet + + * An error is raised if any nonlinearity is detected, even + if it could be removed with expansion + + >>> linsolve([x*(1/x - 1)], x) + Traceback (most recent call last): + ... + NonlinearError: nonlinear term: 1/x + + >>> linsolve([x*(y + 1)], x, y) + Traceback (most recent call last): + ... + NonlinearError: nonlinear cross-term: x*(y + 1) + + >>> linsolve([x**2 - 1], x) + Traceback (most recent call last): + ... + NonlinearError: nonlinear term: x**2 + """ + if not system: + return S.EmptySet + + # If second argument is an iterable + if symbols and hasattr(symbols[0], '__iter__'): + symbols = symbols[0] + sym_gen = isinstance(symbols, GeneratorType) + dup_msg = 'duplicate symbols given' + + + b = None # if we don't get b the input was bad + # unpack system + + if hasattr(system, '__iter__'): + + # 1). (A, b) + if len(system) == 2 and isinstance(system[0], MatrixBase): + A, b = system + + # 2). (eq1, eq2, ...) + if not isinstance(system[0], MatrixBase): + if sym_gen or not symbols: + raise ValueError(filldedent(''' + When passing a system of equations, the explicit + symbols for which a solution is being sought must + be given as a sequence, too. + ''')) + if len(set(symbols)) != len(symbols): + raise ValueError(dup_msg) + + # + # Pass to the sparse solver implemented in polys. It is important + # that we do not attempt to convert the equations to a matrix + # because that would be very inefficient for large sparse systems + # of equations. + # + eqs = system + eqs = [sympify(eq) for eq in eqs] + try: + sol = _linsolve(eqs, symbols) + except PolyNonlinearError as exc: + # e.g. cos(x) contains an element of the set of generators + raise NonlinearError(str(exc)) + + if sol is None: + return S.EmptySet + + sol = FiniteSet(Tuple(*(sol.get(sym, sym) for sym in symbols))) + return sol + + elif isinstance(system, MatrixBase) and not ( + symbols and not isinstance(symbols, GeneratorType) and + isinstance(symbols[0], MatrixBase)): + # 3). A augmented with b + A, b = system[:, :-1], system[:, -1:] + + if b is None: + raise ValueError("Invalid arguments") + if sym_gen: + symbols = [next(symbols) for i in range(A.cols)] + symset = set(symbols) + if any(symset & (A.free_symbols | b.free_symbols)): + raise ValueError(filldedent(''' + At least one of the symbols provided + already appears in the system to be solved. + One way to avoid this is to use Dummy symbols in + the generator, e.g. numbered_symbols('%s', cls=Dummy) + ''' % symbols[0].name.rstrip('1234567890'))) + elif len(symset) != len(symbols): + raise ValueError(dup_msg) + + if not symbols: + symbols = [Dummy() for _ in range(A.cols)] + name = _uniquely_named_symbol('tau', (A, b), + compare=lambda i: str(i).rstrip('1234567890')).name + gen = numbered_symbols(name) + else: + gen = None + + # This is just a wrapper for solve_lin_sys + eqs = [] + rows = A.tolist() + for rowi, bi in zip(rows, b): + terms = [elem * sym for elem, sym in zip(rowi, symbols) if elem] + terms.append(-bi) + eqs.append(Add(*terms)) + + eqs, ring = sympy_eqs_to_ring(eqs, symbols) + sol = solve_lin_sys(eqs, ring, _raw=False) + if sol is None: + return S.EmptySet + #sol = {sym:val for sym, val in sol.items() if sym != val} + sol = FiniteSet(Tuple(*(sol.get(sym, sym) for sym in symbols))) + + if gen is not None: + solsym = sol.free_symbols + rep = {sym: next(gen) for sym in symbols if sym in solsym} + sol = sol.subs(rep) + + return sol + + +############################################################################## +# ------------------------------nonlinsolve ---------------------------------# +############################################################################## + + +def _return_conditionset(eqs, symbols): + # return conditionset + eqs = (Eq(lhs, 0) for lhs in eqs) + condition_set = ConditionSet( + Tuple(*symbols), And(*eqs), S.Complexes**len(symbols)) + return condition_set + + +def substitution(system, symbols, result=[{}], known_symbols=[], + exclude=[], all_symbols=None): + r""" + Solves the `system` using substitution method. It is used in + :func:`~.nonlinsolve`. This will be called from :func:`~.nonlinsolve` when any + equation(s) is non polynomial equation. + + Parameters + ========== + + system : list of equations + The target system of equations + symbols : list of symbols to be solved. + The variable(s) for which the system is solved + known_symbols : list of solved symbols + Values are known for these variable(s) + result : An empty list or list of dict + If No symbol values is known then empty list otherwise + symbol as keys and corresponding value in dict. + exclude : Set of expression. + Mostly denominator expression(s) of the equations of the system. + Final solution should not satisfy these expressions. + all_symbols : known_symbols + symbols(unsolved). + + Returns + ======= + + A FiniteSet of ordered tuple of values of `all_symbols` for which the + `system` has solution. Order of values in the tuple is same as symbols + present in the parameter `all_symbols`. If parameter `all_symbols` is None + then same as symbols present in the parameter `symbols`. + + Please note that general FiniteSet is unordered, the solution returned + here is not simply a FiniteSet of solutions, rather it is a FiniteSet of + ordered tuple, i.e. the first & only argument to FiniteSet is a tuple of + solutions, which is ordered, & hence the returned solution is ordered. + + Also note that solution could also have been returned as an ordered tuple, + FiniteSet is just a wrapper `{}` around the tuple. It has no other + significance except for the fact it is just used to maintain a consistent + output format throughout the solveset. + + Raises + ====== + + ValueError + The input is not valid. + The symbols are not given. + AttributeError + The input symbols are not :class:`~.Symbol` type. + + Examples + ======== + + >>> from sympy import symbols, substitution + >>> x, y = symbols('x, y', real=True) + >>> substitution([x + y], [x], [{y: 1}], [y], set([]), [x, y]) + {(-1, 1)} + + * When you want a soln not satisfying $x + 1 = 0$ + + >>> substitution([x + y], [x], [{y: 1}], [y], set([x + 1]), [y, x]) + EmptySet + >>> substitution([x + y], [x], [{y: 1}], [y], set([x - 1]), [y, x]) + {(1, -1)} + >>> substitution([x + y - 1, y - x**2 + 5], [x, y]) + {(-3, 4), (2, -1)} + + * Returns both real and complex solution + + >>> x, y, z = symbols('x, y, z') + >>> from sympy import exp, sin + >>> substitution([exp(x) - sin(y), y**2 - 4], [x, y]) + {(ImageSet(Lambda(_n, I*(2*_n*pi + pi) + log(sin(2))), Integers), -2), + (ImageSet(Lambda(_n, 2*_n*I*pi + log(sin(2))), Integers), 2)} + + >>> eqs = [z**2 + exp(2*x) - sin(y), -3 + exp(-y)] + >>> substitution(eqs, [y, z]) + {(-log(3), -sqrt(-exp(2*x) - sin(log(3)))), + (-log(3), sqrt(-exp(2*x) - sin(log(3)))), + (ImageSet(Lambda(_n, 2*_n*I*pi - log(3)), Integers), + ImageSet(Lambda(_n, -sqrt(-exp(2*x) + sin(2*_n*I*pi - log(3)))), Integers)), + (ImageSet(Lambda(_n, 2*_n*I*pi - log(3)), Integers), + ImageSet(Lambda(_n, sqrt(-exp(2*x) + sin(2*_n*I*pi - log(3)))), Integers))} + + """ + + if not system: + return S.EmptySet + + for i, e in enumerate(system): + if isinstance(e, Eq): + system[i] = e.lhs - e.rhs + + if not symbols: + msg = ('Symbols must be given, for which solution of the ' + 'system is to be found.') + raise ValueError(filldedent(msg)) + + if not is_sequence(symbols): + msg = ('symbols should be given as a sequence, e.g. a list.' + 'Not type %s: %s') + raise TypeError(filldedent(msg % (type(symbols), symbols))) + + if not getattr(symbols[0], 'is_Symbol', False): + msg = ('Iterable of symbols must be given as ' + 'second argument, not type %s: %s') + raise ValueError(filldedent(msg % (type(symbols[0]), symbols[0]))) + + # By default `all_symbols` will be same as `symbols` + if all_symbols is None: + all_symbols = symbols + + old_result = result + # storing complements and intersection for particular symbol + complements = {} + intersections = {} + + # when total_solveset_call equals total_conditionset + # it means that solveset failed to solve all eqs. + total_conditionset = -1 + total_solveset_call = -1 + + def _unsolved_syms(eq, sort=False): + """Returns the unsolved symbol present + in the equation `eq`. + """ + free = eq.free_symbols + unsolved = (free - set(known_symbols)) & set(all_symbols) + if sort: + unsolved = list(unsolved) + unsolved.sort(key=default_sort_key) + return unsolved + + # sort such that equation with the fewest potential symbols is first. + # means eq with less number of variable first in the list. + eqs_in_better_order = list( + ordered(system, lambda _: len(_unsolved_syms(_)))) + + def add_intersection_complement(result, intersection_dict, complement_dict): + # If solveset has returned some intersection/complement + # for any symbol, it will be added in the final solution. + final_result = [] + for res in result: + res_copy = res + for key_res, value_res in res.items(): + intersect_set, complement_set = None, None + for key_sym, value_sym in intersection_dict.items(): + if key_sym == key_res: + intersect_set = value_sym + for key_sym, value_sym in complement_dict.items(): + if key_sym == key_res: + complement_set = value_sym + if intersect_set or complement_set: + new_value = FiniteSet(value_res) + if intersect_set and intersect_set != S.Complexes: + new_value = Intersection(new_value, intersect_set) + if complement_set: + new_value = Complement(new_value, complement_set) + if new_value is S.EmptySet: + res_copy = None + break + elif new_value.is_FiniteSet and len(new_value) == 1: + res_copy[key_res] = set(new_value).pop() + else: + res_copy[key_res] = new_value + + if res_copy is not None: + final_result.append(res_copy) + return final_result + + def _extract_main_soln(sym, sol, soln_imageset): + """Separate the Complements, Intersections, ImageSet lambda expr and + its base_set. This function returns the unmasked sol from different classes + of sets and also returns the appended ImageSet elements in a + soln_imageset dict: `{unmasked element: ImageSet}`. + """ + # if there is union, then need to check + # Complement, Intersection, Imageset. + # Order should not be changed. + if isinstance(sol, ConditionSet): + # extracts any solution in ConditionSet + sol = sol.base_set + + if isinstance(sol, Complement): + # extract solution and complement + complements[sym] = sol.args[1] + sol = sol.args[0] + # complement will be added at the end + # using `add_intersection_complement` method + + # if there is union of Imageset or other in soln. + # no testcase is written for this if block + if isinstance(sol, Union): + sol_args = sol.args + sol = S.EmptySet + # We need in sequence so append finteset elements + # and then imageset or other. + for sol_arg2 in sol_args: + if isinstance(sol_arg2, FiniteSet): + sol += sol_arg2 + else: + # ImageSet, Intersection, complement then + # append them directly + sol += FiniteSet(sol_arg2) + + if isinstance(sol, Intersection): + # Interval/Set will be at 0th index always + if sol.args[0] not in (S.Reals, S.Complexes): + # Sometimes solveset returns soln with intersection + # S.Reals or S.Complexes. We don't consider that + # intersection. + intersections[sym] = sol.args[0] + sol = sol.args[1] + # after intersection and complement Imageset should + # be checked. + if isinstance(sol, ImageSet): + soln_imagest = sol + expr2 = sol.lamda.expr + sol = FiniteSet(expr2) + soln_imageset[expr2] = soln_imagest + + if not isinstance(sol, FiniteSet): + sol = FiniteSet(sol) + return sol, soln_imageset + + def _check_exclude(rnew, imgset_yes): + rnew_ = rnew + if imgset_yes: + # replace all dummy variables (Imageset lambda variables) + # with zero before `checksol`. Considering fundamental soln + # for `checksol`. + rnew_copy = rnew.copy() + dummy_n = imgset_yes[0] + for key_res, value_res in rnew_copy.items(): + rnew_copy[key_res] = value_res.subs(dummy_n, 0) + rnew_ = rnew_copy + # satisfy_exclude == true if it satisfies the expr of `exclude` list. + try: + # something like : `Mod(-log(3), 2*I*pi)` can't be + # simplified right now, so `checksol` returns `TypeError`. + # when this issue is fixed this try block should be + # removed. Mod(-log(3), 2*I*pi) == -log(3) + satisfy_exclude = any( + checksol(d, rnew_) for d in exclude) + except TypeError: + satisfy_exclude = None + return satisfy_exclude + + def _restore_imgset(rnew, original_imageset, newresult): + restore_sym = set(rnew.keys()) & \ + set(original_imageset.keys()) + for key_sym in restore_sym: + img = original_imageset[key_sym] + rnew[key_sym] = img + if rnew not in newresult: + newresult.append(rnew) + + def _append_eq(eq, result, res, delete_soln, n=None): + u = Dummy('u') + if n: + eq = eq.subs(n, 0) + satisfy = eq if eq in (True, False) else checksol(u, u, eq, minimal=True) + if satisfy is False: + delete_soln = True + res = {} + else: + result.append(res) + return result, res, delete_soln + + def _append_new_soln(rnew, sym, sol, imgset_yes, soln_imageset, + original_imageset, newresult, eq=None): + """If `rnew` (A dict ) contains valid soln + append it to `newresult` list. + `imgset_yes` is (base, dummy_var) if there was imageset in previously + calculated result(otherwise empty tuple). `original_imageset` is dict + of imageset expr and imageset from this result. + `soln_imageset` dict of imageset expr and imageset of new soln. + """ + satisfy_exclude = _check_exclude(rnew, imgset_yes) + delete_soln = False + # soln should not satisfy expr present in `exclude` list. + if not satisfy_exclude: + local_n = None + # if it is imageset + if imgset_yes: + local_n = imgset_yes[0] + base = imgset_yes[1] + if sym and sol: + # when `sym` and `sol` is `None` means no new + # soln. In that case we will append rnew directly after + # substituting original imagesets in rnew values if present + # (second last line of this function using _restore_imgset) + dummy_list = list(sol.atoms(Dummy)) + # use one dummy `n` which is in + # previous imageset + local_n_list = [ + local_n for i in range( + 0, len(dummy_list))] + + dummy_zip = zip(dummy_list, local_n_list) + lam = Lambda(local_n, sol.subs(dummy_zip)) + rnew[sym] = ImageSet(lam, base) + if eq is not None: + newresult, rnew, delete_soln = _append_eq( + eq, newresult, rnew, delete_soln, local_n) + elif eq is not None: + newresult, rnew, delete_soln = _append_eq( + eq, newresult, rnew, delete_soln) + elif sol in soln_imageset.keys(): + rnew[sym] = soln_imageset[sol] + # restore original imageset + _restore_imgset(rnew, original_imageset, newresult) + else: + newresult.append(rnew) + elif satisfy_exclude: + delete_soln = True + rnew = {} + _restore_imgset(rnew, original_imageset, newresult) + return newresult, delete_soln + + def _new_order_result(result, eq): + # separate first, second priority. `res` that makes `eq` value equals + # to zero, should be used first then other result(second priority). + # If it is not done then we may miss some soln. + first_priority = [] + second_priority = [] + for res in result: + if not any(isinstance(val, ImageSet) for val in res.values()): + if eq.subs(res) == 0: + first_priority.append(res) + else: + second_priority.append(res) + if first_priority or second_priority: + return first_priority + second_priority + return result + + def _solve_using_known_values(result, solver): + """Solves the system using already known solution + (result contains the dict ). + solver is :func:`~.solveset_complex` or :func:`~.solveset_real`. + """ + # stores imageset . + soln_imageset = {} + total_solvest_call = 0 + total_conditionst = 0 + + # sort equations so the one with the fewest potential + # symbols appears first + for index, eq in enumerate(eqs_in_better_order): + newresult = [] + # if imageset, expr is used to solve for other symbol + imgset_yes = False + for res in result: + original_imageset = {} + got_symbol = set() # symbols solved in one iteration + # find the imageset and use its expr. + for k, v in res.items(): + if isinstance(v, ImageSet): + res[k] = v.lamda.expr + original_imageset[k] = v + dummy_n = v.lamda.expr.atoms(Dummy).pop() + (base,) = v.base_sets + imgset_yes = (dummy_n, base) + assert not isinstance(v, FiniteSet) # if so, internal error + # update eq with everything that is known so far + eq2 = eq.subs(res).expand() + if imgset_yes and not eq2.has(imgset_yes[0]): + # The substituted equation simplified in such a way that + # it's no longer necessary to encapsulate a potential new + # solution in an ImageSet. (E.g. at the previous step some + # {n*2*pi} was found as partial solution for one of the + # unknowns, but its main solution expression n*2*pi has now + # been substituted in a trigonometric function.) + imgset_yes = False + + unsolved_syms = _unsolved_syms(eq2, sort=True) + if not unsolved_syms: + if res: + newresult, delete_res = _append_new_soln( + res, None, None, imgset_yes, soln_imageset, + original_imageset, newresult, eq2) + if delete_res: + # `delete_res` is true, means substituting `res` in + # eq2 doesn't return `zero` or deleting the `res` + # (a soln) since it satisfies expr of `exclude` + # list. + result.remove(res) + continue # skip as it's independent of desired symbols + depen1, depen2 = eq2.as_independent(*unsolved_syms) + if (depen1.has(Abs) or depen2.has(Abs)) and solver == solveset_complex: + # Absolute values cannot be inverted in the + # complex domain + continue + soln_imageset = {} + for sym in unsolved_syms: + not_solvable = False + try: + soln = solver(eq2, sym) + total_solvest_call += 1 + soln_new = S.EmptySet + if isinstance(soln, Complement): + # separate solution and complement + complements[sym] = soln.args[1] + soln = soln.args[0] + # complement will be added at the end + if isinstance(soln, Intersection): + # Interval will be at 0th index always + if soln.args[0] != Interval(-oo, oo): + # sometimes solveset returns soln + # with intersection S.Reals, to confirm that + # soln is in domain=S.Reals + intersections[sym] = soln.args[0] + soln_new += soln.args[1] + soln = soln_new if soln_new else soln + if index > 0 and solver == solveset_real: + # one symbol's real soln, another symbol may have + # corresponding complex soln. + if not isinstance(soln, (ImageSet, ConditionSet)): + soln += solveset_complex(eq2, sym) # might give ValueError with Abs + except (NotImplementedError, ValueError): + # If solveset is not able to solve equation `eq2`. Next + # time we may get soln using next equation `eq2` + continue + if isinstance(soln, ConditionSet): + if soln.base_set in (S.Reals, S.Complexes): + soln = S.EmptySet + # don't do `continue` we may get soln + # in terms of other symbol(s) + not_solvable = True + total_conditionst += 1 + else: + soln = soln.base_set + + if soln is not S.EmptySet: + soln, soln_imageset = _extract_main_soln( + sym, soln, soln_imageset) + + for sol in soln: + # sol is not a `Union` since we checked it + # before this loop + sol, soln_imageset = _extract_main_soln( + sym, sol, soln_imageset) + sol = set(sol).pop() # XXX what if there are more solutions? + free = sol.free_symbols + if got_symbol and any( + ss in free for ss in got_symbol + ): + # sol depends on previously solved symbols + # then continue + continue + rnew = res.copy() + # put each solution in res and append the new result + # in the new result list (solution for symbol `s`) + # along with old results. + for k, v in res.items(): + if isinstance(v, Expr) and isinstance(sol, Expr): + # if any unsolved symbol is present + # Then subs known value + rnew[k] = v.subs(sym, sol) + # and add this new solution + if sol in soln_imageset.keys(): + # replace all lambda variables with 0. + imgst = soln_imageset[sol] + rnew[sym] = imgst.lamda( + *[0 for i in range(0, len( + imgst.lamda.variables))]) + else: + rnew[sym] = sol + newresult, delete_res = _append_new_soln( + rnew, sym, sol, imgset_yes, soln_imageset, + original_imageset, newresult) + if delete_res: + # deleting the `res` (a soln) since it satisfies + # eq of `exclude` list + result.remove(res) + # solution got for sym + if not not_solvable: + got_symbol.add(sym) + # next time use this new soln + if newresult: + result = newresult + return result, total_solvest_call, total_conditionst + + new_result_real, solve_call1, cnd_call1 = _solve_using_known_values( + old_result, solveset_real) + new_result_complex, solve_call2, cnd_call2 = _solve_using_known_values( + old_result, solveset_complex) + + # If total_solveset_call is equal to total_conditionset + # then solveset failed to solve all of the equations. + # In this case we return a ConditionSet here. + total_conditionset += (cnd_call1 + cnd_call2) + total_solveset_call += (solve_call1 + solve_call2) + + if total_conditionset == total_solveset_call and total_solveset_call != -1: + return _return_conditionset(eqs_in_better_order, all_symbols) + + # don't keep duplicate solutions + filtered_complex = [] + for i in list(new_result_complex): + for j in list(new_result_real): + if i.keys() != j.keys(): + continue + if all(a.dummy_eq(b) for a, b in zip(i.values(), j.values()) \ + if not (isinstance(a, int) and isinstance(b, int))): + break + else: + filtered_complex.append(i) + # overall result + result = new_result_real + filtered_complex + + result_all_variables = [] + result_infinite = [] + for res in result: + if not res: + # means {None : None} + continue + # If length < len(all_symbols) means infinite soln. + # Some or all the soln is dependent on 1 symbol. + # eg. {x: y+2} then final soln {x: y+2, y: y} + if len(res) < len(all_symbols): + solved_symbols = res.keys() + unsolved = list(filter( + lambda x: x not in solved_symbols, all_symbols)) + for unsolved_sym in unsolved: + res[unsolved_sym] = unsolved_sym + result_infinite.append(res) + if res not in result_all_variables: + result_all_variables.append(res) + + if result_infinite: + # we have general soln + # eg : [{x: -1, y : 1}, {x : -y, y: y}] then + # return [{x : -y, y : y}] + result_all_variables = result_infinite + if intersections or complements: + result_all_variables = add_intersection_complement( + result_all_variables, intersections, complements) + + # convert to ordered tuple + result = S.EmptySet + for r in result_all_variables: + temp = [r[symb] for symb in all_symbols] + result += FiniteSet(tuple(temp)) + return result + + +def _solveset_work(system, symbols): + soln = solveset(system[0], symbols[0]) + if isinstance(soln, FiniteSet): + _soln = FiniteSet(*[(s,) for s in soln]) + return _soln + else: + return FiniteSet(tuple(FiniteSet(soln))) + + +def _handle_positive_dimensional(polys, symbols, denominators): + from sympy.polys.polytools import groebner + # substitution method where new system is groebner basis of the system + _symbols = list(symbols) + _symbols.sort(key=default_sort_key) + basis = groebner(polys, _symbols, polys=True) + new_system = [] + for poly_eq in basis: + new_system.append(poly_eq.as_expr()) + result = [{}] + result = substitution( + new_system, symbols, result, [], + denominators) + return result + + +def _handle_zero_dimensional(polys, symbols, system): + # solve 0 dimensional poly system using `solve_poly_system` + result = solve_poly_system(polys, *symbols) + # May be some extra soln is added because + # we used `unrad` in `_separate_poly_nonpoly`, so + # need to check and remove if it is not a soln. + result_update = S.EmptySet + for res in result: + dict_sym_value = dict(list(zip(symbols, res))) + if all(checksol(eq, dict_sym_value) for eq in system): + result_update += FiniteSet(res) + return result_update + + +def _separate_poly_nonpoly(system, symbols): + polys = [] + polys_expr = [] + nonpolys = [] + # unrad_changed stores a list of expressions containing + # radicals that were processed using unrad + # this is useful if solutions need to be checked later. + unrad_changed = [] + denominators = set() + poly = None + for eq in system: + # Store denom expressions that contain symbols + denominators.update(_simple_dens(eq, symbols)) + # Convert equality to expression + if isinstance(eq, Eq): + eq = eq.lhs - eq.rhs + # try to remove sqrt and rational power + without_radicals = unrad(simplify(eq), *symbols) + if without_radicals: + unrad_changed.append(eq) + eq_unrad, cov = without_radicals + if not cov: + eq = eq_unrad + if isinstance(eq, Expr): + eq = eq.as_numer_denom()[0] + poly = eq.as_poly(*symbols, extension=True) + elif simplify(eq).is_number: + continue + if poly is not None: + polys.append(poly) + polys_expr.append(poly.as_expr()) + else: + nonpolys.append(eq) + return polys, polys_expr, nonpolys, denominators, unrad_changed + + +def _handle_poly(polys, symbols): + # _handle_poly(polys, symbols) -> (poly_sol, poly_eqs) + # + # We will return possible solution information to nonlinsolve as well as a + # new system of polynomial equations to be solved if we cannot solve + # everything directly here. The new system of polynomial equations will be + # a lex-order Groebner basis for the original system. The lex basis + # hopefully separate some of the variables and equations and give something + # easier for substitution to work with. + + # The format for representing solution sets in nonlinsolve and substitution + # is a list of dicts. These are the special cases: + no_information = [{}] # No equations solved yet + no_solutions = [] # The system is inconsistent and has no solutions. + + # If there is no need to attempt further solution of these equations then + # we return no equations: + no_equations = [] + + inexact = any(not p.domain.is_Exact for p in polys) + if inexact: + # The use of Groebner over RR is likely to result incorrectly in an + # inconsistent Groebner basis. So, convert any float coefficients to + # Rational before computing the Groebner basis. + polys = [poly(nsimplify(p, rational=True)) for p in polys] + + # Compute a Groebner basis in grevlex order wrt the ordering given. We will + # try to convert this to lex order later. Usually it seems to be more + # efficient to compute a lex order basis by computing a grevlex basis and + # converting to lex with fglm. + basis = groebner(polys, symbols, order='grevlex', polys=False) + + # + # No solutions (inconsistent equations)? + # + if 1 in basis: + + # No solutions: + poly_sol = no_solutions + poly_eqs = no_equations + + # + # Finite number of solutions (zero-dimensional case) + # + elif basis.is_zero_dimensional: + + # Convert Groebner basis to lex ordering + basis = basis.fglm('lex') + + # Convert polynomial coefficients back to float before calling + # solve_poly_system + if inexact: + basis = [nfloat(p) for p in basis] + + # Solve the zero-dimensional case using solve_poly_system if possible. + # If some polynomials have factors that cannot be solved in radicals + # then this will fail. Using solve_poly_system(..., strict=True) + # ensures that we either get a complete solution set in radicals or + # UnsolvableFactorError will be raised. + try: + result = solve_poly_system(basis, *symbols, strict=True) + except UnsolvableFactorError: + # Failure... not fully solvable in radicals. Return the lex-order + # basis for substitution to handle. + poly_sol = no_information + poly_eqs = list(basis) + else: + # Success! We have a finite solution set and solve_poly_system has + # succeeded in finding all solutions. Return the solutions and also + # an empty list of remaining equations to be solved. + poly_sol = [dict(zip(symbols, res)) for res in result] + poly_eqs = no_equations + + # + # Infinite families of solutions (positive-dimensional case) + # + else: + # In this case the grevlex basis cannot be converted to lex using the + # fglm method and also solve_poly_system cannot solve the equations. We + # would like to return a lex basis but since we can't use fglm we + # compute the lex basis directly here. The time required to recompute + # the basis is generally significantly less than the time required by + # substitution to solve the new system. + poly_sol = no_information + poly_eqs = list(groebner(polys, symbols, order='lex', polys=False)) + + if inexact: + poly_eqs = [nfloat(p) for p in poly_eqs] + + return poly_sol, poly_eqs + + +def nonlinsolve(system, *symbols): + r""" + Solve system of $N$ nonlinear equations with $M$ variables, which means both + under and overdetermined systems are supported. Positive dimensional + system is also supported (A system with infinitely many solutions is said + to be positive-dimensional). In a positive dimensional system the solution will + be dependent on at least one symbol. Returns both real solution + and complex solution (if they exist). + + Parameters + ========== + + system : list of equations + The target system of equations + symbols : list of Symbols + symbols should be given as a sequence eg. list + + Returns + ======= + + A :class:`~.FiniteSet` of ordered tuple of values of `symbols` for which the `system` + has solution. Order of values in the tuple is same as symbols present in + the parameter `symbols`. + + Please note that general :class:`~.FiniteSet` is unordered, the solution + returned here is not simply a :class:`~.FiniteSet` of solutions, rather it + is a :class:`~.FiniteSet` of ordered tuple, i.e. the first and only + argument to :class:`~.FiniteSet` is a tuple of solutions, which is + ordered, and, hence ,the returned solution is ordered. + + Also note that solution could also have been returned as an ordered tuple, + FiniteSet is just a wrapper ``{}`` around the tuple. It has no other + significance except for the fact it is just used to maintain a consistent + output format throughout the solveset. + + For the given set of equations, the respective input types + are given below: + + .. math:: xy - 1 = 0 + .. math:: 4x^2 + y^2 - 5 = 0 + + :: + + system = [x*y - 1, 4*x**2 + y**2 - 5] + symbols = [x, y] + + Raises + ====== + + ValueError + The input is not valid. + The symbols are not given. + AttributeError + The input symbols are not `Symbol` type. + + Examples + ======== + + >>> from sympy import symbols, nonlinsolve + >>> x, y, z = symbols('x, y, z', real=True) + >>> nonlinsolve([x*y - 1, 4*x**2 + y**2 - 5], [x, y]) + {(-1, -1), (-1/2, -2), (1/2, 2), (1, 1)} + + 1. Positive dimensional system and complements: + + >>> from sympy import pprint + >>> from sympy.polys.polytools import is_zero_dimensional + >>> a, b, c, d = symbols('a, b, c, d', extended_real=True) + >>> eq1 = a + b + c + d + >>> eq2 = a*b + b*c + c*d + d*a + >>> eq3 = a*b*c + b*c*d + c*d*a + d*a*b + >>> eq4 = a*b*c*d - 1 + >>> system = [eq1, eq2, eq3, eq4] + >>> is_zero_dimensional(system) + False + >>> pprint(nonlinsolve(system, [a, b, c, d]), use_unicode=False) + -1 1 1 -1 + {(---, -d, -, {d} \ {0}), (-, -d, ---, {d} \ {0})} + d d d d + >>> nonlinsolve([(x+y)**2 - 4, x + y - 2], [x, y]) + {(2 - y, y)} + + 2. If some of the equations are non-polynomial then `nonlinsolve` + will call the ``substitution`` function and return real and complex solutions, + if present. + + >>> from sympy import exp, sin + >>> nonlinsolve([exp(x) - sin(y), y**2 - 4], [x, y]) + {(ImageSet(Lambda(_n, I*(2*_n*pi + pi) + log(sin(2))), Integers), -2), + (ImageSet(Lambda(_n, 2*_n*I*pi + log(sin(2))), Integers), 2)} + + 3. If system is non-linear polynomial and zero-dimensional then it + returns both solution (real and complex solutions, if present) using + :func:`~.solve_poly_system`: + + >>> from sympy import sqrt + >>> nonlinsolve([x**2 - 2*y**2 -2, x*y - 2], [x, y]) + {(-2, -1), (2, 1), (-sqrt(2)*I, sqrt(2)*I), (sqrt(2)*I, -sqrt(2)*I)} + + 4. ``nonlinsolve`` can solve some linear (zero or positive dimensional) + system (because it uses the :func:`sympy.polys.polytools.groebner` function to get the + groebner basis and then uses the ``substitution`` function basis as the + new `system`). But it is not recommended to solve linear system using + ``nonlinsolve``, because :func:`~.linsolve` is better for general linear systems. + + >>> nonlinsolve([x + 2*y -z - 3, x - y - 4*z + 9, y + z - 4], [x, y, z]) + {(3*z - 5, 4 - z, z)} + + 5. System having polynomial equations and only real solution is + solved using :func:`~.solve_poly_system`: + + >>> e1 = sqrt(x**2 + y**2) - 10 + >>> e2 = sqrt(y**2 + (-x + 10)**2) - 3 + >>> nonlinsolve((e1, e2), (x, y)) + {(191/20, -3*sqrt(391)/20), (191/20, 3*sqrt(391)/20)} + >>> nonlinsolve([x**2 + 2/y - 2, x + y - 3], [x, y]) + {(1, 2), (1 - sqrt(5), 2 + sqrt(5)), (1 + sqrt(5), 2 - sqrt(5))} + >>> nonlinsolve([x**2 + 2/y - 2, x + y - 3], [y, x]) + {(2, 1), (2 - sqrt(5), 1 + sqrt(5)), (2 + sqrt(5), 1 - sqrt(5))} + + 6. It is better to use symbols instead of trigonometric functions or + :class:`~.Function`. For example, replace $\sin(x)$ with a symbol, replace + $f(x)$ with a symbol and so on. Get a solution from ``nonlinsolve`` and then + use :func:`~.solveset` to get the value of $x$. + + How nonlinsolve is better than old solver ``_solve_system`` : + ============================================================= + + 1. A positive dimensional system solver: nonlinsolve can return + solution for positive dimensional system. It finds the + Groebner Basis of the positive dimensional system(calling it as + basis) then we can start solving equation(having least number of + variable first in the basis) using solveset and substituting that + solved solutions into other equation(of basis) to get solution in + terms of minimum variables. Here the important thing is how we + are substituting the known values and in which equations. + + 2. Real and complex solutions: nonlinsolve returns both real + and complex solution. If all the equations in the system are polynomial + then using :func:`~.solve_poly_system` both real and complex solution is returned. + If all the equations in the system are not polynomial equation then goes to + ``substitution`` method with this polynomial and non polynomial equation(s), + to solve for unsolved variables. Here to solve for particular variable + solveset_real and solveset_complex is used. For both real and complex + solution ``_solve_using_known_values`` is used inside ``substitution`` + (``substitution`` will be called when any non-polynomial equation is present). + If a solution is valid its general solution is added to the final result. + + 3. :class:`~.Complement` and :class:`~.Intersection` will be added: + nonlinsolve maintains dict for complements and intersections. If solveset + find complements or/and intersections with any interval or set during the + execution of ``substitution`` function, then complement or/and + intersection for that variable is added before returning final solution. + + """ + if not system: + return S.EmptySet + + if not symbols: + msg = ('Symbols must be given, for which solution of the ' + 'system is to be found.') + raise ValueError(filldedent(msg)) + + if hasattr(symbols[0], '__iter__'): + symbols = symbols[0] + + if not is_sequence(symbols) or not symbols: + msg = ('Symbols must be given, for which solution of the ' + 'system is to be found.') + raise IndexError(filldedent(msg)) + + symbols = list(map(_sympify, symbols)) + system, symbols, swap = recast_to_symbols(system, symbols) + if swap: + soln = nonlinsolve(system, symbols) + return FiniteSet(*[tuple(i.xreplace(swap) for i in s) for s in soln]) + + if len(system) == 1 and len(symbols) == 1: + return _solveset_work(system, symbols) + + # main code of def nonlinsolve() starts from here + + polys, polys_expr, nonpolys, denominators, unrad_changed = \ + _separate_poly_nonpoly(system, symbols) + + poly_eqs = [] + poly_sol = [{}] + + if polys: + poly_sol, poly_eqs = _handle_poly(polys, symbols) + if poly_sol and poly_sol[0]: + poly_syms = set().union(*(eq.free_symbols for eq in polys)) + unrad_syms = set().union(*(eq.free_symbols for eq in unrad_changed)) + if unrad_syms == poly_syms and unrad_changed: + # if all the symbols have been solved by _handle_poly + # and unrad has been used then check solutions + poly_sol = [sol for sol in poly_sol if checksol(unrad_changed, sol)] + + # Collect together the unsolved polynomials with the non-polynomial + # equations. + remaining = poly_eqs + nonpolys + + # to_tuple converts a solution dictionary to a tuple containing the + # value for each symbol + to_tuple = lambda sol: tuple(sol[s] for s in symbols) + + if not remaining: + # If there is nothing left to solve then return the solution from + # solve_poly_system directly. + return FiniteSet(*map(to_tuple, poly_sol)) + else: + # Here we handle: + # + # 1. The Groebner basis if solve_poly_system failed. + # 2. The Groebner basis in the positive-dimensional case. + # 3. Any non-polynomial equations + # + # If solve_poly_system did succeed then we pass those solutions in as + # preliminary results. + subs_res = substitution(remaining, symbols, result=poly_sol, exclude=denominators) + + if not isinstance(subs_res, FiniteSet): + return subs_res + + # check solutions produced by substitution. Currently, checking is done for + # only those solutions which have non-Set variable values. + if unrad_changed: + result = [dict(zip(symbols, sol)) for sol in subs_res.args] + correct_sols = [sol for sol in result if any(isinstance(v, Set) for v in sol) + or checksol(unrad_changed, sol) != False] + return FiniteSet(*map(to_tuple, correct_sols)) + else: + return subs_res diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/__init__.py b/lib/python3.12/site-packages/sympy/solvers/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8a718c8947327428a1da5038579711c02b29afc Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_constantsimp.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_constantsimp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06fcf4f691486e3e0a68d71c13709cf7da08b916 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_constantsimp.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_decompogen.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_decompogen.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e0f86bc118a5c4f965b4383013249f76c3ff752e Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_decompogen.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_inequalities.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_inequalities.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..31c6ce5fe82ccfefbcf53fd8fbcf068c0cf6f7c0 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_inequalities.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_numeric.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_numeric.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0f23bea33b36b5cb2f469e6efaaa842322541307 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_numeric.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_pde.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_pde.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55a35e7117c4f3f2f835ed23af44d5f0b1330c65 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_pde.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_polysys.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_polysys.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75f7e22e2781023003b44a1eddd1806dbee42249 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_polysys.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_recurr.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_recurr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..477016d38c1fb3fe120eed78a896d68d13ef7a37 Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_recurr.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_simplex.cpython-312.pyc b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_simplex.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d57889a85044fd9dff061a4c3380a624bb2831c Binary files /dev/null and b/lib/python3.12/site-packages/sympy/solvers/tests/__pycache__/test_simplex.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/test_constantsimp.py b/lib/python3.12/site-packages/sympy/solvers/tests/test_constantsimp.py new file mode 100644 index 0000000000000000000000000000000000000000..efb966a4c8c2f93558d05e7c330f06530e69180c --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/tests/test_constantsimp.py @@ -0,0 +1,179 @@ +""" +If the arbitrary constant class from issue 4435 is ever implemented, this +should serve as a set of test cases. +""" + +from sympy.core.function import Function +from sympy.core.numbers import I +from sympy.core.power import Pow +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import Symbol +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.hyperbolic import (cosh, sinh) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (acos, cos, sin) +from sympy.integrals.integrals import Integral +from sympy.solvers.ode.ode import constantsimp, constant_renumber +from sympy.testing.pytest import XFAIL + + +x = Symbol('x') +y = Symbol('y') +z = Symbol('z') +u2 = Symbol('u2') +_a = Symbol('_a') +C1 = Symbol('C1') +C2 = Symbol('C2') +C3 = Symbol('C3') +f = Function('f') + + +def test_constant_mul(): + # We want C1 (Constant) below to absorb the y's, but not the x's + assert constant_renumber(constantsimp(y*C1, [C1])) == C1*y + assert constant_renumber(constantsimp(C1*y, [C1])) == C1*y + assert constant_renumber(constantsimp(x*C1, [C1])) == x*C1 + assert constant_renumber(constantsimp(C1*x, [C1])) == x*C1 + assert constant_renumber(constantsimp(2*C1, [C1])) == C1 + assert constant_renumber(constantsimp(C1*2, [C1])) == C1 + assert constant_renumber(constantsimp(y*C1*x, [C1, y])) == C1*x + assert constant_renumber(constantsimp(x*y*C1, [C1, y])) == x*C1 + assert constant_renumber(constantsimp(y*x*C1, [C1, y])) == x*C1 + assert constant_renumber(constantsimp(C1*x*y, [C1, y])) == C1*x + assert constant_renumber(constantsimp(x*C1*y, [C1, y])) == x*C1 + assert constant_renumber(constantsimp(C1*y*(y + 1), [C1])) == C1*y*(y+1) + assert constant_renumber(constantsimp(y*C1*(y + 1), [C1])) == C1*y*(y+1) + assert constant_renumber(constantsimp(x*(y*C1), [C1])) == x*y*C1 + assert constant_renumber(constantsimp(x*(C1*y), [C1])) == x*y*C1 + assert constant_renumber(constantsimp(C1*(x*y), [C1, y])) == C1*x + assert constant_renumber(constantsimp((x*y)*C1, [C1, y])) == x*C1 + assert constant_renumber(constantsimp((y*x)*C1, [C1, y])) == x*C1 + assert constant_renumber(constantsimp(y*(y + 1)*C1, [C1, y])) == C1 + assert constant_renumber(constantsimp((C1*x)*y, [C1, y])) == C1*x + assert constant_renumber(constantsimp(y*(x*C1), [C1, y])) == x*C1 + assert constant_renumber(constantsimp((x*C1)*y, [C1, y])) == x*C1 + assert constant_renumber(constantsimp(C1*x*y*x*y*2, [C1, y])) == C1*x**2 + assert constant_renumber(constantsimp(C1*x*y*z, [C1, y, z])) == C1*x + assert constant_renumber(constantsimp(C1*x*y**2*sin(z), [C1, y, z])) == C1*x + assert constant_renumber(constantsimp(C1*C1, [C1])) == C1 + assert constant_renumber(constantsimp(C1*C2, [C1, C2])) == C1 + assert constant_renumber(constantsimp(C2*C2, [C1, C2])) == C1 + assert constant_renumber(constantsimp(C1*C1*C2, [C1, C2])) == C1 + assert constant_renumber(constantsimp(C1*x*2**x, [C1])) == C1*x*2**x + +def test_constant_add(): + assert constant_renumber(constantsimp(C1 + C1, [C1])) == C1 + assert constant_renumber(constantsimp(C1 + 2, [C1])) == C1 + assert constant_renumber(constantsimp(2 + C1, [C1])) == C1 + assert constant_renumber(constantsimp(C1 + y, [C1, y])) == C1 + assert constant_renumber(constantsimp(C1 + x, [C1])) == C1 + x + assert constant_renumber(constantsimp(C1 + C1, [C1])) == C1 + assert constant_renumber(constantsimp(C1 + C2, [C1, C2])) == C1 + assert constant_renumber(constantsimp(C2 + C1, [C1, C2])) == C1 + assert constant_renumber(constantsimp(C1 + C2 + C1, [C1, C2])) == C1 + + +def test_constant_power_as_base(): + assert constant_renumber(constantsimp(C1**C1, [C1])) == C1 + assert constant_renumber(constantsimp(Pow(C1, C1), [C1])) == C1 + assert constant_renumber(constantsimp(C1**C1, [C1])) == C1 + assert constant_renumber(constantsimp(C1**C2, [C1, C2])) == C1 + assert constant_renumber(constantsimp(C2**C1, [C1, C2])) == C1 + assert constant_renumber(constantsimp(C2**C2, [C1, C2])) == C1 + assert constant_renumber(constantsimp(C1**y, [C1, y])) == C1 + assert constant_renumber(constantsimp(C1**x, [C1])) == C1**x + assert constant_renumber(constantsimp(C1**2, [C1])) == C1 + assert constant_renumber( + constantsimp(C1**(x*y), [C1])) == C1**(x*y) + + +def test_constant_power_as_exp(): + assert constant_renumber(constantsimp(x**C1, [C1])) == x**C1 + assert constant_renumber(constantsimp(y**C1, [C1, y])) == C1 + assert constant_renumber(constantsimp(x**y**C1, [C1, y])) == x**C1 + assert constant_renumber( + constantsimp((x**y)**C1, [C1])) == (x**y)**C1 + assert constant_renumber( + constantsimp(x**(y**C1), [C1, y])) == x**C1 + assert constant_renumber(constantsimp(x**C1**y, [C1, y])) == x**C1 + assert constant_renumber( + constantsimp(x**(C1**y), [C1, y])) == x**C1 + assert constant_renumber( + constantsimp((x**C1)**y, [C1])) == (x**C1)**y + assert constant_renumber(constantsimp(2**C1, [C1])) == C1 + assert constant_renumber(constantsimp(S(2)**C1, [C1])) == C1 + assert constant_renumber(constantsimp(exp(C1), [C1])) == C1 + assert constant_renumber( + constantsimp(exp(C1 + x), [C1])) == C1*exp(x) + assert constant_renumber(constantsimp(Pow(2, C1), [C1])) == C1 + + +def test_constant_function(): + assert constant_renumber(constantsimp(sin(C1), [C1])) == C1 + assert constant_renumber(constantsimp(f(C1), [C1])) == C1 + assert constant_renumber(constantsimp(f(C1, C1), [C1])) == C1 + assert constant_renumber(constantsimp(f(C1, C2), [C1, C2])) == C1 + assert constant_renumber(constantsimp(f(C2, C1), [C1, C2])) == C1 + assert constant_renumber(constantsimp(f(C2, C2), [C1, C2])) == C1 + assert constant_renumber( + constantsimp(f(C1, x), [C1])) == f(C1, x) + assert constant_renumber(constantsimp(f(C1, y), [C1, y])) == C1 + assert constant_renumber(constantsimp(f(y, C1), [C1, y])) == C1 + assert constant_renumber(constantsimp(f(C1, y, C2), [C1, C2, y])) == C1 + + +def test_constant_function_multiple(): + # The rules to not renumber in this case would be too complicated, and + # dsolve is not likely to ever encounter anything remotely like this. + assert constant_renumber( + constantsimp(f(C1, C1, x), [C1])) == f(C1, C1, x) + + +def test_constant_multiple(): + assert constant_renumber(constantsimp(C1*2 + 2, [C1])) == C1 + assert constant_renumber(constantsimp(x*2/C1, [C1])) == C1*x + assert constant_renumber(constantsimp(C1**2*2 + 2, [C1])) == C1 + assert constant_renumber( + constantsimp(sin(2*C1) + x + sqrt(2), [C1])) == C1 + x + assert constant_renumber(constantsimp(2*C1 + C2, [C1, C2])) == C1 + +def test_constant_repeated(): + assert C1 + C1*x == constant_renumber( C1 + C1*x) + +def test_ode_solutions(): + # only a few examples here, the rest will be tested in the actual dsolve tests + assert constant_renumber(constantsimp(C1*exp(2*x) + exp(x)*(C2 + C3), [C1, C2, C3])) == \ + constant_renumber(C1*exp(x) + C2*exp(2*x)) + assert constant_renumber( + constantsimp(Eq(f(x), I*C1*sinh(x/3) + C2*cosh(x/3)), [C1, C2]) + ) == constant_renumber(Eq(f(x), C1*sinh(x/3) + C2*cosh(x/3))) + assert constant_renumber(constantsimp(Eq(f(x), acos((-C1)/cos(x))), [C1])) == \ + Eq(f(x), acos(C1/cos(x))) + assert constant_renumber( + constantsimp(Eq(log(f(x)/C1) + 2*exp(x/f(x)), 0), [C1]) + ) == Eq(log(C1*f(x)) + 2*exp(x/f(x)), 0) + assert constant_renumber(constantsimp(Eq(log(x*sqrt(2)*sqrt(1/x)*sqrt(f(x)) + /C1) + x**2/(2*f(x)**2), 0), [C1])) == \ + Eq(log(C1*sqrt(x)*sqrt(f(x))) + x**2/(2*f(x)**2), 0) + assert constant_renumber(constantsimp(Eq(-exp(-f(x)/x)*sin(f(x)/x)/2 + log(x/C1) - + cos(f(x)/x)*exp(-f(x)/x)/2, 0), [C1])) == \ + Eq(-exp(-f(x)/x)*sin(f(x)/x)/2 + log(C1*x) - cos(f(x)/x)* + exp(-f(x)/x)/2, 0) + assert constant_renumber(constantsimp(Eq(-Integral(-1/(sqrt(1 - u2**2)*u2), + (u2, _a, x/f(x))) + log(f(x)/C1), 0), [C1])) == \ + Eq(-Integral(-1/(u2*sqrt(1 - u2**2)), (u2, _a, x/f(x))) + + log(C1*f(x)), 0) + assert [constantsimp(i, [C1]) for i in [Eq(f(x), sqrt(-C1*x + x**2)), Eq(f(x), -sqrt(-C1*x + x**2))]] == \ + [Eq(f(x), sqrt(x*(C1 + x))), Eq(f(x), -sqrt(x*(C1 + x)))] + + +@XFAIL +def test_nonlocal_simplification(): + assert constantsimp(C1 + C2+x*C2, [C1, C2]) == C1 + C2*x + + +def test_constant_Eq(): + # C1 on the rhs is well-tested, but the lhs is only tested here + assert constantsimp(Eq(C1, 3 + f(x)*x), [C1]) == Eq(x*f(x), C1) + assert constantsimp(Eq(C1, 3 * f(x)*x), [C1]) == Eq(f(x)*x, C1) diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/test_decompogen.py b/lib/python3.12/site-packages/sympy/solvers/tests/test_decompogen.py new file mode 100644 index 0000000000000000000000000000000000000000..1ba03f4b42558231b626b6ed169f8b0a81a72bf9 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/tests/test_decompogen.py @@ -0,0 +1,59 @@ +from sympy.solvers.decompogen import decompogen, compogen +from sympy.core.symbol import symbols +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.exponential import exp +from sympy.functions.elementary.miscellaneous import sqrt, Max +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.testing.pytest import XFAIL, raises + +x, y = symbols('x y') + + +def test_decompogen(): + assert decompogen(sin(cos(x)), x) == [sin(x), cos(x)] + assert decompogen(sin(x)**2 + sin(x) + 1, x) == [x**2 + x + 1, sin(x)] + assert decompogen(sqrt(6*x**2 - 5), x) == [sqrt(x), 6*x**2 - 5] + assert decompogen(sin(sqrt(cos(x**2 + 1))), x) == [sin(x), sqrt(x), cos(x), x**2 + 1] + assert decompogen(Abs(cos(x)**2 + 3*cos(x) - 4), x) == [Abs(x), x**2 + 3*x - 4, cos(x)] + assert decompogen(sin(x)**2 + sin(x) - sqrt(3)/2, x) == [x**2 + x - sqrt(3)/2, sin(x)] + assert decompogen(Abs(cos(y)**2 + 3*cos(x) - 4), x) == [Abs(x), 3*x + cos(y)**2 - 4, cos(x)] + assert decompogen(x, y) == [x] + assert decompogen(1, x) == [1] + assert decompogen(Max(3, x), x) == [Max(3, x)] + raises(TypeError, lambda: decompogen(x < 5, x)) + u = 2*x + 3 + assert decompogen(Max(sqrt(u),(u)**2), x) == [Max(sqrt(x), x**2), u] + assert decompogen(Max(u, u**2, y), x) == [Max(x, x**2, y), u] + assert decompogen(Max(sin(x), u), x) == [Max(2*x + 3, sin(x))] + + +def test_decompogen_poly(): + assert decompogen(x**4 + 2*x**2 + 1, x) == [x**2 + 2*x + 1, x**2] + assert decompogen(x**4 + 2*x**3 - x - 1, x) == [x**2 - x - 1, x**2 + x] + + +@XFAIL +def test_decompogen_fails(): + A = lambda x: x**2 + 2*x + 3 + B = lambda x: 4*x**2 + 5*x + 6 + assert decompogen(A(x*exp(x)), x) == [x**2 + 2*x + 3, x*exp(x)] + assert decompogen(A(B(x)), x) == [x**2 + 2*x + 3, 4*x**2 + 5*x + 6] + assert decompogen(A(1/x + 1/x**2), x) == [x**2 + 2*x + 3, 1/x + 1/x**2] + assert decompogen(A(1/x + 2/(x + 1)), x) == [x**2 + 2*x + 3, 1/x + 2/(x + 1)] + + +def test_compogen(): + assert compogen([sin(x), cos(x)], x) == sin(cos(x)) + assert compogen([x**2 + x + 1, sin(x)], x) == sin(x)**2 + sin(x) + 1 + assert compogen([sqrt(x), 6*x**2 - 5], x) == sqrt(6*x**2 - 5) + assert compogen([sin(x), sqrt(x), cos(x), x**2 + 1], x) == sin(sqrt( + cos(x**2 + 1))) + assert compogen([Abs(x), x**2 + 3*x - 4, cos(x)], x) == Abs(cos(x)**2 + + 3*cos(x) - 4) + assert compogen([x**2 + x - sqrt(3)/2, sin(x)], x) == (sin(x)**2 + sin(x) - + sqrt(3)/2) + assert compogen([Abs(x), 3*x + cos(y)**2 - 4, cos(x)], x) == \ + Abs(3*cos(x) + cos(y)**2 - 4) + assert compogen([x**2 + 2*x + 1, x**2], x) == x**4 + 2*x**2 + 1 + # the result is in unsimplified form + assert compogen([x**2 - x - 1, x**2 + x], x) == -x**2 - x + (x**2 + x)**2 - 1 diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/test_inequalities.py b/lib/python3.12/site-packages/sympy/solvers/tests/test_inequalities.py new file mode 100644 index 0000000000000000000000000000000000000000..6ce6f4520b52d8714102c95457c90d44543c685c --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/tests/test_inequalities.py @@ -0,0 +1,500 @@ +"""Tests for tools for solving inequalities and systems of inequalities. """ + +from sympy.concrete.summations import Sum +from sympy.core.function import Function +from sympy.core.numbers import I, Rational, oo, pi +from sympy.core.relational import Eq, Ge, Gt, Le, Lt, Ne +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol) +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.exponential import exp, log +from sympy.functions.elementary.miscellaneous import root, sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import cos, sin, tan +from sympy.integrals.integrals import Integral +from sympy.logic.boolalg import And, Or +from sympy.polys.polytools import Poly, PurePoly +from sympy.sets.sets import FiniteSet, Interval, Union +from sympy.solvers.inequalities import (reduce_inequalities, + solve_poly_inequality as psolve, + reduce_rational_inequalities, + solve_univariate_inequality as isolve, + reduce_abs_inequality, + _solve_inequality) +from sympy.polys.rootoftools import rootof +from sympy.solvers.solvers import solve +from sympy.solvers.solveset import solveset +from sympy.core.mod import Mod +from sympy.abc import x, y + +from sympy.testing.pytest import raises, XFAIL + + +inf = oo.evalf() + + +def test_solve_poly_inequality(): + assert psolve(Poly(0, x), '==') == [S.Reals] + assert psolve(Poly(1, x), '==') == [S.EmptySet] + assert psolve(PurePoly(x + 1, x), ">") == [Interval(-1, oo, True, False)] + + +def test_reduce_poly_inequalities_real_interval(): + assert reduce_rational_inequalities( + [[Eq(x**2, 0)]], x, relational=False) == FiniteSet(0) + assert reduce_rational_inequalities( + [[Le(x**2, 0)]], x, relational=False) == FiniteSet(0) + assert reduce_rational_inequalities( + [[Lt(x**2, 0)]], x, relational=False) == S.EmptySet + assert reduce_rational_inequalities( + [[Ge(x**2, 0)]], x, relational=False) == \ + S.Reals if x.is_real else Interval(-oo, oo) + assert reduce_rational_inequalities( + [[Gt(x**2, 0)]], x, relational=False) == \ + FiniteSet(0).complement(S.Reals) + assert reduce_rational_inequalities( + [[Ne(x**2, 0)]], x, relational=False) == \ + FiniteSet(0).complement(S.Reals) + + assert reduce_rational_inequalities( + [[Eq(x**2, 1)]], x, relational=False) == FiniteSet(-1, 1) + assert reduce_rational_inequalities( + [[Le(x**2, 1)]], x, relational=False) == Interval(-1, 1) + assert reduce_rational_inequalities( + [[Lt(x**2, 1)]], x, relational=False) == Interval(-1, 1, True, True) + assert reduce_rational_inequalities( + [[Ge(x**2, 1)]], x, relational=False) == \ + Union(Interval(-oo, -1), Interval(1, oo)) + assert reduce_rational_inequalities( + [[Gt(x**2, 1)]], x, relational=False) == \ + Interval(-1, 1).complement(S.Reals) + assert reduce_rational_inequalities( + [[Ne(x**2, 1)]], x, relational=False) == \ + FiniteSet(-1, 1).complement(S.Reals) + assert reduce_rational_inequalities([[Eq( + x**2, 1.0)]], x, relational=False) == FiniteSet(-1.0, 1.0).evalf() + assert reduce_rational_inequalities( + [[Le(x**2, 1.0)]], x, relational=False) == Interval(-1.0, 1.0) + assert reduce_rational_inequalities([[Lt( + x**2, 1.0)]], x, relational=False) == Interval(-1.0, 1.0, True, True) + assert reduce_rational_inequalities( + [[Ge(x**2, 1.0)]], x, relational=False) == \ + Union(Interval(-inf, -1.0), Interval(1.0, inf)) + assert reduce_rational_inequalities( + [[Gt(x**2, 1.0)]], x, relational=False) == \ + Union(Interval(-inf, -1.0, right_open=True), + Interval(1.0, inf, left_open=True)) + assert reduce_rational_inequalities([[Ne( + x**2, 1.0)]], x, relational=False) == \ + FiniteSet(-1.0, 1.0).complement(S.Reals) + + s = sqrt(2) + + assert reduce_rational_inequalities([[Lt( + x**2 - 1, 0), Gt(x**2 - 1, 0)]], x, relational=False) == S.EmptySet + assert reduce_rational_inequalities([[Le(x**2 - 1, 0), Ge( + x**2 - 1, 0)]], x, relational=False) == FiniteSet(-1, 1) + assert reduce_rational_inequalities( + [[Le(x**2 - 2, 0), Ge(x**2 - 1, 0)]], x, relational=False + ) == Union(Interval(-s, -1, False, False), Interval(1, s, False, False)) + assert reduce_rational_inequalities( + [[Le(x**2 - 2, 0), Gt(x**2 - 1, 0)]], x, relational=False + ) == Union(Interval(-s, -1, False, True), Interval(1, s, True, False)) + assert reduce_rational_inequalities( + [[Lt(x**2 - 2, 0), Ge(x**2 - 1, 0)]], x, relational=False + ) == Union(Interval(-s, -1, True, False), Interval(1, s, False, True)) + assert reduce_rational_inequalities( + [[Lt(x**2 - 2, 0), Gt(x**2 - 1, 0)]], x, relational=False + ) == Union(Interval(-s, -1, True, True), Interval(1, s, True, True)) + assert reduce_rational_inequalities( + [[Lt(x**2 - 2, 0), Ne(x**2 - 1, 0)]], x, relational=False + ) == Union(Interval(-s, -1, True, True), Interval(-1, 1, True, True), + Interval(1, s, True, True)) + + assert reduce_rational_inequalities([[Lt(x**2, -1.)]], x) is S.false + + +def test_reduce_poly_inequalities_complex_relational(): + assert reduce_rational_inequalities( + [[Eq(x**2, 0)]], x, relational=True) == Eq(x, 0) + assert reduce_rational_inequalities( + [[Le(x**2, 0)]], x, relational=True) == Eq(x, 0) + assert reduce_rational_inequalities( + [[Lt(x**2, 0)]], x, relational=True) == False + assert reduce_rational_inequalities( + [[Ge(x**2, 0)]], x, relational=True) == And(Lt(-oo, x), Lt(x, oo)) + assert reduce_rational_inequalities( + [[Gt(x**2, 0)]], x, relational=True) == \ + And(Gt(x, -oo), Lt(x, oo), Ne(x, 0)) + assert reduce_rational_inequalities( + [[Ne(x**2, 0)]], x, relational=True) == \ + And(Gt(x, -oo), Lt(x, oo), Ne(x, 0)) + + for one in (S.One, S(1.0)): + inf = one*oo + assert reduce_rational_inequalities( + [[Eq(x**2, one)]], x, relational=True) == \ + Or(Eq(x, -one), Eq(x, one)) + assert reduce_rational_inequalities( + [[Le(x**2, one)]], x, relational=True) == \ + And(And(Le(-one, x), Le(x, one))) + assert reduce_rational_inequalities( + [[Lt(x**2, one)]], x, relational=True) == \ + And(And(Lt(-one, x), Lt(x, one))) + assert reduce_rational_inequalities( + [[Ge(x**2, one)]], x, relational=True) == \ + And(Or(And(Le(one, x), Lt(x, inf)), And(Le(x, -one), Lt(-inf, x)))) + assert reduce_rational_inequalities( + [[Gt(x**2, one)]], x, relational=True) == \ + And(Or(And(Lt(-inf, x), Lt(x, -one)), And(Lt(one, x), Lt(x, inf)))) + assert reduce_rational_inequalities( + [[Ne(x**2, one)]], x, relational=True) == \ + Or(And(Lt(-inf, x), Lt(x, -one)), + And(Lt(-one, x), Lt(x, one)), + And(Lt(one, x), Lt(x, inf))) + + +def test_reduce_rational_inequalities_real_relational(): + assert reduce_rational_inequalities([], x) == False + assert reduce_rational_inequalities( + [[(x**2 + 3*x + 2)/(x**2 - 16) >= 0]], x, relational=False) == \ + Union(Interval.open(-oo, -4), Interval(-2, -1), Interval.open(4, oo)) + + assert reduce_rational_inequalities( + [[((-2*x - 10)*(3 - x))/((x**2 + 5)*(x - 2)**2) < 0]], x, + relational=False) == \ + Union(Interval.open(-5, 2), Interval.open(2, 3)) + + assert reduce_rational_inequalities([[(x + 1)/(x - 5) <= 0]], x, + relational=False) == \ + Interval.Ropen(-1, 5) + + assert reduce_rational_inequalities([[(x**2 + 4*x + 3)/(x - 1) > 0]], x, + relational=False) == \ + Union(Interval.open(-3, -1), Interval.open(1, oo)) + + assert reduce_rational_inequalities([[(x**2 - 16)/(x - 1)**2 < 0]], x, + relational=False) == \ + Union(Interval.open(-4, 1), Interval.open(1, 4)) + + assert reduce_rational_inequalities([[(3*x + 1)/(x + 4) >= 1]], x, + relational=False) == \ + Union(Interval.open(-oo, -4), Interval.Ropen(Rational(3, 2), oo)) + + assert reduce_rational_inequalities([[(x - 8)/x <= 3 - x]], x, + relational=False) == \ + Union(Interval.Lopen(-oo, -2), Interval.Lopen(0, 4)) + + # issue sympy/sympy#10237 + assert reduce_rational_inequalities( + [[x < oo, x >= 0, -oo < x]], x, relational=False) == Interval(0, oo) + + +def test_reduce_abs_inequalities(): + e = abs(x - 5) < 3 + ans = And(Lt(2, x), Lt(x, 8)) + assert reduce_inequalities(e) == ans + assert reduce_inequalities(e, x) == ans + assert reduce_inequalities(abs(x - 5)) == Eq(x, 5) + assert reduce_inequalities( + abs(2*x + 3) >= 8) == Or(And(Le(Rational(5, 2), x), Lt(x, oo)), + And(Le(x, Rational(-11, 2)), Lt(-oo, x))) + assert reduce_inequalities(abs(x - 4) + abs( + 3*x - 5) < 7) == And(Lt(S.Half, x), Lt(x, 4)) + assert reduce_inequalities(abs(x - 4) + abs(3*abs(x) - 5) < 7) == \ + Or(And(S(-2) < x, x < -1), And(S.Half < x, x < 4)) + + nr = Symbol('nr', extended_real=False) + raises(TypeError, lambda: reduce_inequalities(abs(nr - 5) < 3)) + assert reduce_inequalities(x < 3, symbols=[x, nr]) == And(-oo < x, x < 3) + + +def test_reduce_inequalities_general(): + assert reduce_inequalities(Ge(sqrt(2)*x, 1)) == And(sqrt(2)/2 <= x, x < oo) + assert reduce_inequalities(x + 1 > 0) == And(S.NegativeOne < x, x < oo) + + +def test_reduce_inequalities_boolean(): + assert reduce_inequalities( + [Eq(x**2, 0), True]) == Eq(x, 0) + assert reduce_inequalities([Eq(x**2, 0), False]) == False + assert reduce_inequalities(x**2 >= 0) is S.true # issue 10196 + + +def test_reduce_inequalities_multivariate(): + assert reduce_inequalities([Ge(x**2, 1), Ge(y**2, 1)]) == And( + Or(And(Le(S.One, x), Lt(x, oo)), And(Le(x, -1), Lt(-oo, x))), + Or(And(Le(S.One, y), Lt(y, oo)), And(Le(y, -1), Lt(-oo, y)))) + + +def test_reduce_inequalities_errors(): + raises(NotImplementedError, lambda: reduce_inequalities(Ge(sin(x) + x, 1))) + raises(NotImplementedError, lambda: reduce_inequalities(Ge(x**2*y + y, 1))) + + +def test__solve_inequalities(): + assert reduce_inequalities(x + y < 1, symbols=[x]) == (x < 1 - y) + assert reduce_inequalities(x + y >= 1, symbols=[x]) == (x < oo) & (x >= -y + 1) + assert reduce_inequalities(Eq(0, x - y), symbols=[x]) == Eq(x, y) + assert reduce_inequalities(Ne(0, x - y), symbols=[x]) == Ne(x, y) + + +def test_issue_6343(): + eq = -3*x**2/2 - x*Rational(45, 4) + Rational(33, 2) > 0 + assert reduce_inequalities(eq) == \ + And(x < Rational(-15, 4) + sqrt(401)/4, -sqrt(401)/4 - Rational(15, 4) < x) + + +def test_issue_8235(): + assert reduce_inequalities(x**2 - 1 < 0) == \ + And(S.NegativeOne < x, x < 1) + assert reduce_inequalities(x**2 - 1 <= 0) == \ + And(S.NegativeOne <= x, x <= 1) + assert reduce_inequalities(x**2 - 1 > 0) == \ + Or(And(-oo < x, x < -1), And(x < oo, S.One < x)) + assert reduce_inequalities(x**2 - 1 >= 0) == \ + Or(And(-oo < x, x <= -1), And(S.One <= x, x < oo)) + + eq = x**8 + x - 9 # we want CRootOf solns here + sol = solve(eq >= 0) + tru = Or(And(rootof(eq, 1) <= x, x < oo), And(-oo < x, x <= rootof(eq, 0))) + assert sol == tru + + # recast vanilla as real + assert solve(sqrt((-x + 1)**2) < 1) == And(S.Zero < x, x < 2) + + +def test_issue_5526(): + assert reduce_inequalities(0 <= + x + Integral(y**2, (y, 1, 3)) - 1, [x]) == \ + (x >= -Integral(y**2, (y, 1, 3)) + 1) + f = Function('f') + e = Sum(f(x), (x, 1, 3)) + assert reduce_inequalities(0 <= x + e + y**2, [x]) == \ + (x >= -y**2 - Sum(f(x), (x, 1, 3))) + + +def test_solve_univariate_inequality(): + assert isolve(x**2 >= 4, x, relational=False) == Union(Interval(-oo, -2), + Interval(2, oo)) + assert isolve(x**2 >= 4, x) == Or(And(Le(2, x), Lt(x, oo)), And(Le(x, -2), + Lt(-oo, x))) + assert isolve((x - 1)*(x - 2)*(x - 3) >= 0, x, relational=False) == \ + Union(Interval(1, 2), Interval(3, oo)) + assert isolve((x - 1)*(x - 2)*(x - 3) >= 0, x) == \ + Or(And(Le(1, x), Le(x, 2)), And(Le(3, x), Lt(x, oo))) + assert isolve((x - 1)*(x - 2)*(x - 4) < 0, x, domain = FiniteSet(0, 3)) == \ + Or(Eq(x, 0), Eq(x, 3)) + # issue 2785: + assert isolve(x**3 - 2*x - 1 > 0, x, relational=False) == \ + Union(Interval(-1, -sqrt(5)/2 + S.Half, True, True), + Interval(S.Half + sqrt(5)/2, oo, True, True)) + # issue 2794: + assert isolve(x**3 - x**2 + x - 1 > 0, x, relational=False) == \ + Interval(1, oo, True) + #issue 13105 + assert isolve((x + I)*(x + 2*I) < 0, x) == Eq(x, 0) + assert isolve(((x - 1)*(x - 2) + I)*((x - 1)*(x - 2) + 2*I) < 0, x) == Or(Eq(x, 1), Eq(x, 2)) + assert isolve((((x - 1)*(x - 2) + I)*((x - 1)*(x - 2) + 2*I))/(x - 2) > 0, x) == Eq(x, 1) + raises (ValueError, lambda: isolve((x**2 - 3*x*I + 2)/x < 0, x)) + + # numerical testing in valid() is needed + assert isolve(x**7 - x - 2 > 0, x) == \ + And(rootof(x**7 - x - 2, 0) < x, x < oo) + + # handle numerator and denominator; although these would be handled as + # rational inequalities, these test confirm that the right thing is done + # when the domain is EX (e.g. when 2 is replaced with sqrt(2)) + assert isolve(1/(x - 2) > 0, x) == And(S(2) < x, x < oo) + den = ((x - 1)*(x - 2)).expand() + assert isolve((x - 1)/den <= 0, x) == \ + (x > -oo) & (x < 2) & Ne(x, 1) + + n = Dummy('n') + raises(NotImplementedError, lambda: isolve(Abs(x) <= n, x, relational=False)) + c1 = Dummy("c1", positive=True) + raises(NotImplementedError, lambda: isolve(n/c1 < 0, c1)) + n = Dummy('n', negative=True) + assert isolve(n/c1 > -2, c1) == (-n/2 < c1) + assert isolve(n/c1 < 0, c1) == True + assert isolve(n/c1 > 0, c1) == False + + zero = cos(1)**2 + sin(1)**2 - 1 + raises(NotImplementedError, lambda: isolve(x**2 < zero, x)) + raises(NotImplementedError, lambda: isolve( + x**2 < zero*I, x)) + raises(NotImplementedError, lambda: isolve(1/(x - y) < 2, x)) + raises(NotImplementedError, lambda: isolve(1/(x - y) < 0, x)) + raises(TypeError, lambda: isolve(x - I < 0, x)) + + zero = x**2 + x - x*(x + 1) + assert isolve(zero < 0, x, relational=False) is S.EmptySet + assert isolve(zero <= 0, x, relational=False) is S.Reals + + # make sure iter_solutions gets a default value + raises(NotImplementedError, lambda: isolve( + Eq(cos(x)**2 + sin(x)**2, 1), x)) + + +def test_trig_inequalities(): + # all the inequalities are solved in a periodic interval. + assert isolve(sin(x) < S.Half, x, relational=False) == \ + Union(Interval(0, pi/6, False, True), Interval.open(pi*Rational(5, 6), 2*pi)) + assert isolve(sin(x) > S.Half, x, relational=False) == \ + Interval(pi/6, pi*Rational(5, 6), True, True) + assert isolve(cos(x) < S.Zero, x, relational=False) == \ + Interval(pi/2, pi*Rational(3, 2), True, True) + assert isolve(cos(x) >= S.Zero, x, relational=False) == \ + Union(Interval(0, pi/2), Interval.Ropen(pi*Rational(3, 2), 2*pi)) + + assert isolve(tan(x) < S.One, x, relational=False) == \ + Union(Interval.Ropen(0, pi/4), Interval.open(pi/2, pi)) + + assert isolve(sin(x) <= S.Zero, x, relational=False) == \ + Union(FiniteSet(S.Zero), Interval.Ropen(pi, 2*pi)) + + assert isolve(sin(x) <= S.One, x, relational=False) == S.Reals + assert isolve(cos(x) < S(-2), x, relational=False) == S.EmptySet + assert isolve(sin(x) >= S.NegativeOne, x, relational=False) == S.Reals + assert isolve(cos(x) > S.One, x, relational=False) == S.EmptySet + + +def test_issue_9954(): + assert isolve(x**2 >= 0, x, relational=False) == S.Reals + assert isolve(x**2 >= 0, x, relational=True) == S.Reals.as_relational(x) + assert isolve(x**2 < 0, x, relational=False) == S.EmptySet + assert isolve(x**2 < 0, x, relational=True) == S.EmptySet.as_relational(x) + + +@XFAIL +def test_slow_general_univariate(): + r = rootof(x**5 - x**2 + 1, 0) + assert solve(sqrt(x) + 1/root(x, 3) > 1) == \ + Or(And(0 < x, x < r**6), And(r**6 < x, x < oo)) + + +def test_issue_8545(): + eq = 1 - x - abs(1 - x) + ans = And(Lt(1, x), Lt(x, oo)) + assert reduce_abs_inequality(eq, '<', x) == ans + eq = 1 - x - sqrt((1 - x)**2) + assert reduce_inequalities(eq < 0) == ans + + +def test_issue_8974(): + assert isolve(-oo < x, x) == And(-oo < x, x < oo) + assert isolve(oo > x, x) == And(-oo < x, x < oo) + + +def test_issue_10198(): + assert reduce_inequalities( + -1 + 1/abs(1/x - 1) < 0) == (x > -oo) & (x < S(1)/2) & Ne(x, 0) + + assert reduce_inequalities(abs(1/sqrt(x)) - 1, x) == Eq(x, 1) + assert reduce_abs_inequality(-3 + 1/abs(1 - 1/x), '<', x) == \ + Or(And(-oo < x, x < 0), + And(S.Zero < x, x < Rational(3, 4)), And(Rational(3, 2) < x, x < oo)) + raises(ValueError,lambda: reduce_abs_inequality(-3 + 1/abs( + 1 - 1/sqrt(x)), '<', x)) + + +def test_issue_10047(): + # issue 10047: this must remain an inequality, not True, since if x + # is not real the inequality is invalid + # assert solve(sin(x) < 2) == (x <= oo) + + # with PR 16956, (x <= oo) autoevaluates when x is extended_real + # which is assumed in the current implementation of inequality solvers + assert solve(sin(x) < 2) == True + assert solveset(sin(x) < 2, domain=S.Reals) == S.Reals + + +def test_issue_10268(): + assert solve(log(x) < 1000) == And(S.Zero < x, x < exp(1000)) + + +@XFAIL +def test_isolve_Sets(): + n = Dummy('n') + assert isolve(Abs(x) <= n, x, relational=False) == \ + Piecewise((S.EmptySet, n < 0), (Interval(-n, n), True)) + + +def test_integer_domain_relational_isolve(): + + dom = FiniteSet(0, 3) + x = Symbol('x',zero=False) + assert isolve((x - 1)*(x - 2)*(x - 4) < 0, x, domain=dom) == Eq(x, 3) + + x = Symbol('x') + assert isolve(x + 2 < 0, x, domain=S.Integers) == \ + (x <= -3) & (x > -oo) & Eq(Mod(x, 1), 0) + assert isolve(2 * x + 3 > 0, x, domain=S.Integers) == \ + (x >= -1) & (x < oo) & Eq(Mod(x, 1), 0) + assert isolve((x ** 2 + 3 * x - 2) < 0, x, domain=S.Integers) == \ + (x >= -3) & (x <= 0) & Eq(Mod(x, 1), 0) + assert isolve((x ** 2 + 3 * x - 2) > 0, x, domain=S.Integers) == \ + ((x >= 1) & (x < oo) & Eq(Mod(x, 1), 0)) | ( + (x <= -4) & (x > -oo) & Eq(Mod(x, 1), 0)) + + +def test_issue_10671_12466(): + assert solveset(sin(y), y, Interval(0, pi)) == FiniteSet(0, pi) + i = Interval(1, 10) + assert solveset((1/x).diff(x) < 0, x, i) == i + assert solveset((log(x - 6)/x) <= 0, x, S.Reals) == \ + Interval.Lopen(6, 7) + + +def test__solve_inequality(): + for op in (Gt, Lt, Le, Ge, Eq, Ne): + assert _solve_inequality(op(x, 1), x).lhs == x + assert _solve_inequality(op(S.One, x), x).lhs == x + # don't get tricked by symbol on right: solve it + assert _solve_inequality(Eq(2*x - 1, x), x) == Eq(x, 1) + ie = Eq(S.One, y) + assert _solve_inequality(ie, x) == ie + for fx in (x**2, exp(x), sin(x) + cos(x), x*(1 + x)): + for c in (0, 1): + e = 2*fx - c > 0 + assert _solve_inequality(e, x, linear=True) == ( + fx > c/S(2)) + assert _solve_inequality(2*x**2 + 2*x - 1 < 0, x, linear=True) == ( + x*(x + 1) < S.Half) + assert _solve_inequality(Eq(x*y, 1), x) == Eq(x*y, 1) + nz = Symbol('nz', nonzero=True) + assert _solve_inequality(Eq(x*nz, 1), x) == Eq(x, 1/nz) + assert _solve_inequality(x*nz < 1, x) == (x*nz < 1) + a = Symbol('a', positive=True) + assert _solve_inequality(a/x > 1, x) == (S.Zero < x) & (x < a) + assert _solve_inequality(a/x > 1, x, linear=True) == (1/x > 1/a) + # make sure to include conditions under which solution is valid + e = Eq(1 - x, x*(1/x - 1)) + assert _solve_inequality(e, x) == Ne(x, 0) + assert _solve_inequality(x < x*(1/x - 1), x) == (x < S.Half) & Ne(x, 0) + + +def test__pt(): + from sympy.solvers.inequalities import _pt + assert _pt(-oo, oo) == 0 + assert _pt(S.One, S(3)) == 2 + assert _pt(S.One, oo) == _pt(oo, S.One) == 2 + assert _pt(S.One, -oo) == _pt(-oo, S.One) == S.Half + assert _pt(S.NegativeOne, oo) == _pt(oo, S.NegativeOne) == Rational(-1, 2) + assert _pt(S.NegativeOne, -oo) == _pt(-oo, S.NegativeOne) == -2 + assert _pt(x, oo) == _pt(oo, x) == x + 1 + assert _pt(x, -oo) == _pt(-oo, x) == x - 1 + raises(ValueError, lambda: _pt(Dummy('i', infinite=True), S.One)) + + +def test_issue_25697(): + assert _solve_inequality(log(x, 3) <= 2, x) == (x <= 9) & (S.Zero < x) + + +def test_issue_25738(): + assert reduce_inequalities(3 < abs(x) + ) == reduce_inequalities(pi < abs(x)).subs(pi, 3) + + +def test_issue_25983(): + assert(reduce_inequalities(pi/Abs(x) <= 1) == ((pi <= x) & (x < oo)) | ((-oo < x) & (x <= -pi))) diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/test_numeric.py b/lib/python3.12/site-packages/sympy/solvers/tests/test_numeric.py new file mode 100644 index 0000000000000000000000000000000000000000..f40bab6965233b82984148960a62ed57a7ddb178 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/tests/test_numeric.py @@ -0,0 +1,139 @@ +from sympy.core.function import nfloat +from sympy.core.numbers import (Float, I, Rational, pi) +from sympy.core.relational import Eq +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import sin +from sympy.integrals.integrals import Integral +from sympy.matrices.dense import Matrix +from mpmath import mnorm, mpf +from sympy.solvers import nsolve +from sympy.utilities.lambdify import lambdify +from sympy.testing.pytest import raises, XFAIL +from sympy.utilities.decorator import conserve_mpmath_dps + +@XFAIL +def test_nsolve_fail(): + x = symbols('x') + # Sometimes it is better to use the numerator (issue 4829) + # but sometimes it is not (issue 11768) so leave this to + # the discretion of the user + ans = nsolve(x**2/(1 - x)/(1 - 2*x)**2 - 100, x, 0) + assert ans > 0.46 and ans < 0.47 + + +def test_nsolve_denominator(): + x = symbols('x') + # Test that nsolve uses the full expression (numerator and denominator). + ans = nsolve((x**2 + 3*x + 2)/(x + 2), -2.1) + # The root -2 was divided out, so make sure we don't find it. + assert ans == -1.0 + +def test_nsolve(): + # onedimensional + x = Symbol('x') + assert nsolve(sin(x), 2) - pi.evalf() < 1e-15 + assert nsolve(Eq(2*x, 2), x, -10) == nsolve(2*x - 2, -10) + # Testing checks on number of inputs + raises(TypeError, lambda: nsolve(Eq(2*x, 2))) + raises(TypeError, lambda: nsolve(Eq(2*x, 2), x, 1, 2)) + # multidimensional + x1 = Symbol('x1') + x2 = Symbol('x2') + f1 = 3 * x1**2 - 2 * x2**2 - 1 + f2 = x1**2 - 2 * x1 + x2**2 + 2 * x2 - 8 + f = Matrix((f1, f2)).T + F = lambdify((x1, x2), f.T, modules='mpmath') + for x0 in [(-1, 1), (1, -2), (4, 4), (-4, -4)]: + x = nsolve(f, (x1, x2), x0, tol=1.e-8) + assert mnorm(F(*x), 1) <= 1.e-10 + # The Chinese mathematician Zhu Shijie was the very first to solve this + # nonlinear system 700 years ago (z was added to make it 3-dimensional) + x = Symbol('x') + y = Symbol('y') + z = Symbol('z') + f1 = -x + 2*y + f2 = (x**2 + x*(y**2 - 2) - 4*y) / (x + 4) + f3 = sqrt(x**2 + y**2)*z + f = Matrix((f1, f2, f3)).T + F = lambdify((x, y, z), f.T, modules='mpmath') + + def getroot(x0): + root = nsolve(f, (x, y, z), x0) + assert mnorm(F(*root), 1) <= 1.e-8 + return root + assert list(map(round, getroot((1, 1, 1)))) == [2, 1, 0] + assert nsolve([Eq( + f1, 0), Eq(f2, 0), Eq(f3, 0)], [x, y, z], (1, 1, 1)) # just see that it works + a = Symbol('a') + assert abs(nsolve(1/(0.001 + a)**3 - 6/(0.9 - a)**3, a, 0.3) - + mpf('0.31883011387318591')) < 1e-15 + + +def test_issue_6408(): + x = Symbol('x') + assert nsolve(Piecewise((x, x < 1), (x**2, True)), x, 2) == 0.0 + + +def test_issue_6408_integral(): + x, y = symbols('x y') + assert nsolve(Integral(x*y, (x, 0, 5)), y, 2) == 0.0 + + +@conserve_mpmath_dps +def test_increased_dps(): + # Issue 8564 + import mpmath + mpmath.mp.dps = 128 + x = Symbol('x') + e1 = x**2 - pi + q = nsolve(e1, x, 3.0) + + assert abs(sqrt(pi).evalf(128) - q) < 1e-128 + +def test_nsolve_precision(): + x, y = symbols('x y') + sol = nsolve(x**2 - pi, x, 3, prec=128) + assert abs(sqrt(pi).evalf(128) - sol) < 1e-128 + assert isinstance(sol, Float) + + sols = nsolve((y**2 - x, x**2 - pi), (x, y), (3, 3), prec=128) + assert isinstance(sols, Matrix) + assert sols.shape == (2, 1) + assert abs(sqrt(pi).evalf(128) - sols[0]) < 1e-128 + assert abs(sqrt(sqrt(pi)).evalf(128) - sols[1]) < 1e-128 + assert all(isinstance(i, Float) for i in sols) + +def test_nsolve_complex(): + x, y = symbols('x y') + + assert nsolve(x**2 + 2, 1j) == sqrt(2.)*I + assert nsolve(x**2 + 2, I) == sqrt(2.)*I + + assert nsolve([x**2 + 2, y**2 + 2], [x, y], [I, I]) == Matrix([sqrt(2.)*I, sqrt(2.)*I]) + assert nsolve([x**2 + 2, y**2 + 2], [x, y], [I, I]) == Matrix([sqrt(2.)*I, sqrt(2.)*I]) + +def test_nsolve_dict_kwarg(): + x, y = symbols('x y') + # one variable + assert nsolve(x**2 - 2, 1, dict = True) == \ + [{x: sqrt(2.)}] + # one variable with complex solution + assert nsolve(x**2 + 2, I, dict = True) == \ + [{x: sqrt(2.)*I}] + # two variables + assert nsolve([x**2 + y**2 - 5, x**2 - y**2 + 1], [x, y], [1, 1], dict = True) == \ + [{x: sqrt(2.), y: sqrt(3.)}] + +def test_nsolve_rational(): + x = symbols('x') + assert nsolve(x - Rational(1, 3), 0, prec=100) == Rational(1, 3).evalf(100) + + +def test_issue_14950(): + x = Matrix(symbols('t s')) + x0 = Matrix([17, 23]) + eqn = x + x0 + assert nsolve(eqn, x, x0) == nfloat(-x0) + assert nsolve(eqn.T, x.T, x0.T) == nfloat(-x0) diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/test_pde.py b/lib/python3.12/site-packages/sympy/solvers/tests/test_pde.py new file mode 100644 index 0000000000000000000000000000000000000000..948d90c7be21a9e0e03753e723ef04f1fb08a5d6 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/tests/test_pde.py @@ -0,0 +1,239 @@ +from sympy.core.function import (Derivative as D, Function) +from sympy.core.relational import Eq +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.elementary.exponential import (exp, log) +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.core import S +from sympy.solvers.pde import (pde_separate, pde_separate_add, pde_separate_mul, + pdsolve, classify_pde, checkpdesol) +from sympy.testing.pytest import raises + + +a, b, c, x, y = symbols('a b c x y') + +def test_pde_separate_add(): + x, y, z, t = symbols("x,y,z,t") + F, T, X, Y, Z, u = map(Function, 'FTXYZu') + + eq = Eq(D(u(x, t), x), D(u(x, t), t)*exp(u(x, t))) + res = pde_separate_add(eq, u(x, t), [X(x), T(t)]) + assert res == [D(X(x), x)*exp(-X(x)), D(T(t), t)*exp(T(t))] + + +def test_pde_separate(): + x, y, z, t = symbols("x,y,z,t") + F, T, X, Y, Z, u = map(Function, 'FTXYZu') + + eq = Eq(D(u(x, t), x), D(u(x, t), t)*exp(u(x, t))) + raises(ValueError, lambda: pde_separate(eq, u(x, t), [X(x), T(t)], 'div')) + + +def test_pde_separate_mul(): + x, y, z, t = symbols("x,y,z,t") + c = Symbol("C", real=True) + Phi = Function('Phi') + F, R, T, X, Y, Z, u = map(Function, 'FRTXYZu') + r, theta, z = symbols('r,theta,z') + + # Something simple :) + eq = Eq(D(F(x, y, z), x) + D(F(x, y, z), y) + D(F(x, y, z), z), 0) + + # Duplicate arguments in functions + raises( + ValueError, lambda: pde_separate_mul(eq, F(x, y, z), [X(x), u(z, z)])) + # Wrong number of arguments + raises(ValueError, lambda: pde_separate_mul(eq, F(x, y, z), [X(x), Y(y)])) + # Wrong variables: [x, y] -> [x, z] + raises( + ValueError, lambda: pde_separate_mul(eq, F(x, y, z), [X(t), Y(x, y)])) + + assert pde_separate_mul(eq, F(x, y, z), [Y(y), u(x, z)]) == \ + [D(Y(y), y)/Y(y), -D(u(x, z), x)/u(x, z) - D(u(x, z), z)/u(x, z)] + assert pde_separate_mul(eq, F(x, y, z), [X(x), Y(y), Z(z)]) == \ + [D(X(x), x)/X(x), -D(Z(z), z)/Z(z) - D(Y(y), y)/Y(y)] + + # wave equation + wave = Eq(D(u(x, t), t, t), c**2*D(u(x, t), x, x)) + res = pde_separate_mul(wave, u(x, t), [X(x), T(t)]) + assert res == [D(X(x), x, x)/X(x), D(T(t), t, t)/(c**2*T(t))] + + # Laplace equation in cylindrical coords + eq = Eq(1/r * D(Phi(r, theta, z), r) + D(Phi(r, theta, z), r, 2) + + 1/r**2 * D(Phi(r, theta, z), theta, 2) + D(Phi(r, theta, z), z, 2), 0) + # Separate z + res = pde_separate_mul(eq, Phi(r, theta, z), [Z(z), u(theta, r)]) + assert res == [D(Z(z), z, z)/Z(z), + -D(u(theta, r), r, r)/u(theta, r) - + D(u(theta, r), r)/(r*u(theta, r)) - + D(u(theta, r), theta, theta)/(r**2*u(theta, r))] + # Lets use the result to create a new equation... + eq = Eq(res[1], c) + # ...and separate theta... + res = pde_separate_mul(eq, u(theta, r), [T(theta), R(r)]) + assert res == [D(T(theta), theta, theta)/T(theta), + -r*D(R(r), r)/R(r) - r**2*D(R(r), r, r)/R(r) - c*r**2] + # ...or r... + res = pde_separate_mul(eq, u(theta, r), [R(r), T(theta)]) + assert res == [r*D(R(r), r)/R(r) + r**2*D(R(r), r, r)/R(r) + c*r**2, + -D(T(theta), theta, theta)/T(theta)] + + +def test_issue_11726(): + x, t = symbols("x t") + f = symbols("f", cls=Function) + X, T = symbols("X T", cls=Function) + + u = f(x, t) + eq = u.diff(x, 2) - u.diff(t, 2) + res = pde_separate(eq, u, [T(x), X(t)]) + assert res == [D(T(x), x, x)/T(x),D(X(t), t, t)/X(t)] + + +def test_pde_classify(): + # When more number of hints are added, add tests for classifying here. + f = Function('f') + eq1 = a*f(x,y) + b*f(x,y).diff(x) + c*f(x,y).diff(y) + eq2 = 3*f(x,y) + 2*f(x,y).diff(x) + f(x,y).diff(y) + eq3 = a*f(x,y) + b*f(x,y).diff(x) + 2*f(x,y).diff(y) + eq4 = x*f(x,y) + f(x,y).diff(x) + 3*f(x,y).diff(y) + eq5 = x**2*f(x,y) + x*f(x,y).diff(x) + x*y*f(x,y).diff(y) + eq6 = y*x**2*f(x,y) + y*f(x,y).diff(x) + f(x,y).diff(y) + for eq in [eq1, eq2, eq3]: + assert classify_pde(eq) == ('1st_linear_constant_coeff_homogeneous',) + for eq in [eq4, eq5, eq6]: + assert classify_pde(eq) == ('1st_linear_variable_coeff',) + + +def test_checkpdesol(): + f, F = map(Function, ['f', 'F']) + eq1 = a*f(x,y) + b*f(x,y).diff(x) + c*f(x,y).diff(y) + eq2 = 3*f(x,y) + 2*f(x,y).diff(x) + f(x,y).diff(y) + eq3 = a*f(x,y) + b*f(x,y).diff(x) + 2*f(x,y).diff(y) + for eq in [eq1, eq2, eq3]: + assert checkpdesol(eq, pdsolve(eq))[0] + eq4 = x*f(x,y) + f(x,y).diff(x) + 3*f(x,y).diff(y) + eq5 = 2*f(x,y) + 1*f(x,y).diff(x) + 3*f(x,y).diff(y) + eq6 = f(x,y) + 1*f(x,y).diff(x) + 3*f(x,y).diff(y) + assert checkpdesol(eq4, [pdsolve(eq5), pdsolve(eq6)]) == [ + (False, (x - 2)*F(3*x - y)*exp(-x/S(5) - 3*y/S(5))), + (False, (x - 1)*F(3*x - y)*exp(-x/S(10) - 3*y/S(10)))] + for eq in [eq4, eq5, eq6]: + assert checkpdesol(eq, pdsolve(eq))[0] + sol = pdsolve(eq4) + sol4 = Eq(sol.lhs - sol.rhs, 0) + raises(NotImplementedError, lambda: + checkpdesol(eq4, sol4, solve_for_func=False)) + + +def test_solvefun(): + f, F, G, H = map(Function, ['f', 'F', 'G', 'H']) + eq1 = f(x,y) + f(x,y).diff(x) + f(x,y).diff(y) + assert pdsolve(eq1) == Eq(f(x, y), F(x - y)*exp(-x/2 - y/2)) + assert pdsolve(eq1, solvefun=G) == Eq(f(x, y), G(x - y)*exp(-x/2 - y/2)) + assert pdsolve(eq1, solvefun=H) == Eq(f(x, y), H(x - y)*exp(-x/2 - y/2)) + + +def test_pde_1st_linear_constant_coeff_homogeneous(): + f, F = map(Function, ['f', 'F']) + u = f(x, y) + eq = 2*u + u.diff(x) + u.diff(y) + assert classify_pde(eq) == ('1st_linear_constant_coeff_homogeneous',) + sol = pdsolve(eq) + assert sol == Eq(u, F(x - y)*exp(-x - y)) + assert checkpdesol(eq, sol)[0] + + eq = 4 + (3*u.diff(x)/u) + (2*u.diff(y)/u) + assert classify_pde(eq) == ('1st_linear_constant_coeff_homogeneous',) + sol = pdsolve(eq) + assert sol == Eq(u, F(2*x - 3*y)*exp(-S(12)*x/13 - S(8)*y/13)) + assert checkpdesol(eq, sol)[0] + + eq = u + (6*u.diff(x)) + (7*u.diff(y)) + assert classify_pde(eq) == ('1st_linear_constant_coeff_homogeneous',) + sol = pdsolve(eq) + assert sol == Eq(u, F(7*x - 6*y)*exp(-6*x/S(85) - 7*y/S(85))) + assert checkpdesol(eq, sol)[0] + + eq = a*u + b*u.diff(x) + c*u.diff(y) + sol = pdsolve(eq) + assert checkpdesol(eq, sol)[0] + + +def test_pde_1st_linear_constant_coeff(): + f, F = map(Function, ['f', 'F']) + u = f(x,y) + eq = -2*u.diff(x) + 4*u.diff(y) + 5*u - exp(x + 3*y) + sol = pdsolve(eq) + assert sol == Eq(f(x,y), + (F(4*x + 2*y)*exp(x/2) + exp(x + 4*y)/15)*exp(-y)) + assert classify_pde(eq) == ('1st_linear_constant_coeff', + '1st_linear_constant_coeff_Integral') + assert checkpdesol(eq, sol)[0] + + eq = (u.diff(x)/u) + (u.diff(y)/u) + 1 - (exp(x + y)/u) + sol = pdsolve(eq) + assert sol == Eq(f(x, y), F(x - y)*exp(-x/2 - y/2) + exp(x + y)/3) + assert classify_pde(eq) == ('1st_linear_constant_coeff', + '1st_linear_constant_coeff_Integral') + assert checkpdesol(eq, sol)[0] + + eq = 2*u + -u.diff(x) + 3*u.diff(y) + sin(x) + sol = pdsolve(eq) + assert sol == Eq(f(x, y), + F(3*x + y)*exp(x/5 - 3*y/5) - 2*sin(x)/5 - cos(x)/5) + assert classify_pde(eq) == ('1st_linear_constant_coeff', + '1st_linear_constant_coeff_Integral') + assert checkpdesol(eq, sol)[0] + + eq = u + u.diff(x) + u.diff(y) + x*y + sol = pdsolve(eq) + assert sol.expand() == Eq(f(x, y), + x + y + (x - y)**2/4 - (x + y)**2/4 + F(x - y)*exp(-x/2 - y/2) - 2).expand() + assert classify_pde(eq) == ('1st_linear_constant_coeff', + '1st_linear_constant_coeff_Integral') + assert checkpdesol(eq, sol)[0] + eq = u + u.diff(x) + u.diff(y) + log(x) + assert classify_pde(eq) == ('1st_linear_constant_coeff', + '1st_linear_constant_coeff_Integral') + + +def test_pdsolve_all(): + f, F = map(Function, ['f', 'F']) + u = f(x,y) + eq = u + u.diff(x) + u.diff(y) + x**2*y + sol = pdsolve(eq, hint = 'all') + keys = ['1st_linear_constant_coeff', + '1st_linear_constant_coeff_Integral', 'default', 'order'] + assert sorted(sol.keys()) == keys + assert sol['order'] == 1 + assert sol['default'] == '1st_linear_constant_coeff' + assert sol['1st_linear_constant_coeff'].expand() == Eq(f(x, y), + -x**2*y + x**2 + 2*x*y - 4*x - 2*y + F(x - y)*exp(-x/2 - y/2) + 6).expand() + + +def test_pdsolve_variable_coeff(): + f, F = map(Function, ['f', 'F']) + u = f(x, y) + eq = x*(u.diff(x)) - y*(u.diff(y)) + y**2*u - y**2 + sol = pdsolve(eq, hint="1st_linear_variable_coeff") + assert sol == Eq(u, F(x*y)*exp(y**2/2) + 1) + assert checkpdesol(eq, sol)[0] + + eq = x**2*u + x*u.diff(x) + x*y*u.diff(y) + sol = pdsolve(eq, hint='1st_linear_variable_coeff') + assert sol == Eq(u, F(y*exp(-x))*exp(-x**2/2)) + assert checkpdesol(eq, sol)[0] + + eq = y*x**2*u + y*u.diff(x) + u.diff(y) + sol = pdsolve(eq, hint='1st_linear_variable_coeff') + assert sol == Eq(u, F(-2*x + y**2)*exp(-x**3/3)) + assert checkpdesol(eq, sol)[0] + + eq = exp(x)**2*(u.diff(x)) + y + sol = pdsolve(eq, hint='1st_linear_variable_coeff') + assert sol == Eq(u, y*exp(-2*x)/2 + F(y)) + assert checkpdesol(eq, sol)[0] + + eq = exp(2*x)*(u.diff(y)) + y*u - u + sol = pdsolve(eq, hint='1st_linear_variable_coeff') + assert sol == Eq(u, F(x)*exp(-y*(y - 2)*exp(-2*x)/2)) diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/test_polysys.py b/lib/python3.12/site-packages/sympy/solvers/tests/test_polysys.py new file mode 100644 index 0000000000000000000000000000000000000000..9f0a70c89cd94e9f03cb7cc5a009bc8209a21178 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/tests/test_polysys.py @@ -0,0 +1,178 @@ +"""Tests for solvers of systems of polynomial equations. """ +from sympy.core.numbers import (I, Integer, Rational) +from sympy.core.singleton import S +from sympy.core.symbol import symbols +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.polys.domains.rationalfield import QQ +from sympy.polys.polyerrors import UnsolvableFactorError +from sympy.polys.polyoptions import Options +from sympy.polys.polytools import Poly +from sympy.solvers.solvers import solve +from sympy.utilities.iterables import flatten +from sympy.abc import x, y, z +from sympy.polys import PolynomialError +from sympy.solvers.polysys import (solve_poly_system, + solve_triangulated, + solve_biquadratic, SolveFailed, + solve_generic) +from sympy.polys.polytools import parallel_poly_from_expr +from sympy.testing.pytest import raises + + +def test_solve_poly_system(): + assert solve_poly_system([x - 1], x) == [(S.One,)] + + assert solve_poly_system([y - x, y - x - 1], x, y) is None + + assert solve_poly_system([y - x**2, y + x**2], x, y) == [(S.Zero, S.Zero)] + + assert solve_poly_system([2*x - 3, y*Rational(3, 2) - 2*x, z - 5*y], x, y, z) == \ + [(Rational(3, 2), Integer(2), Integer(10))] + + assert solve_poly_system([x*y - 2*y, 2*y**2 - x**2], x, y) == \ + [(0, 0), (2, -sqrt(2)), (2, sqrt(2))] + + assert solve_poly_system([y - x**2, y + x**2 + 1], x, y) == \ + [(-I*sqrt(S.Half), Rational(-1, 2)), (I*sqrt(S.Half), Rational(-1, 2))] + + f_1 = x**2 + y + z - 1 + f_2 = x + y**2 + z - 1 + f_3 = x + y + z**2 - 1 + + a, b = sqrt(2) - 1, -sqrt(2) - 1 + + assert solve_poly_system([f_1, f_2, f_3], x, y, z) == \ + [(0, 0, 1), (0, 1, 0), (1, 0, 0), (a, a, a), (b, b, b)] + + solution = [(1, -1), (1, 1)] + + assert solve_poly_system([Poly(x**2 - y**2), Poly(x - 1)]) == solution + assert solve_poly_system([x**2 - y**2, x - 1], x, y) == solution + assert solve_poly_system([x**2 - y**2, x - 1]) == solution + + assert solve_poly_system( + [x + x*y - 3, y + x*y - 4], x, y) == [(-3, -2), (1, 2)] + + raises(NotImplementedError, lambda: solve_poly_system([x**3 - y**3], x, y)) + raises(NotImplementedError, lambda: solve_poly_system( + [z, -2*x*y**2 + x + y**2*z, y**2*(-z - 4) + 2])) + raises(PolynomialError, lambda: solve_poly_system([1/x], x)) + + raises(NotImplementedError, lambda: solve_poly_system( + [x-1,], (x, y))) + raises(NotImplementedError, lambda: solve_poly_system( + [y-1,], (x, y))) + + # solve_poly_system should ideally construct solutions using + # CRootOf for the following four tests + assert solve_poly_system([x**5 - x + 1], [x], strict=False) == [] + raises(UnsolvableFactorError, lambda: solve_poly_system( + [x**5 - x + 1], [x], strict=True)) + + assert solve_poly_system([(x - 1)*(x**5 - x + 1), y**2 - 1], [x, y], + strict=False) == [(1, -1), (1, 1)] + raises(UnsolvableFactorError, + lambda: solve_poly_system([(x - 1)*(x**5 - x + 1), y**2-1], + [x, y], strict=True)) + + +def test_solve_generic(): + NewOption = Options((x, y), {'domain': 'ZZ'}) + assert solve_generic([x**2 - 2*y**2, y**2 - y + 1], NewOption) == \ + [(-sqrt(-1 - sqrt(3)*I), Rational(1, 2) - sqrt(3)*I/2), + (sqrt(-1 - sqrt(3)*I), Rational(1, 2) - sqrt(3)*I/2), + (-sqrt(-1 + sqrt(3)*I), Rational(1, 2) + sqrt(3)*I/2), + (sqrt(-1 + sqrt(3)*I), Rational(1, 2) + sqrt(3)*I/2)] + + # solve_generic should ideally construct solutions using + # CRootOf for the following two tests + assert solve_generic( + [2*x - y, (y - 1)*(y**5 - y + 1)], NewOption, strict=False) == \ + [(Rational(1, 2), 1)] + raises(UnsolvableFactorError, lambda: solve_generic( + [2*x - y, (y - 1)*(y**5 - y + 1)], NewOption, strict=True)) + + +def test_solve_biquadratic(): + x0, y0, x1, y1, r = symbols('x0 y0 x1 y1 r') + + f_1 = (x - 1)**2 + (y - 1)**2 - r**2 + f_2 = (x - 2)**2 + (y - 2)**2 - r**2 + s = sqrt(2*r**2 - 1) + a = (3 - s)/2 + b = (3 + s)/2 + assert solve_poly_system([f_1, f_2], x, y) == [(a, b), (b, a)] + + f_1 = (x - 1)**2 + (y - 2)**2 - r**2 + f_2 = (x - 1)**2 + (y - 1)**2 - r**2 + + assert solve_poly_system([f_1, f_2], x, y) == \ + [(1 - sqrt((2*r - 1)*(2*r + 1))/2, Rational(3, 2)), + (1 + sqrt((2*r - 1)*(2*r + 1))/2, Rational(3, 2))] + + query = lambda expr: expr.is_Pow and expr.exp is S.Half + + f_1 = (x - 1 )**2 + (y - 2)**2 - r**2 + f_2 = (x - x1)**2 + (y - 1)**2 - r**2 + + result = solve_poly_system([f_1, f_2], x, y) + + assert len(result) == 2 and all(len(r) == 2 for r in result) + assert all(r.count(query) == 1 for r in flatten(result)) + + f_1 = (x - x0)**2 + (y - y0)**2 - r**2 + f_2 = (x - x1)**2 + (y - y1)**2 - r**2 + + result = solve_poly_system([f_1, f_2], x, y) + + assert len(result) == 2 and all(len(r) == 2 for r in result) + assert all(len(r.find(query)) == 1 for r in flatten(result)) + + s1 = (x*y - y, x**2 - x) + assert solve(s1) == [{x: 1}, {x: 0, y: 0}] + s2 = (x*y - x, y**2 - y) + assert solve(s2) == [{y: 1}, {x: 0, y: 0}] + gens = (x, y) + for seq in (s1, s2): + (f, g), opt = parallel_poly_from_expr(seq, *gens) + raises(SolveFailed, lambda: solve_biquadratic(f, g, opt)) + seq = (x**2 + y**2 - 2, y**2 - 1) + (f, g), opt = parallel_poly_from_expr(seq, *gens) + assert solve_biquadratic(f, g, opt) == [ + (-1, -1), (-1, 1), (1, -1), (1, 1)] + ans = [(0, -1), (0, 1)] + seq = (x**2 + y**2 - 1, y**2 - 1) + (f, g), opt = parallel_poly_from_expr(seq, *gens) + assert solve_biquadratic(f, g, opt) == ans + seq = (x**2 + y**2 - 1, x**2 - x + y**2 - 1) + (f, g), opt = parallel_poly_from_expr(seq, *gens) + assert solve_biquadratic(f, g, opt) == ans + + +def test_solve_triangulated(): + f_1 = x**2 + y + z - 1 + f_2 = x + y**2 + z - 1 + f_3 = x + y + z**2 - 1 + + a, b = sqrt(2) - 1, -sqrt(2) - 1 + + assert solve_triangulated([f_1, f_2, f_3], x, y, z) == \ + [(0, 0, 1), (0, 1, 0), (1, 0, 0)] + + dom = QQ.algebraic_field(sqrt(2)) + + assert solve_triangulated([f_1, f_2, f_3], x, y, z, domain=dom) == \ + [(0, 0, 1), (0, 1, 0), (1, 0, 0), (a, a, a), (b, b, b)] + + +def test_solve_issue_3686(): + roots = solve_poly_system([((x - 5)**2/250000 + (y - Rational(5, 10))**2/250000) - 1, x], x, y) + assert roots == [(0, S.Half - 15*sqrt(1111)), (0, S.Half + 15*sqrt(1111))] + + roots = solve_poly_system([((x - 5)**2/250000 + (y - 5.0/10)**2/250000) - 1, x], x, y) + # TODO: does this really have to be so complicated?! + assert len(roots) == 2 + assert roots[0][0] == 0 + assert roots[0][1].epsilon_eq(-499.474999374969, 1e12) + assert roots[1][0] == 0 + assert roots[1][1].epsilon_eq(500.474999374969, 1e12) diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/test_recurr.py b/lib/python3.12/site-packages/sympy/solvers/tests/test_recurr.py new file mode 100644 index 0000000000000000000000000000000000000000..5a6306b51a5cf33ccd9fae131430a24690d540a7 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/tests/test_recurr.py @@ -0,0 +1,295 @@ +from sympy.core.function import (Function, Lambda, expand) +from sympy.core.numbers import (I, Rational) +from sympy.core.relational import Eq +from sympy.core.singleton import S +from sympy.core.symbol import (Symbol, symbols) +from sympy.functions.combinatorial.factorials import (rf, binomial, factorial) +from sympy.functions.elementary.complexes import Abs +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.functions.elementary.trigonometric import (cos, sin) +from sympy.polys.polytools import factor +from sympy.solvers.recurr import rsolve, rsolve_hyper, rsolve_poly, rsolve_ratio +from sympy.testing.pytest import raises, slow, XFAIL +from sympy.abc import a, b + +y = Function('y') +n, k = symbols('n,k', integer=True) +C0, C1, C2 = symbols('C0,C1,C2') + + +def test_rsolve_poly(): + assert rsolve_poly([-1, -1, 1], 0, n) == 0 + assert rsolve_poly([-1, -1, 1], 1, n) == -1 + + assert rsolve_poly([-1, n + 1], n, n) == 1 + assert rsolve_poly([-1, 1], n, n) == C0 + (n**2 - n)/2 + assert rsolve_poly([-n - 1, n], 1, n) == C0*n - 1 + assert rsolve_poly([-4*n - 2, 1], 4*n + 1, n) == -1 + + assert rsolve_poly([-1, 1], n**5 + n**3, n) == \ + C0 - n**3 / 2 - n**5 / 2 + n**2 / 6 + n**6 / 6 + 2*n**4 / 3 + + +def test_rsolve_ratio(): + solution = rsolve_ratio([-2*n**3 + n**2 + 2*n - 1, 2*n**3 + n**2 - 6*n, + -2*n**3 - 11*n**2 - 18*n - 9, 2*n**3 + 13*n**2 + 22*n + 8], 0, n) + assert solution == C0*(2*n - 3)/(n**2 - 1)/2 + + +def test_rsolve_hyper(): + assert rsolve_hyper([-1, -1, 1], 0, n) in [ + C0*(S.Half - S.Half*sqrt(5))**n + C1*(S.Half + S.Half*sqrt(5))**n, + C1*(S.Half - S.Half*sqrt(5))**n + C0*(S.Half + S.Half*sqrt(5))**n, + ] + + assert rsolve_hyper([n**2 - 2, -2*n - 1, 1], 0, n) in [ + C0*rf(sqrt(2), n) + C1*rf(-sqrt(2), n), + C1*rf(sqrt(2), n) + C0*rf(-sqrt(2), n), + ] + + assert rsolve_hyper([n**2 - k, -2*n - 1, 1], 0, n) in [ + C0*rf(sqrt(k), n) + C1*rf(-sqrt(k), n), + C1*rf(sqrt(k), n) + C0*rf(-sqrt(k), n), + ] + + assert rsolve_hyper( + [2*n*(n + 1), -n**2 - 3*n + 2, n - 1], 0, n) == C1*factorial(n) + C0*2**n + + assert rsolve_hyper( + [n + 2, -(2*n + 3)*(17*n**2 + 51*n + 39), n + 1], 0, n) == 0 + + assert rsolve_hyper([-n - 1, -1, 1], 0, n) == 0 + + assert rsolve_hyper([-1, 1], n, n).expand() == C0 + n**2/2 - n/2 + + assert rsolve_hyper([-1, 1], 1 + n, n).expand() == C0 + n**2/2 + n/2 + + assert rsolve_hyper([-1, 1], 3*(n + n**2), n).expand() == C0 + n**3 - n + + assert rsolve_hyper([-a, 1],0,n).expand() == C0*a**n + + assert rsolve_hyper([-a, 0, 1], 0, n).expand() == (-1)**n*C1*a**(n/2) + C0*a**(n/2) + + assert rsolve_hyper([1, 1, 1], 0, n).expand() == \ + C0*(Rational(-1, 2) - sqrt(3)*I/2)**n + C1*(Rational(-1, 2) + sqrt(3)*I/2)**n + + assert rsolve_hyper([1, -2*n/a - 2/a, 1], 0, n) == 0 + + +@XFAIL +def test_rsolve_ratio_missed(): + # this arises during computation + # assert rsolve_hyper([-1, 1], 3*(n + n**2), n).expand() == C0 + n**3 - n + assert rsolve_ratio([-n, n + 2], n, n) is not None + + +def recurrence_term(c, f): + """Compute RHS of recurrence in f(n) with coefficients in c.""" + return sum(c[i]*f.subs(n, n + i) for i in range(len(c))) + + +def test_rsolve_bulk(): + """Some bulk-generated tests.""" + funcs = [ n, n + 1, n**2, n**3, n**4, n + n**2, 27*n + 52*n**2 - 3* + n**3 + 12*n**4 - 52*n**5 ] + coeffs = [ [-2, 1], [-2, -1, 1], [-1, 1, 1, -1, 1], [-n, 1], [n**2 - + n + 12, 1] ] + for p in funcs: + # compute difference + for c in coeffs: + q = recurrence_term(c, p) + if p.is_polynomial(n): + assert rsolve_poly(c, q, n) == p + # See issue 3956: + if p.is_hypergeometric(n) and len(c) <= 3: + assert rsolve_hyper(c, q, n).subs(zip(symbols('C:3'), [0, 0, 0])).expand() == p + + +def test_rsolve_0_sol_homogeneous(): + # fixed by cherry-pick from + # https://github.com/diofant/diofant/commit/e1d2e52125199eb3df59f12e8944f8a5f24b00a5 + assert rsolve_hyper([n**2 - n + 12, 1], n*(n**2 - n + 12) + n + 1, n) == n + + +def test_rsolve(): + f = y(n + 2) - y(n + 1) - y(n) + h = sqrt(5)*(S.Half + S.Half*sqrt(5))**n \ + - sqrt(5)*(S.Half - S.Half*sqrt(5))**n + + assert rsolve(f, y(n)) in [ + C0*(S.Half - S.Half*sqrt(5))**n + C1*(S.Half + S.Half*sqrt(5))**n, + C1*(S.Half - S.Half*sqrt(5))**n + C0*(S.Half + S.Half*sqrt(5))**n, + ] + + assert rsolve(f, y(n), [0, 5]) == h + assert rsolve(f, y(n), {0: 0, 1: 5}) == h + assert rsolve(f, y(n), {y(0): 0, y(1): 5}) == h + assert rsolve(y(n) - y(n - 1) - y(n - 2), y(n), [0, 5]) == h + assert rsolve(Eq(y(n), y(n - 1) + y(n - 2)), y(n), [0, 5]) == h + + assert f.subs(y, Lambda(k, rsolve(f, y(n)).subs(n, k))).simplify() == 0 + + f = (n - 1)*y(n + 2) - (n**2 + 3*n - 2)*y(n + 1) + 2*n*(n + 1)*y(n) + g = C1*factorial(n) + C0*2**n + h = -3*factorial(n) + 3*2**n + + assert rsolve(f, y(n)) == g + assert rsolve(f, y(n), []) == g + assert rsolve(f, y(n), {}) == g + + assert rsolve(f, y(n), [0, 3]) == h + assert rsolve(f, y(n), {0: 0, 1: 3}) == h + assert rsolve(f, y(n), {y(0): 0, y(1): 3}) == h + + assert f.subs(y, Lambda(k, rsolve(f, y(n)).subs(n, k))).simplify() == 0 + + f = y(n) - y(n - 1) - 2 + + assert rsolve(f, y(n), {y(0): 0}) == 2*n + assert rsolve(f, y(n), {y(0): 1}) == 2*n + 1 + assert rsolve(f, y(n), {y(0): 0, y(1): 1}) is None + + assert f.subs(y, Lambda(k, rsolve(f, y(n)).subs(n, k))).simplify() == 0 + + f = 3*y(n - 1) - y(n) - 1 + + assert rsolve(f, y(n), {y(0): 0}) == -3**n/2 + S.Half + assert rsolve(f, y(n), {y(0): 1}) == 3**n/2 + S.Half + assert rsolve(f, y(n), {y(0): 2}) == 3*3**n/2 + S.Half + + assert f.subs(y, Lambda(k, rsolve(f, y(n)).subs(n, k))).simplify() == 0 + + f = y(n) - 1/n*y(n - 1) + assert rsolve(f, y(n)) == C0/factorial(n) + assert f.subs(y, Lambda(k, rsolve(f, y(n)).subs(n, k))).simplify() == 0 + + f = y(n) - 1/n*y(n - 1) - 1 + assert rsolve(f, y(n)) is None + + f = 2*y(n - 1) + (1 - n)*y(n)/n + + assert rsolve(f, y(n), {y(1): 1}) == 2**(n - 1)*n + assert rsolve(f, y(n), {y(1): 2}) == 2**(n - 1)*n*2 + assert rsolve(f, y(n), {y(1): 3}) == 2**(n - 1)*n*3 + + assert f.subs(y, Lambda(k, rsolve(f, y(n)).subs(n, k))).simplify() == 0 + + f = (n - 1)*(n - 2)*y(n + 2) - (n + 1)*(n + 2)*y(n) + + assert rsolve(f, y(n), {y(3): 6, y(4): 24}) == n*(n - 1)*(n - 2) + assert rsolve( + f, y(n), {y(3): 6, y(4): -24}) == -n*(n - 1)*(n - 2)*(-1)**(n) + + assert f.subs(y, Lambda(k, rsolve(f, y(n)).subs(n, k))).simplify() == 0 + + assert rsolve(Eq(y(n + 1), a*y(n)), y(n), {y(1): a}).simplify() == a**n + + assert rsolve(y(n) - a*y(n-2),y(n), \ + {y(1): sqrt(a)*(a + b), y(2): a*(a - b)}).simplify() == \ + a**(n/2 + 1) - b*(-sqrt(a))**n + + f = (-16*n**2 + 32*n - 12)*y(n - 1) + (4*n**2 - 12*n + 9)*y(n) + + yn = rsolve(f, y(n), {y(1): binomial(2*n + 1, 3)}) + sol = 2**(2*n)*n*(2*n - 1)**2*(2*n + 1)/12 + assert factor(expand(yn, func=True)) == sol + + sol = rsolve(y(n) + a*(y(n + 1) + y(n - 1))/2, y(n)) + assert str(sol) == 'C0*((-sqrt(1 - a**2) - 1)/a)**n + C1*((sqrt(1 - a**2) - 1)/a)**n' + + assert rsolve((k + 1)*y(k), y(k)) is None + assert (rsolve((k + 1)*y(k) + (k + 3)*y(k + 1) + (k + 5)*y(k + 2), y(k)) + is None) + + assert rsolve(y(n) + y(n + 1) + 2**n + 3**n, y(n)) == (-1)**n*C0 - 2**n/3 - 3**n/4 + + +def test_rsolve_raises(): + x = Function('x') + raises(ValueError, lambda: rsolve(y(n) - y(k + 1), y(n))) + raises(ValueError, lambda: rsolve(y(n) - y(n + 1), x(n))) + raises(ValueError, lambda: rsolve(y(n) - x(n + 1), y(n))) + raises(ValueError, lambda: rsolve(y(n) - sqrt(n)*y(n + 1), y(n))) + raises(ValueError, lambda: rsolve(y(n) - y(n + 1), y(n), {x(0): 0})) + raises(ValueError, lambda: rsolve(y(n) + y(n + 1) + 2**n + cos(n), y(n))) + + +def test_issue_6844(): + f = y(n + 2) - y(n + 1) + y(n)/4 + assert rsolve(f, y(n)) == 2**(-n + 1)*C1*n + 2**(-n)*C0 + assert rsolve(f, y(n), {y(0): 0, y(1): 1}) == 2**(1 - n)*n + + +def test_issue_18751(): + r = Symbol('r', positive=True) + theta = Symbol('theta', real=True) + f = y(n) - 2 * r * cos(theta) * y(n - 1) + r**2 * y(n - 2) + assert rsolve(f, y(n)) == \ + C0*(r*(cos(theta) - I*Abs(sin(theta))))**n + C1*(r*(cos(theta) + I*Abs(sin(theta))))**n + + +def test_constant_naming(): + #issue 8697 + assert rsolve(y(n+3) - y(n+2) - y(n+1) + y(n), y(n)) == (-1)**n*C1 + C0 + C2*n + assert rsolve(y(n+3)+3*y(n+2)+3*y(n+1)+y(n), y(n)).expand() == (-1)**n*C0 - (-1)**n*C1*n - (-1)**n*C2*n**2 + assert rsolve(y(n) - 2*y(n - 3) + 5*y(n - 2) - 4*y(n - 1),y(n),[1,3,8]) == 3*2**n - n - 2 + + #issue 19630 + assert rsolve(y(n+3) - 3*y(n+1) + 2*y(n), y(n), {y(1):0, y(2):8, y(3):-2}) == (-2)**n + 2*n + + +@slow +def test_issue_15751(): + f = y(n) + 21*y(n + 1) - 273*y(n + 2) - 1092*y(n + 3) + 1820*y(n + 4) + 1092*y(n + 5) - 273*y(n + 6) - 21*y(n + 7) + y(n + 8) + assert rsolve(f, y(n)) is not None + + +def test_issue_17990(): + f = -10*y(n) + 4*y(n + 1) + 6*y(n + 2) + 46*y(n + 3) + sol = rsolve(f, y(n)) + expected = C0*((86*18**(S(1)/3)/69 + (-12 + (-1 + sqrt(3)*I)*(290412 + + 3036*sqrt(9165))**(S(1)/3))*(1 - sqrt(3)*I)*(24201 + 253*sqrt(9165))** + (S(1)/3)/276)/((1 - sqrt(3)*I)*(24201 + 253*sqrt(9165))**(S(1)/3)) + )**n + C1*((86*18**(S(1)/3)/69 + (-12 + (-1 - sqrt(3)*I)*(290412 + 3036 + *sqrt(9165))**(S(1)/3))*(1 + sqrt(3)*I)*(24201 + 253*sqrt(9165))** + (S(1)/3)/276)/((1 + sqrt(3)*I)*(24201 + 253*sqrt(9165))**(S(1)/3)) + )**n + C2*(-43*18**(S(1)/3)/(69*(24201 + 253*sqrt(9165))**(S(1)/3)) - + S(1)/23 + (290412 + 3036*sqrt(9165))**(S(1)/3)/138)**n + assert sol == expected + e = sol.subs({C0: 1, C1: 1, C2: 1, n: 1}).evalf() + assert abs(e + 0.130434782608696) < 1e-13 + + +def test_issue_8697(): + a = Function('a') + eq = a(n + 3) - a(n + 2) - a(n + 1) + a(n) + assert rsolve(eq, a(n)) == (-1)**n*C1 + C0 + C2*n + eq2 = a(n + 3) + 3*a(n + 2) + 3*a(n + 1) + a(n) + assert (rsolve(eq2, a(n)) == + (-1)**n*C0 + (-1)**(n + 1)*C1*n + (-1)**(n + 1)*C2*n**2) + + assert rsolve(a(n) - 2*a(n - 3) + 5*a(n - 2) - 4*a(n - 1), + a(n), {a(0): 1, a(1): 3, a(2): 8}) == 3*2**n - n - 2 + + # From issue thread (but fixed by https://github.com/diofant/diofant/commit/da9789c6cd7d0c2ceeea19fbf59645987125b289): + assert rsolve(a(n) - 2*a(n - 1) - n, a(n), {a(0): 1}) == 3*2**n - n - 2 + + +def test_diofantissue_294(): + f = y(n) - y(n - 1) - 2*y(n - 2) - 2*n + assert rsolve(f, y(n)) == (-1)**n*C0 + 2**n*C1 - n - Rational(5, 2) + # issue sympy/sympy#11261 + assert rsolve(f, y(n), {y(0): -1, y(1): 1}) == (-(-1)**n/2 + 2*2**n - + n - Rational(5, 2)) + # issue sympy/sympy#7055 + assert rsolve(-2*y(n) + y(n + 1) + n - 1, y(n)) == 2**n*C0 + n + + +def test_issue_15553(): + f = Function("f") + assert rsolve(Eq(f(n), 2*f(n - 1) + n), f(n)) == 2**n*C0 - n - 2 + assert rsolve(Eq(f(n + 1), 2*f(n) + n**2 + 1), f(n)) == 2**n*C0 - n**2 - 2*n - 4 + assert rsolve(Eq(f(n + 1), 2*f(n) + n**2 + 1), f(n), {f(1): 0}) == 7*2**n/2 - n**2 - 2*n - 4 + assert rsolve(Eq(f(n), 2*f(n - 1) + 3*n**2), f(n)) == 2**n*C0 - 3*n**2 - 12*n - 18 + assert rsolve(Eq(f(n), 2*f(n - 1) + n**2), f(n)) == 2**n*C0 - n**2 - 4*n - 6 + assert rsolve(Eq(f(n), 2*f(n - 1) + n), f(n), {f(0): 1}) == 3*2**n - n - 2 diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/test_simplex.py b/lib/python3.12/site-packages/sympy/solvers/tests/test_simplex.py new file mode 100644 index 0000000000000000000000000000000000000000..611205f5df009a6d0de6e687501695b63bb932c9 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/tests/test_simplex.py @@ -0,0 +1,254 @@ +from sympy.core.numbers import Rational +from sympy.core.relational import Eq, Ne +from sympy.core.symbol import symbols +from sympy.core.sympify import sympify +from sympy.core.singleton import S +from sympy.core.random import random, choice +from sympy.functions.elementary.miscellaneous import sqrt +from sympy.ntheory.generate import randprime +from sympy.matrices.dense import Matrix +from sympy.solvers.solveset import linear_eq_to_matrix +from sympy.solvers.simplex import (_lp as lp, _primal_dual, + UnboundedLPError, InfeasibleLPError, lpmin, lpmax, + _m, _abcd, _simplex, linprog) + +from sympy.external.importtools import import_module + +from sympy.testing.pytest import raises + +from sympy.abc import x, y, z + + +np = import_module("numpy") +scipy = import_module("scipy") + + +def test_lp(): + r1 = y + 2*z <= 3 + r2 = -x - 3*z <= -2 + r3 = 2*x + y + 7*z <= 5 + constraints = [r1, r2, r3, x >= 0, y >= 0, z >= 0] + objective = -x - y - 5 * z + ans = optimum, argmax = lp(max, objective, constraints) + assert ans == lpmax(objective, constraints) + assert objective.subs(argmax) == optimum + for constr in constraints: + assert constr.subs(argmax) == True + + r1 = x - y + 2*z <= 3 + r2 = -x + 2*y - 3*z <= -2 + r3 = 2*x + y - 7*z <= -5 + constraints = [r1, r2, r3, x >= 0, y >= 0, z >= 0] + objective = -x - y - 5*z + ans = optimum, argmax = lp(max, objective, constraints) + assert ans == lpmax(objective, constraints) + assert objective.subs(argmax) == optimum + for constr in constraints: + assert constr.subs(argmax) == True + + r1 = x - y + 2*z <= -4 + r2 = -x + 2*y - 3*z <= 8 + r3 = 2*x + y - 7*z <= 10 + constraints = [r1, r2, r3, x >= 0, y >= 0, z >= 0] + const = 2 + objective = -x-y-5*z+const # has constant term + ans = optimum, argmax = lp(max, objective, constraints) + assert ans == lpmax(objective, constraints) + assert objective.subs(argmax) == optimum + for constr in constraints: + assert constr.subs(argmax) == True + + # Section 4 Problem 1 from + # http://web.tecnico.ulisboa.pt/mcasquilho/acad/or/ftp/FergusonUCLA_LP.pdf + # answer on page 55 + v = x1, x2, x3, x4 = symbols('x1 x2 x3 x4') + r1 = x1 - x2 - 2*x3 - x4 <= 4 + r2 = 2*x1 + x3 -4*x4 <= 2 + r3 = -2*x1 + x2 + x4 <= 1 + objective, constraints = x1 - 2*x2 - 3*x3 - x4, [r1, r2, r3] + [ + i >= 0 for i in v] + ans = optimum, argmax = lp(max, objective, constraints) + assert ans == lpmax(objective, constraints) + assert ans == (4, {x1: 7, x2: 0, x3: 0, x4: 3}) + + # input contains Floats + r1 = x - y + 2.0*z <= -4 + r2 = -x + 2*y - 3.0*z <= 8 + r3 = 2*x + y - 7*z <= 10 + constraints = [r1, r2, r3] + [i >= 0 for i in (x, y, z)] + objective = -x-y-5*z + optimum, argmax = lp(max, objective, constraints) + assert objective.subs(argmax) == optimum + for constr in constraints: + assert constr.subs(argmax) == True + + # input contains non-float or non-Rational + r1 = x - y + sqrt(2) * z <= -4 + r2 = -x + 2*y - 3*z <= 8 + r3 = 2*x + y - 7*z <= 10 + raises(TypeError, lambda: lp(max, -x-y-5*z, [r1, r2, r3])) + + r1 = x >= 0 + raises(UnboundedLPError, lambda: lp(max, x, [r1])) + r2 = x <= -1 + raises(InfeasibleLPError, lambda: lp(max, x, [r1, r2])) + + # strict inequalities are not allowed + r1 = x > 0 + raises(TypeError, lambda: lp(max, x, [r1])) + + # not equals not allowed + r1 = Ne(x, 0) + raises(TypeError, lambda: lp(max, x, [r1])) + + def make_random_problem(nvar=2, num_constraints=2, sparsity=.1): + def rand(): + if random() < sparsity: + return sympify(0) + int1, int2 = [randprime(0, 200) for _ in range(2)] + return Rational(int1, int2)*choice([-1, 1]) + variables = symbols('x1:%s' % (nvar + 1)) + constraints = [(sum(rand()*x for x in variables) <= rand()) + for _ in range(num_constraints)] + objective = sum(rand() * x for x in variables) + return objective, constraints, variables + + # equality + r1 = Eq(x, y) + r2 = Eq(y, z) + r3 = z <= 3 + constraints = [r1, r2, r3] + objective = x + ans = optimum, argmax = lp(max, objective, constraints) + assert ans == lpmax(objective, constraints) + assert objective.subs(argmax) == optimum + for constr in constraints: + assert constr.subs(argmax) == True + + +def test_simplex(): + L = [ + [[1, 1], [-1, 1], [0, 1], [-1, 0]], + [5, 1, 2, -1], + [[1, 1]], + [-1]] + A, B, C, D = _abcd(_m(*L), list=False) + assert _simplex(A, B, -C, -D) == (-6, [3, 2], [1, 0, 0, 0]) + assert _simplex(A, B, -C, -D, dual=True) == (-6, + [1, 0, 0, 0], [5, 0]) + + assert _simplex([[]],[],[[1]],[0]) == (0, [0], []) + + # handling of Eq (or Eq-like x<=y, x>=y conditions) + assert lpmax(x - y, [x <= y + 2, x >= y + 2, x >= 0, y >= 0] + ) == (2, {x: 2, y: 0}) + assert lpmax(x - y, [x <= y + 2, Eq(x, y + 2), x >= 0, y >= 0] + ) == (2, {x: 2, y: 0}) + assert lpmax(x - y, [x <= y + 2, Eq(x, 2)]) == (2, {x: 2, y: 0}) + assert lpmax(y, [Eq(y, 2)]) == (2, {y: 2}) + + # the conditions are equivalent to Eq(x, y + 2) + assert lpmin(y, [x <= y + 2, x >= y + 2, y >= 0] + ) == (0, {x: 2, y: 0}) + # equivalent to Eq(y, -2) + assert lpmax(y, [0 <= y + 2, 0 >= y + 2]) == (-2, {y: -2}) + assert lpmax(y, [0 <= y + 2, 0 >= y + 2, y <= 0] + ) == (-2, {y: -2}) + + # extra symbols symbols + assert lpmin(x, [y >= 1, x >= y]) == (1, {x: 1, y: 1}) + assert lpmin(x, [y >= 1, x >= y + z, x >= 0, z >= 0] + ) == (1, {x: 1, y: 1, z: 0}) + + # detect oscillation + # o1 + v = x1, x2, x3, x4 = symbols('x1 x2 x3 x4') + raises(InfeasibleLPError, lambda: lpmin( + 9*x2 - 8*x3 + 3*x4 + 6, + [5*x2 - 2*x3 <= 0, + -x1 - 8*x2 + 9*x3 <= -3, + 10*x1 - x2+ 9*x4 <= -4] + [i >= 0 for i in v])) + # o2 - equations fed to lpmin are changed into a matrix + # system that doesn't oscillate and has the same solution + # as below + M = linear_eq_to_matrix + f = 5*x2 + x3 + 4*x4 - x1 + L = 5*x2 + 2*x3 + 5*x4 - (x1 + 5) + cond = [L <= 0] + [Eq(3*x2 + x4, 2), Eq(-x1 + x3 + 2*x4, 1)] + c, d = M(f, v) + a, b = M(L, v) + aeq, beq = M(cond[1:], v) + ans = (S(9)/2, [0, S(1)/2, 0, S(1)/2]) + assert linprog(c, a, b, aeq, beq, bounds=(0, 1)) == ans + lpans = lpmin(f, cond + [x1 >= 0, x1 <= 1, + x2 >= 0, x2 <= 1, x3 >= 0, x3 <= 1, x4 >= 0, x4 <= 1]) + assert (lpans[0], list(lpans[1].values())) == ans + + +def test_lpmin_lpmax(): + v = x1, x2, y1, y2 = symbols('x1 x2 y1 y2') + L = [[1, -1]], [1], [[1, 1]], [2] + a, b, c, d = [Matrix(i) for i in L] + m = Matrix([[a, b], [c, d]]) + f, constr = _primal_dual(m)[0] + ans = lpmin(f, constr + [i >= 0 for i in v[:2]]) + assert ans == (-1, {x1: 1, x2: 0}),ans + + L = [[1, -1], [1, 1]], [1, 1], [[1, 1]], [2] + a, b, c, d = [Matrix(i) for i in L] + m = Matrix([[a, b], [c, d]]) + f, constr = _primal_dual(m)[1] + ans = lpmax(f, constr + [i >= 0 for i in v[-2:]]) + assert ans == (-1, {y1: 1, y2: 0}) + + +def test_linprog(): + for do in range(2): + if not do: + M = lambda a, b: linear_eq_to_matrix(a, b) + else: + # check matrices as list + M = lambda a, b: tuple([ + i.tolist() for i in linear_eq_to_matrix(a, b)]) + + v = x, y, z = symbols('x1:4') + f = x + y - 2*z + c = M(f, v)[0] + ineq = [7*x + 4*y - 7*z <= 3, + 3*x - y + 10*z <= 6, + x >= 0, y >= 0, z >= 0] + ab = M([i.lts - i.gts for i in ineq], v) + ans = (-S(6)/5, [0, 0, S(3)/5]) + assert lpmin(f, ineq) == (ans[0], dict(zip(v, ans[1]))) + assert linprog(c, *ab) == ans + + f += 1 + c = M(f, v)[0] + eq = [Eq(y - 9*x, 1)] + abeq = M([i.lhs - i.rhs for i in eq], v) + ans = (1 - S(2)/5, [0, 1, S(7)/10]) + assert lpmin(f, ineq + eq) == (ans[0], dict(zip(v, ans[1]))) + assert linprog(c, *ab, *abeq) == (ans[0] - 1, ans[1]) + + eq = [z - y <= S.Half] + abeq = M([i.lhs - i.rhs for i in eq], v) + ans = (1 - S(10)/9, [0, S(1)/9, S(11)/18]) + assert lpmin(f, ineq + eq) == (ans[0], dict(zip(v, ans[1]))) + assert linprog(c, *ab, *abeq) == (ans[0] - 1, ans[1]) + + bounds = [(0, None), (0, None), (None, S.Half)] + ans = (0, [0, 0, S.Half]) + assert lpmin(f, ineq + [z <= S.Half]) == ( + ans[0], dict(zip(v, ans[1]))) + assert linprog(c, *ab, bounds=bounds) == (ans[0] - 1, ans[1]) + assert linprog(c, *ab, bounds={v.index(z): bounds[-1]} + ) == (ans[0] - 1, ans[1]) + eq = [z - y <= S.Half] + + assert linprog([[1]], [], [], bounds=(2, 3)) == (2, [2]) + assert linprog([1], [], [], bounds=(2, 3)) == (2, [2]) + assert linprog([1], bounds=(2, 3)) == (2, [2]) + assert linprog([1, -1], [[1, 1]], [2], bounds={1:(None, None)} + ) == (-2, [0, 2]) + assert linprog([1, -1], [[1, 1]], [5], bounds={1:(3, None)} + ) == (-5, [0, 5]) diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/test_solvers.py b/lib/python3.12/site-packages/sympy/solvers/tests/test_solvers.py new file mode 100644 index 0000000000000000000000000000000000000000..c3ef819bbecd171adebdad76d9e52dead4f9fe31 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/tests/test_solvers.py @@ -0,0 +1,2703 @@ +from sympy.assumptions.ask import (Q, ask) +from sympy.core.add import Add +from sympy.core.containers import Tuple +from sympy.core.function import (Derivative, Function, diff) +from sympy.core.mod import Mod +from sympy.core.mul import Mul +from sympy.core import (GoldenRatio, TribonacciConstant) +from sympy.core.numbers import (E, Float, I, Rational, oo, pi) +from sympy.core.relational import (Eq, Gt, Lt, Ne) +from sympy.core.singleton import S +from sympy.core.symbol import (Dummy, Symbol, Wild, symbols) +from sympy.core.sympify import sympify +from sympy.functions.combinatorial.factorials import binomial +from sympy.functions.elementary.complexes import (Abs, arg, conjugate, im, re) +from sympy.functions.elementary.exponential import (LambertW, exp, log) +from sympy.functions.elementary.hyperbolic import (atanh, cosh, sinh, tanh) +from sympy.functions.elementary.integers import floor +from sympy.functions.elementary.miscellaneous import (cbrt, root, sqrt) +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import (acos, asin, atan, atan2, cos, sec, sin, tan) +from sympy.functions.special.error_functions import (erf, erfc, erfcinv, erfinv) +from sympy.integrals.integrals import Integral +from sympy.logic.boolalg import (And, Or) +from sympy.matrices.dense import Matrix +from sympy.matrices import SparseMatrix +from sympy.polys.polytools import Poly +from sympy.printing.str import sstr +from sympy.simplify.radsimp import denom +from sympy.solvers.solvers import (nsolve, solve, solve_linear) + +from sympy.core.function import nfloat +from sympy.solvers import solve_linear_system, solve_linear_system_LU, \ + solve_undetermined_coeffs +from sympy.solvers.bivariate import _filtered_gens, _solve_lambert, _lambert +from sympy.solvers.solvers import _invert, unrad, checksol, posify, _ispow, \ + det_quick, det_perm, det_minor, _simple_dens, denoms + +from sympy.physics.units import cm +from sympy.polys.rootoftools import CRootOf + +from sympy.testing.pytest import slow, XFAIL, SKIP, raises +from sympy.core.random import verify_numerically as tn + +from sympy.abc import a, b, c, d, e, k, h, p, x, y, z, t, q, m, R + + +def NS(e, n=15, **options): + return sstr(sympify(e).evalf(n, **options), full_prec=True) + + +def test_swap_back(): + f, g = map(Function, 'fg') + fx, gx = f(x), g(x) + assert solve([fx + y - 2, fx - gx - 5], fx, y, gx) == \ + {fx: gx + 5, y: -gx - 3} + assert solve(fx + gx*x - 2, [fx, gx], dict=True) == [{fx: 2, gx: 0}] + assert solve(fx + gx**2*x - y, [fx, gx], dict=True) == [{fx: y, gx: 0}] + assert solve([f(1) - 2, x + 2], dict=True) == [{x: -2, f(1): 2}] + + +def guess_solve_strategy(eq, symbol): + try: + solve(eq, symbol) + return True + except (TypeError, NotImplementedError): + return False + + +def test_guess_poly(): + # polynomial equations + assert guess_solve_strategy( S(4), x ) # == GS_POLY + assert guess_solve_strategy( x, x ) # == GS_POLY + assert guess_solve_strategy( x + a, x ) # == GS_POLY + assert guess_solve_strategy( 2*x, x ) # == GS_POLY + assert guess_solve_strategy( x + sqrt(2), x) # == GS_POLY + assert guess_solve_strategy( x + 2**Rational(1, 4), x) # == GS_POLY + assert guess_solve_strategy( x**2 + 1, x ) # == GS_POLY + assert guess_solve_strategy( x**2 - 1, x ) # == GS_POLY + assert guess_solve_strategy( x*y + y, x ) # == GS_POLY + assert guess_solve_strategy( x*exp(y) + y, x) # == GS_POLY + assert guess_solve_strategy( + (x - y**3)/(y**2*sqrt(1 - y**2)), x) # == GS_POLY + + +def test_guess_poly_cv(): + # polynomial equations via a change of variable + assert guess_solve_strategy( sqrt(x) + 1, x ) # == GS_POLY_CV_1 + assert guess_solve_strategy( + x**Rational(1, 3) + sqrt(x) + 1, x ) # == GS_POLY_CV_1 + assert guess_solve_strategy( 4*x*(1 - sqrt(x)), x ) # == GS_POLY_CV_1 + + # polynomial equation multiplying both sides by x**n + assert guess_solve_strategy( x + 1/x + y, x ) # == GS_POLY_CV_2 + + +def test_guess_rational_cv(): + # rational functions + assert guess_solve_strategy( (x + 1)/(x**2 + 2), x) # == GS_RATIONAL + assert guess_solve_strategy( + (x - y**3)/(y**2*sqrt(1 - y**2)), y) # == GS_RATIONAL_CV_1 + + # rational functions via the change of variable y -> x**n + assert guess_solve_strategy( (sqrt(x) + 1)/(x**Rational(1, 3) + sqrt(x) + 1), x ) \ + #== GS_RATIONAL_CV_1 + + +def test_guess_transcendental(): + #transcendental functions + assert guess_solve_strategy( exp(x) + 1, x ) # == GS_TRANSCENDENTAL + assert guess_solve_strategy( 2*cos(x) - y, x ) # == GS_TRANSCENDENTAL + assert guess_solve_strategy( + exp(x) + exp(-x) - y, x ) # == GS_TRANSCENDENTAL + assert guess_solve_strategy(3**x - 10, x) # == GS_TRANSCENDENTAL + assert guess_solve_strategy(-3**x + 10, x) # == GS_TRANSCENDENTAL + + assert guess_solve_strategy(a*x**b - y, x) # == GS_TRANSCENDENTAL + + +def test_solve_args(): + # equation container, issue 5113 + ans = {x: -3, y: 1} + eqs = (x + 5*y - 2, -3*x + 6*y - 15) + assert all(solve(container(eqs), x, y) == ans for container in + (tuple, list, set, frozenset)) + assert solve(Tuple(*eqs), x, y) == ans + # implicit symbol to solve for + assert set(solve(x**2 - 4)) == {S(2), -S(2)} + assert solve([x + y - 3, x - y - 5]) == {x: 4, y: -1} + assert solve(x - exp(x), x, implicit=True) == [exp(x)] + # no symbol to solve for + assert solve(42) == solve(42, x) == [] + assert solve([1, 2]) == [] + assert solve([sqrt(2)],[x]) == [] + # duplicate symbols raises + raises(ValueError, lambda: solve((x - 3, y + 2), x, y, x)) + raises(ValueError, lambda: solve(x, x, x)) + # no error in exclude + assert solve(x, x, exclude=[y, y]) == [0] + # duplicate symbols raises + raises(ValueError, lambda: solve((x - 3, y + 2), x, y, x)) + raises(ValueError, lambda: solve(x, x, x)) + # no error in exclude + assert solve(x, x, exclude=[y, y]) == [0] + # unordered symbols + # only 1 + assert solve(y - 3, {y}) == [3] + # more than 1 + assert solve(y - 3, {x, y}) == [{y: 3}] + # multiple symbols: take the first linear solution+ + # - return as tuple with values for all requested symbols + assert solve(x + y - 3, [x, y]) == [(3 - y, y)] + # - unless dict is True + assert solve(x + y - 3, [x, y], dict=True) == [{x: 3 - y}] + # - or no symbols are given + assert solve(x + y - 3) == [{x: 3 - y}] + # multiple symbols might represent an undetermined coefficients system + assert solve(a + b*x - 2, [a, b]) == {a: 2, b: 0} + assert solve((a + b)*x + b - c, [a, b]) == {a: -c, b: c} + eq = a*x**2 + b*x + c - ((x - h)**2 + 4*p*k)/4/p + # - check that flags are obeyed + sol = solve(eq, [h, p, k], exclude=[a, b, c]) + assert sol == {h: -b/(2*a), k: (4*a*c - b**2)/(4*a), p: 1/(4*a)} + assert solve(eq, [h, p, k], dict=True) == [sol] + assert solve(eq, [h, p, k], set=True) == \ + ([h, p, k], {(-b/(2*a), 1/(4*a), (4*a*c - b**2)/(4*a))}) + # issue 23889 - polysys not simplified + assert solve(eq, [h, p, k], exclude=[a, b, c], simplify=False) == \ + {h: -b/(2*a), k: (4*a*c - b**2)/(4*a), p: 1/(4*a)} + # but this only happens when system has a single solution + args = (a + b)*x - b**2 + 2, a, b + assert solve(*args) == [((b**2 - b*x - 2)/x, b)] + # and if the system has a solution; the following doesn't so + # an algebraic solution is returned + assert solve(a*x + b**2/(x + 4) - 3*x - 4/x, a, b, dict=True) == \ + [{a: (-b**2*x + 3*x**3 + 12*x**2 + 4*x + 16)/(x**2*(x + 4))}] + # failed single equation + assert solve(1/(1/x - y + exp(y))) == [] + raises( + NotImplementedError, lambda: solve(exp(x) + sin(x) + exp(y) + sin(y))) + # failed system + # -- when no symbols given, 1 fails + assert solve([y, exp(x) + x]) == [{x: -LambertW(1), y: 0}] + # both fail + assert solve( + (exp(x) - x, exp(y) - y)) == [{x: -LambertW(-1), y: -LambertW(-1)}] + # -- when symbols given + assert solve([y, exp(x) + x], x, y) == [(-LambertW(1), 0)] + # symbol is a number + assert solve(x**2 - pi, pi) == [x**2] + # no equations + assert solve([], [x]) == [] + # nonlinear system + assert solve((x**2 - 4, y - 2), x, y) == [(-2, 2), (2, 2)] + assert solve((x**2 - 4, y - 2), y, x) == [(2, -2), (2, 2)] + assert solve((x**2 - 4 + z, y - 2 - z), a, z, y, x, set=True + ) == ([a, z, y, x], { + (a, z, z + 2, -sqrt(4 - z)), + (a, z, z + 2, sqrt(4 - z))}) + # overdetermined system + # - nonlinear + assert solve([(x + y)**2 - 4, x + y - 2]) == [{x: -y + 2}] + # - linear + assert solve((x + y - 2, 2*x + 2*y - 4)) == {x: -y + 2} + # When one or more args are Boolean + assert solve(Eq(x**2, 0.0)) == [0.0] # issue 19048 + assert solve([True, Eq(x, 0)], [x], dict=True) == [{x: 0}] + assert solve([Eq(x, x), Eq(x, 0), Eq(x, x+1)], [x], dict=True) == [] + assert not solve([Eq(x, x+1), x < 2], x) + assert solve([Eq(x, 0), x+1<2]) == Eq(x, 0) + assert solve([Eq(x, x), Eq(x, x+1)], x) == [] + assert solve(True, x) == [] + assert solve([x - 1, False], [x], set=True) == ([], set()) + assert solve([-y*(x + y - 1)/2, (y - 1)/x/y + 1/y], + set=True, check=False) == ([x, y], {(1 - y, y), (x, 0)}) + # ordering should be canonical, fastest to order by keys instead + # of by size + assert list(solve((y - 1, x - sqrt(3)*z)).keys()) == [x, y] + # as set always returns as symbols, set even if no solution + assert solve([x - 1, x], (y, x), set=True) == ([y, x], set()) + assert solve([x - 1, x], {y, x}, set=True) == ([x, y], set()) + + +def test_solve_polynomial1(): + assert solve(3*x - 2, x) == [Rational(2, 3)] + assert solve(Eq(3*x, 2), x) == [Rational(2, 3)] + + assert set(solve(x**2 - 1, x)) == {-S.One, S.One} + assert set(solve(Eq(x**2, 1), x)) == {-S.One, S.One} + + assert solve(x - y**3, x) == [y**3] + rx = root(x, 3) + assert solve(x - y**3, y) == [ + rx, -rx/2 - sqrt(3)*I*rx/2, -rx/2 + sqrt(3)*I*rx/2] + a11, a12, a21, a22, b1, b2 = symbols('a11,a12,a21,a22,b1,b2') + + assert solve([a11*x + a12*y - b1, a21*x + a22*y - b2], x, y) == \ + { + x: (a22*b1 - a12*b2)/(a11*a22 - a12*a21), + y: (a11*b2 - a21*b1)/(a11*a22 - a12*a21), + } + + solution = {x: S.Zero, y: S.Zero} + + assert solve((x - y, x + y), x, y ) == solution + assert solve((x - y, x + y), (x, y)) == solution + assert solve((x - y, x + y), [x, y]) == solution + + assert set(solve(x**3 - 15*x - 4, x)) == { + -2 + 3**S.Half, + S(4), + -2 - 3**S.Half + } + + assert set(solve((x**2 - 1)**2 - a, x)) == \ + {sqrt(1 + sqrt(a)), -sqrt(1 + sqrt(a)), + sqrt(1 - sqrt(a)), -sqrt(1 - sqrt(a))} + + +def test_solve_polynomial2(): + assert solve(4, x) == [] + + +def test_solve_polynomial_cv_1a(): + """ + Test for solving on equations that can be converted to a polynomial equation + using the change of variable y -> x**Rational(p, q) + """ + assert solve( sqrt(x) - 1, x) == [1] + assert solve( sqrt(x) - 2, x) == [4] + assert solve( x**Rational(1, 4) - 2, x) == [16] + assert solve( x**Rational(1, 3) - 3, x) == [27] + assert solve(sqrt(x) + x**Rational(1, 3) + x**Rational(1, 4), x) == [0] + + +def test_solve_polynomial_cv_1b(): + assert set(solve(4*x*(1 - a*sqrt(x)), x)) == {S.Zero, 1/a**2} + assert set(solve(x*(root(x, 3) - 3), x)) == {S.Zero, S(27)} + + +def test_solve_polynomial_cv_2(): + """ + Test for solving on equations that can be converted to a polynomial equation + multiplying both sides of the equation by x**m + """ + assert solve(x + 1/x - 1, x) in \ + [[ S.Half + I*sqrt(3)/2, S.Half - I*sqrt(3)/2], + [ S.Half - I*sqrt(3)/2, S.Half + I*sqrt(3)/2]] + + +def test_quintics_1(): + f = x**5 - 110*x**3 - 55*x**2 + 2310*x + 979 + s = solve(f, check=False) + for r in s: + res = f.subs(x, r.n()).n() + assert tn(res, 0) + + f = x**5 - 15*x**3 - 5*x**2 + 10*x + 20 + s = solve(f) + for r in s: + assert r.func == CRootOf + + # if one uses solve to get the roots of a polynomial that has a CRootOf + # solution, make sure that the use of nfloat during the solve process + # doesn't fail. Note: if you want numerical solutions to a polynomial + # it is *much* faster to use nroots to get them than to solve the + # equation only to get RootOf solutions which are then numerically + # evaluated. So for eq = x**5 + 3*x + 7 do Poly(eq).nroots() rather + # than [i.n() for i in solve(eq)] to get the numerical roots of eq. + assert nfloat(solve(x**5 + 3*x**3 + 7)[0], exponent=False) == \ + CRootOf(x**5 + 3*x**3 + 7, 0).n() + + +def test_quintics_2(): + f = x**5 + 15*x + 12 + s = solve(f, check=False) + for r in s: + res = f.subs(x, r.n()).n() + assert tn(res, 0) + + f = x**5 - 15*x**3 - 5*x**2 + 10*x + 20 + s = solve(f) + for r in s: + assert r.func == CRootOf + + assert solve(x**5 - 6*x**3 - 6*x**2 + x - 6) == [ + CRootOf(x**5 - 6*x**3 - 6*x**2 + x - 6, 0), + CRootOf(x**5 - 6*x**3 - 6*x**2 + x - 6, 1), + CRootOf(x**5 - 6*x**3 - 6*x**2 + x - 6, 2), + CRootOf(x**5 - 6*x**3 - 6*x**2 + x - 6, 3), + CRootOf(x**5 - 6*x**3 - 6*x**2 + x - 6, 4)] + + +def test_quintics_3(): + y = x**5 + x**3 - 2**Rational(1, 3) + assert solve(y) == solve(-y) == [] + + +def test_highorder_poly(): + # just testing that the uniq generator is unpacked + sol = solve(x**6 - 2*x + 2) + assert all(isinstance(i, CRootOf) for i in sol) and len(sol) == 6 + + +def test_solve_rational(): + """Test solve for rational functions""" + assert solve( ( x - y**3 )/( (y**2)*sqrt(1 - y**2) ), x) == [y**3] + + +def test_solve_conjugate(): + """Test solve for simple conjugate functions""" + assert solve(conjugate(x) -3 + I) == [3 + I] + + +def test_solve_nonlinear(): + assert solve(x**2 - y**2, x, y, dict=True) == [{x: -y}, {x: y}] + assert solve(x**2 - y**2/exp(x), y, x, dict=True) == [{y: -x*sqrt(exp(x))}, + {y: x*sqrt(exp(x))}] + + +def test_issue_8666(): + x = symbols('x') + assert solve(Eq(x**2 - 1/(x**2 - 4), 4 - 1/(x**2 - 4)), x) == [] + assert solve(Eq(x + 1/x, 1/x), x) == [] + + +def test_issue_7228(): + assert solve(4**(2*(x**2) + 2*x) - 8, x) == [Rational(-3, 2), S.Half] + + +def test_issue_7190(): + assert solve(log(x-3) + log(x+3), x) == [sqrt(10)] + + +def test_issue_21004(): + x = symbols('x') + f = x/sqrt(x**2+1) + f_diff = f.diff(x) + assert solve(f_diff, x) == [] + + +def test_issue_24650(): + x = symbols('x') + r = solve(Eq(Piecewise((x, Eq(x, 0) | (x > 1))), 0)) + assert r == [0] + r = checksol(Eq(Piecewise((x, Eq(x, 0) | (x > 1))), 0), x, sol=0) + assert r is True + + +def test_linear_system(): + x, y, z, t, n = symbols('x, y, z, t, n') + + assert solve([x - 1, x - y, x - 2*y, y - 1], [x, y]) == [] + + assert solve([x - 1, x - y, x - 2*y, x - 1], [x, y]) == [] + assert solve([x - 1, x - 1, x - y, x - 2*y], [x, y]) == [] + + assert solve([x + 5*y - 2, -3*x + 6*y - 15], x, y) == {x: -3, y: 1} + + M = Matrix([[0, 0, n*(n + 1), (n + 1)**2, 0], + [n + 1, n + 1, -2*n - 1, -(n + 1), 0], + [-1, 0, 1, 0, 0]]) + + assert solve_linear_system(M, x, y, z, t) == \ + {x: t*(-n-1)/n, y: 0, z: t*(-n-1)/n} + + assert solve([x + y + z + t, -z - t], x, y, z, t) == {x: -y, z: -t} + + +@XFAIL +def test_linear_system_xfail(): + # https://github.com/sympy/sympy/issues/6420 + M = Matrix([[0, 15.0, 10.0, 700.0], + [1, 1, 1, 100.0], + [0, 10.0, 5.0, 200.0], + [-5.0, 0, 0, 0 ]]) + + assert solve_linear_system(M, x, y, z) == {x: 0, y: -60.0, z: 160.0} + + +def test_linear_system_function(): + a = Function('a') + assert solve([a(0, 0) + a(0, 1) + a(1, 0) + a(1, 1), -a(1, 0) - a(1, 1)], + a(0, 0), a(0, 1), a(1, 0), a(1, 1)) == {a(1, 0): -a(1, 1), a(0, 0): -a(0, 1)} + + +def test_linear_system_symbols_doesnt_hang_1(): + + def _mk_eqs(wy): + # Equations for fitting a wy*2 - 1 degree polynomial between two points, + # at end points derivatives are known up to order: wy - 1 + order = 2*wy - 1 + x, x0, x1 = symbols('x, x0, x1', real=True) + y0s = symbols('y0_:{}'.format(wy), real=True) + y1s = symbols('y1_:{}'.format(wy), real=True) + c = symbols('c_:{}'.format(order+1), real=True) + + expr = sum(coeff*x**o for o, coeff in enumerate(c)) + eqs = [] + for i in range(wy): + eqs.append(expr.diff(x, i).subs({x: x0}) - y0s[i]) + eqs.append(expr.diff(x, i).subs({x: x1}) - y1s[i]) + return eqs, c + + # + # The purpose of this test is just to see that these calls don't hang. The + # expressions returned are complicated so are not included here. Testing + # their correctness takes longer than solving the system. + # + + for n in range(1, 7+1): + eqs, c = _mk_eqs(n) + solve(eqs, c) + + +def test_linear_system_symbols_doesnt_hang_2(): + + M = Matrix([ + [66, 24, 39, 50, 88, 40, 37, 96, 16, 65, 31, 11, 37, 72, 16, 19, 55, 37, 28, 76], + [10, 93, 34, 98, 59, 44, 67, 74, 74, 94, 71, 61, 60, 23, 6, 2, 57, 8, 29, 78], + [19, 91, 57, 13, 64, 65, 24, 53, 77, 34, 85, 58, 87, 39, 39, 7, 36, 67, 91, 3], + [74, 70, 15, 53, 68, 43, 86, 83, 81, 72, 25, 46, 67, 17, 59, 25, 78, 39, 63, 6], + [69, 40, 67, 21, 67, 40, 17, 13, 93, 44, 46, 89, 62, 31, 30, 38, 18, 20, 12, 81], + [50, 22, 74, 76, 34, 45, 19, 76, 28, 28, 11, 99, 97, 82, 8, 46, 99, 57, 68, 35], + [58, 18, 45, 88, 10, 64, 9, 34, 90, 82, 17, 41, 43, 81, 45, 83, 22, 88, 24, 39], + [42, 21, 70, 68, 6, 33, 64, 81, 83, 15, 86, 75, 86, 17, 77, 34, 62, 72, 20, 24], + [ 7, 8, 2, 72, 71, 52, 96, 5, 32, 51, 31, 36, 79, 88, 25, 77, 29, 26, 33, 13], + [19, 31, 30, 85, 81, 39, 63, 28, 19, 12, 16, 49, 37, 66, 38, 13, 3, 71, 61, 51], + [29, 82, 80, 49, 26, 85, 1, 37, 2, 74, 54, 82, 26, 47, 54, 9, 35, 0, 99, 40], + [15, 49, 82, 91, 93, 57, 45, 25, 45, 97, 15, 98, 48, 52, 66, 24, 62, 54, 97, 37], + [62, 23, 73, 53, 52, 86, 28, 38, 0, 74, 92, 38, 97, 70, 71, 29, 26, 90, 67, 45], + [ 2, 32, 23, 24, 71, 37, 25, 71, 5, 41, 97, 65, 93, 13, 65, 45, 25, 88, 69, 50], + [40, 56, 1, 29, 79, 98, 79, 62, 37, 28, 45, 47, 3, 1, 32, 74, 98, 35, 84, 32], + [33, 15, 87, 79, 65, 9, 14, 63, 24, 19, 46, 28, 74, 20, 29, 96, 84, 91, 93, 1], + [97, 18, 12, 52, 1, 2, 50, 14, 52, 76, 19, 82, 41, 73, 51, 79, 13, 3, 82, 96], + [40, 28, 52, 10, 10, 71, 56, 78, 82, 5, 29, 48, 1, 26, 16, 18, 50, 76, 86, 52], + [38, 89, 83, 43, 29, 52, 90, 77, 57, 0, 67, 20, 81, 88, 48, 96, 88, 58, 14, 3]]) + + syms = x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18 = symbols('x:19') + + sol = { + x0: -S(1967374186044955317099186851240896179)/3166636564687820453598895768302256588, + x1: -S(84268280268757263347292368432053826)/791659141171955113399723942075564147, + x2: -S(229962957341664730974463872411844965)/1583318282343910226799447884151128294, + x3: S(990156781744251750886760432229180537)/6333273129375640907197791536604513176, + x4: -S(2169830351210066092046760299593096265)/18999819388126922721593374609813539528, + x5: S(4680868883477577389628494526618745355)/9499909694063461360796687304906769764, + x6: -S(1590820774344371990683178396480879213)/3166636564687820453598895768302256588, + x7: -S(54104723404825537735226491634383072)/339282489073695048599881689460956063, + x8: S(3182076494196560075964847771774733847)/6333273129375640907197791536604513176, + x9: -S(10870817431029210431989147852497539675)/18999819388126922721593374609813539528, + x10: -S(13118019242576506476316318268573312603)/18999819388126922721593374609813539528, + x11: -S(5173852969886775824855781403820641259)/4749954847031730680398343652453384882, + x12: S(4261112042731942783763341580651820563)/4749954847031730680398343652453384882, + x13: -S(821833082694661608993818117038209051)/6333273129375640907197791536604513176, + x14: S(906881575107250690508618713632090559)/904753304196520129599684505229216168, + x15: -S(732162528717458388995329317371283987)/6333273129375640907197791536604513176, + x16: S(4524215476705983545537087360959896817)/9499909694063461360796687304906769764, + x17: -S(3898571347562055611881270844646055217)/6333273129375640907197791536604513176, + x18: S(7513502486176995632751685137907442269)/18999819388126922721593374609813539528 + } + + eqs = list(M * Matrix(syms + (1,))) + assert solve(eqs, syms) == sol + + y = Symbol('y') + eqs = list(y * M * Matrix(syms + (1,))) + assert solve(eqs, syms) == sol + + +def test_linear_systemLU(): + n = Symbol('n') + + M = Matrix([[1, 2, 0, 1], [1, 3, 2*n, 1], [4, -1, n**2, 1]]) + + assert solve_linear_system_LU(M, [x, y, z]) == {z: -3/(n**2 + 18*n), + x: 1 - 12*n/(n**2 + 18*n), + y: 6*n/(n**2 + 18*n)} + +# Note: multiple solutions exist for some of these equations, so the tests +# should be expected to break if the implementation of the solver changes +# in such a way that a different branch is chosen + +@slow +def test_solve_transcendental(): + from sympy.abc import a, b + + assert solve(exp(x) - 3, x) == [log(3)] + assert set(solve((a*x + b)*(exp(x) - 3), x)) == {-b/a, log(3)} + assert solve(cos(x) - y, x) == [-acos(y) + 2*pi, acos(y)] + assert solve(2*cos(x) - y, x) == [-acos(y/2) + 2*pi, acos(y/2)] + assert solve(Eq(cos(x), sin(x)), x) == [pi/4] + + assert set(solve(exp(x) + exp(-x) - y, x)) in [{ + log(y/2 - sqrt(y**2 - 4)/2), + log(y/2 + sqrt(y**2 - 4)/2), + }, { + log(y - sqrt(y**2 - 4)) - log(2), + log(y + sqrt(y**2 - 4)) - log(2)}, + { + log(y/2 - sqrt((y - 2)*(y + 2))/2), + log(y/2 + sqrt((y - 2)*(y + 2))/2)}] + assert solve(exp(x) - 3, x) == [log(3)] + assert solve(Eq(exp(x), 3), x) == [log(3)] + assert solve(log(x) - 3, x) == [exp(3)] + assert solve(sqrt(3*x) - 4, x) == [Rational(16, 3)] + assert solve(3**(x + 2), x) == [] + assert solve(3**(2 - x), x) == [] + assert solve(x + 2**x, x) == [-LambertW(log(2))/log(2)] + assert solve(2*x + 5 + log(3*x - 2), x) == \ + [Rational(2, 3) + LambertW(2*exp(Rational(-19, 3))/3)/2] + assert solve(3*x + log(4*x), x) == [LambertW(Rational(3, 4))/3] + assert set(solve((2*x + 8)*(8 + exp(x)), x)) == {S(-4), log(8) + pi*I} + eq = 2*exp(3*x + 4) - 3 + ans = solve(eq, x) # this generated a failure in flatten + assert len(ans) == 3 and all(eq.subs(x, a).n(chop=True) == 0 for a in ans) + assert solve(2*log(3*x + 4) - 3, x) == [(exp(Rational(3, 2)) - 4)/3] + assert solve(exp(x) + 1, x) == [pi*I] + + eq = 2*(3*x + 4)**5 - 6*7**(3*x + 9) + result = solve(eq, x) + x0 = -log(2401) + x1 = 3**Rational(1, 5) + x2 = log(7**(7*x1/20)) + x3 = sqrt(2) + x4 = sqrt(5) + x5 = x3*sqrt(x4 - 5) + x6 = x4 + 1 + x7 = 1/(3*log(7)) + x8 = -x4 + x9 = x3*sqrt(x8 - 5) + x10 = x8 + 1 + ans = [x7*(x0 - 5*LambertW(x2*(-x5 + x6))), + x7*(x0 - 5*LambertW(x2*(x5 + x6))), + x7*(x0 - 5*LambertW(x2*(x10 - x9))), + x7*(x0 - 5*LambertW(x2*(x10 + x9))), + x7*(x0 - 5*LambertW(-log(7**(7*x1/5))))] + assert result == ans, result + # it works if expanded, too + assert solve(eq.expand(), x) == result + + assert solve(z*cos(x) - y, x) == [-acos(y/z) + 2*pi, acos(y/z)] + assert solve(z*cos(2*x) - y, x) == [-acos(y/z)/2 + pi, acos(y/z)/2] + assert solve(z*cos(sin(x)) - y, x) == [ + pi - asin(acos(y/z)), asin(acos(y/z) - 2*pi) + pi, + -asin(acos(y/z) - 2*pi), asin(acos(y/z))] + + assert solve(z*cos(x), x) == [pi/2, pi*Rational(3, 2)] + + # issue 4508 + assert solve(y - b*x/(a + x), x) in [[-a*y/(y - b)], [a*y/(b - y)]] + assert solve(y - b*exp(a/x), x) == [a/log(y/b)] + # issue 4507 + assert solve(y - b/(1 + a*x), x) in [[(b - y)/(a*y)], [-((y - b)/(a*y))]] + # issue 4506 + assert solve(y - a*x**b, x) == [(y/a)**(1/b)] + # issue 4505 + assert solve(z**x - y, x) == [log(y)/log(z)] + # issue 4504 + assert solve(2**x - 10, x) == [1 + log(5)/log(2)] + # issue 6744 + assert solve(x*y) == [{x: 0}, {y: 0}] + assert solve([x*y]) == [{x: 0}, {y: 0}] + assert solve(x**y - 1) == [{x: 1}, {y: 0}] + assert solve([x**y - 1]) == [{x: 1}, {y: 0}] + assert solve(x*y*(x**2 - y**2)) == [{x: 0}, {x: -y}, {x: y}, {y: 0}] + assert solve([x*y*(x**2 - y**2)]) == [{x: 0}, {x: -y}, {x: y}, {y: 0}] + # issue 4739 + assert solve(exp(log(5)*x) - 2**x, x) == [0] + # issue 14791 + assert solve(exp(log(5)*x) - exp(log(2)*x), x) == [0] + f = Function('f') + assert solve(y*f(log(5)*x) - y*f(log(2)*x), x) == [0] + assert solve(f(x) - f(0), x) == [0] + assert solve(f(x) - f(2 - x), x) == [1] + raises(NotImplementedError, lambda: solve(f(x, y) - f(1, 2), x)) + raises(NotImplementedError, lambda: solve(f(x, y) - f(2 - x, 2), x)) + raises(ValueError, lambda: solve(f(x, y) - f(1 - x), x)) + raises(ValueError, lambda: solve(f(x, y) - f(1), x)) + + # misc + # make sure that the right variables is picked up in tsolve + # shouldn't generate a GeneratorsNeeded error in _tsolve when the NaN is generated + # for eq_down. Actual answers, as determined numerically are approx. +/- 0.83 + raises(NotImplementedError, lambda: + solve(sinh(x)*sinh(sinh(x)) + cosh(x)*cosh(sinh(x)) - 3)) + + # watch out for recursive loop in tsolve + raises(NotImplementedError, lambda: solve((x + 2)**y*x - 3, x)) + + # issue 7245 + assert solve(sin(sqrt(x))) == [0, pi**2] + + # issue 7602 + a, b = symbols('a, b', real=True, negative=False) + assert str(solve(Eq(a, 0.5 - cos(pi*b)/2), b)) == \ + '[2.0 - 0.318309886183791*acos(1.0 - 2.0*a), 0.318309886183791*acos(1.0 - 2.0*a)]' + + # issue 15325 + assert solve(y**(1/x) - z, x) == [log(y)/log(z)] + + # issue 25685 (basic trig identies should give simple solutions) + for yi in [cos(2*x),sin(2*x),cos(x - pi/3)]: + sol = solve([cos(x) - S(3)/5, yi - y]) + assert (sol[0][y] + sol[1][y]).is_Rational, (yi,sol) + # don't allow massive expansion + assert solve(cos(1000*x) - S.Half) == [pi/3000, pi/600] + assert solve(cos(x - 1000*y) - 1, x) == [1000*y, 1000*y + 2*pi] + assert solve(cos(x + y + z) - 1, x) == [-y - z, -y - z + 2*pi] + + # issue 26008 + assert solve(sin(x + pi/6)) == [-pi/6, 5*pi/6] + + +def test_solve_for_functions_derivatives(): + t = Symbol('t') + x = Function('x')(t) + y = Function('y')(t) + a11, a12, a21, a22, b1, b2 = symbols('a11,a12,a21,a22,b1,b2') + + soln = solve([a11*x + a12*y - b1, a21*x + a22*y - b2], x, y) + assert soln == { + x: (a22*b1 - a12*b2)/(a11*a22 - a12*a21), + y: (a11*b2 - a21*b1)/(a11*a22 - a12*a21), + } + + assert solve(x - 1, x) == [1] + assert solve(3*x - 2, x) == [Rational(2, 3)] + + soln = solve([a11*x.diff(t) + a12*y.diff(t) - b1, a21*x.diff(t) + + a22*y.diff(t) - b2], x.diff(t), y.diff(t)) + assert soln == { y.diff(t): (a11*b2 - a21*b1)/(a11*a22 - a12*a21), + x.diff(t): (a22*b1 - a12*b2)/(a11*a22 - a12*a21) } + + assert solve(x.diff(t) - 1, x.diff(t)) == [1] + assert solve(3*x.diff(t) - 2, x.diff(t)) == [Rational(2, 3)] + + eqns = {3*x - 1, 2*y - 4} + assert solve(eqns, {x, y}) == { x: Rational(1, 3), y: 2 } + x = Symbol('x') + f = Function('f') + F = x**2 + f(x)**2 - 4*x - 1 + assert solve(F.diff(x), diff(f(x), x)) == [(-x + 2)/f(x)] + + # Mixed cased with a Symbol and a Function + x = Symbol('x') + y = Function('y')(t) + + soln = solve([a11*x + a12*y.diff(t) - b1, a21*x + + a22*y.diff(t) - b2], x, y.diff(t)) + assert soln == { y.diff(t): (a11*b2 - a21*b1)/(a11*a22 - a12*a21), + x: (a22*b1 - a12*b2)/(a11*a22 - a12*a21) } + + # issue 13263 + x = Symbol('x') + f = Function('f') + soln = solve([f(x).diff(x) + f(x).diff(x, 2) - 1, f(x).diff(x) - f(x).diff(x, 2)], + f(x).diff(x), f(x).diff(x, 2)) + assert soln == { f(x).diff(x, 2): S(1)/2, f(x).diff(x): S(1)/2 } + + soln = solve([f(x).diff(x, 2) + f(x).diff(x, 3) - 1, 1 - f(x).diff(x, 2) - + f(x).diff(x, 3), 1 - f(x).diff(x,3)], f(x).diff(x, 2), f(x).diff(x, 3)) + assert soln == { f(x).diff(x, 2): 0, f(x).diff(x, 3): 1 } + + +def test_issue_3725(): + f = Function('f') + F = x**2 + f(x)**2 - 4*x - 1 + e = F.diff(x) + assert solve(e, f(x).diff(x)) in [[(2 - x)/f(x)], [-((x - 2)/f(x))]] + + +def test_issue_3870(): + a, b, c, d = symbols('a b c d') + A = Matrix(2, 2, [a, b, c, d]) + B = Matrix(2, 2, [0, 2, -3, 0]) + C = Matrix(2, 2, [1, 2, 3, 4]) + + assert solve(A*B - C, [a, b, c, d]) == {a: 1, b: Rational(-1, 3), c: 2, d: -1} + assert solve([A*B - C], [a, b, c, d]) == {a: 1, b: Rational(-1, 3), c: 2, d: -1} + assert solve(Eq(A*B, C), [a, b, c, d]) == {a: 1, b: Rational(-1, 3), c: 2, d: -1} + + assert solve([A*B - B*A], [a, b, c, d]) == {a: d, b: Rational(-2, 3)*c} + assert solve([A*C - C*A], [a, b, c, d]) == {a: d - c, b: Rational(2, 3)*c} + assert solve([A*B - B*A, A*C - C*A], [a, b, c, d]) == {a: d, b: 0, c: 0} + + assert solve([Eq(A*B, B*A)], [a, b, c, d]) == {a: d, b: Rational(-2, 3)*c} + assert solve([Eq(A*C, C*A)], [a, b, c, d]) == {a: d - c, b: Rational(2, 3)*c} + assert solve([Eq(A*B, B*A), Eq(A*C, C*A)], [a, b, c, d]) == {a: d, b: 0, c: 0} + + +def test_solve_linear(): + w = Wild('w') + assert solve_linear(x, x) == (0, 1) + assert solve_linear(x, exclude=[x]) == (0, 1) + assert solve_linear(x, symbols=[w]) == (0, 1) + assert solve_linear(x, y - 2*x) in [(x, y/3), (y, 3*x)] + assert solve_linear(x, y - 2*x, exclude=[x]) == (y, 3*x) + assert solve_linear(3*x - y, 0) in [(x, y/3), (y, 3*x)] + assert solve_linear(3*x - y, 0, [x]) == (x, y/3) + assert solve_linear(3*x - y, 0, [y]) == (y, 3*x) + assert solve_linear(x**2/y, 1) == (y, x**2) + assert solve_linear(w, x) in [(w, x), (x, w)] + assert solve_linear(cos(x)**2 + sin(x)**2 + 2 + y) == \ + (y, -2 - cos(x)**2 - sin(x)**2) + assert solve_linear(cos(x)**2 + sin(x)**2 + 2 + y, symbols=[x]) == (0, 1) + assert solve_linear(Eq(x, 3)) == (x, 3) + assert solve_linear(1/(1/x - 2)) == (0, 0) + assert solve_linear((x + 1)*exp(-x), symbols=[x]) == (x, -1) + assert solve_linear((x + 1)*exp(x), symbols=[x]) == ((x + 1)*exp(x), 1) + assert solve_linear(x*exp(-x**2), symbols=[x]) == (x, 0) + assert solve_linear(0**x - 1) == (0**x - 1, 1) + assert solve_linear(1 + 1/(x - 1)) == (x, 0) + eq = y*cos(x)**2 + y*sin(x)**2 - y # = y*(1 - 1) = 0 + assert solve_linear(eq) == (0, 1) + eq = cos(x)**2 + sin(x)**2 # = 1 + assert solve_linear(eq) == (0, 1) + raises(ValueError, lambda: solve_linear(Eq(x, 3), 3)) + + +def test_solve_undetermined_coeffs(): + assert solve_undetermined_coeffs( + a*x**2 + b*x**2 + b*x + 2*c*x + c + 1, [a, b, c], x + ) == {a: -2, b: 2, c: -1} + # Test that rational functions work + assert solve_undetermined_coeffs(a/x + b/(x + 1) + - (2*x + 1)/(x**2 + x), [a, b], x) == {a: 1, b: 1} + # Test cancellation in rational functions + assert solve_undetermined_coeffs( + ((c + 1)*a*x**2 + (c + 1)*b*x**2 + + (c + 1)*b*x + (c + 1)*2*c*x + (c + 1)**2)/(c + 1), + [a, b, c], x) == \ + {a: -2, b: 2, c: -1} + # multivariate + X, Y, Z = y, x**y, y*x**y + eq = a*X + b*Y + c*Z - X - 2*Y - 3*Z + coeffs = a, b, c + syms = x, y + assert solve_undetermined_coeffs(eq, coeffs) == { + a: 1, b: 2, c: 3} + assert solve_undetermined_coeffs(eq, coeffs, syms) == { + a: 1, b: 2, c: 3} + assert solve_undetermined_coeffs(eq, coeffs, *syms) == { + a: 1, b: 2, c: 3} + # check output format + assert solve_undetermined_coeffs(a*x + a - 2, [a]) == [] + assert solve_undetermined_coeffs(a**2*x - 4*x, [a]) == [ + {a: -2}, {a: 2}] + assert solve_undetermined_coeffs(0, [a]) == [] + assert solve_undetermined_coeffs(0, [a], dict=True) == [] + assert solve_undetermined_coeffs(0, [a], set=True) == ([], {}) + assert solve_undetermined_coeffs(1, [a]) == [] + abeq = a*x - 2*x + b - 3 + s = {b, a} + assert solve_undetermined_coeffs(abeq, s, x) == {a: 2, b: 3} + assert solve_undetermined_coeffs(abeq, s, x, set=True) == ([a, b], {(2, 3)}) + assert solve_undetermined_coeffs(sin(a*x) - sin(2*x), (a,)) is None + assert solve_undetermined_coeffs(a*x + b*x - 2*x, (a, b)) == {a: 2 - b} + + +def test_solve_inequalities(): + x = Symbol('x') + sol = And(S.Zero < x, x < oo) + assert solve(x + 1 > 1) == sol + assert solve([x + 1 > 1]) == sol + assert solve([x + 1 > 1], x) == sol + assert solve([x + 1 > 1], [x]) == sol + + system = [Lt(x**2 - 2, 0), Gt(x**2 - 1, 0)] + assert solve(system) == \ + And(Or(And(Lt(-sqrt(2), x), Lt(x, -1)), + And(Lt(1, x), Lt(x, sqrt(2)))), Eq(0, 0)) + + x = Symbol('x', real=True) + system = [Lt(x**2 - 2, 0), Gt(x**2 - 1, 0)] + assert solve(system) == \ + Or(And(Lt(-sqrt(2), x), Lt(x, -1)), And(Lt(1, x), Lt(x, sqrt(2)))) + + # issues 6627, 3448 + assert solve((x - 3)/(x - 2) < 0, x) == And(Lt(2, x), Lt(x, 3)) + assert solve(x/(x + 1) > 1, x) == And(Lt(-oo, x), Lt(x, -1)) + + assert solve(sin(x) > S.Half) == And(pi/6 < x, x < pi*Rational(5, 6)) + + assert solve(Eq(False, x < 1)) == (S.One <= x) & (x < oo) + assert solve(Eq(True, x < 1)) == (-oo < x) & (x < 1) + assert solve(Eq(x < 1, False)) == (S.One <= x) & (x < oo) + assert solve(Eq(x < 1, True)) == (-oo < x) & (x < 1) + + assert solve(Eq(False, x)) == False + assert solve(Eq(0, x)) == [0] + assert solve(Eq(True, x)) == True + assert solve(Eq(1, x)) == [1] + assert solve(Eq(False, ~x)) == True + assert solve(Eq(True, ~x)) == False + assert solve(Ne(True, x)) == False + assert solve(Ne(1, x)) == (x > -oo) & (x < oo) & Ne(x, 1) + + +def test_issue_4793(): + assert solve(1/x) == [] + assert solve(x*(1 - 5/x)) == [5] + assert solve(x + sqrt(x) - 2) == [1] + assert solve(-(1 + x)/(2 + x)**2 + 1/(2 + x)) == [] + assert solve(-x**2 - 2*x + (x + 1)**2 - 1) == [] + assert solve((x/(x + 1) + 3)**(-2)) == [] + assert solve(x/sqrt(x**2 + 1), x) == [0] + assert solve(exp(x) - y, x) == [log(y)] + assert solve(exp(x)) == [] + assert solve(x**2 + x + sin(y)**2 + cos(y)**2 - 1, x) in [[0, -1], [-1, 0]] + eq = 4*3**(5*x + 2) - 7 + ans = solve(eq, x) + assert len(ans) == 5 and all(eq.subs(x, a).n(chop=True) == 0 for a in ans) + assert solve(log(x**2) - y**2/exp(x), x, y, set=True) == ( + [x, y], + {(x, sqrt(exp(x) * log(x ** 2))), (x, -sqrt(exp(x) * log(x ** 2)))}) + assert solve(x**2*z**2 - z**2*y**2) == [{x: -y}, {x: y}, {z: 0}] + assert solve((x - 1)/(1 + 1/(x - 1))) == [] + assert solve(x**(y*z) - x, x) == [1] + raises(NotImplementedError, lambda: solve(log(x) - exp(x), x)) + raises(NotImplementedError, lambda: solve(2**x - exp(x) - 3)) + + +def test_PR1964(): + # issue 5171 + assert solve(sqrt(x)) == solve(sqrt(x**3)) == [0] + assert solve(sqrt(x - 1)) == [1] + # issue 4462 + a = Symbol('a') + assert solve(-3*a/sqrt(x), x) == [] + # issue 4486 + assert solve(2*x/(x + 2) - 1, x) == [2] + # issue 4496 + assert set(solve((x**2/(7 - x)).diff(x))) == {S.Zero, S(14)} + # issue 4695 + f = Function('f') + assert solve((3 - 5*x/f(x))*f(x), f(x)) == [x*Rational(5, 3)] + # issue 4497 + assert solve(1/root(5 + x, 5) - 9, x) == [Rational(-295244, 59049)] + + assert solve(sqrt(x) + sqrt(sqrt(x)) - 4) == [(Rational(-1, 2) + sqrt(17)/2)**4] + assert set(solve(Poly(sqrt(exp(x)) + sqrt(exp(-x)) - 4))) in \ + [ + {log((-sqrt(3) + 2)**2), log((sqrt(3) + 2)**2)}, + {2*log(-sqrt(3) + 2), 2*log(sqrt(3) + 2)}, + {log(-4*sqrt(3) + 7), log(4*sqrt(3) + 7)}, + ] + assert set(solve(Poly(exp(x) + exp(-x) - 4))) == \ + {log(-sqrt(3) + 2), log(sqrt(3) + 2)} + assert set(solve(x**y + x**(2*y) - 1, x)) == \ + {(Rational(-1, 2) + sqrt(5)/2)**(1/y), (Rational(-1, 2) - sqrt(5)/2)**(1/y)} + + assert solve(exp(x/y)*exp(-z/y) - 2, y) == [(x - z)/log(2)] + assert solve( + x**z*y**z - 2, z) in [[log(2)/(log(x) + log(y))], [log(2)/(log(x*y))]] + # if you do inversion too soon then multiple roots (as for the following) + # will be missed, e.g. if exp(3*x) = exp(3) -> 3*x = 3 + E = S.Exp1 + assert solve(exp(3*x) - exp(3), x) in [ + [1, log(E*(Rational(-1, 2) - sqrt(3)*I/2)), log(E*(Rational(-1, 2) + sqrt(3)*I/2))], + [1, log(-E/2 - sqrt(3)*E*I/2), log(-E/2 + sqrt(3)*E*I/2)], + ] + + # coverage test + p = Symbol('p', positive=True) + assert solve((1/p + 1)**(p + 1)) == [] + + +def test_issue_5197(): + x = Symbol('x', real=True) + assert solve(x**2 + 1, x) == [] + n = Symbol('n', integer=True, positive=True) + assert solve((n - 1)*(n + 2)*(2*n - 1), n) == [1] + x = Symbol('x', positive=True) + y = Symbol('y') + assert solve([x + 5*y - 2, -3*x + 6*y - 15], x, y) == [] + # not {x: -3, y: 1} b/c x is positive + # The solution following should not contain (-sqrt(2), sqrt(2)) + assert solve([(x + y), 2 - y**2], x, y) == [(sqrt(2), -sqrt(2))] + y = Symbol('y', positive=True) + # The solution following should not contain {y: -x*exp(x/2)} + assert solve(x**2 - y**2/exp(x), y, x, dict=True) == [{y: x*exp(x/2)}] + x, y, z = symbols('x y z', positive=True) + assert solve(z**2*x**2 - z**2*y**2/exp(x), y, x, z, dict=True) == [{y: x*exp(x/2)}] + + +def test_checking(): + assert set( + solve(x*(x - y/x), x, check=False)) == {sqrt(y), S.Zero, -sqrt(y)} + assert set(solve(x*(x - y/x), x, check=True)) == {sqrt(y), -sqrt(y)} + # {x: 0, y: 4} sets denominator to 0 in the following so system should return None + assert solve((1/(1/x + 2), 1/(y - 3) - 1)) == [] + # 0 sets denominator of 1/x to zero so None is returned + assert solve(1/(1/x + 2)) == [] + + +def test_issue_4671_4463_4467(): + assert solve(sqrt(x**2 - 1) - 2) in ([sqrt(5), -sqrt(5)], + [-sqrt(5), sqrt(5)]) + assert solve((2**exp(y**2/x) + 2)/(x**2 + 15), y) == [ + -sqrt(x*log(1 + I*pi/log(2))), sqrt(x*log(1 + I*pi/log(2)))] + + C1, C2 = symbols('C1 C2') + f = Function('f') + assert solve(C1 + C2/x**2 - exp(-f(x)), f(x)) == [log(x**2/(C1*x**2 + C2))] + a = Symbol('a') + E = S.Exp1 + assert solve(1 - log(a + 4*x**2), x) in ( + [-sqrt(-a + E)/2, sqrt(-a + E)/2], + [sqrt(-a + E)/2, -sqrt(-a + E)/2] + ) + assert solve(log(a**(-3) - x**2)/a, x) in ( + [-sqrt(-1 + a**(-3)), sqrt(-1 + a**(-3))], + [sqrt(-1 + a**(-3)), -sqrt(-1 + a**(-3))],) + assert solve(1 - log(a + 4*x**2), x) in ( + [-sqrt(-a + E)/2, sqrt(-a + E)/2], + [sqrt(-a + E)/2, -sqrt(-a + E)/2],) + assert solve((a**2 + 1)*(sin(a*x) + cos(a*x)), x) == [-pi/(4*a)] + assert solve(3 - (sinh(a*x) + cosh(a*x)), x) == [log(3)/a] + assert set(solve(3 - (sinh(a*x) + cosh(a*x)**2), x)) == \ + {log(-2 + sqrt(5))/a, log(-sqrt(2) + 1)/a, + log(-sqrt(5) - 2)/a, log(1 + sqrt(2))/a} + assert solve(atan(x) - 1) == [tan(1)] + + +def test_issue_5132(): + r, t = symbols('r,t') + assert set(solve([r - x**2 - y**2, tan(t) - y/x], [x, y])) == \ + {( + -sqrt(r*cos(t)**2), -1*sqrt(r*cos(t)**2)*tan(t)), + (sqrt(r*cos(t)**2), sqrt(r*cos(t)**2)*tan(t))} + assert solve([exp(x) - sin(y), 1/y - 3], [x, y]) == \ + [(log(sin(Rational(1, 3))), Rational(1, 3))] + assert solve([exp(x) - sin(y), 1/exp(y) - 3], [x, y]) == \ + [(log(-sin(log(3))), -log(3))] + assert set(solve([exp(x) - sin(y), y**2 - 4], [x, y])) == \ + {(log(-sin(2)), -S(2)), (log(sin(2)), S(2))} + eqs = [exp(x)**2 - sin(y) + z**2, 1/exp(y) - 3] + assert solve(eqs, set=True) == \ + ([y, z], { + (-log(3), sqrt(-exp(2*x) - sin(log(3)))), + (-log(3), -sqrt(-exp(2*x) - sin(log(3))))}) + assert solve(eqs, x, z, set=True) == ( + [x, z], + {(x, sqrt(-exp(2*x) + sin(y))), (x, -sqrt(-exp(2*x) + sin(y)))}) + assert set(solve(eqs, x, y)) == \ + { + (log(-sqrt(-z**2 - sin(log(3)))), -log(3)), + (log(-z**2 - sin(log(3)))/2, -log(3))} + assert set(solve(eqs, y, z)) == \ + { + (-log(3), -sqrt(-exp(2*x) - sin(log(3)))), + (-log(3), sqrt(-exp(2*x) - sin(log(3))))} + eqs = [exp(x)**2 - sin(y) + z, 1/exp(y) - 3] + assert solve(eqs, set=True) == ([y, z], { + (-log(3), -exp(2*x) - sin(log(3)))}) + assert solve(eqs, x, z, set=True) == ( + [x, z], {(x, -exp(2*x) + sin(y))}) + assert set(solve(eqs, x, y)) == { + (log(-sqrt(-z - sin(log(3)))), -log(3)), + (log(-z - sin(log(3)))/2, -log(3))} + assert solve(eqs, z, y) == \ + [(-exp(2*x) - sin(log(3)), -log(3))] + assert solve((sqrt(x**2 + y**2) - sqrt(10), x + y - 4), set=True) == ( + [x, y], {(S.One, S(3)), (S(3), S.One)}) + assert set(solve((sqrt(x**2 + y**2) - sqrt(10), x + y - 4), x, y)) == \ + {(S.One, S(3)), (S(3), S.One)} + + +def test_issue_5335(): + lam, a0, conc = symbols('lam a0 conc') + a = 0.005 + b = 0.743436700916726 + eqs = [lam + 2*y - a0*(1 - x/2)*x - a*x/2*x, + a0*(1 - x/2)*x - 1*y - b*y, + x + y - conc] + sym = [x, y, a0] + # there are 4 solutions obtained manually but only two are valid + assert len(solve(eqs, sym, manual=True, minimal=True)) == 2 + assert len(solve(eqs, sym)) == 2 # cf below with rational=False + + +@SKIP("Hangs") +def _test_issue_5335_float(): + # gives ZeroDivisionError: polynomial division + lam, a0, conc = symbols('lam a0 conc') + a = 0.005 + b = 0.743436700916726 + eqs = [lam + 2*y - a0*(1 - x/2)*x - a*x/2*x, + a0*(1 - x/2)*x - 1*y - b*y, + x + y - conc] + sym = [x, y, a0] + assert len(solve(eqs, sym, rational=False)) == 2 + + +def test_issue_5767(): + assert set(solve([x**2 + y + 4], [x])) == \ + {(-sqrt(-y - 4),), (sqrt(-y - 4),)} + + +def _make_example_24609(): + D, R, H, B_g, V, D_c = symbols("D, R, H, B_g, V, D_c", real=True, positive=True) + Sigma_f, Sigma_a, nu = symbols("Sigma_f, Sigma_a, nu", real=True, positive=True) + x = symbols("x", real=True, positive=True) + eq = ( + 2**(S(2)/3)*pi**(S(2)/3)*D_c*(S(231361)/10000 + pi**2/x**2) + /(6*V**(S(2)/3)*x**(S(1)/3)) + - 2**(S(2)/3)*pi**(S(8)/3)*D_c/(2*V**(S(2)/3)*x**(S(7)/3)) + ) + expected = 100*sqrt(2)*pi/481 + return eq, expected, x + + +def test_issue_24609(): + # https://github.com/sympy/sympy/issues/24609 + eq, expected, x = _make_example_24609() + assert solve(eq, x, simplify=True) == [expected] + [solapprox] = solve(eq.n(), x) + assert abs(solapprox - expected.n()) < 1e-14 + + +@XFAIL +def test_issue_24609_xfail(): + # + # This returns 5 solutions when it should be 1 (with x positive). + # Simplification reveals all solutions to be equivalent. It is expected + # that solve without simplify=True returns duplicate solutions in some + # cases but the core of this equation is a simple quadratic that can easily + # be solved without introducing any redundant solutions: + # + # >>> print(factor_terms(eq.as_numer_denom()[0])) + # 2**(2/3)*pi**(2/3)*D_c*V**(2/3)*x**(7/3)*(231361*x**2 - 20000*pi**2) + # + eq, expected, x = _make_example_24609() + assert len(solve(eq, x)) == [expected] + # + # We do not want to pass this test just by using simplify so if the above + # passes then uncomment the additional test below: + # + # assert len(solve(eq, x, simplify=False)) == 1 + + +def test_polysys(): + assert set(solve([x**2 + 2/y - 2, x + y - 3], [x, y])) == \ + {(S.One, S(2)), (1 + sqrt(5), 2 - sqrt(5)), + (1 - sqrt(5), 2 + sqrt(5))} + assert solve([x**2 + y - 2, x**2 + y]) == [] + # the ordering should be whatever the user requested + assert solve([x**2 + y - 3, x - y - 4], (x, y)) != solve([x**2 + + y - 3, x - y - 4], (y, x)) + + +@slow +def test_unrad1(): + raises(NotImplementedError, lambda: + unrad(sqrt(x) + sqrt(x + 1) + sqrt(1 - sqrt(x)) + 3)) + raises(NotImplementedError, lambda: + unrad(sqrt(x) + (x + 1)**Rational(1, 3) + 2*sqrt(y))) + + s = symbols('s', cls=Dummy) + + # checkers to deal with possibility of answer coming + # back with a sign change (cf issue 5203) + def check(rv, ans): + assert bool(rv[1]) == bool(ans[1]) + if ans[1]: + return s_check(rv, ans) + e = rv[0].expand() + a = ans[0].expand() + return e in [a, -a] and rv[1] == ans[1] + + def s_check(rv, ans): + # get the dummy + rv = list(rv) + d = rv[0].atoms(Dummy) + reps = list(zip(d, [s]*len(d))) + # replace s with this dummy + rv = (rv[0].subs(reps).expand(), [rv[1][0].subs(reps), rv[1][1].subs(reps)]) + ans = (ans[0].subs(reps).expand(), [ans[1][0].subs(reps), ans[1][1].subs(reps)]) + return str(rv[0]) in [str(ans[0]), str(-ans[0])] and \ + str(rv[1]) == str(ans[1]) + + assert unrad(1) is None + assert check(unrad(sqrt(x)), + (x, [])) + assert check(unrad(sqrt(x) + 1), + (x - 1, [])) + assert check(unrad(sqrt(x) + root(x, 3) + 2), + (s**3 + s**2 + 2, [s, s**6 - x])) + assert check(unrad(sqrt(x)*root(x, 3) + 2), + (x**5 - 64, [])) + assert check(unrad(sqrt(x) + (x + 1)**Rational(1, 3)), + (x**3 - (x + 1)**2, [])) + assert check(unrad(sqrt(x) + sqrt(x + 1) + sqrt(2*x)), + (-2*sqrt(2)*x - 2*x + 1, [])) + assert check(unrad(sqrt(x) + sqrt(x + 1) + 2), + (16*x - 9, [])) + assert check(unrad(sqrt(x) + sqrt(x + 1) + sqrt(1 - x)), + (5*x**2 - 4*x, [])) + assert check(unrad(a*sqrt(x) + b*sqrt(x) + c*sqrt(y) + d*sqrt(y)), + ((a*sqrt(x) + b*sqrt(x))**2 - (c*sqrt(y) + d*sqrt(y))**2, [])) + assert check(unrad(sqrt(x) + sqrt(1 - x)), + (2*x - 1, [])) + assert check(unrad(sqrt(x) + sqrt(1 - x) - 3), + (x**2 - x + 16, [])) + assert check(unrad(sqrt(x) + sqrt(1 - x) + sqrt(2 + x)), + (5*x**2 - 2*x + 1, [])) + assert unrad(sqrt(x) + sqrt(1 - x) + sqrt(2 + x) - 3) in [ + (25*x**4 + 376*x**3 + 1256*x**2 - 2272*x + 784, []), + (25*x**8 - 476*x**6 + 2534*x**4 - 1468*x**2 + 169, [])] + assert unrad(sqrt(x) + sqrt(1 - x) + sqrt(2 + x) - sqrt(1 - 2*x)) == \ + (41*x**4 + 40*x**3 + 232*x**2 - 160*x + 16, []) # orig root at 0.487 + assert check(unrad(sqrt(x) + sqrt(x + 1)), (S.One, [])) + + eq = sqrt(x) + sqrt(x + 1) + sqrt(1 - sqrt(x)) + assert check(unrad(eq), + (16*x**2 - 9*x, [])) + assert set(solve(eq, check=False)) == {S.Zero, Rational(9, 16)} + assert solve(eq) == [] + # but this one really does have those solutions + assert set(solve(sqrt(x) - sqrt(x + 1) + sqrt(1 - sqrt(x)))) == \ + {S.Zero, Rational(9, 16)} + + assert check(unrad(sqrt(x) + root(x + 1, 3) + 2*sqrt(y), y), + (S('2*sqrt(x)*(x + 1)**(1/3) + x - 4*y + (x + 1)**(2/3)'), [])) + assert check(unrad(sqrt(x/(1 - x)) + (x + 1)**Rational(1, 3)), + (x**5 - x**4 - x**3 + 2*x**2 + x - 1, [])) + assert check(unrad(sqrt(x/(1 - x)) + 2*sqrt(y), y), + (4*x*y + x - 4*y, [])) + assert check(unrad(sqrt(x)*sqrt(1 - x) + 2, x), + (x**2 - x + 4, [])) + + # http://tutorial.math.lamar.edu/ + # Classes/Alg/SolveRadicalEqns.aspx#Solve_Rad_Ex2_a + assert solve(Eq(x, sqrt(x + 6))) == [3] + assert solve(Eq(x + sqrt(x - 4), 4)) == [4] + assert solve(Eq(1, x + sqrt(2*x - 3))) == [] + assert set(solve(Eq(sqrt(5*x + 6) - 2, x))) == {-S.One, S(2)} + assert set(solve(Eq(sqrt(2*x - 1) - sqrt(x - 4), 2))) == {S(5), S(13)} + assert solve(Eq(sqrt(x + 7) + 2, sqrt(3 - x))) == [-6] + # http://www.purplemath.com/modules/solverad.htm + assert solve((2*x - 5)**Rational(1, 3) - 3) == [16] + assert set(solve(x + 1 - root(x**4 + 4*x**3 - x, 4))) == \ + {Rational(-1, 2), Rational(-1, 3)} + assert set(solve(sqrt(2*x**2 - 7) - (3 - x))) == {-S(8), S(2)} + assert solve(sqrt(2*x + 9) - sqrt(x + 1) - sqrt(x + 4)) == [0] + assert solve(sqrt(x + 4) + sqrt(2*x - 1) - 3*sqrt(x - 1)) == [5] + assert solve(sqrt(x)*sqrt(x - 7) - 12) == [16] + assert solve(sqrt(x - 3) + sqrt(x) - 3) == [4] + assert solve(sqrt(9*x**2 + 4) - (3*x + 2)) == [0] + assert solve(sqrt(x) - 2 - 5) == [49] + assert solve(sqrt(x - 3) - sqrt(x) - 3) == [] + assert solve(sqrt(x - 1) - x + 7) == [10] + assert solve(sqrt(x - 2) - 5) == [27] + assert solve(sqrt(17*x - sqrt(x**2 - 5)) - 7) == [3] + assert solve(sqrt(x) - sqrt(x - 1) + sqrt(sqrt(x))) == [] + + # don't posify the expression in unrad and do use _mexpand + z = sqrt(2*x + 1)/sqrt(x) - sqrt(2 + 1/x) + p = posify(z)[0] + assert solve(p) == [] + assert solve(z) == [] + assert solve(z + 6*I) == [Rational(-1, 11)] + assert solve(p + 6*I) == [] + # issue 8622 + assert unrad(root(x + 1, 5) - root(x, 3)) == ( + -(x**5 - x**3 - 3*x**2 - 3*x - 1), []) + # issue #8679 + assert check(unrad(x + root(x, 3) + root(x, 3)**2 + sqrt(y), x), + (s**3 + s**2 + s + sqrt(y), [s, s**3 - x])) + + # for coverage + assert check(unrad(sqrt(x) + root(x, 3) + y), + (s**3 + s**2 + y, [s, s**6 - x])) + assert solve(sqrt(x) + root(x, 3) - 2) == [1] + raises(NotImplementedError, lambda: + solve(sqrt(x) + root(x, 3) + root(x + 1, 5) - 2)) + # fails through a different code path + raises(NotImplementedError, lambda: solve(-sqrt(2) + cosh(x)/x)) + # unrad some + assert solve(sqrt(x + root(x, 3))+root(x - y, 5), y) == [ + x + (x**Rational(1, 3) + x)**Rational(5, 2)] + assert check(unrad(sqrt(x) - root(x + 1, 3)*sqrt(x + 2) + 2), + (s**10 + 8*s**8 + 24*s**6 - 12*s**5 - 22*s**4 - 160*s**3 - 212*s**2 - + 192*s - 56, [s, s**2 - x])) + e = root(x + 1, 3) + root(x, 3) + assert unrad(e) == (2*x + 1, []) + eq = (sqrt(x) + sqrt(x + 1) + sqrt(1 - x) - 6*sqrt(5)/5) + assert check(unrad(eq), + (15625*x**4 + 173000*x**3 + 355600*x**2 - 817920*x + 331776, [])) + assert check(unrad(root(x, 4) + root(x, 4)**3 - 1), + (s**3 + s - 1, [s, s**4 - x])) + assert check(unrad(root(x, 2) + root(x, 2)**3 - 1), + (x**3 + 2*x**2 + x - 1, [])) + assert unrad(x**0.5) is None + assert check(unrad(t + root(x + y, 5) + root(x + y, 5)**3), + (s**3 + s + t, [s, s**5 - x - y])) + assert check(unrad(x + root(x + y, 5) + root(x + y, 5)**3, y), + (s**3 + s + x, [s, s**5 - x - y])) + assert check(unrad(x + root(x + y, 5) + root(x + y, 5)**3, x), + (s**5 + s**3 + s - y, [s, s**5 - x - y])) + assert check(unrad(root(x - 1, 3) + root(x + 1, 5) + root(2, 5)), + (s**5 + 5*2**Rational(1, 5)*s**4 + s**3 + 10*2**Rational(2, 5)*s**3 + + 10*2**Rational(3, 5)*s**2 + 5*2**Rational(4, 5)*s + 4, [s, s**3 - x + 1])) + raises(NotImplementedError, lambda: + unrad((root(x, 2) + root(x, 3) + root(x, 4)).subs(x, x**5 - x + 1))) + + # the simplify flag should be reset to False for unrad results; + # if it's not then this next test will take a long time + assert solve(root(x, 3) + root(x, 5) - 2) == [1] + eq = (sqrt(x) + sqrt(x + 1) + sqrt(1 - x) - 6*sqrt(5)/5) + assert check(unrad(eq), + ((5*x - 4)*(3125*x**3 + 37100*x**2 + 100800*x - 82944), [])) + ans = S(''' + [4/5, -1484/375 + 172564/(140625*(114*sqrt(12657)/78125 + + 12459439/52734375)**(1/3)) + + 4*(114*sqrt(12657)/78125 + 12459439/52734375)**(1/3)]''') + assert solve(eq) == ans + # duplicate radical handling + assert check(unrad(sqrt(x + root(x + 1, 3)) - root(x + 1, 3) - 2), + (s**3 - s**2 - 3*s - 5, [s, s**3 - x - 1])) + # cov post-processing + e = root(x**2 + 1, 3) - root(x**2 - 1, 5) - 2 + assert check(unrad(e), + (s**5 - 10*s**4 + 39*s**3 - 80*s**2 + 80*s - 30, + [s, s**3 - x**2 - 1])) + + e = sqrt(x + root(x + 1, 2)) - root(x + 1, 3) - 2 + assert check(unrad(e), + (s**6 - 2*s**5 - 7*s**4 - 3*s**3 + 26*s**2 + 40*s + 25, + [s, s**3 - x - 1])) + assert check(unrad(e, _reverse=True), + (s**6 - 14*s**5 + 73*s**4 - 187*s**3 + 276*s**2 - 228*s + 89, + [s, s**2 - x - sqrt(x + 1)])) + # this one needs r0, r1 reversal to work + assert check(unrad(sqrt(x + sqrt(root(x, 3) - 1)) - root(x, 6) - 2), + (s**12 - 2*s**8 - 8*s**7 - 8*s**6 + s**4 + 8*s**3 + 23*s**2 + + 32*s + 17, [s, s**6 - x])) + + # why does this pass + assert unrad(root(cosh(x), 3)/x*root(x + 1, 5) - 1) == ( + -(x**15 - x**3*cosh(x)**5 - 3*x**2*cosh(x)**5 - 3*x*cosh(x)**5 + - cosh(x)**5), []) + # and this fail? + #assert unrad(sqrt(cosh(x)/x) + root(x + 1, 3)*sqrt(x) - 1) == ( + # -s**6 + 6*s**5 - 15*s**4 + 20*s**3 - 15*s**2 + 6*s + x**5 + + # 2*x**4 + x**3 - 1, [s, s**2 - cosh(x)/x]) + + # watch for symbols in exponents + assert unrad(S('(x+y)**(2*y/3) + (x+y)**(1/3) + 1')) is None + assert check(unrad(S('(x+y)**(2*y/3) + (x+y)**(1/3) + 1'), x), + (s**(2*y) + s + 1, [s, s**3 - x - y])) + # should _Q be so lenient? + assert unrad(x**(S.Half/y) + y, x) == (x**(1/y) - y**2, []) + + # This tests two things: that if full unrad is attempted and fails + # the solution should still be found; also it tests that the use of + # composite + assert len(solve(sqrt(y)*x + x**3 - 1, x)) == 3 + assert len(solve(-512*y**3 + 1344*(x + 2)**Rational(1, 3)*y**2 - + 1176*(x + 2)**Rational(2, 3)*y - 169*x + 686, y, _unrad=False)) == 3 + + # watch out for when the cov doesn't involve the symbol of interest + eq = S('-x + (7*y/8 - (27*x/2 + 27*sqrt(x**2)/2)**(1/3)/3)**3 - 1') + assert solve(eq, y) == [ + 2**(S(2)/3)*(27*x + 27*sqrt(x**2))**(S(1)/3)*S(4)/21 + (512*x/343 + + S(512)/343)**(S(1)/3)*(-S(1)/2 - sqrt(3)*I/2), 2**(S(2)/3)*(27*x + + 27*sqrt(x**2))**(S(1)/3)*S(4)/21 + (512*x/343 + + S(512)/343)**(S(1)/3)*(-S(1)/2 + sqrt(3)*I/2), 2**(S(2)/3)*(27*x + + 27*sqrt(x**2))**(S(1)/3)*S(4)/21 + (512*x/343 + S(512)/343)**(S(1)/3)] + + eq = root(x + 1, 3) - (root(x, 3) + root(x, 5)) + assert check(unrad(eq), + (3*s**13 + 3*s**11 + s**9 - 1, [s, s**15 - x])) + assert check(unrad(eq - 2), + (3*s**13 + 3*s**11 + 6*s**10 + s**9 + 12*s**8 + 6*s**6 + 12*s**5 + + 12*s**3 + 7, [s, s**15 - x])) + assert check(unrad(root(x, 3) - root(x + 1, 4)/2 + root(x + 2, 3)), + (s*(4096*s**9 + 960*s**8 + 48*s**7 - s**6 - 1728), + [s, s**4 - x - 1])) # orig expr has two real roots: -1, -.389 + assert check(unrad(root(x, 3) + root(x + 1, 4) - root(x + 2, 3)/2), + (343*s**13 + 2904*s**12 + 1344*s**11 + 512*s**10 - 1323*s**9 - + 3024*s**8 - 1728*s**7 + 1701*s**5 + 216*s**4 - 729*s, [s, s**4 - x - + 1])) # orig expr has one real root: -0.048 + assert check(unrad(root(x, 3)/2 - root(x + 1, 4) + root(x + 2, 3)), + (729*s**13 - 216*s**12 + 1728*s**11 - 512*s**10 + 1701*s**9 - + 3024*s**8 + 1344*s**7 + 1323*s**5 - 2904*s**4 + 343*s, [s, s**4 - x - + 1])) # orig expr has 2 real roots: -0.91, -0.15 + assert check(unrad(root(x, 3)/2 - root(x + 1, 4) + root(x + 2, 3) - 2), + (729*s**13 + 1242*s**12 + 18496*s**10 + 129701*s**9 + 388602*s**8 + + 453312*s**7 - 612864*s**6 - 3337173*s**5 - 6332418*s**4 - 7134912*s**3 + - 5064768*s**2 - 2111913*s - 398034, [s, s**4 - x - 1])) + # orig expr has 1 real root: 19.53 + + ans = solve(sqrt(x) + sqrt(x + 1) - + sqrt(1 - x) - sqrt(2 + x)) + assert len(ans) == 1 and NS(ans[0])[:4] == '0.73' + # the fence optimization problem + # https://github.com/sympy/sympy/issues/4793#issuecomment-36994519 + F = Symbol('F') + eq = F - (2*x + 2*y + sqrt(x**2 + y**2)) + ans = F*Rational(2, 7) - sqrt(2)*F/14 + X = solve(eq, x, check=False) + for xi in reversed(X): # reverse since currently, ans is the 2nd one + Y = solve((x*y).subs(x, xi).diff(y), y, simplify=False, check=False) + if any((a - ans).expand().is_zero for a in Y): + break + else: + assert None # no answer was found + assert solve(sqrt(x + 1) + root(x, 3) - 2) == S(''' + [(-11/(9*(47/54 + sqrt(93)/6)**(1/3)) + 1/3 + (47/54 + + sqrt(93)/6)**(1/3))**3]''') + assert solve(sqrt(sqrt(x + 1)) + x**Rational(1, 3) - 2) == S(''' + [(-sqrt(-2*(-1/16 + sqrt(6913)/16)**(1/3) + 6/(-1/16 + + sqrt(6913)/16)**(1/3) + 17/2 + 121/(4*sqrt(-6/(-1/16 + + sqrt(6913)/16)**(1/3) + 2*(-1/16 + sqrt(6913)/16)**(1/3) + 17/4)))/2 + + sqrt(-6/(-1/16 + sqrt(6913)/16)**(1/3) + 2*(-1/16 + + sqrt(6913)/16)**(1/3) + 17/4)/2 + 9/4)**3]''') + assert solve(sqrt(x) + root(sqrt(x) + 1, 3) - 2) == S(''' + [(-(81/2 + 3*sqrt(741)/2)**(1/3)/3 + (81/2 + 3*sqrt(741)/2)**(-1/3) + + 2)**2]''') + eq = S(''' + -x + (1/2 - sqrt(3)*I/2)*(3*x**3/2 - x*(3*x**2 - 34)/2 + sqrt((-3*x**3 + + x*(3*x**2 - 34) + 90)**2/4 - 39304/27) - 45)**(1/3) + 34/(3*(1/2 - + sqrt(3)*I/2)*(3*x**3/2 - x*(3*x**2 - 34)/2 + sqrt((-3*x**3 + x*(3*x**2 + - 34) + 90)**2/4 - 39304/27) - 45)**(1/3))''') + assert check(unrad(eq), + (s*-(-s**6 + sqrt(3)*s**6*I - 153*2**Rational(2, 3)*3**Rational(1, 3)*s**4 + + 51*12**Rational(1, 3)*s**4 - 102*2**Rational(2, 3)*3**Rational(5, 6)*s**4*I - 1620*s**3 + + 1620*sqrt(3)*s**3*I + 13872*18**Rational(1, 3)*s**2 - 471648 + + 471648*sqrt(3)*I), [s, s**3 - 306*x - sqrt(3)*sqrt(31212*x**2 - + 165240*x + 61484) + 810])) + + assert solve(eq) == [] # not other code errors + eq = root(x, 3) - root(y, 3) + root(x, 5) + assert check(unrad(eq), + (s**15 + 3*s**13 + 3*s**11 + s**9 - y, [s, s**15 - x])) + eq = root(x, 3) + root(y, 3) + root(x*y, 4) + assert check(unrad(eq), + (s*y*(-s**12 - 3*s**11*y - 3*s**10*y**2 - s**9*y**3 - + 3*s**8*y**2 + 21*s**7*y**3 - 3*s**6*y**4 - 3*s**4*y**4 - + 3*s**3*y**5 - y**6), [s, s**4 - x*y])) + raises(NotImplementedError, + lambda: unrad(root(x, 3) + root(y, 3) + root(x*y, 5))) + + # Test unrad with an Equality + eq = Eq(-x**(S(1)/5) + x**(S(1)/3), -3**(S(1)/3) - (-1)**(S(3)/5)*3**(S(1)/5)) + assert check(unrad(eq), + (-s**5 + s**3 - 3**(S(1)/3) - (-1)**(S(3)/5)*3**(S(1)/5), [s, s**15 - x])) + + # make sure buried radicals are exposed + s = sqrt(x) - 1 + assert unrad(s**2 - s**3) == (x**3 - 6*x**2 + 9*x - 4, []) + # make sure numerators which are already polynomial are rejected + assert unrad((x/(x + 1) + 3)**(-2), x) is None + + # https://github.com/sympy/sympy/issues/23707 + eq = sqrt(x - y)*exp(t*sqrt(x - y)) - exp(t*sqrt(x - y)) + assert solve(eq, y) == [x - 1] + assert unrad(eq) is None + + +@slow +def test_unrad_slow(): + # this has roots with multiplicity > 1; there should be no + # repeats in roots obtained, however + eq = (sqrt(1 + sqrt(1 - 4*x**2)) - x*(1 + sqrt(1 + 2*sqrt(1 - 4*x**2)))) + assert solve(eq) == [S.Half] + + +@XFAIL +def test_unrad_fail(): + # this only works if we check real_root(eq.subs(x, Rational(1, 3))) + # but checksol doesn't work like that + assert solve(root(x**3 - 3*x**2, 3) + 1 - x) == [Rational(1, 3)] + assert solve(root(x + 1, 3) + root(x**2 - 2, 5) + 1) == [ + -1, -1 + CRootOf(x**5 + x**4 + 5*x**3 + 8*x**2 + 10*x + 5, 0)**3] + + +def test_checksol(): + x, y, r, t = symbols('x, y, r, t') + eq = r - x**2 - y**2 + dict_var_soln = {y: - sqrt(r) / sqrt(tan(t)**2 + 1), + x: -sqrt(r)*tan(t)/sqrt(tan(t)**2 + 1)} + assert checksol(eq, dict_var_soln) == True + assert checksol(Eq(x, False), {x: False}) is True + assert checksol(Ne(x, False), {x: False}) is False + assert checksol(Eq(x < 1, True), {x: 0}) is True + assert checksol(Eq(x < 1, True), {x: 1}) is False + assert checksol(Eq(x < 1, False), {x: 1}) is True + assert checksol(Eq(x < 1, False), {x: 0}) is False + assert checksol(Eq(x + 1, x**2 + 1), {x: 1}) is True + assert checksol([x - 1, x**2 - 1], x, 1) is True + assert checksol([x - 1, x**2 - 2], x, 1) is False + assert checksol(Poly(x**2 - 1), x, 1) is True + assert checksol(0, {}) is True + assert checksol([1e-10, x - 2], x, 2) is False + assert checksol([0.5, 0, x], x, 0) is False + assert checksol(y, x, 2) is False + assert checksol(x+1e-10, x, 0, numerical=True) is True + assert checksol(x+1e-10, x, 0, numerical=False) is False + assert checksol(exp(92*x), {x: log(sqrt(2)/2)}) is False + assert checksol(exp(92*x), {x: log(sqrt(2)/2) + I*pi}) is False + assert checksol(1/x**5, x, 1000) is False + raises(ValueError, lambda: checksol(x, 1)) + raises(ValueError, lambda: checksol([], x, 1)) + + +def test__invert(): + assert _invert(x - 2) == (2, x) + assert _invert(2) == (2, 0) + assert _invert(exp(1/x) - 3, x) == (1/log(3), x) + assert _invert(exp(1/x + a/x) - 3, x) == ((a + 1)/log(3), x) + assert _invert(a, x) == (a, 0) + + +def test_issue_4463(): + assert solve(-a*x + 2*x*log(x), x) == [exp(a/2)] + assert solve(x**x) == [] + assert solve(x**x - 2) == [exp(LambertW(log(2)))] + assert solve(((x - 3)*(x - 2))**((x - 3)*(x - 4))) == [2] + +@slow +def test_issue_5114_solvers(): + a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r = symbols('a:r') + + # there is no 'a' in the equation set but this is how the + # problem was originally posed + syms = a, b, c, f, h, k, n + eqs = [b + r/d - c/d, + c*(1/d + 1/e + 1/g) - f/g - r/d, + f*(1/g + 1/i + 1/j) - c/g - h/i, + h*(1/i + 1/l + 1/m) - f/i - k/m, + k*(1/m + 1/o + 1/p) - h/m - n/p, + n*(1/p + 1/q) - k/p] + assert len(solve(eqs, syms, manual=True, check=False, simplify=False)) == 1 + + +def test_issue_5849(): + # + # XXX: This system does not have a solution for most values of the + # parameters. Generally solve returns the empty set for systems that are + # generically inconsistent. + # + I1, I2, I3, I4, I5, I6 = symbols('I1:7') + dI1, dI4, dQ2, dQ4, Q2, Q4 = symbols('dI1,dI4,dQ2,dQ4,Q2,Q4') + + e = ( + I1 - I2 - I3, + I3 - I4 - I5, + I4 + I5 - I6, + -I1 + I2 + I6, + -2*I1 - 2*I3 - 2*I5 - 3*I6 - dI1/2 + 12, + -I4 + dQ4, + -I2 + dQ2, + 2*I3 + 2*I5 + 3*I6 - Q2, + I4 - 2*I5 + 2*Q4 + dI4 + ) + + ans = [{ + I1: I2 + I3, + dI1: -4*I2 - 8*I3 - 4*I5 - 6*I6 + 24, + I4: I3 - I5, + dQ4: I3 - I5, + Q4: -I3/2 + 3*I5/2 - dI4/2, + dQ2: I2, + Q2: 2*I3 + 2*I5 + 3*I6}] + + v = I1, I4, Q2, Q4, dI1, dI4, dQ2, dQ4 + assert solve(e, *v, manual=True, check=False, dict=True) == ans + assert solve(e, *v, manual=True, check=False) == [ + tuple([a.get(i, i) for i in v]) for a in ans] + assert solve(e, *v, manual=True) == [] + assert solve(e, *v) == [] + + # the matrix solver (tested below) doesn't like this because it produces + # a zero row in the matrix. Is this related to issue 4551? + assert [ei.subs( + ans[0]) for ei in e] == [0, 0, I3 - I6, -I3 + I6, 0, 0, 0, 0, 0] + + +def test_issue_5849_matrix(): + '''Same as test_issue_5849 but solved with the matrix solver. + + A solution only exists if I3 == I6 which is not generically true, + but `solve` does not return conditions under which the solution is + valid, only a solution that is canonical and consistent with the input. + ''' + # a simple example with the same issue + # assert solve([x+y+z, x+y], [x, y]) == {x: y} + # the longer example + I1, I2, I3, I4, I5, I6 = symbols('I1:7') + dI1, dI4, dQ2, dQ4, Q2, Q4 = symbols('dI1,dI4,dQ2,dQ4,Q2,Q4') + + e = ( + I1 - I2 - I3, + I3 - I4 - I5, + I4 + I5 - I6, + -I1 + I2 + I6, + -2*I1 - 2*I3 - 2*I5 - 3*I6 - dI1/2 + 12, + -I4 + dQ4, + -I2 + dQ2, + 2*I3 + 2*I5 + 3*I6 - Q2, + I4 - 2*I5 + 2*Q4 + dI4 + ) + assert solve(e, I1, I4, Q2, Q4, dI1, dI4, dQ2, dQ4) == [] + + +def test_issue_21882(): + + a, b, c, d, f, g, k = unknowns = symbols('a, b, c, d, f, g, k') + + equations = [ + -k*a + b + 5*f/6 + 2*c/9 + 5*d/6 + 4*a/3, + -k*f + 4*f/3 + d/2, + -k*d + f/6 + d, + 13*b/18 + 13*c/18 + 13*a/18, + -k*c + b/2 + 20*c/9 + a, + -k*b + b + c/18 + a/6, + 5*b/3 + c/3 + a, + 2*b/3 + 2*c + 4*a/3, + -g, + ] + + answer = [ + {a: 0, f: 0, b: 0, d: 0, c: 0, g: 0}, + {a: 0, f: -d, b: 0, k: S(5)/6, c: 0, g: 0}, + {a: -2*c, f: 0, b: c, d: 0, k: S(13)/18, g: 0}] + # but not {a: 0, f: 0, b: 0, k: S(3)/2, c: 0, d: 0, g: 0} + # since this is already covered by the first solution + got = solve(equations, unknowns, dict=True) + assert got == answer, (got,answer) + + +def test_issue_5901(): + f, g, h = map(Function, 'fgh') + a = Symbol('a') + D = Derivative(f(x), x) + G = Derivative(g(a), a) + assert solve(f(x) + f(x).diff(x), f(x)) == \ + [-D] + assert solve(f(x) - 3, f(x)) == \ + [3] + assert solve(f(x) - 3*f(x).diff(x), f(x)) == \ + [3*D] + assert solve([f(x) - 3*f(x).diff(x)], f(x)) == \ + {f(x): 3*D} + assert solve([f(x) - 3*f(x).diff(x), f(x)**2 - y + 4], f(x), y) == \ + [(3*D, 9*D**2 + 4)] + assert solve(-f(a)**2*g(a)**2 + f(a)**2*h(a)**2 + g(a).diff(a), + h(a), g(a), set=True) == \ + ([h(a), g(a)], { + (-sqrt(f(a)**2*g(a)**2 - G)/f(a), g(a)), + (sqrt(f(a)**2*g(a)**2 - G)/f(a), g(a))}), solve(-f(a)**2*g(a)**2 + f(a)**2*h(a)**2 + g(a).diff(a), + h(a), g(a), set=True) + args = [[f(x).diff(x, 2)*(f(x) + g(x)), 2 - g(x)**2], f(x), g(x)] + assert solve(*args, set=True)[1] == \ + {(-sqrt(2), sqrt(2)), (sqrt(2), -sqrt(2))} + eqs = [f(x)**2 + g(x) - 2*f(x).diff(x), g(x)**2 - 4] + assert solve(eqs, f(x), g(x), set=True) == \ + ([f(x), g(x)], { + (-sqrt(2*D - 2), S(2)), + (sqrt(2*D - 2), S(2)), + (-sqrt(2*D + 2), -S(2)), + (sqrt(2*D + 2), -S(2))}) + + # the underlying problem was in solve_linear that was not masking off + # anything but a Mul or Add; it now raises an error if it gets anything + # but a symbol and solve handles the substitutions necessary so solve_linear + # won't make this error + raises( + ValueError, lambda: solve_linear(f(x) + f(x).diff(x), symbols=[f(x)])) + assert solve_linear(f(x) + f(x).diff(x), symbols=[x]) == \ + (f(x) + Derivative(f(x), x), 1) + assert solve_linear(f(x) + Integral(x, (x, y)), symbols=[x]) == \ + (f(x) + Integral(x, (x, y)), 1) + assert solve_linear(f(x) + Integral(x, (x, y)) + x, symbols=[x]) == \ + (x + f(x) + Integral(x, (x, y)), 1) + assert solve_linear(f(y) + Integral(x, (x, y)) + x, symbols=[x]) == \ + (x, -f(y) - Integral(x, (x, y))) + assert solve_linear(x - f(x)/a + (f(x) - 1)/a, symbols=[x]) == \ + (x, 1/a) + assert solve_linear(x + Derivative(2*x, x)) == \ + (x, -2) + assert solve_linear(x + Integral(x, y), symbols=[x]) == \ + (x, 0) + assert solve_linear(x + Integral(x, y) - 2, symbols=[x]) == \ + (x, 2/(y + 1)) + + assert set(solve(x + exp(x)**2, exp(x))) == \ + {-sqrt(-x), sqrt(-x)} + assert solve(x + exp(x), x, implicit=True) == \ + [-exp(x)] + assert solve(cos(x) - sin(x), x, implicit=True) == [] + assert solve(x - sin(x), x, implicit=True) == \ + [sin(x)] + assert solve(x**2 + x - 3, x, implicit=True) == \ + [-x**2 + 3] + assert solve(x**2 + x - 3, x**2, implicit=True) == \ + [-x + 3] + + +def test_issue_5912(): + assert set(solve(x**2 - x - 0.1, rational=True)) == \ + {S.Half + sqrt(35)/10, -sqrt(35)/10 + S.Half} + ans = solve(x**2 - x - 0.1, rational=False) + assert len(ans) == 2 and all(a.is_Number for a in ans) + ans = solve(x**2 - x - 0.1) + assert len(ans) == 2 and all(a.is_Number for a in ans) + + +def test_float_handling(): + def test(e1, e2): + return len(e1.atoms(Float)) == len(e2.atoms(Float)) + assert solve(x - 0.5, rational=True)[0].is_Rational + assert solve(x - 0.5, rational=False)[0].is_Float + assert solve(x - S.Half, rational=False)[0].is_Rational + assert solve(x - 0.5, rational=None)[0].is_Float + assert solve(x - S.Half, rational=None)[0].is_Rational + assert test(nfloat(1 + 2*x), 1.0 + 2.0*x) + for contain in [list, tuple, set]: + ans = nfloat(contain([1 + 2*x])) + assert type(ans) is contain and test(list(ans)[0], 1.0 + 2.0*x) + k, v = list(nfloat({2*x: [1 + 2*x]}).items())[0] + assert test(k, 2*x) and test(v[0], 1.0 + 2.0*x) + assert test(nfloat(cos(2*x)), cos(2.0*x)) + assert test(nfloat(3*x**2), 3.0*x**2) + assert test(nfloat(3*x**2, exponent=True), 3.0*x**2.0) + assert test(nfloat(exp(2*x)), exp(2.0*x)) + assert test(nfloat(x/3), x/3.0) + assert test(nfloat(x**4 + 2*x + cos(Rational(1, 3)) + 1), + x**4 + 2.0*x + 1.94495694631474) + # don't call nfloat if there is no solution + tot = 100 + c + z + t + assert solve(((.7 + c)/tot - .6, (.2 + z)/tot - .3, t/tot - .1)) == [] + + +def test_check_assumptions(): + x = symbols('x', positive=True) + assert solve(x**2 - 1) == [1] + + +def test_issue_6056(): + assert solve(tanh(x + 3)*tanh(x - 3) - 1) == [] + assert solve(tanh(x - 1)*tanh(x + 1) + 1) == \ + [I*pi*Rational(-3, 4), -I*pi/4, I*pi/4, I*pi*Rational(3, 4)] + assert solve((tanh(x + 3)*tanh(x - 3) + 1)**2) == \ + [I*pi*Rational(-3, 4), -I*pi/4, I*pi/4, I*pi*Rational(3, 4)] + + +def test_issue_5673(): + eq = -x + exp(exp(LambertW(log(x)))*LambertW(log(x))) + assert checksol(eq, x, 2) is True + assert checksol(eq, x, 2, numerical=False) is None + + +def test_exclude(): + R, C, Ri, Vout, V1, Vminus, Vplus, s = \ + symbols('R, C, Ri, Vout, V1, Vminus, Vplus, s') + Rf = symbols('Rf', positive=True) # to eliminate Rf = 0 soln + eqs = [C*V1*s + Vplus*(-2*C*s - 1/R), + Vminus*(-1/Ri - 1/Rf) + Vout/Rf, + C*Vplus*s + V1*(-C*s - 1/R) + Vout/R, + -Vminus + Vplus] + assert solve(eqs, exclude=s*C*R) == [ + { + Rf: Ri*(C*R*s + 1)**2/(C*R*s), + Vminus: Vplus, + V1: 2*Vplus + Vplus/(C*R*s), + Vout: C*R*Vplus*s + 3*Vplus + Vplus/(C*R*s)}, + { + Vplus: 0, + Vminus: 0, + V1: 0, + Vout: 0}, + ] + + # TODO: Investigate why currently solution [0] is preferred over [1]. + assert solve(eqs, exclude=[Vplus, s, C]) in [[{ + Vminus: Vplus, + V1: Vout/2 + Vplus/2 + sqrt((Vout - 5*Vplus)*(Vout - Vplus))/2, + R: (Vout - 3*Vplus - sqrt(Vout**2 - 6*Vout*Vplus + 5*Vplus**2))/(2*C*Vplus*s), + Rf: Ri*(Vout - Vplus)/Vplus, + }, { + Vminus: Vplus, + V1: Vout/2 + Vplus/2 - sqrt((Vout - 5*Vplus)*(Vout - Vplus))/2, + R: (Vout - 3*Vplus + sqrt(Vout**2 - 6*Vout*Vplus + 5*Vplus**2))/(2*C*Vplus*s), + Rf: Ri*(Vout - Vplus)/Vplus, + }], [{ + Vminus: Vplus, + Vout: (V1**2 - V1*Vplus - Vplus**2)/(V1 - 2*Vplus), + Rf: Ri*(V1 - Vplus)**2/(Vplus*(V1 - 2*Vplus)), + R: Vplus/(C*s*(V1 - 2*Vplus)), + }]] + + +def test_high_order_roots(): + s = x**5 + 4*x**3 + 3*x**2 + Rational(7, 4) + assert set(solve(s)) == set(Poly(s*4, domain='ZZ').all_roots()) + + +def test_minsolve_linear_system(): + pqt = {"quick": True, "particular": True} + pqf = {"quick": False, "particular": True} + assert solve([x + y - 5, 2*x - y - 1], **pqt) == {x: 2, y: 3} + assert solve([x + y - 5, 2*x - y - 1], **pqf) == {x: 2, y: 3} + def count(dic): + return len([x for x in dic.values() if x == 0]) + assert count(solve([x + y + z, y + z + a + t], **pqt)) == 3 + assert count(solve([x + y + z, y + z + a + t], **pqf)) == 3 + assert count(solve([x + y + z, y + z + a], **pqt)) == 1 + assert count(solve([x + y + z, y + z + a], **pqf)) == 2 + # issue 22718 + A = Matrix([ + [ 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0], + [ 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, -1, -1, 0, 0], + [-1, -1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 1, 0, 1], + [ 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, -1, 0, -1, 0], + [-1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 1], + [-1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1], + [ 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, -1, -1, 0], + [ 0, -1, -1, 0, 0, 0, 0, -1, 0, 0, 0, 1, 1, 1], + [ 0, -1, 0, -1, 0, 0, 0, 0, -1, 0, 0, -1, 0, -1], + [ 0, 0, -1, -1, 0, 0, 0, 0, 0, -1, 0, 0, -1, -1], + [ 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], + [ 0, 0, 0, 0, -1, -1, 0, -1, 0, 0, 0, 0, 0, 0]]) + v = Matrix(symbols("v:14", integer=True)) + B = Matrix([[2], [-2], [0], [0], [0], [0], [0], [0], [0], + [0], [0], [0]]) + eqs = A@v-B + assert solve(eqs) == [] + assert solve(eqs, particular=True) == [] # assumption violated + assert all(v for v in solve([x + y + z, y + z + a]).values()) + for _q in (True, False): + assert not all(v for v in solve( + [x + y + z, y + z + a], quick=_q, + particular=True).values()) + # raise error if quick used w/o particular=True + raises(ValueError, lambda: solve([x + 1], quick=_q)) + raises(ValueError, lambda: solve([x + 1], quick=_q, particular=False)) + # and give a good error message if someone tries to use + # particular with a single equation + raises(ValueError, lambda: solve(x + 1, particular=True)) + + +def test_real_roots(): + # cf. issue 6650 + x = Symbol('x', real=True) + assert len(solve(x**5 + x**3 + 1)) == 1 + + +def test_issue_6528(): + eqs = [ + 327600995*x**2 - 37869137*x + 1809975124*y**2 - 9998905626, + 895613949*x**2 - 273830224*x*y + 530506983*y**2 - 10000000000] + # two expressions encountered are > 1400 ops long so if this hangs + # it is likely because simplification is being done + assert len(solve(eqs, y, x, check=False)) == 4 + + +def test_overdetermined(): + x = symbols('x', real=True) + eqs = [Abs(4*x - 7) - 5, Abs(3 - 8*x) - 1] + assert solve(eqs, x) == [(S.Half,)] + assert solve(eqs, x, manual=True) == [(S.Half,)] + assert solve(eqs, x, manual=True, check=False) == [(S.Half,), (S(3),)] + + +def test_issue_6605(): + x = symbols('x') + assert solve(4**(x/2) - 2**(x/3)) == [0, 3*I*pi/log(2)] + # while the first one passed, this one failed + x = symbols('x', real=True) + assert solve(5**(x/2) - 2**(x/3)) == [0] + b = sqrt(6)*sqrt(log(2))/sqrt(log(5)) + assert solve(5**(x/2) - 2**(3/x)) == [-b, b] + + +def test__ispow(): + assert _ispow(x**2) + assert not _ispow(x) + assert not _ispow(True) + + +def test_issue_6644(): + eq = -sqrt((m - q)**2 + (-m/(2*q) + S.Half)**2) + sqrt((-m**2/2 - sqrt( + 4*m**4 - 4*m**2 + 8*m + 1)/4 - Rational(1, 4))**2 + (m**2/2 - m - sqrt( + 4*m**4 - 4*m**2 + 8*m + 1)/4 - Rational(1, 4))**2) + sol = solve(eq, q, simplify=False, check=False) + assert len(sol) == 5 + + +def test_issue_6752(): + assert solve([a**2 + a, a - b], [a, b]) == [(-1, -1), (0, 0)] + assert solve([a**2 + a*c, a - b], [a, b]) == [(0, 0), (-c, -c)] + + +def test_issue_6792(): + assert solve(x*(x - 1)**2*(x + 1)*(x**6 - x + 1)) == [ + -1, 0, 1, CRootOf(x**6 - x + 1, 0), CRootOf(x**6 - x + 1, 1), + CRootOf(x**6 - x + 1, 2), CRootOf(x**6 - x + 1, 3), + CRootOf(x**6 - x + 1, 4), CRootOf(x**6 - x + 1, 5)] + + +def test_issues_6819_6820_6821_6248_8692_25777_25779(): + # issue 6821 + x, y = symbols('x y', real=True) + assert solve(abs(x + 3) - 2*abs(x - 3)) == [1, 9] + assert solve([abs(x) - 2, arg(x) - pi], x) == [(-2,)] + assert set(solve(abs(x - 7) - 8)) == {-S.One, S(15)} + + # issue 8692 + assert solve(Eq(Abs(x + 1) + Abs(x**2 - 7), 9), x) == [ + Rational(-1, 2) + sqrt(61)/2, -sqrt(69)/2 + S.Half] + + # issue 7145 + assert solve(2*abs(x) - abs(x - 1)) == [-1, Rational(1, 3)] + + # 25777 + assert solve(abs(x**3 + x + 2)/(x + 1)) == [] + + # 25779 + assert solve(abs(x)) == [0] + assert solve(Eq(abs(x**2 - 2*x), 4), x) == [ + 1 - sqrt(5), 1 + sqrt(5)] + nn = symbols('nn', nonnegative=True) + assert solve(abs(sqrt(nn))) == [0] + nz = symbols('nz', nonzero=True) + assert solve(Eq(Abs(4 + 1 / (4*nz)), 0)) == [-Rational(1, 16)] + + x = symbols('x') + assert solve([re(x) - 1, im(x) - 2], x) == [ + {x: 1 + 2*I, re(x): 1, im(x): 2}] + + # check for 'dict' handling of solution + eq = sqrt(re(x)**2 + im(x)**2) - 3 + assert solve(eq) == solve(eq, x) + + i = symbols('i', imaginary=True) + assert solve(abs(i) - 3) == [-3*I, 3*I] + raises(NotImplementedError, lambda: solve(abs(x) - 3)) + + w = symbols('w', integer=True) + assert solve(2*x**w - 4*y**w, w) == solve((x/y)**w - 2, w) + + x, y = symbols('x y', real=True) + assert solve(x + y*I + 3) == {y: 0, x: -3} + # issue 2642 + assert solve(x*(1 + I)) == [0] + + x, y = symbols('x y', imaginary=True) + assert solve(x + y*I + 3 + 2*I) == {x: -2*I, y: 3*I} + + x = symbols('x', real=True) + assert solve(x + y + 3 + 2*I) == {x: -3, y: -2*I} + + # issue 6248 + f = Function('f') + assert solve(f(x + 1) - f(2*x - 1)) == [2] + assert solve(log(x + 1) - log(2*x - 1)) == [2] + + x = symbols('x') + assert solve(2**x + 4**x) == [I*pi/log(2)] + +def test_issue_17638(): + + assert solve(((2-exp(2*x))*exp(x))/(exp(2*x)+2)**2 > 0, x) == (-oo < x) & (x < log(2)/2) + assert solve(((2-exp(2*x)+2)*exp(x+2))/(exp(x)+2)**2 > 0, x) == (-oo < x) & (x < log(4)/2) + assert solve((exp(x)+2+x**2)*exp(2*x+2)/(exp(x)+2)**2 > 0, x) == (-oo < x) & (x < oo) + + + +def test_issue_14607(): + # issue 14607 + s, tau_c, tau_1, tau_2, phi, K = symbols( + 's, tau_c, tau_1, tau_2, phi, K') + + target = (s**2*tau_1*tau_2 + s*tau_1 + s*tau_2 + 1)/(K*s*(-phi + tau_c)) + + K_C, tau_I, tau_D = symbols('K_C, tau_I, tau_D', + positive=True, nonzero=True) + PID = K_C*(1 + 1/(tau_I*s) + tau_D*s) + + eq = (target - PID).together() + eq *= denom(eq).simplify() + eq = Poly(eq, s) + c = eq.coeffs() + + vars = [K_C, tau_I, tau_D] + s = solve(c, vars, dict=True) + + assert len(s) == 1 + + knownsolution = {K_C: -(tau_1 + tau_2)/(K*(phi - tau_c)), + tau_I: tau_1 + tau_2, + tau_D: tau_1*tau_2/(tau_1 + tau_2)} + + for var in vars: + assert s[0][var].simplify() == knownsolution[var].simplify() + + +def test_lambert_multivariate(): + from sympy.abc import x, y + assert _filtered_gens(Poly(x + 1/x + exp(x) + y), x) == {x, exp(x)} + assert _lambert(x, x) == [] + assert solve((x**2 - 2*x + 1).subs(x, log(x) + 3*x)) == [LambertW(3*S.Exp1)/3] + assert solve((x**2 - 2*x + 1).subs(x, (log(x) + 3*x)**2 - 1)) == \ + [LambertW(3*exp(-sqrt(2)))/3, LambertW(3*exp(sqrt(2)))/3] + assert solve((x**2 - 2*x - 2).subs(x, log(x) + 3*x)) == \ + [LambertW(3*exp(1 - sqrt(3)))/3, LambertW(3*exp(1 + sqrt(3)))/3] + eq = (x*exp(x) - 3).subs(x, x*exp(x)) + assert solve(eq) == [LambertW(3*exp(-LambertW(3)))] + # coverage test + raises(NotImplementedError, lambda: solve(x - sin(x)*log(y - x), x)) + ans = [3, -3*LambertW(-log(3)/3)/log(3)] # 3 and 2.478... + assert solve(x**3 - 3**x, x) == ans + assert set(solve(3*log(x) - x*log(3))) == set(ans) + assert solve(LambertW(2*x) - y, x) == [y*exp(y)/2] + + +@XFAIL +def test_other_lambert(): + assert solve(3*sin(x) - x*sin(3), x) == [3] + assert set(solve(x**a - a**x), x) == { + a, -a*LambertW(-log(a)/a)/log(a)} + + +@slow +def test_lambert_bivariate(): + # tests passing current implementation + assert solve((x**2 + x)*exp(x**2 + x) - 1) == [ + Rational(-1, 2) + sqrt(1 + 4*LambertW(1))/2, + Rational(-1, 2) - sqrt(1 + 4*LambertW(1))/2] + assert solve((x**2 + x)*exp((x**2 + x)*2) - 1) == [ + Rational(-1, 2) + sqrt(1 + 2*LambertW(2))/2, + Rational(-1, 2) - sqrt(1 + 2*LambertW(2))/2] + assert solve(a/x + exp(x/2), x) == [2*LambertW(-a/2)] + assert solve((a/x + exp(x/2)).diff(x), x) == \ + [4*LambertW(-sqrt(2)*sqrt(a)/4), 4*LambertW(sqrt(2)*sqrt(a)/4)] + assert solve((1/x + exp(x/2)).diff(x), x) == \ + [4*LambertW(-sqrt(2)/4), + 4*LambertW(sqrt(2)/4), # nsimplifies as 2*2**(141/299)*3**(206/299)*5**(205/299)*7**(37/299)/21 + 4*LambertW(-sqrt(2)/4, -1)] + assert solve(x*log(x) + 3*x + 1, x) == \ + [exp(-3 + LambertW(-exp(3)))] + assert solve(-x**2 + 2**x, x) == [2, 4, -2*LambertW(log(2)/2)/log(2)] + assert solve(x**2 - 2**x, x) == [2, 4, -2*LambertW(log(2)/2)/log(2)] + ans = solve(3*x + 5 + 2**(-5*x + 3), x) + assert len(ans) == 1 and ans[0].expand() == \ + Rational(-5, 3) + LambertW(-10240*root(2, 3)*log(2)/3)/(5*log(2)) + assert solve(5*x - 1 + 3*exp(2 - 7*x), x) == \ + [Rational(1, 5) + LambertW(-21*exp(Rational(3, 5))/5)/7] + assert solve((log(x) + x).subs(x, x**2 + 1)) == [ + -I*sqrt(-LambertW(1) + 1), sqrt(-1 + LambertW(1))] + # check collection + ax = a**(3*x + 5) + ans = solve(3*log(ax) + b*log(ax) + ax, x) + x0 = 1/log(a) + x1 = sqrt(3)*I + x2 = b + 3 + x3 = x2*LambertW(1/x2)/a**5 + x4 = x3**Rational(1, 3)/2 + assert ans == [ + x0*log(x4*(-x1 - 1)), + x0*log(x4*(x1 - 1)), + x0*log(x3)/3] + x1 = LambertW(Rational(1, 3)) + x2 = a**(-5) + x3 = -3**Rational(1, 3) + x4 = 3**Rational(5, 6)*I + x5 = x1**Rational(1, 3)*x2**Rational(1, 3)/2 + ans = solve(3*log(ax) + ax, x) + assert ans == [ + x0*log(3*x1*x2)/3, + x0*log(x5*(x3 - x4)), + x0*log(x5*(x3 + x4))] + # coverage + p = symbols('p', positive=True) + eq = 4*2**(2*p + 3) - 2*p - 3 + assert _solve_lambert(eq, p, _filtered_gens(Poly(eq), p)) == [ + Rational(-3, 2) - LambertW(-4*log(2))/(2*log(2))] + assert set(solve(3**cos(x) - cos(x)**3)) == { + acos(3), acos(-3*LambertW(-log(3)/3)/log(3))} + # should give only one solution after using `uniq` + assert solve(2*log(x) - 2*log(z) + log(z + log(x) + log(z)), x) == [ + exp(-z + LambertW(2*z**4*exp(2*z))/2)/z] + # cases when p != S.One + # issue 4271 + ans = solve((a/x + exp(x/2)).diff(x, 2), x) + x0 = (-a)**Rational(1, 3) + x1 = sqrt(3)*I + x2 = x0/6 + assert ans == [ + 6*LambertW(x0/3), + 6*LambertW(x2*(-x1 - 1)), + 6*LambertW(x2*(x1 - 1))] + assert solve((1/x + exp(x/2)).diff(x, 2), x) == \ + [6*LambertW(Rational(-1, 3)), 6*LambertW(Rational(1, 6) - sqrt(3)*I/6), \ + 6*LambertW(Rational(1, 6) + sqrt(3)*I/6), 6*LambertW(Rational(-1, 3), -1)] + assert solve(x**2 - y**2/exp(x), x, y, dict=True) == \ + [{x: 2*LambertW(-y/2)}, {x: 2*LambertW(y/2)}] + # this is slow but not exceedingly slow + assert solve((x**3)**(x/2) + pi/2, x) == [ + exp(LambertW(-2*log(2)/3 + 2*log(pi)/3 + I*pi*Rational(2, 3)))] + + # issue 23253 + assert solve((1/log(sqrt(x) + 2)**2 - 1/x)) == [ + (LambertW(-exp(-2), -1) + 2)**2] + assert solve((1/log(1/sqrt(x) + 2)**2 - x)) == [ + (LambertW(-exp(-2), -1) + 2)**-2] + assert solve((1/log(x**2 + 2)**2 - x**-4)) == [ + -I*sqrt(2 - LambertW(exp(2))), + -I*sqrt(LambertW(-exp(-2)) + 2), + sqrt(-2 - LambertW(-exp(-2))), + sqrt(-2 + LambertW(exp(2))), + -sqrt(-2 - LambertW(-exp(-2), -1)), + sqrt(-2 - LambertW(-exp(-2), -1))] + + +def test_rewrite_trig(): + assert solve(sin(x) + tan(x)) == [0, -pi, pi, 2*pi] + assert solve(sin(x) + sec(x)) == [ + -2*atan(Rational(-1, 2) + sqrt(2)*sqrt(1 - sqrt(3)*I)/2 + sqrt(3)*I/2), + 2*atan(S.Half - sqrt(2)*sqrt(1 + sqrt(3)*I)/2 + sqrt(3)*I/2), 2*atan(S.Half + + sqrt(2)*sqrt(1 + sqrt(3)*I)/2 + sqrt(3)*I/2), 2*atan(S.Half - + sqrt(3)*I/2 + sqrt(2)*sqrt(1 - sqrt(3)*I)/2)] + assert solve(sinh(x) + tanh(x)) == [0, I*pi] + + # issue 6157 + assert solve(2*sin(x) - cos(x), x) == [atan(S.Half)] + + +@XFAIL +def test_rewrite_trigh(): + # if this import passes then the test below should also pass + from sympy.functions.elementary.hyperbolic import sech + assert solve(sinh(x) + sech(x)) == [ + 2*atanh(Rational(-1, 2) + sqrt(5)/2 - sqrt(-2*sqrt(5) + 2)/2), + 2*atanh(Rational(-1, 2) + sqrt(5)/2 + sqrt(-2*sqrt(5) + 2)/2), + 2*atanh(-sqrt(5)/2 - S.Half + sqrt(2 + 2*sqrt(5))/2), + 2*atanh(-sqrt(2 + 2*sqrt(5))/2 - sqrt(5)/2 - S.Half)] + + +def test_uselogcombine(): + eq = z - log(x) + log(y/(x*(-1 + y**2/x**2))) + assert solve(eq, x, force=True) == [-sqrt(y*(y - exp(z))), sqrt(y*(y - exp(z)))] + assert solve(log(x + 3) + log(1 + 3/x) - 3) in [ + [-3 + sqrt(-12 + exp(3))*exp(Rational(3, 2))/2 + exp(3)/2, + -sqrt(-12 + exp(3))*exp(Rational(3, 2))/2 - 3 + exp(3)/2], + [-3 + sqrt(-36 + (-exp(3) + 6)**2)/2 + exp(3)/2, + -3 - sqrt(-36 + (-exp(3) + 6)**2)/2 + exp(3)/2], + ] + assert solve(log(exp(2*x) + 1) + log(-tanh(x) + 1) - log(2)) == [] + + +def test_atan2(): + assert solve(atan2(x, 2) - pi/3, x) == [2*sqrt(3)] + + +def test_errorinverses(): + assert solve(erf(x) - y, x) == [erfinv(y)] + assert solve(erfinv(x) - y, x) == [erf(y)] + assert solve(erfc(x) - y, x) == [erfcinv(y)] + assert solve(erfcinv(x) - y, x) == [erfc(y)] + + +def test_issue_2725(): + R = Symbol('R') + eq = sqrt(2)*R*sqrt(1/(R + 1)) + (R + 1)*(sqrt(2)*sqrt(1/(R + 1)) - 1) + sol = solve(eq, R, set=True)[1] + assert sol == {(Rational(5, 3) + (Rational(-1, 2) - sqrt(3)*I/2)*(Rational(251, 27) + + sqrt(111)*I/9)**Rational(1, 3) + 40/(9*((Rational(-1, 2) - sqrt(3)*I/2)*(Rational(251, 27) + + sqrt(111)*I/9)**Rational(1, 3))),), (Rational(5, 3) + 40/(9*(Rational(251, 27) + + sqrt(111)*I/9)**Rational(1, 3)) + (Rational(251, 27) + sqrt(111)*I/9)**Rational(1, 3),)} + + +def test_issue_5114_6611(): + # See that it doesn't hang; this solves in about 2 seconds. + # Also check that the solution is relatively small. + # Note: the system in issue 6611 solves in about 5 seconds and has + # an op-count of 138336 (with simplify=False). + b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r = symbols('b:r') + eqs = Matrix([ + [b - c/d + r/d], [c*(1/g + 1/e + 1/d) - f/g - r/d], + [-c/g + f*(1/j + 1/i + 1/g) - h/i], [-f/i + h*(1/m + 1/l + 1/i) - k/m], + [-h/m + k*(1/p + 1/o + 1/m) - n/p], [-k/p + n*(1/q + 1/p)]]) + v = Matrix([f, h, k, n, b, c]) + ans = solve(list(eqs), list(v), simplify=False) + # If time is taken to simplify then then 2617 below becomes + # 1168 and the time is about 50 seconds instead of 2. + assert sum(s.count_ops() for s in ans.values()) <= 3270 + + +def test_det_quick(): + m = Matrix(3, 3, symbols('a:9')) + assert m.det() == det_quick(m) # calls det_perm + m[0, 0] = 1 + assert m.det() == det_quick(m) # calls det_minor + m = Matrix(3, 3, list(range(9))) + assert m.det() == det_quick(m) # defaults to .det() + # make sure they work with Sparse + s = SparseMatrix(2, 2, (1, 2, 1, 4)) + assert det_perm(s) == det_minor(s) == s.det() + + +def test_real_imag_splitting(): + a, b = symbols('a b', real=True) + assert solve(sqrt(a**2 + b**2) - 3, a) == \ + [-sqrt(-b**2 + 9), sqrt(-b**2 + 9)] + a, b = symbols('a b', imaginary=True) + assert solve(sqrt(a**2 + b**2) - 3, a) == [] + + +def test_issue_7110(): + y = -2*x**3 + 4*x**2 - 2*x + 5 + assert any(ask(Q.real(i)) for i in solve(y)) + + +def test_units(): + assert solve(1/x - 1/(2*cm)) == [2*cm] + + +def test_issue_7547(): + A, B, V = symbols('A,B,V') + eq1 = Eq(630.26*(V - 39.0)*V*(V + 39) - A + B, 0) + eq2 = Eq(B, 1.36*10**8*(V - 39)) + eq3 = Eq(A, 5.75*10**5*V*(V + 39.0)) + sol = Matrix(nsolve(Tuple(eq1, eq2, eq3), [A, B, V], (0, 0, 0))) + assert str(sol) == str(Matrix( + [['4442890172.68209'], + ['4289299466.1432'], + ['70.5389666628177']])) + + +def test_issue_7895(): + r = symbols('r', real=True) + assert solve(sqrt(r) - 2) == [4] + + +def test_issue_2777(): + # the equations represent two circles + x, y = symbols('x y', real=True) + e1, e2 = sqrt(x**2 + y**2) - 10, sqrt(y**2 + (-x + 10)**2) - 3 + a, b = Rational(191, 20), 3*sqrt(391)/20 + ans = [(a, -b), (a, b)] + assert solve((e1, e2), (x, y)) == ans + assert solve((e1, e2/(x - a)), (x, y)) == [] + # make the 2nd circle's radius be -3 + e2 += 6 + assert solve((e1, e2), (x, y)) == [] + assert solve((e1, e2), (x, y), check=False) == ans + + +def test_issue_7322(): + number = 5.62527e-35 + assert solve(x - number, x)[0] == number + + +def test_nsolve(): + raises(ValueError, lambda: nsolve(x, (-1, 1), method='bisect')) + raises(TypeError, lambda: nsolve((x - y + 3,x + y,z - y),(x,y,z),(-50,50))) + raises(TypeError, lambda: nsolve((x + y, x - y), (0, 1))) + raises(TypeError, lambda: nsolve(x < 0.5, x, 1)) + + +@slow +def test_high_order_multivariate(): + assert len(solve(a*x**3 - x + 1, x)) == 3 + assert len(solve(a*x**4 - x + 1, x)) == 4 + assert solve(a*x**5 - x + 1, x) == [] # incomplete solution allowed + raises(NotImplementedError, lambda: + solve(a*x**5 - x + 1, x, incomplete=False)) + + # result checking must always consider the denominator and CRootOf + # must be checked, too + d = x**5 - x + 1 + assert solve(d*(1 + 1/d)) == [CRootOf(d + 1, i) for i in range(5)] + d = x - 1 + assert solve(d*(2 + 1/d)) == [S.Half] + + +def test_base_0_exp_0(): + assert solve(0**x - 1) == [0] + assert solve(0**(x - 2) - 1) == [2] + assert solve(S('x*(1/x**0 - x)', evaluate=False)) == \ + [0, 1] + + +def test__simple_dens(): + assert _simple_dens(1/x**0, [x]) == set() + assert _simple_dens(1/x**y, [x]) == {x**y} + assert _simple_dens(1/root(x, 3), [x]) == {x} + + +def test_issue_8755(): + # This tests two things: that if full unrad is attempted and fails + # the solution should still be found; also it tests the use of + # keyword `composite`. + assert len(solve(sqrt(y)*x + x**3 - 1, x)) == 3 + assert len(solve(-512*y**3 + 1344*(x + 2)**Rational(1, 3)*y**2 - + 1176*(x + 2)**Rational(2, 3)*y - 169*x + 686, y, _unrad=False)) == 3 + + +@slow +def test_issue_8828(): + x1 = 0 + y1 = -620 + r1 = 920 + x2 = 126 + y2 = 276 + x3 = 51 + y3 = 205 + r3 = 104 + v = x, y, z + + f1 = (x - x1)**2 + (y - y1)**2 - (r1 - z)**2 + f2 = (x - x2)**2 + (y - y2)**2 - z**2 + f3 = (x - x3)**2 + (y - y3)**2 - (r3 - z)**2 + F = f1,f2,f3 + + g1 = sqrt((x - x1)**2 + (y - y1)**2) + z - r1 + g2 = f2 + g3 = sqrt((x - x3)**2 + (y - y3)**2) + z - r3 + G = g1,g2,g3 + + A = solve(F, v) + B = solve(G, v) + C = solve(G, v, manual=True) + + p, q, r = [{tuple(i.evalf(2) for i in j) for j in R} for R in [A, B, C]] + assert p == q == r + + +def test_issue_2840_8155(): + # with parameter-free solutions (i.e. no `n`), we want to avoid + # excessive periodic solutions + assert solve(sin(3*x) + sin(6*x)) == [0, -2*pi/9, 2*pi/9] + assert solve(sin(300*x) + sin(600*x)) == [0, -pi/450, pi/450] + assert solve(2*sin(x) - 2*sin(2*x)) == [0, -pi/3, pi/3] + + +def test_issue_9567(): + assert solve(1 + 1/(x - 1)) == [0] + + +def test_issue_11538(): + assert solve(x + E) == [-E] + assert solve(x**2 + E) == [-I*sqrt(E), I*sqrt(E)] + assert solve(x**3 + 2*E) == [ + -cbrt(2 * E), + cbrt(2)*cbrt(E)/2 - cbrt(2)*sqrt(3)*I*cbrt(E)/2, + cbrt(2)*cbrt(E)/2 + cbrt(2)*sqrt(3)*I*cbrt(E)/2] + assert solve([x + 4, y + E], x, y) == {x: -4, y: -E} + assert solve([x**2 + 4, y + E], x, y) == [ + (-2*I, -E), (2*I, -E)] + + e1 = x - y**3 + 4 + e2 = x + y + 4 + 4 * E + assert len(solve([e1, e2], x, y)) == 3 + + +@slow +def test_issue_12114(): + a, b, c, d, e, f, g = symbols('a,b,c,d,e,f,g') + terms = [1 + a*b + d*e, 1 + a*c + d*f, 1 + b*c + e*f, + g - a**2 - d**2, g - b**2 - e**2, g - c**2 - f**2] + sol = solve(terms, [a, b, c, d, e, f, g], dict=True) + s = sqrt(-f**2 - 1) + s2 = sqrt(2 - f**2) + s3 = sqrt(6 - 3*f**2) + s4 = sqrt(3)*f + s5 = sqrt(3)*s2 + assert sol == [ + {a: -s, b: -s, c: -s, d: f, e: f, g: -1}, + {a: s, b: s, c: s, d: f, e: f, g: -1}, + {a: -s4/2 - s2/2, b: s4/2 - s2/2, c: s2, + d: -f/2 + s3/2, e: -f/2 - s5/2, g: 2}, + {a: -s4/2 + s2/2, b: s4/2 + s2/2, c: -s2, + d: -f/2 - s3/2, e: -f/2 + s5/2, g: 2}, + {a: s4/2 - s2/2, b: -s4/2 - s2/2, c: s2, + d: -f/2 - s3/2, e: -f/2 + s5/2, g: 2}, + {a: s4/2 + s2/2, b: -s4/2 + s2/2, c: -s2, + d: -f/2 + s3/2, e: -f/2 - s5/2, g: 2}] + + +def test_inf(): + assert solve(1 - oo*x) == [] + assert solve(oo*x, x) == [] + assert solve(oo*x - oo, x) == [] + + +def test_issue_12448(): + f = Function('f') + fun = [f(i) for i in range(15)] + sym = symbols('x:15') + reps = dict(zip(fun, sym)) + + (x, y, z), c = sym[:3], sym[3:] + ssym = solve([c[4*i]*x + c[4*i + 1]*y + c[4*i + 2]*z + c[4*i + 3] + for i in range(3)], (x, y, z)) + + (x, y, z), c = fun[:3], fun[3:] + sfun = solve([c[4*i]*x + c[4*i + 1]*y + c[4*i + 2]*z + c[4*i + 3] + for i in range(3)], (x, y, z)) + + assert sfun[fun[0]].xreplace(reps).count_ops() == \ + ssym[sym[0]].count_ops() + + +def test_denoms(): + assert denoms(x/2 + 1/y) == {2, y} + assert denoms(x/2 + 1/y, y) == {y} + assert denoms(x/2 + 1/y, [y]) == {y} + assert denoms(1/x + 1/y + 1/z, [x, y]) == {x, y} + assert denoms(1/x + 1/y + 1/z, x, y) == {x, y} + assert denoms(1/x + 1/y + 1/z, {x, y}) == {x, y} + + +def test_issue_12476(): + x0, x1, x2, x3, x4, x5 = symbols('x0 x1 x2 x3 x4 x5') + eqns = [x0**2 - x0, x0*x1 - x1, x0*x2 - x2, x0*x3 - x3, x0*x4 - x4, x0*x5 - x5, + x0*x1 - x1, -x0/3 + x1**2 - 2*x2/3, x1*x2 - x1/3 - x2/3 - x3/3, + x1*x3 - x2/3 - x3/3 - x4/3, x1*x4 - 2*x3/3 - x5/3, x1*x5 - x4, x0*x2 - x2, + x1*x2 - x1/3 - x2/3 - x3/3, -x0/6 - x1/6 + x2**2 - x2/6 - x3/3 - x4/6, + -x1/6 + x2*x3 - x2/3 - x3/6 - x4/6 - x5/6, x2*x4 - x2/3 - x3/3 - x4/3, + x2*x5 - x3, x0*x3 - x3, x1*x3 - x2/3 - x3/3 - x4/3, + -x1/6 + x2*x3 - x2/3 - x3/6 - x4/6 - x5/6, + -x0/6 - x1/6 - x2/6 + x3**2 - x3/3 - x4/6, -x1/3 - x2/3 + x3*x4 - x3/3, + -x2 + x3*x5, x0*x4 - x4, x1*x4 - 2*x3/3 - x5/3, x2*x4 - x2/3 - x3/3 - x4/3, + -x1/3 - x2/3 + x3*x4 - x3/3, -x0/3 - 2*x2/3 + x4**2, -x1 + x4*x5, x0*x5 - x5, + x1*x5 - x4, x2*x5 - x3, -x2 + x3*x5, -x1 + x4*x5, -x0 + x5**2, x0 - 1] + sols = [{x0: 1, x3: Rational(1, 6), x2: Rational(1, 6), x4: Rational(-2, 3), x1: Rational(-2, 3), x5: 1}, + {x0: 1, x3: S.Half, x2: Rational(-1, 2), x4: 0, x1: 0, x5: -1}, + {x0: 1, x3: Rational(-1, 3), x2: Rational(-1, 3), x4: Rational(1, 3), x1: Rational(1, 3), x5: 1}, + {x0: 1, x3: 1, x2: 1, x4: 1, x1: 1, x5: 1}, + {x0: 1, x3: Rational(-1, 3), x2: Rational(1, 3), x4: sqrt(5)/3, x1: -sqrt(5)/3, x5: -1}, + {x0: 1, x3: Rational(-1, 3), x2: Rational(1, 3), x4: -sqrt(5)/3, x1: sqrt(5)/3, x5: -1}] + + assert solve(eqns) == sols + + +def test_issue_13849(): + t = symbols('t') + assert solve((t*(sqrt(5) + sqrt(2)) - sqrt(2), t), t) == [] + + +def test_issue_14860(): + from sympy.physics.units import newton, kilo + assert solve(8*kilo*newton + x + y, x) == [-8000*newton - y] + + +def test_issue_14721(): + k, h, a, b = symbols(':4') + assert solve([ + -1 + (-k + 1)**2/b**2 + (-h - 1)**2/a**2, + -1 + (-k + 1)**2/b**2 + (-h + 1)**2/a**2, + h, k + 2], h, k, a, b) == [ + (0, -2, -b*sqrt(1/(b**2 - 9)), b), + (0, -2, b*sqrt(1/(b**2 - 9)), b)] + assert solve([ + h, h/a + 1/b**2 - 2, -h/2 + 1/b**2 - 2], a, h, b) == [ + (a, 0, -sqrt(2)/2), (a, 0, sqrt(2)/2)] + assert solve((a + b**2 - 1, a + b**2 - 2)) == [] + + +def test_issue_14779(): + x = symbols('x', real=True) + assert solve(sqrt(x**4 - 130*x**2 + 1089) + sqrt(x**4 - 130*x**2 + + 3969) - 96*Abs(x)/x,x) == [sqrt(130)] + + +def test_issue_15307(): + assert solve((y - 2, Mul(x + 3,x - 2, evaluate=False))) == \ + [{x: -3, y: 2}, {x: 2, y: 2}] + assert solve((y - 2, Mul(3, x - 2, evaluate=False))) == \ + {x: 2, y: 2} + assert solve((y - 2, Add(x + 4, x - 2, evaluate=False))) == \ + {x: -1, y: 2} + eq1 = Eq(12513*x + 2*y - 219093, -5726*x - y) + eq2 = Eq(-2*x + 8, 2*x - 40) + assert solve([eq1, eq2]) == {x:12, y:75} + + +def test_issue_15415(): + assert solve(x - 3, x) == [3] + assert solve([x - 3], x) == {x:3} + assert solve(Eq(y + 3*x**2/2, y + 3*x), y) == [] + assert solve([Eq(y + 3*x**2/2, y + 3*x)], y) == [] + assert solve([Eq(y + 3*x**2/2, y + 3*x), Eq(x, 1)], y) == [] + + +@slow +def test_issue_15731(): + # f(x)**g(x)=c + assert solve(Eq((x**2 - 7*x + 11)**(x**2 - 13*x + 42), 1)) == [2, 3, 4, 5, 6, 7] + assert solve((x)**(x + 4) - 4) == [-2] + assert solve((-x)**(-x + 4) - 4) == [2] + assert solve((x**2 - 6)**(x**2 - 2) - 4) == [-2, 2] + assert solve((x**2 - 2*x - 1)**(x**2 - 3) - 1/(1 - 2*sqrt(2))) == [sqrt(2)] + assert solve(x**(x + S.Half) - 4*sqrt(2)) == [S(2)] + assert solve((x**2 + 1)**x - 25) == [2] + assert solve(x**(2/x) - 2) == [2, 4] + assert solve((x/2)**(2/x) - sqrt(2)) == [4, 8] + assert solve(x**(x + S.Half) - Rational(9, 4)) == [Rational(3, 2)] + # a**g(x)=c + assert solve((-sqrt(sqrt(2)))**x - 2) == [4, log(2)/(log(2**Rational(1, 4)) + I*pi)] + assert solve((sqrt(2))**x - sqrt(sqrt(2))) == [S.Half] + assert solve((-sqrt(2))**x + 2*(sqrt(2))) == [3, + (3*log(2)**2 + 4*pi**2 - 4*I*pi*log(2))/(log(2)**2 + 4*pi**2)] + assert solve((sqrt(2))**x - 2*(sqrt(2))) == [3] + assert solve(I**x + 1) == [2] + assert solve((1 + I)**x - 2*I) == [2] + assert solve((sqrt(2) + sqrt(3))**x - (2*sqrt(6) + 5)**Rational(1, 3)) == [Rational(2, 3)] + # bases of both sides are equal + b = Symbol('b') + assert solve(b**x - b**2, x) == [2] + assert solve(b**x - 1/b, x) == [-1] + assert solve(b**x - b, x) == [1] + b = Symbol('b', positive=True) + assert solve(b**x - b**2, x) == [2] + assert solve(b**x - 1/b, x) == [-1] + + +def test_issue_10933(): + assert solve(x**4 + y*(x + 0.1), x) # doesn't fail + assert solve(I*x**4 + x**3 + x**2 + 1.) # doesn't fail + + +def test_Abs_handling(): + x = symbols('x', real=True) + assert solve(abs(x/y), x) == [0] + + +def test_issue_7982(): + x = Symbol('x') + # Test that no exception happens + assert solve([2*x**2 + 5*x + 20 <= 0, x >= 1.5], x) is S.false + # From #8040 + assert solve([x**3 - 8.08*x**2 - 56.48*x/5 - 106 >= 0, x - 1 <= 0], [x]) is S.false + + +def test_issue_14645(): + x, y = symbols('x y') + assert solve([x*y - x - y, x*y - x - y], [x, y]) == [(y/(y - 1), y)] + + +def test_issue_12024(): + x, y = symbols('x y') + assert solve(Piecewise((0.0, x < 0.1), (x, x >= 0.1)) - y) == \ + [{y: Piecewise((0.0, x < 0.1), (x, True))}] + + +def test_issue_17452(): + assert solve((7**x)**x + pi, x) == [-sqrt(log(pi) + I*pi)/sqrt(log(7)), + sqrt(log(pi) + I*pi)/sqrt(log(7))] + assert solve(x**(x/11) + pi/11, x) == [exp(LambertW(-11*log(11) + 11*log(pi) + 11*I*pi))] + + +def test_issue_17799(): + assert solve(-erf(x**(S(1)/3))**pi + I, x) == [] + + +def test_issue_17650(): + x = Symbol('x', real=True) + assert solve(abs(abs(x**2 - 1) - x) - x) == [1, -1 + sqrt(2), 1 + sqrt(2)] + + +def test_issue_17882(): + eq = -8*x**2/(9*(x**2 - 1)**(S(4)/3)) + 4/(3*(x**2 - 1)**(S(1)/3)) + assert unrad(eq) is None + + +def test_issue_17949(): + assert solve(exp(+x+x**2), x) == [] + assert solve(exp(-x+x**2), x) == [] + assert solve(exp(+x-x**2), x) == [] + assert solve(exp(-x-x**2), x) == [] + + +def test_issue_10993(): + assert solve(Eq(binomial(x, 2), 3)) == [-2, 3] + assert solve(Eq(pow(x, 2) + binomial(x, 3), x)) == [-4, 0, 1] + assert solve(Eq(binomial(x, 2), 0)) == [0, 1] + assert solve(a+binomial(x, 3), a) == [-binomial(x, 3)] + assert solve(x-binomial(a, 3) + binomial(y, 2) + sin(a), x) == [-sin(a) + binomial(a, 3) - binomial(y, 2)] + assert solve((x+1)-binomial(x+1, 3), x) == [-2, -1, 3] + + +def test_issue_11553(): + eq1 = x + y + 1 + eq2 = x + GoldenRatio + assert solve([eq1, eq2], x, y) == {x: -GoldenRatio, y: -1 + GoldenRatio} + eq3 = x + 2 + TribonacciConstant + assert solve([eq1, eq3], x, y) == {x: -2 - TribonacciConstant, y: 1 + TribonacciConstant} + + +def test_issue_19113_19102(): + t = S(1)/3 + solve(cos(x)**5-sin(x)**5) + assert solve(4*cos(x)**3 - 2*sin(x)**3) == [ + atan(2**(t)), -atan(2**(t)*(1 - sqrt(3)*I)/2), + -atan(2**(t)*(1 + sqrt(3)*I)/2)] + h = S.Half + assert solve(cos(x)**2 + sin(x)) == [ + 2*atan(-h + sqrt(5)/2 + sqrt(2)*sqrt(1 - sqrt(5))/2), + -2*atan(h + sqrt(5)/2 + sqrt(2)*sqrt(1 + sqrt(5))/2), + -2*atan(-sqrt(5)/2 + h + sqrt(2)*sqrt(1 - sqrt(5))/2), + -2*atan(-sqrt(2)*sqrt(1 + sqrt(5))/2 + h + sqrt(5)/2)] + assert solve(3*cos(x) - sin(x)) == [atan(3)] + + +def test_issue_19509(): + a = S(3)/4 + b = S(5)/8 + c = sqrt(5)/8 + d = sqrt(5)/4 + assert solve(1/(x -1)**5 - 1) == [2, + -d + a - sqrt(-b + c), + -d + a + sqrt(-b + c), + d + a - sqrt(-b - c), + d + a + sqrt(-b - c)] + +def test_issue_20747(): + THT, HT, DBH, dib, c0, c1, c2, c3, c4 = symbols('THT HT DBH dib c0 c1 c2 c3 c4') + f = DBH*c3 + THT*c4 + c2 + rhs = 1 - ((HT - 1)/(THT - 1))**c1*(1 - exp(c0/f)) + eq = dib - DBH*(c0 - f*log(rhs)) + term = ((1 - exp((DBH*c0 - dib)/(DBH*(DBH*c3 + THT*c4 + c2)))) + / (1 - exp(c0/(DBH*c3 + THT*c4 + c2)))) + sol = [THT*term**(1/c1) - term**(1/c1) + 1] + assert solve(eq, HT) == sol + + +def test_issue_20902(): + f = (t / ((1 + t) ** 2)) + assert solve(f.subs({t: 3 * x + 2}).diff(x) > 0, x) == (S(-1) < x) & (x < S(-1)/3) + assert solve(f.subs({t: 3 * x + 3}).diff(x) > 0, x) == (S(-4)/3 < x) & (x < S(-2)/3) + assert solve(f.subs({t: 3 * x + 4}).diff(x) > 0, x) == (S(-5)/3 < x) & (x < S(-1)) + assert solve(f.subs({t: 3 * x + 2}).diff(x) > 0, x) == (S(-1) < x) & (x < S(-1)/3) + + +def test_issue_21034(): + a = symbols('a', real=True) + system = [x - cosh(cos(4)), y - sinh(cos(a)), z - tanh(x)] + # constants inside hyperbolic functions should not be rewritten in terms of exp + assert solve(system, x, y, z) == [(cosh(cos(4)), sinh(cos(a)), tanh(cosh(cos(4))))] + # but if the variable of interest is present in a hyperbolic function, + # then it should be rewritten in terms of exp and solved further + newsystem = [(exp(x) - exp(-x)) - tanh(x)*(exp(x) + exp(-x)) + x - 5] + assert solve(newsystem, x) == {x: 5} + + +def test_issue_4886(): + z = a*sqrt(R**2*a**2 + R**2*b**2 - c**2)/(a**2 + b**2) + t = b*c/(a**2 + b**2) + sol = [((b*(t - z) - c)/(-a), t - z), ((b*(t + z) - c)/(-a), t + z)] + assert solve([x**2 + y**2 - R**2, a*x + b*y - c], x, y) == sol + + +def test_issue_6819(): + a, b, c, d = symbols('a b c d', positive=True) + assert solve(a*b**x - c*d**x, x) == [log(c/a)/log(b/d)] + + +def test_issue_17454(): + x = Symbol('x') + assert solve((1 - x - I)**4, x) == [1 - I] + + +def test_issue_21852(): + solution = [21 - 21*sqrt(2)/2] + assert solve(2*x + sqrt(2*x**2) - 21) == solution + + +def test_issue_21942(): + eq = -d + (a*c**(1 - e) + b**(1 - e)*(1 - a))**(1/(1 - e)) + sol = solve(eq, c, simplify=False, check=False) + assert sol == [((a*b**(1 - e) - b**(1 - e) + + d**(1 - e))/a)**(1/(1 - e))] + + +def test_solver_flags(): + root = solve(x**5 + x**2 - x - 1, cubics=False) + rad = solve(x**5 + x**2 - x - 1, cubics=True) + assert root != rad + + +def test_issue_22768(): + eq = 2*x**3 - 16*(y - 1)**6*z**3 + assert solve(eq.expand(), x, simplify=False + ) == [2*z*(y - 1)**2, z*(-1 + sqrt(3)*I)*(y - 1)**2, + -z*(1 + sqrt(3)*I)*(y - 1)**2] + + +def test_issue_22717(): + assert solve((-y**2 + log(y**2/x) + 2, -2*x*y + 2*x/y)) == [ + {y: -1, x: E}, {y: 1, x: E}] + + +def test_issue_25176(): + eq = (x - 5)**-8 - 3 + sol = solve(eq) + assert not any(eq.subs(x, i) for i in sol) + + +def test_issue_10169(): + eq = S(-8*a - x**5*(a + b + c + e) - x**4*(4*a - 2**Rational(3,4)*c + 4*c + + d + 2**Rational(3,4)*e + 4*e + k) - x**3*(-4*2**Rational(3,4)*c + sqrt(2)*c - + 2**Rational(3,4)*d + 4*d + sqrt(2)*e + 4*2**Rational(3,4)*e + 2**Rational(3,4)*k + 4*k) - + x**2*(4*sqrt(2)*c - 4*2**Rational(3,4)*d + sqrt(2)*d + 4*sqrt(2)*e + + sqrt(2)*k + 4*2**Rational(3,4)*k) - x*(2*a + 2*b + 4*sqrt(2)*d + + 4*sqrt(2)*k) + 5) + assert solve_undetermined_coeffs(eq, [a, b, c, d, e, k], x) == { + a: Rational(5,8), + b: Rational(-5,1032), + c: Rational(-40,129) - 5*2**Rational(3,4)/129 + 5*2**Rational(1,4)/1032, + d: -20*2**Rational(3,4)/129 - 10*sqrt(2)/129 - 5*2**Rational(1,4)/258, + e: Rational(-40,129) - 5*2**Rational(1,4)/1032 + 5*2**Rational(3,4)/129, + k: -10*sqrt(2)/129 + 5*2**Rational(1,4)/258 + 20*2**Rational(3,4)/129 + } + + +def test_solve_undetermined_coeffs_issue_23927(): + A, B, r, phi = symbols('A, B, r, phi') + e = Eq(A*sin(t) + B*cos(t), r*sin(t - phi)) + eq = (e.lhs - e.rhs).expand(trig=True) + soln = solve_undetermined_coeffs(eq, (r, phi), t) + assert soln == [{ + phi: 2*atan((A - sqrt(A**2 + B**2))/B), + r: (-A**2 + A*sqrt(A**2 + B**2) - B**2)/(A - sqrt(A**2 + B**2)) + }, { + phi: 2*atan((A + sqrt(A**2 + B**2))/B), + r: (A**2 + A*sqrt(A**2 + B**2) + B**2)/(A + sqrt(A**2 + B**2))/-1 + }] + +def test_issue_24368(): + # Ideally these would produce a solution, but for now just check that they + # don't fail with a RuntimeError + raises(NotImplementedError, lambda: solve(Mod(x**2, 49), x)) + s2 = Symbol('s2', integer=True, positive=True) + f = floor(s2/2 - S(1)/2) + raises(NotImplementedError, lambda: solve((Mod(f**2/(f + 1) + 2*f/(f + 1) + 1/(f + 1), 1))*f + Mod(f**2/(f + 1) + 2*f/(f + 1) + 1/(f + 1), 1), s2)) + + +def test_solve_Piecewise(): + assert [S(10)/3] == solve(3*Piecewise( + (S.NaN, x <= 0), + (20*x - 3*(x - 6)**2/2 - 176, (x >= 0) & (x >= 2) & (x>= 4) & (x >= 6) & (x < 10)), + (100 - 26*x, (x >= 0) & (x >= 2) & (x >= 4) & (x < 10)), + (16*x - 3*(x - 6)**2/2 - 176, (x >= 2) & (x >= 4) & (x >= 6) & (x < 10)), + (100 - 30*x, (x >= 2) & (x >= 4) & (x < 10)), + (30*x - 3*(x - 6)**2/2 - 196, (x>= 0) & (x >= 4) & (x >= 6) & (x < 10)), + (80 - 16*x, (x >= 0) & (x >= 4) & (x < 10)), + (26*x - 3*(x - 6)**2/2 - 196, (x >= 4) & (x >= 6) & (x < 10)), + (80 - 20*x, (x >= 4) & (x < 10)), + (40*x - 3*(x - 6)**2/2 - 256, (x >= 0) & (x >= 2) & (x >= 6) & (x < 10)), + (20 - 6*x, (x >= 0) & (x >= 2) & (x < 10)), + (36*x - 3*(x - 6)**2/2 - 256, (x >= 2) & (x >= 6) & (x < 10)), + (20 - 10*x, (x >= 2) & (x < 10)), + (50*x - 3*(x - 6)**2/2 - 276, (x >= 0) & (x >= 6) & (x < 10)), + (4*x, (x >= 0) & (x < 10)), + (46*x - 3*(x - 6)**2/2 - 276, (x >= 6) & (x < 10)), + (0, x < 10), # this will simplify away + (S.NaN,True))) diff --git a/lib/python3.12/site-packages/sympy/solvers/tests/test_solveset.py b/lib/python3.12/site-packages/sympy/solvers/tests/test_solveset.py new file mode 100644 index 0000000000000000000000000000000000000000..a1ba7a11e68ed518c4d83c050947b78756ade181 --- /dev/null +++ b/lib/python3.12/site-packages/sympy/solvers/tests/test_solveset.py @@ -0,0 +1,3548 @@ +from math import isclose + +from sympy.calculus.util import stationary_points +from sympy.core.containers import Tuple +from sympy.core.function import (Function, Lambda, nfloat, diff) +from sympy.core.mod import Mod +from sympy.core.numbers import (E, I, Rational, oo, pi, Integer, all_close) +from sympy.core.relational import (Eq, Gt, Ne, Ge) +from sympy.core.singleton import S +from sympy.core.sorting import ordered +from sympy.core.symbol import (Dummy, Symbol, symbols) +from sympy.core.sympify import sympify +from sympy.functions.elementary.complexes import (Abs, arg, im, re, sign, conjugate) +from sympy.functions.elementary.exponential import (LambertW, exp, log) +from sympy.functions.elementary.hyperbolic import (HyperbolicFunction, + sinh, cosh, tanh, coth, sech, csch, asinh, acosh, atanh, acoth, asech, acsch) +from sympy.functions.elementary.miscellaneous import sqrt, Min, Max +from sympy.functions.elementary.piecewise import Piecewise +from sympy.functions.elementary.trigonometric import ( + TrigonometricFunction, acos, acot, acsc, asec, asin, atan, atan2, + cos, cot, csc, sec, sin, tan) +from sympy.functions.special.error_functions import (erf, erfc, + erfcinv, erfinv) +from sympy.logic.boolalg import And +from sympy.matrices.dense import MutableDenseMatrix as Matrix +from sympy.matrices.immutable import ImmutableDenseMatrix +from sympy.polys.polytools import Poly +from sympy.polys.rootoftools import CRootOf +from sympy.sets.contains import Contains +from sympy.sets.conditionset import ConditionSet +from sympy.sets.fancysets import ImageSet, Range +from sympy.sets.sets import (Complement, FiniteSet, + Intersection, Interval, Union, imageset, ProductSet) +from sympy.simplify import simplify +from sympy.tensor.indexed import Indexed +from sympy.utilities.iterables import numbered_symbols + +from sympy.testing.pytest import (XFAIL, raises, skip, slow, SKIP, _both_exp_pow) +from sympy.core.random import verify_numerically as tn +from sympy.physics.units import cm + +from sympy.solvers import solve +from sympy.solvers.solveset import ( + solveset_real, domain_check, solveset_complex, linear_eq_to_matrix, + linsolve, _is_function_class_equation, invert_real, invert_complex, + _invert_trig_hyp_real, solveset, solve_decomposition, substitution, + nonlinsolve, solvify, + _is_finite_with_finite_vars, _transolve, _is_exponential, + _solve_exponential, _is_logarithmic, _is_lambert, + _solve_logarithm, _term_factors, _is_modular, NonlinearError) + +from sympy.abc import (a, b, c, d, e, f, g, h, i, j, k, l, m, n, q, r, + t, w, x, y, z) + + +def dumeq(i, j): + if type(i) in (list, tuple): + return all(dumeq(i, j) for i, j in zip(i, j)) + return i == j or i.dummy_eq(j) + + +def assert_close_ss(sol1, sol2): + """Test solutions with floats from solveset are close""" + sol1 = sympify(sol1) + sol2 = sympify(sol2) + assert isinstance(sol1, FiniteSet) + assert isinstance(sol2, FiniteSet) + assert len(sol1) == len(sol2) + assert all(isclose(v1, v2) for v1, v2 in zip(sol1, sol2)) + + +def assert_close_nl(sol1, sol2): + """Test solutions with floats from nonlinsolve are close""" + sol1 = sympify(sol1) + sol2 = sympify(sol2) + assert isinstance(sol1, FiniteSet) + assert isinstance(sol2, FiniteSet) + assert len(sol1) == len(sol2) + for s1, s2 in zip(sol1, sol2): + assert len(s1) == len(s2) + assert all(isclose(v1, v2) for v1, v2 in zip(s1, s2)) + + +@_both_exp_pow +def test_invert_real(): + x = Symbol('x', real=True) + + def ireal(x, s=S.Reals): + return Intersection(s, x) + + assert invert_real(exp(x), z, x) == (x, ireal(FiniteSet(log(z)))) + + y = Symbol('y', positive=True) + n = Symbol('n', real=True) + assert invert_real(x + 3, y, x) == (x, FiniteSet(y - 3)) + assert invert_real(x*3, y, x) == (x, FiniteSet(y / 3)) + + assert invert_real(exp(x), y, x) == (x, FiniteSet(log(y))) + assert invert_real(exp(3*x), y, x) == (x, FiniteSet(log(y) / 3)) + assert invert_real(exp(x + 3), y, x) == (x, FiniteSet(log(y) - 3)) + + assert invert_real(exp(x) + 3, y, x) == (x, ireal(FiniteSet(log(y - 3)))) + assert invert_real(exp(x)*3, y, x) == (x, FiniteSet(log(y / 3))) + + assert invert_real(log(x), y, x) == (x, FiniteSet(exp(y))) + assert invert_real(log(3*x), y, x) == (x, FiniteSet(exp(y) / 3)) + assert invert_real(log(x + 3), y, x) == (x, FiniteSet(exp(y) - 3)) + + assert invert_real(Abs(x), y, x) == (x, FiniteSet(y, -y)) + + assert invert_real(2**x, y, x) == (x, FiniteSet(log(y)/log(2))) + assert invert_real(2**exp(x), y, x) == (x, ireal(FiniteSet(log(log(y)/log(2))))) + + assert invert_real(x**2, y, x) == (x, FiniteSet(sqrt(y), -sqrt(y))) + assert invert_real(x**S.Half, y, x) == (x, FiniteSet(y**2)) + + raises(ValueError, lambda: invert_real(x, x, x)) + + # issue 21236 + assert invert_real(x**pi, y, x) == (x, FiniteSet(y**(1/pi))) + assert invert_real(x**pi, -E, x) == (x, S.EmptySet) + assert invert_real(x**Rational(3/2), 1000, x) == (x, FiniteSet(100)) + assert invert_real(x**1.0, 1, x) == (x**1.0, FiniteSet(1)) + + raises(ValueError, lambda: invert_real(S.One, y, x)) + + assert invert_real(x**31 + x, y, x) == (x**31 + x, FiniteSet(y)) + + lhs = x**31 + x + base_values = FiniteSet(y - 1, -y - 1) + assert invert_real(Abs(x**31 + x + 1), y, x) == (lhs, base_values) + + assert dumeq(invert_real(sin(x), y, x), (x, + ConditionSet(x, (S(-1) <= y) & (y <= S(1)), Union( + ImageSet(Lambda(n, 2*n*pi + asin(y)), S.Integers), + ImageSet(Lambda(n, pi*2*n + pi - asin(y)), S.Integers))))) + + assert dumeq(invert_real(sin(exp(x)), y, x), (x, + ConditionSet(x, (S(-1) <= y) & (y <= S(1)), Union( + ImageSet(Lambda(n, log(2*n*pi + asin(y))), S.Integers), + ImageSet(Lambda(n, log(pi*2*n + pi - asin(y))), S.Integers))))) + + assert dumeq(invert_real(csc(x), y, x), (x, + ConditionSet(x, ((S(1) <= y) & (y < oo)) | ((-oo < y) & (y <= S(-1))), + Union(ImageSet(Lambda(n, 2*n*pi + acsc(y)), S.Integers), + ImageSet(Lambda(n, 2*n*pi - acsc(y) + pi), S.Integers))))) + + assert dumeq(invert_real(csc(exp(x)), y, x), (x, + ConditionSet(x, ((S(1) <= y) & (y < oo)) | ((-oo < y) & (y <= S(-1))), + Union(ImageSet(Lambda(n, log(2*n*pi + acsc(y))), S.Integers), + ImageSet(Lambda(n, log(2*n*pi - acsc(y) + pi)), S.Integers))))) + + assert dumeq(invert_real(cos(x), y, x), (x, + ConditionSet(x, (S(-1) <= y) & (y <= S(1)), Union( + ImageSet(Lambda(n, 2*n*pi + acos(y)), S.Integers), + ImageSet(Lambda(n, 2*n*pi - acos(y)), S.Integers))))) + + assert dumeq(invert_real(cos(exp(x)), y, x), (x, + ConditionSet(x, (S(-1) <= y) & (y <= S(1)), Union( + ImageSet(Lambda(n, log(2*n*pi + acos(y))), S.Integers), + ImageSet(Lambda(n, log(2*n*pi - acos(y))), S.Integers))))) + + assert dumeq(invert_real(sec(x), y, x), (x, + ConditionSet(x, ((S(1) <= y) & (y < oo)) | ((-oo < y) & (y <= S(-1))), + Union(ImageSet(Lambda(n, 2*n*pi + asec(y)), S.Integers), \ + ImageSet(Lambda(n, 2*n*pi - asec(y)), S.Integers))))) + + assert dumeq(invert_real(sec(exp(x)), y, x), (x, + ConditionSet(x, ((S(1) <= y) & (y < oo)) | ((-oo < y) & (y <= S(-1))), + Union(ImageSet(Lambda(n, log(2*n*pi - asec(y))), S.Integers), + ImageSet(Lambda(n, log(2*n*pi + asec(y))), S.Integers))))) + + assert dumeq(invert_real(tan(x), y, x), (x, + ConditionSet(x, (-oo < y) & (y < oo), + ImageSet(Lambda(n, n*pi + atan(y)), S.Integers)))) + + assert dumeq(invert_real(tan(exp(x)), y, x), (x, + ConditionSet(x, (-oo < y) & (y < oo), + ImageSet(Lambda(n, log(n*pi + atan(y))), S.Integers)))) + + assert dumeq(invert_real(cot(x), y, x), (x, + ConditionSet(x, (-oo < y) & (y < oo), + ImageSet(Lambda(n, n*pi + acot(y)), S.Integers)))) + + assert dumeq(invert_real(cot(exp(x)), y, x), (x, + ConditionSet(x, (-oo < y) & (y < oo), + ImageSet(Lambda(n, log(n*pi + acot(y))), S.Integers)))) + + assert dumeq(invert_real(tan(tan(x)), y, x), + (x, ConditionSet(x, Eq(tan(tan(x)), y), S.Reals))) + # slight regression compared to previous result: + # (tan(x), imageset(Lambda(n, n*pi + atan(y)), S.Integers))) + + x = Symbol('x', positive=True) + assert invert_real(x**pi, y, x) == (x, FiniteSet(y**(1/pi))) + + r = Symbol('r', real=True) + p = Symbol('p', positive=True) + assert invert_real(sinh(x), r, x) == (x, FiniteSet(asinh(r))) + assert invert_real(sinh(log(x)), p, x) == (x, FiniteSet(exp(asinh(p)))) + + assert invert_real(cosh(x), r, x) == (x, Intersection( + FiniteSet(-acosh(r), acosh(r)), S.Reals)) + assert invert_real(cosh(x), p + 1, x) == (x, + FiniteSet(-acosh(p + 1), acosh(p + 1))) + + assert invert_real(tanh(x), r, x) == (x, Intersection(FiniteSet(atanh(r)), S.Reals)) + assert invert_real(coth(x), p+1, x) == (x, FiniteSet(acoth(p+1))) + assert invert_real(sech(x), r, x) == (x, Intersection( + FiniteSet(-asech(r), asech(r)), S.Reals)) + assert invert_real(csch(x), p, x) == (x, FiniteSet(acsch(p))) + + assert dumeq(invert_real(tanh(sin(x)), r, x), (x, + ConditionSet(x, (S(-1) <= atanh(r)) & (atanh(r) <= S(1)), Union( + ImageSet(Lambda(n, 2*n*pi + asin(atanh(r))), S.Integers), + ImageSet(Lambda(n, 2*n*pi - asin(atanh(r)) + pi), S.Integers))))) + + +def test_invert_trig_hyp_real(): + # check some codepaths that are not as easily reached otherwise + n = Dummy('n') + assert _invert_trig_hyp_real(cosh(x), Range(-5, 10, 1), x)[1].dummy_eq(Union( + ImageSet(Lambda(n, -acosh(n)), Range(1, 10, 1)), + ImageSet(Lambda(n, acosh(n)), Range(1, 10, 1)))) + assert _invert_trig_hyp_real(coth(x), Interval(-3, 2), x) == (x, Union( + Interval(-oo, -acoth(3)), Interval(acoth(2), oo))) + assert _invert_trig_hyp_real(tanh(x), Interval(-S.Half, 1), x) == (x, + Interval(-atanh(S.Half), oo)) + assert _invert_trig_hyp_real(sech(x), imageset(n, S.Half + n/3, S.Naturals0), x) == \ + (x, FiniteSet(-asech(S(1)/2), asech(S(1)/2), -asech(S(5)/6), asech(S(5)/6))) + assert _invert_trig_hyp_real(csch(x), S.Reals, x) == (x, + Union(Interval.open(-oo, 0), Interval.open(0, oo))) + + +def test_invert_complex(): + assert invert_complex(x + 3, y, x) == (x, FiniteSet(y - 3)) + assert invert_complex(x*3, y, x) == (x, FiniteSet(y / 3)) + assert invert_complex((x - 1)**3, 0, x) == (x, FiniteSet(1)) + + assert dumeq(invert_complex(exp(x), y, x), + (x, imageset(Lambda(n, I*(2*pi*n + arg(y)) + log(Abs(y))), S.Integers))) + + assert invert_complex(log(x), y, x) == (x, FiniteSet(exp(y))) + + raises(ValueError, lambda: invert_real(1, y, x)) + raises(ValueError, lambda: invert_complex(x, x, x)) + raises(ValueError, lambda: invert_complex(x, x, 1)) + + assert dumeq(invert_complex(sin(x), I, x), (x, Union( + ImageSet(Lambda(n, 2*n*pi + I*log(1 + sqrt(2))), S.Integers), + ImageSet(Lambda(n, 2*n*pi + pi - I*log(1 + sqrt(2))), S.Integers)))) + assert dumeq(invert_complex(cos(x), 1+I, x), (x, Union( + ImageSet(Lambda(n, 2*n*pi - acos(1 + I)), S.Integers), + ImageSet(Lambda(n, 2*n*pi + acos(1 + I)), S.Integers)))) + assert dumeq(invert_complex(tan(2*x), 1, x), (x, + ImageSet(Lambda(n, n*pi/2 + pi/8), S.Integers))) + assert dumeq(invert_complex(cot(x), 2*I, x), (x, + ImageSet(Lambda(n, n*pi - I*acoth(2)), S.Integers))) + + assert dumeq(invert_complex(sinh(x), 0, x), (x, Union( + ImageSet(Lambda(n, 2*n*I*pi), S.Integers), + ImageSet(Lambda(n, 2*n*I*pi + I*pi), S.Integers)))) + assert dumeq(invert_complex(cosh(x), 0, x), (x, Union( + ImageSet(Lambda(n, 2*n*I*pi + I*pi/2), S.Integers), + ImageSet(Lambda(n, 2*n*I*pi + 3*I*pi/2), S.Integers)))) + assert invert_complex(tanh(x), 1, x) == (x, S.EmptySet) + assert dumeq(invert_complex(tanh(x), a, x), (x, + ConditionSet(x, Ne(a, -1) & Ne(a, 1), + ImageSet(Lambda(n, n*I*pi + atanh(a)), S.Integers)))) + assert invert_complex(coth(x), 1, x) == (x, S.EmptySet) + assert dumeq(invert_complex(coth(x), a, x), (x, + ConditionSet(x, Ne(a, -1) & Ne(a, 1), + ImageSet(Lambda(n, n*I*pi + acoth(a)), S.Integers)))) + assert dumeq(invert_complex(sech(x), 2, x), (x, Union( + ImageSet(Lambda(n, 2*n*I*pi + I*pi/3), S.Integers), + ImageSet(Lambda(n, 2*n*I*pi + 5*I*pi/3), S.Integers)))) + + +def test_domain_check(): + assert domain_check(1/(1 + (1/(x+1))**2), x, -1) is False + assert domain_check(x**2, x, 0) is True + assert domain_check(x, x, oo) is False + assert domain_check(0, x, oo) is False + + +def test_issue_11536(): + assert solveset(0**x - 100, x, S.Reals) == S.EmptySet + assert solveset(0**x - 1, x, S.Reals) == FiniteSet(0) + + +def test_issue_17479(): + f = (x**2 + y**2)**2 + (x**2 + z**2)**2 - 2*(2*x**2 + y**2 + z**2) + fx = f.diff(x) + fy = f.diff(y) + fz = f.diff(z) + sol = nonlinsolve([fx, fy, fz], [x, y, z]) + assert len(sol) >= 4 and len(sol) <= 20 + # nonlinsolve has been giving a varying number of solutions + # (originally 18, then 20, now 19) due to various internal changes. + # Unfortunately not all the solutions are actually valid and some are + # redundant. Since the original issue was that an exception was raised, + # this first test only checks that nonlinsolve returns a "plausible" + # solution set. The next test checks the result for correctness. + + +@XFAIL +def test_issue_18449(): + x, y, z = symbols("x, y, z") + f = (x**2 + y**2)**2 + (x**2 + z**2)**2 - 2*(2*x**2 + y**2 + z**2) + fx = diff(f, x) + fy = diff(f, y) + fz = diff(f, z) + sol = nonlinsolve([fx, fy, fz], [x, y, z]) + for (xs, ys, zs) in sol: + d = {x: xs, y: ys, z: zs} + assert tuple(_.subs(d).simplify() for _ in (fx, fy, fz)) == (0, 0, 0) + # After simplification and removal of duplicate elements, there should + # only be 4 parametric solutions left: + # simplifiedsolutions = FiniteSet((sqrt(1 - z**2), z, z), + # (-sqrt(1 - z**2), z, z), + # (sqrt(1 - z**2), -z, z), + # (-sqrt(1 - z**2), -z, z)) + # TODO: Is the above solution set definitely complete? + + +def test_issue_21047(): + f = (2 - x)**2 + (sqrt(x - 1) - 1)**6 + assert solveset(f, x, S.Reals) == FiniteSet(2) + + f = (sqrt(x)-1)**2 + (sqrt(x)+1)**2 -2*x**2 + sqrt(2) + assert solveset(f, x, S.Reals) == FiniteSet( + S.Half - sqrt(2*sqrt(2) + 5)/2, S.Half + sqrt(2*sqrt(2) + 5)/2) + + +def test_is_function_class_equation(): + assert _is_function_class_equation(TrigonometricFunction, + tan(x), x) is True + assert _is_function_class_equation(TrigonometricFunction, + tan(x) - 1, x) is True + assert _is_function_class_equation(TrigonometricFunction, + tan(x) + sin(x), x) is True + assert _is_function_class_equation(TrigonometricFunction, + tan(x) + sin(x) - a, x) is True + assert _is_function_class_equation(TrigonometricFunction, + sin(x)*tan(x) + sin(x), x) is True + assert _is_function_class_equation(TrigonometricFunction, + sin(x)*tan(x + a) + sin(x), x) is True + assert _is_function_class_equation(TrigonometricFunction, + sin(x)*tan(x*a) + sin(x), x) is True + assert _is_function_class_equation(TrigonometricFunction, + a*tan(x) - 1, x) is True + assert _is_function_class_equation(TrigonometricFunction, + tan(x)**2 + sin(x) - 1, x) is True + assert _is_function_class_equation(TrigonometricFunction, + tan(x) + x, x) is False + assert _is_function_class_equation(TrigonometricFunction, + tan(x**2), x) is False + assert _is_function_class_equation(TrigonometricFunction, + tan(x**2) + sin(x), x) is False + assert _is_function_class_equation(TrigonometricFunction, + tan(x)**sin(x), x) is False + assert _is_function_class_equation(TrigonometricFunction, + tan(sin(x)) + sin(x), x) is False + assert _is_function_class_equation(HyperbolicFunction, + tanh(x), x) is True + assert _is_function_class_equation(HyperbolicFunction, + tanh(x) - 1, x) is True + assert _is_function_class_equation(HyperbolicFunction, + tanh(x) + sinh(x), x) is True + assert _is_function_class_equation(HyperbolicFunction, + tanh(x) + sinh(x) - a, x) is True + assert _is_function_class_equation(HyperbolicFunction, + sinh(x)*tanh(x) + sinh(x), x) is True + assert _is_function_class_equation(HyperbolicFunction, + sinh(x)*tanh(x + a) + sinh(x), x) is True + assert _is_function_class_equation(HyperbolicFunction, + sinh(x)*tanh(x*a) + sinh(x), x) is True + assert _is_function_class_equation(HyperbolicFunction, + a*tanh(x) - 1, x) is True + assert _is_function_class_equation(HyperbolicFunction, + tanh(x)**2 + sinh(x) - 1, x) is True + assert _is_function_class_equation(HyperbolicFunction, + tanh(x) + x, x) is False + assert _is_function_class_equation(HyperbolicFunction, + tanh(x**2), x) is False + assert _is_function_class_equation(HyperbolicFunction, + tanh(x**2) + sinh(x), x) is False + assert _is_function_class_equation(HyperbolicFunction, + tanh(x)**sinh(x), x) is False + assert _is_function_class_equation(HyperbolicFunction, + tanh(sinh(x)) + sinh(x), x) is False + + +def test_garbage_input(): + raises(ValueError, lambda: solveset_real([y], y)) + x = Symbol('x', real=True) + assert solveset_real(x, 1) == S.EmptySet + assert solveset_real(x - 1, 1) == FiniteSet(x) + assert solveset_real(x, pi) == S.EmptySet + assert solveset_real(x, x**2) == S.EmptySet + + raises(ValueError, lambda: solveset_complex([x], x)) + assert solveset_complex(x, pi) == S.EmptySet + + raises(ValueError, lambda: solveset((x, y), x)) + raises(ValueError, lambda: solveset(x + 1, S.Reals)) + raises(ValueError, lambda: solveset(x + 1, x, 2)) + + +def test_solve_mul(): + assert solveset_real((a*x + b)*(exp(x) - 3), x) == \ + Union({log(3)}, Intersection({-b/a}, S.Reals)) + anz = Symbol('anz', nonzero=True) + bb = Symbol('bb', real=True) + assert solveset_real((anz*x + bb)*(exp(x) - 3), x) == \ + FiniteSet(-bb/anz, log(3)) + assert solveset_real((2*x + 8)*(8 + exp(x)), x) == FiniteSet(S(-4)) + assert solveset_real(x/log(x), x) is S.EmptySet + + +def test_solve_invert(): + assert solveset_real(exp(x) - 3, x) == FiniteSet(log(3)) + assert solveset_real(log(x) - 3, x) == FiniteSet(exp(3)) + + assert solveset_real(3**(x + 2), x) == FiniteSet() + assert solveset_real(3**(2 - x), x) == FiniteSet() + + assert solveset_real(y - b*exp(a/x), x) == Intersection( + S.Reals, FiniteSet(a/log(y/b))) + + # issue 4504 + assert solveset_real(2**x - 10, x) == FiniteSet(1 + log(5)/log(2)) + + +def test_issue_25768(): + assert dumeq(solveset_real(sin(x) - S.Half, x), Union( + ImageSet(Lambda(n, pi*2*n + pi/6), S.Integers), + ImageSet(Lambda(n, pi*2*n + pi*5/6), S.Integers))) + n1 = solveset_real(sin(x) - 0.5, x).n(5) + n2 = solveset_real(sin(x) - S.Half, x).n(5) + # help pass despite fp differences + eq = [i.replace( + lambda x:x.is_Float, + lambda x:Rational(x).limit_denominator(1000)) for i in (n1, n2)] + assert dumeq(*eq),(n1,n2) + + +def test_errorinverses(): + assert solveset_real(erf(x) - S.Half, x) == \ + FiniteSet(erfinv(S.Half)) + assert solveset_real(erfinv(x) - 2, x) == \ + FiniteSet(erf(2)) + assert solveset_real(erfc(x) - S.One, x) == \ + FiniteSet(erfcinv(S.One)) + assert solveset_real(erfcinv(x) - 2, x) == FiniteSet(erfc(2)) + + +def test_solve_polynomial(): + x = Symbol('x', real=True) + y = Symbol('y', real=True) + assert solveset_real(3*x - 2, x) == FiniteSet(Rational(2, 3)) + + assert solveset_real(x**2 - 1, x) == FiniteSet(-S.One, S.One) + assert solveset_real(x - y**3, x) == FiniteSet(y ** 3) + + assert solveset_real(x**3 - 15*x - 4, x) == FiniteSet( + -2 + 3 ** S.Half, + S(4), + -2 - 3 ** S.Half) + + assert solveset_real(sqrt(x) - 1, x) == FiniteSet(1) + assert solveset_real(sqrt(x) - 2, x) == FiniteSet(4) + assert solveset_real(x**Rational(1, 4) - 2, x) == FiniteSet(16) + assert solveset_real(x**Rational(1, 3) - 3, x) == FiniteSet(27) + assert len(solveset_real(x**5 + x**3 + 1, x)) == 1 + assert len(solveset_real(-2*x**3 + 4*x**2 - 2*x + 6, x)) > 0 + assert solveset_real(x**6 + x**4 + I, x) is S.EmptySet + + +def test_return_root_of(): + f = x**5 - 15*x**3 - 5*x**2 + 10*x + 20 + s = list(solveset_complex(f, x)) + for root in s: + assert root.func == CRootOf + + # if one uses solve to get the roots of a polynomial that has a CRootOf + # solution, make sure that the use of nfloat during the solve process + # doesn't fail. Note: if you want numerical solutions to a polynomial + # it is *much* faster to use nroots to get them than to solve the + # equation only to get CRootOf solutions which are then numerically + # evaluated. So for eq = x**5 + 3*x + 7 do Poly(eq).nroots() rather + # than [i.n() for i in solve(eq)] to get the numerical roots of eq. + assert nfloat(list(solveset_complex(x**5 + 3*x**3 + 7, x))[0], + exponent=False) == CRootOf(x**5 + 3*x**3 + 7, 0).n() + + sol = list(solveset_complex(x**6 - 2*x + 2, x)) + assert all(isinstance(i, CRootOf) for i in sol) and len(sol) == 6 + + f = x**5 - 15*x**3 - 5*x**2 + 10*x + 20 + s = list(solveset_complex(f, x)) + for root in s: + assert root.func == CRootOf + + s = x**5 + 4*x**3 + 3*x**2 + Rational(7, 4) + assert solveset_complex(s, x) == \ + FiniteSet(*Poly(s*4, domain='ZZ').all_roots()) + + # Refer issue #7876 + eq = x*(x - 1)**2*(x + 1)*(x**6 - x + 1) + assert solveset_complex(eq, x) == \ + FiniteSet(-1, 0, 1, CRootOf(x**6 - x + 1, 0), + CRootOf(x**6 - x + 1, 1), + CRootOf(x**6 - x + 1, 2), + CRootOf(x**6 - x + 1, 3), + CRootOf(x**6 - x + 1, 4), + CRootOf(x**6 - x + 1, 5)) + + +def test_solveset_sqrt_1(): + assert solveset_real(sqrt(5*x + 6) - 2 - x, x) == \ + FiniteSet(-S.One, S(2)) + assert solveset_real(sqrt(x - 1) - x + 7, x) == FiniteSet(10) + assert solveset_real(sqrt(x - 2) - 5, x) == FiniteSet(27) + assert solveset_real(sqrt(x) - 2 - 5, x) == FiniteSet(49) + assert solveset_real(sqrt(x**3), x) == FiniteSet(0) + assert solveset_real(sqrt(x - 1), x) == FiniteSet(1) + assert solveset_real(sqrt((x-3)/x), x) == FiniteSet(3) + assert solveset_real(sqrt((x-3)/x)-Rational(1, 2), x) == \ + FiniteSet(4) + +def test_solveset_sqrt_2(): + x = Symbol('x', real=True) + y = Symbol('y', real=True) + # http://tutorial.math.lamar.edu/Classes/Alg/SolveRadicalEqns.aspx#Solve_Rad_Ex2_a + assert solveset_real(sqrt(2*x - 1) - sqrt(x - 4) - 2, x) == \ + FiniteSet(S(5), S(13)) + assert solveset_real(sqrt(x + 7) + 2 - sqrt(3 - x), x) == \ + FiniteSet(-6) + + # http://www.purplemath.com/modules/solverad.htm + assert solveset_real(sqrt(17*x - sqrt(x**2 - 5)) - 7, x) == \ + FiniteSet(3) + + eq = x + 1 - (x**4 + 4*x**3 - x)**Rational(1, 4) + assert solveset_real(eq, x) == FiniteSet(Rational(-1, 2), Rational(-1, 3)) + + eq = sqrt(2*x + 9) - sqrt(x + 1) - sqrt(x + 4) + assert solveset_real(eq, x) == FiniteSet(0) + + eq = sqrt(x + 4) + sqrt(2*x - 1) - 3*sqrt(x - 1) + assert solveset_real(eq, x) == FiniteSet(5) + + eq = sqrt(x)*sqrt(x - 7) - 12 + assert solveset_real(eq, x) == FiniteSet(16) + + eq = sqrt(x - 3) + sqrt(x) - 3 + assert solveset_real(eq, x) == FiniteSet(4) + + eq = sqrt(2*x**2 - 7) - (3 - x) + assert solveset_real(eq, x) == FiniteSet(-S(8), S(2)) + + # others + eq = sqrt(9*x**2 + 4) - (3*x + 2) + assert solveset_real(eq, x) == FiniteSet(0) + + assert solveset_real(sqrt(x - 3) - sqrt(x) - 3, x) == FiniteSet() + + eq = (2*x - 5)**Rational(1, 3) - 3 + assert solveset_real(eq, x) == FiniteSet(16) + + assert solveset_real(sqrt(x) + sqrt(sqrt(x)) - 4, x) == \ + FiniteSet((Rational(-1, 2) + sqrt(17)/2)**4) + + eq = sqrt(x) - sqrt(x - 1) + sqrt(sqrt(x)) + assert solveset_real(eq, x) == FiniteSet() + + eq = (x - 4)**2 + (sqrt(x) - 2)**4 + assert solveset_real(eq, x) == FiniteSet(-4, 4) + + eq = (sqrt(x) + sqrt(x + 1) + sqrt(1 - x) - 6*sqrt(5)/5) + ans = solveset_real(eq, x) + ra = S('''-1484/375 - 4*(-S(1)/2 + sqrt(3)*I/2)*(-12459439/52734375 + + 114*sqrt(12657)/78125)**(S(1)/3) - 172564/(140625*(-S(1)/2 + + sqrt(3)*I/2)*(-12459439/52734375 + 114*sqrt(12657)/78125)**(S(1)/3))''') + rb = Rational(4, 5) + assert all(abs(eq.subs(x, i).n()) < 1e-10 for i in (ra, rb)) and \ + len(ans) == 2 and \ + {i.n(chop=True) for i in ans} == \ + {i.n(chop=True) for i in (ra, rb)} + + assert solveset_real(sqrt(x) + x**Rational(1, 3) + + x**Rational(1, 4), x) == FiniteSet(0) + + assert solveset_real(x/sqrt(x**2 + 1), x) == FiniteSet(0) + + eq = (x - y**3)/((y**2)*sqrt(1 - y**2)) + assert solveset_real(eq, x) == FiniteSet(y**3) + + # issue 4497 + assert solveset_real(1/(5 + x)**Rational(1, 5) - 9, x) == \ + FiniteSet(Rational(-295244, 59049)) + + +@XFAIL +def test_solve_sqrt_fail(): + # this only works if we check real_root(eq.subs(x, Rational(1, 3))) + # but checksol doesn't work like that + eq = (x**3 - 3*x**2)**Rational(1, 3) + 1 - x + assert solveset_real(eq, x) == FiniteSet(Rational(1, 3)) + + +@slow +def test_solve_sqrt_3(): + R = Symbol('R') + eq = sqrt(2)*R*sqrt(1/(R + 1)) + (R + 1)*(sqrt(2)*sqrt(1/(R + 1)) - 1) + sol = solveset_complex(eq, R) + fset = [Rational(5, 3) + 4*sqrt(10)*cos(atan(3*sqrt(111)/251)/3)/3, + -sqrt(10)*cos(atan(3*sqrt(111)/251)/3)/3 + + 40*re(1/((Rational(-1, 2) - sqrt(3)*I/2)*(Rational(251, 27) + sqrt(111)*I/9)**Rational(1, 3)))/9 + + sqrt(30)*sin(atan(3*sqrt(111)/251)/3)/3 + Rational(5, 3) + + I*(-sqrt(30)*cos(atan(3*sqrt(111)/251)/3)/3 - + sqrt(10)*sin(atan(3*sqrt(111)/251)/3)/3 + + 40*im(1/((Rational(-1, 2) - sqrt(3)*I/2)*(Rational(251, 27) + sqrt(111)*I/9)**Rational(1, 3)))/9)] + cset = [40*re(1/((Rational(-1, 2) + sqrt(3)*I/2)*(Rational(251, 27) + sqrt(111)*I/9)**Rational(1, 3)))/9 - + sqrt(10)*cos(atan(3*sqrt(111)/251)/3)/3 - sqrt(30)*sin(atan(3*sqrt(111)/251)/3)/3 + + Rational(5, 3) + + I*(40*im(1/((Rational(-1, 2) + sqrt(3)*I/2)*(Rational(251, 27) + sqrt(111)*I/9)**Rational(1, 3)))/9 - + sqrt(10)*sin(atan(3*sqrt(111)/251)/3)/3 + + sqrt(30)*cos(atan(3*sqrt(111)/251)/3)/3)] + + fs = FiniteSet(*fset) + cs = ConditionSet(R, Eq(eq, 0), FiniteSet(*cset)) + assert sol == (fs - {-1}) | (cs - {-1}) + + # the number of real roots will depend on the value of m: for m=1 there are 4 + # and for m=-1 there are none. + eq = -sqrt((m - q)**2 + (-m/(2*q) + S.Half)**2) + sqrt((-m**2/2 - sqrt( + 4*m**4 - 4*m**2 + 8*m + 1)/4 - Rational(1, 4))**2 + (m**2/2 - m - sqrt( + 4*m**4 - 4*m**2 + 8*m + 1)/4 - Rational(1, 4))**2) + unsolved_object = ConditionSet(q, Eq(sqrt((m - q)**2 + (-m/(2*q) + S.Half)**2) - + sqrt((-m**2/2 - sqrt(4*m**4 - 4*m**2 + 8*m + 1)/4 - Rational(1, 4))**2 + (m**2/2 - m - + sqrt(4*m**4 - 4*m**2 + 8*m + 1)/4 - Rational(1, 4))**2), 0), S.Reals) + assert solveset_real(eq, q) == unsolved_object + + +def test_solve_polynomial_symbolic_param(): + assert solveset_complex((x**2 - 1)**2 - a, x) == \ + FiniteSet(sqrt(1 + sqrt(a)), -sqrt(1 + sqrt(a)), + sqrt(1 - sqrt(a)), -sqrt(1 - sqrt(a))) + + # issue 4507 + assert solveset_complex(y - b/(1 + a*x), x) == \ + FiniteSet((b/y - 1)/a) - FiniteSet(-1/a) + + # issue 4508 + assert solveset_complex(y - b*x/(a + x), x) == \ + FiniteSet(-a*y/(y - b)) - FiniteSet(-a) + + +def test_solve_rational(): + assert solveset_real(1/x + 1, x) == FiniteSet(-S.One) + assert solveset_real(1/exp(x) - 1, x) == FiniteSet(0) + assert solveset_real(x*(1 - 5/x), x) == FiniteSet(5) + assert solveset_real(2*x/(x + 2) - 1, x) == FiniteSet(2) + assert solveset_real((x**2/(7 - x)).diff(x), x) == \ + FiniteSet(S.Zero, S(14)) + + +def test_solveset_real_gen_is_pow(): + assert solveset_real(sqrt(1) + 1, x) is S.EmptySet + + +def test_no_sol(): + assert solveset(1 - oo*x) is S.EmptySet + assert solveset(oo*x, x) is S.EmptySet + assert solveset(oo*x - oo, x) is S.EmptySet + assert solveset_real(4, x) is S.EmptySet + assert solveset_real(exp(x), x) is S.EmptySet + assert solveset_real(x**2 + 1, x) is S.EmptySet + assert solveset_real(-3*a/sqrt(x), x) is S.EmptySet + assert solveset_real(1/x, x) is S.EmptySet + assert solveset_real(-(1 + x)/(2 + x)**2 + 1/(2 + x), x + ) is S.EmptySet + + +def test_sol_zero_real(): + assert solveset_real(0, x) == S.Reals + assert solveset(0, x, Interval(1, 2)) == Interval(1, 2) + assert solveset_real(-x**2 - 2*x + (x + 1)**2 - 1, x) == S.Reals + + +def test_no_sol_rational_extragenous(): + assert solveset_real((x/(x + 1) + 3)**(-2), x) is S.EmptySet + assert solveset_real((x - 1)/(1 + 1/(x - 1)), x) is S.EmptySet + + +def test_solve_polynomial_cv_1a(): + """ + Test for solving on equations that can be converted to + a polynomial equation using the change of variable y -> x**Rational(p, q) + """ + assert solveset_real(sqrt(x) - 1, x) == FiniteSet(1) + assert solveset_real(sqrt(x) - 2, x) == FiniteSet(4) + assert solveset_real(x**Rational(1, 4) - 2, x) == FiniteSet(16) + assert solveset_real(x**Rational(1, 3) - 3, x) == FiniteSet(27) + assert solveset_real(x*(x**(S.One / 3) - 3), x) == \ + FiniteSet(S.Zero, S(27)) + + +def test_solveset_real_rational(): + """Test solveset_real for rational functions""" + x = Symbol('x', real=True) + y = Symbol('y', real=True) + assert solveset_real((x - y**3) / ((y**2)*sqrt(1 - y**2)), x) \ + == FiniteSet(y**3) + # issue 4486 + assert solveset_real(2*x/(x + 2) - 1, x) == FiniteSet(2) + + +def test_solveset_real_log(): + assert solveset_real(log((x-1)*(x+1)), x) == \ + FiniteSet(sqrt(2), -sqrt(2)) + + +def test_poly_gens(): + assert solveset_real(4**(2*(x**2) + 2*x) - 8, x) == \ + FiniteSet(Rational(-3, 2), S.Half) + + +def test_solve_abs(): + n = Dummy('n') + raises(ValueError, lambda: solveset(Abs(x) - 1, x)) + assert solveset(Abs(x) - n, x, S.Reals).dummy_eq( + ConditionSet(x, Contains(n, Interval(0, oo)), {-n, n})) + assert solveset_real(Abs(x) - 2, x) == FiniteSet(-2, 2) + assert solveset_real(Abs(x) + 2, x) is S.EmptySet + assert solveset_real(Abs(x + 3) - 2*Abs(x - 3), x) == \ + FiniteSet(1, 9) + assert solveset_real(2*Abs(x) - Abs(x - 1), x) == \ + FiniteSet(-1, Rational(1, 3)) + + sol = ConditionSet( + x, + And( + Contains(b, Interval(0, oo)), + Contains(a + b, Interval(0, oo)), + Contains(a - b, Interval(0, oo))), + FiniteSet(-a - b - 3, -a + b - 3, a - b - 3, a + b - 3)) + eq = Abs(Abs(x + 3) - a) - b + assert invert_real(eq, 0, x)[1] == sol + reps = {a: 3, b: 1} + eqab = eq.subs(reps) + for si in sol.subs(reps): + assert not eqab.subs(x, si) + assert dumeq(solveset(Eq(sin(Abs(x)), 1), x, domain=S.Reals), Union( + Intersection(Interval(0, oo), Union( + Intersection(ImageSet(Lambda(n, 2*n*pi + 3*pi/2), S.Integers), + Interval(-oo, 0)), + Intersection(ImageSet(Lambda(n, 2*n*pi + pi/2), S.Integers), + Interval(0, oo)))))) + + +def test_issue_9824(): + assert dumeq(solveset(sin(x)**2 - 2*sin(x) + 1, x), ImageSet(Lambda(n, 2*n*pi + pi/2), S.Integers)) + assert dumeq(solveset(cos(x)**2 - 2*cos(x) + 1, x), ImageSet(Lambda(n, 2*n*pi), S.Integers)) + + +def test_issue_9565(): + assert solveset_real(Abs((x - 1)/(x - 5)) <= Rational(1, 3), x) == Interval(-1, 2) + + +def test_issue_10069(): + eq = abs(1/(x - 1)) - 1 > 0 + assert solveset_real(eq, x) == Union( + Interval.open(0, 1), Interval.open(1, 2)) + + +def test_real_imag_splitting(): + a, b = symbols('a b', real=True) + assert solveset_real(sqrt(a**2 - b**2) - 3, a) == \ + FiniteSet(-sqrt(b**2 + 9), sqrt(b**2 + 9)) + assert solveset_real(sqrt(a**2 + b**2) - 3, a) != \ + S.EmptySet + + +def test_units(): + assert solveset_real(1/x - 1/(2*cm), x) == FiniteSet(2*cm) + + +def test_solve_only_exp_1(): + y = Symbol('y', positive=True) + assert solveset_real(exp(x) - y, x) == FiniteSet(log(y)) + assert solveset_real(exp(x) + exp(-x) - 4, x) == \ + FiniteSet(log(-sqrt(3) + 2), log(sqrt(3) + 2)) + assert solveset_real(exp(x) + exp(-x) - y, x) != S.EmptySet + + +def test_atan2(): + # The .inverse() method on atan2 works only if x.is_real is True and the + # second argument is a real constant + assert solveset_real(atan2(x, 2) - pi/3, x) == FiniteSet(2*sqrt(3)) + + +def test_piecewise_solveset(): + eq = Piecewise((x - 2, Gt(x, 2)), (2 - x, True)) - 3 + assert set(solveset_real(eq, x)) == set(FiniteSet(-1, 5)) + + absxm3 = Piecewise( + (x - 3, 0 <= x - 3), + (3 - x, 0 > x - 3)) + y = Symbol('y', positive=True) + assert solveset_real(absxm3 - y, x) == FiniteSet(-y + 3, y + 3) + + f = Piecewise(((x - 2)**2, x >= 0), (0, True)) + assert solveset(f, x, domain=S.Reals) == Union(FiniteSet(2), Interval(-oo, 0, True, True)) + + assert solveset( + Piecewise((x + 1, x > 0), (I, True)) - I, x, S.Reals + ) == Interval(-oo, 0) + + assert solveset(Piecewise((x - 1, Ne(x, I)), (x, True)), x) == FiniteSet(1) + + # issue 19718 + g = Piecewise((1, x > 10), (0, True)) + assert solveset(g > 0, x, S.Reals) == Interval.open(10, oo) + + from sympy.logic.boolalg import BooleanTrue + f = BooleanTrue() + assert solveset(f, x, domain=Interval(-3, 10)) == Interval(-3, 10) + + # issue 20552 + f = Piecewise((0, Eq(x, 0)), (x**2/Abs(x), True)) + g = Piecewise((0, Eq(x, pi)), ((x - pi)/sin(x), True)) + assert solveset(f, x, domain=S.Reals) == FiniteSet(0) + assert solveset(g) == FiniteSet(pi) + + +def test_solveset_complex_polynomial(): + assert solveset_complex(a*x**2 + b*x + c, x) == \ + FiniteSet(-b/(2*a) - sqrt(-4*a*c + b**2)/(2*a), + -b/(2*a) + sqrt(-4*a*c + b**2)/(2*a)) + + assert solveset_complex(x - y**3, y) == FiniteSet( + (-x**Rational(1, 3))/2 + I*sqrt(3)*x**Rational(1, 3)/2, + x**Rational(1, 3), + (-x**Rational(1, 3))/2 - I*sqrt(3)*x**Rational(1, 3)/2) + + assert solveset_complex(x + 1/x - 1, x) == \ + FiniteSet(S.Half + I*sqrt(3)/2, S.Half - I*sqrt(3)/2) + + +def test_sol_zero_complex(): + assert solveset_complex(0, x) is S.Complexes + + +def test_solveset_complex_rational(): + assert solveset_complex((x - 1)*(x - I)/(x - 3), x) == \ + FiniteSet(1, I) + + assert solveset_complex((x - y**3)/((y**2)*sqrt(1 - y**2)), x) == \ + FiniteSet(y**3) + assert solveset_complex(-x**2 - I, x) == \ + FiniteSet(-sqrt(2)/2 + sqrt(2)*I/2, sqrt(2)/2 - sqrt(2)*I/2) + + +def test_solve_quintics(): + skip("This test is too slow") + f = x**5 - 110*x**3 - 55*x**2 + 2310*x + 979 + s = solveset_complex(f, x) + for root in s: + res = f.subs(x, root.n()).n() + assert tn(res, 0) + + f = x**5 + 15*x + 12 + s = solveset_complex(f, x) + for root in s: + res = f.subs(x, root.n()).n() + assert tn(res, 0) + + +def test_solveset_complex_exp(): + assert dumeq(solveset_complex(exp(x) - 1, x), + imageset(Lambda(n, I*2*n*pi), S.Integers)) + assert dumeq(solveset_complex(exp(x) - I, x), + imageset(Lambda(n, I*(2*n*pi + pi/2)), S.Integers)) + assert solveset_complex(1/exp(x), x) == S.EmptySet + assert dumeq(solveset_complex(sinh(x).rewrite(exp), x), + imageset(Lambda(n, n*pi*I), S.Integers)) + + +def test_solveset_real_exp(): + assert solveset(Eq((-2)**x, 4), x, S.Reals) == FiniteSet(2) + assert solveset(Eq(-2**x, 4), x, S.Reals) == S.EmptySet + assert solveset(Eq((-3)**x, 27), x, S.Reals) == S.EmptySet + assert solveset(Eq((-5)**(x+1), 625), x, S.Reals) == FiniteSet(3) + assert solveset(Eq(2**(x-3), -16), x, S.Reals) == S.EmptySet + assert solveset(Eq((-3)**(x - 3), -3**39), x, S.Reals) == FiniteSet(42) + assert solveset(Eq(2**x, y), x, S.Reals) == Intersection(S.Reals, FiniteSet(log(y)/log(2))) + + assert invert_real((-2)**(2*x) - 16, 0, x) == (x, FiniteSet(2)) + + +def test_solve_complex_log(): + assert solveset_complex(log(x), x) == FiniteSet(1) + assert solveset_complex(1 - log(a + 4*x**2), x) == \ + FiniteSet(-sqrt(-a + E)/2, sqrt(-a + E)/2) + + +def test_solve_complex_sqrt(): + assert solveset_complex(sqrt(5*x + 6) - 2 - x, x) == \ + FiniteSet(-S.One, S(2)) + assert solveset_complex(sqrt(5*x + 6) - (2 + 2*I) - x, x) == \ + FiniteSet(-S(2), 3 - 4*I) + assert solveset_complex(4*x*(1 - a * sqrt(x)), x) == \ + FiniteSet(S.Zero, 1 / a ** 2) + + +def test_solveset_complex_tan(): + s = solveset_complex(tan(x).rewrite(exp), x) + assert dumeq(s, imageset(Lambda(n, pi*n), S.Integers) - \ + imageset(Lambda(n, pi*n + pi/2), S.Integers)) + + +@_both_exp_pow +def test_solve_trig(): + assert dumeq(solveset_real(sin(x), x), + Union(imageset(Lambda(n, 2*pi*n), S.Integers), + imageset(Lambda(n, 2*pi*n + pi), S.Integers))) + + assert dumeq(solveset_real(sin(x) - 1, x), + imageset(Lambda(n, 2*pi*n + pi/2), S.Integers)) + + assert dumeq(solveset_real(cos(x), x), + Union(imageset(Lambda(n, 2*pi*n + pi/2), S.Integers), + imageset(Lambda(n, 2*pi*n + pi*Rational(3, 2)), S.Integers))) + + assert dumeq(solveset_real(sin(x) + cos(x), x), + Union(imageset(Lambda(n, 2*n*pi + pi*Rational(3, 4)), S.Integers), + imageset(Lambda(n, 2*n*pi + pi*Rational(7, 4)), S.Integers))) + + assert solveset_real(sin(x)**2 + cos(x)**2, x) == S.EmptySet + + assert dumeq(solveset_complex(cos(x) - S.Half, x), + Union(imageset(Lambda(n, 2*n*pi + pi*Rational(5, 3)), S.Integers), + imageset(Lambda(n, 2*n*pi + pi/3), S.Integers))) + + assert dumeq(solveset(sin(y + a) - sin(y), a, domain=S.Reals), + ConditionSet(a, (S(-1) <= sin(y)) & (sin(y) <= S(1)), Union( + ImageSet(Lambda(n, 2*n*pi - y + asin(sin(y))), S.Integers), + ImageSet(Lambda(n, 2*n*pi - y - asin(sin(y)) + pi), S.Integers)))) + + assert dumeq(solveset_real(sin(2*x)*cos(x) + cos(2*x)*sin(x)-1, x), + ImageSet(Lambda(n, n*pi*Rational(2, 3) + pi/6), S.Integers)) + + assert dumeq(solveset_real(2*tan(x)*sin(x) + 1, x), Union( + ImageSet(Lambda(n, 2*n*pi + atan(sqrt(2)*sqrt(-1 + sqrt(17))/ + (1 - sqrt(17))) + pi), S.Integers), + ImageSet(Lambda(n, 2*n*pi - atan(sqrt(2)*sqrt(-1 + sqrt(17))/ + (1 - sqrt(17))) + pi), S.Integers))) + + assert dumeq(solveset_real(cos(2*x)*cos(4*x) - 1, x), + ImageSet(Lambda(n, n*pi), S.Integers)) + + assert dumeq(solveset(sin(x/10) + Rational(3, 4)), Union( + ImageSet(Lambda(n, 20*n*pi - 10*asin(S(3)/4) + 20*pi), S.Integers), + ImageSet(Lambda(n, 20*n*pi + 10*asin(S(3)/4) + 10*pi), S.Integers))) + + assert dumeq(solveset(cos(x/15) + cos(x/5)), Union( + ImageSet(Lambda(n, 30*n*pi + 15*pi/2), S.Integers), + ImageSet(Lambda(n, 30*n*pi + 45*pi/2), S.Integers), + ImageSet(Lambda(n, 30*n*pi + 75*pi/4), S.Integers), + ImageSet(Lambda(n, 30*n*pi + 45*pi/4), S.Integers), + ImageSet(Lambda(n, 30*n*pi + 105*pi/4), S.Integers), + ImageSet(Lambda(n, 30*n*pi + 15*pi/4), S.Integers))) + + assert dumeq(solveset(sec(sqrt(2)*x/3) + 5), Union( + ImageSet(Lambda(n, 3*sqrt(2)*(2*n*pi - asec(-5))/2), S.Integers), + ImageSet(Lambda(n, 3*sqrt(2)*(2*n*pi + asec(-5))/2), S.Integers))) + + assert dumeq(simplify(solveset(tan(pi*x) - cot(pi/2*x))), Union( + ImageSet(Lambda(n, 4*n + 1), S.Integers), + ImageSet(Lambda(n, 4*n + 3), S.Integers), + ImageSet(Lambda(n, 4*n + Rational(7, 3)), S.Integers), + ImageSet(Lambda(n, 4*n + Rational(5, 3)), S.Integers), + ImageSet(Lambda(n, 4*n + Rational(11, 3)), S.Integers), + ImageSet(Lambda(n, 4*n + Rational(1, 3)), S.Integers))) + + assert dumeq(solveset(cos(9*x)), Union( + ImageSet(Lambda(n, 2*n*pi/9 + pi/18), S.Integers), + ImageSet(Lambda(n, 2*n*pi/9 + pi/6), S.Integers))) + + assert dumeq(solveset(sin(8*x) + cot(12*x), x, S.Reals), Union( + ImageSet(Lambda(n, n*pi/2 + pi/8), S.Integers), + ImageSet(Lambda(n, n*pi/2 + 3*pi/8), S.Integers), + ImageSet(Lambda(n, n*pi/2 + 5*pi/16), S.Integers), + ImageSet(Lambda(n, n*pi/2 + 3*pi/16), S.Integers), + ImageSet(Lambda(n, n*pi/2 + 7*pi/16), S.Integers), + ImageSet(Lambda(n, n*pi/2 + pi/16), S.Integers))) + + # This is the only remaining solveset test that actually ends up being solved + # by _solve_trig2(). All others are handled by the improved _solve_trig1. + assert dumeq(solveset_real(2*cos(x)*cos(2*x) - 1, x), + Union(ImageSet(Lambda(n, 2*n*pi + 2*atan(sqrt(-2*2**Rational(1, 3)*(67 + + 9*sqrt(57))**Rational(2, 3) + 8*2**Rational(2, 3) + 11*(67 + + 9*sqrt(57))**Rational(1, 3))/(3*(67 + 9*sqrt(57))**Rational(1, 6)))), S.Integers), + ImageSet(Lambda(n, 2*n*pi - 2*atan(sqrt(-2*2**Rational(1, 3)*(67 + + 9*sqrt(57))**Rational(2, 3) + 8*2**Rational(2, 3) + 11*(67 + + 9*sqrt(57))**Rational(1, 3))/(3*(67 + 9*sqrt(57))**Rational(1, 6))) + + 2*pi), S.Integers))) + + # issue #16870 + assert dumeq(simplify(solveset(sin(x/180*pi) - S.Half, x, S.Reals)), Union( + ImageSet(Lambda(n, 360*n + 150), S.Integers), + ImageSet(Lambda(n, 360*n + 30), S.Integers))) + + +def test_solve_trig_hyp_by_inversion(): + n = Dummy('n') + assert solveset_real(sin(2*x + 3) - S(1)/2, x).dummy_eq(Union( + ImageSet(Lambda(n, n*pi - S(3)/2 + 13*pi/12), S.Integers), + ImageSet(Lambda(n, n*pi - S(3)/2 + 17*pi/12), S.Integers))) + assert solveset_complex(sin(2*x + 3) - S(1)/2, x).dummy_eq(Union( + ImageSet(Lambda(n, n*pi - S(3)/2 + 13*pi/12), S.Integers), + ImageSet(Lambda(n, n*pi - S(3)/2 + 17*pi/12), S.Integers))) + assert solveset_real(tan(x) - tan(pi/10), x).dummy_eq( + ImageSet(Lambda(n, n*pi + pi/10), S.Integers)) + assert solveset_complex(tan(x) - tan(pi/10), x).dummy_eq( + ImageSet(Lambda(n, n*pi + pi/10), S.Integers)) + + assert solveset_real(3*cosh(2*x) - 5, x) == FiniteSet( + -acosh(S(5)/3)/2, acosh(S(5)/3)/2) + assert solveset_complex(3*cosh(2*x) - 5, x).dummy_eq(Union( + ImageSet(Lambda(n, n*I*pi - acosh(S(5)/3)/2), S.Integers), + ImageSet(Lambda(n, n*I*pi + acosh(S(5)/3)/2), S.Integers))) + assert solveset_real(sinh(x - 3) - 2, x) == FiniteSet( + asinh(2) + 3) + assert solveset_complex(sinh(x - 3) - 2, x).dummy_eq(Union( + ImageSet(Lambda(n, 2*n*I*pi + asinh(2) + 3), S.Integers), + ImageSet(Lambda(n, 2*n*I*pi - asinh(2) + 3 + I*pi), S.Integers))) + + assert solveset_real(cos(sinh(x))-cos(pi/12), x).dummy_eq(Union( + ImageSet(Lambda(n, asinh(2*n*pi + pi/12)), S.Integers), + ImageSet(Lambda(n, asinh(2*n*pi + 23*pi/12)), S.Integers))) + assert solveset(cos(sinh(x))-cos(pi/12), x, Interval(2,3)) == \ + FiniteSet(asinh(23*pi/12), asinh(25*pi/12)) + assert solveset_real(cosh(x**2-1)-2, x) == FiniteSet( + -sqrt(1 + acosh(2)), sqrt(1 + acosh(2))) + + assert solveset_real(sin(x) - 2, x) == S.EmptySet # issue #17334 + assert solveset_real(cos(x) + 2, x) == S.EmptySet + assert solveset_real(sec(x), x) == S.EmptySet + assert solveset_real(csc(x), x) == S.EmptySet + assert solveset_real(cosh(x) + 1, x) == S.EmptySet + assert solveset_real(coth(x), x) == S.EmptySet + assert solveset_real(sech(x) - 2, x) == S.EmptySet + assert solveset_real(sech(x), x) == S.EmptySet + assert solveset_real(tanh(x) + 1, x) == S.EmptySet + assert solveset_complex(tanh(x), 1) == S.EmptySet + assert solveset_complex(coth(x), -1) == S.EmptySet + assert solveset_complex(sech(x), 0) == S.EmptySet + assert solveset_complex(csch(x), 0) == S.EmptySet + + assert solveset_real(abs(csch(x)) - 3, x) == FiniteSet(-acsch(3), acsch(3)) + + assert solveset_real(tanh(x**2 - 1) - exp(-9), x) == FiniteSet( + -sqrt(atanh(exp(-9)) + 1), sqrt(atanh(exp(-9)) + 1)) + + assert solveset_real(coth(log(x)) + 2, x) == FiniteSet(exp(-acoth(2))) + assert solveset_real(coth(exp(x)) + 2, x) == S.EmptySet + + assert solveset_complex(sinh(x) - I/2, x).dummy_eq(Union( + ImageSet(Lambda(n, 2*I*pi*n + 5*I*pi/6), S.Integers), + ImageSet(Lambda(n, 2*I*pi*n + I*pi/6), S.Integers))) + assert solveset_complex(sinh(x/10) + Rational(3, 4), x).dummy_eq(Union( + ImageSet(Lambda(n, 20*n*I*pi - 10*asinh(S(3)/4)), S.Integers), + ImageSet(Lambda(n, 20*n*I*pi + 10*asinh(S(3)/4) + 10*I*pi), S.Integers))) + assert solveset_complex(sech(sqrt(2)*x/3) + 5, x).dummy_eq(Union( + ImageSet(Lambda(n, 3*sqrt(2)*(2*n*I*pi - asech(-5))/2), S.Integers), + ImageSet(Lambda(n, 3*sqrt(2)*(2*n*I*pi + asech(-5))/2), S.Integers))) + assert solveset_complex(cosh(9*x), x).dummy_eq(Union( + ImageSet(Lambda(n, 2*n*I*pi/9 + I*pi/18), S.Integers), + ImageSet(Lambda(n, 2*n*I*pi/9 + I*pi/6), S.Integers))) + + eq = (x**5 -4*x + 1).subs(x, coth(z)) + assert solveset(eq, z, S.Complexes).dummy_eq(Union( + ImageSet(Lambda(n, n*I*pi + acoth(CRootOf(x**5 -4*x + 1, 0))), S.Integers), + ImageSet(Lambda(n, n*I*pi + acoth(CRootOf(x**5 -4*x + 1, 1))), S.Integers), + ImageSet(Lambda(n, n*I*pi + acoth(CRootOf(x**5 -4*x + 1, 2))), S.Integers), + ImageSet(Lambda(n, n*I*pi + acoth(CRootOf(x**5 -4*x + 1, 3))), S.Integers), + ImageSet(Lambda(n, n*I*pi + acoth(CRootOf(x**5 -4*x + 1, 4))), S.Integers))) + assert solveset(eq, z, S.Reals) == FiniteSet( + acoth(CRootOf(x**5 - 4*x + 1, 0)), acoth(CRootOf(x**5 - 4*x + 1, 2))) + + eq = ((x-sqrt(3)/2)*(x+2)).expand().subs(x, cos(x)) + assert solveset(eq, x, S.Complexes).dummy_eq(Union( + ImageSet(Lambda(n, 2*n*pi - acos(-2)), S.Integers), + ImageSet(Lambda(n, 2*n*pi + acos(-2)), S.Integers), + ImageSet(Lambda(n, 2*n*pi + pi/6), S.Integers), + ImageSet(Lambda(n, 2*n*pi + 11*pi/6), S.Integers))) + assert solveset(eq, x, S.Reals).dummy_eq(Union( + ImageSet(Lambda(n, 2*n*pi + pi/6), S.Integers), + ImageSet(Lambda(n, 2*n*pi + 11*pi/6), S.Integers))) + + assert solveset((1+sec(sqrt(3)*x+4)**2)/(1-sec(sqrt(3)*x+4))).dummy_eq(Union( + ImageSet(Lambda(n, sqrt(3)*(2*n*pi - 4 - asec(I))/3), S.Integers), + ImageSet(Lambda(n, sqrt(3)*(2*n*pi - 4 + asec(I))/3), S.Integers), + ImageSet(Lambda(n, sqrt(3)*(2*n*pi - 4 - asec(-I))/3), S.Integers), + ImageSet(Lambda(n, sqrt(3)*(2*n*pi - 4 + asec(-I))/3), S.Integers))) + + assert all_close(solveset(tan(3.14*x)**(S(3)/2)-5.678, x, Interval(0, 3)), + FiniteSet(0.403301114561067, 0.403301114561067 + 0.318471337579618*pi, + 0.403301114561067 + 0.636942675159236*pi)) + + +def test_old_trig_issues(): + # issues #9606 / #9531: + assert solveset(sinh(x), x, S.Reals) == FiniteSet(0) + assert solveset(sinh(x), x, S.Complexes).dummy_eq(Union( + ImageSet(Lambda(n, 2*n*I*pi), S.Integers), + ImageSet(Lambda(n, 2*n*I*pi + I*pi), S.Integers))) + + # issues #11218 / #18427 + assert solveset(sin(pi*x), x, S.Reals).dummy_eq(Union( + ImageSet(Lambda(n, (2*n*pi + pi)/pi), S.Integers), + ImageSet(Lambda(n, 2*n), S.Integers))) + assert solveset(sin(pi*x), x).dummy_eq(Union( + ImageSet(Lambda(n, (2*n*pi + pi)/pi), S.Integers), + ImageSet(Lambda(n, 2*n), S.Integers))) + + # issue #17543 + assert solveset(I*cot(8*x - 8*E), x).dummy_eq( + ImageSet(Lambda(n, pi*n/8 - 13*pi/16 + E), S.Integers)) + + # issue #20798 + assert all_close(solveset(cos(2*x) - 0.5, x, Interval(0, 2*pi)), FiniteSet( + 0.523598775598299, -0.523598775598299 + pi, + -0.523598775598299 + 2*pi, 0.523598775598299 + pi)) + sol = Union(ImageSet(Lambda(n, n*pi - 0.523598775598299), S.Integers), + ImageSet(Lambda(n, n*pi + 0.523598775598299), S.Integers)) + ret = solveset(cos(2*x) - 0.5, x, S.Reals) + # replace Dummy n by the regular Symbol n to allow all_close comparison. + ret = ret.subs(ret.atoms(Dummy).pop(), n) + assert all_close(ret, sol) + ret = solveset(cos(2*x) - 0.5, x, S.Complexes) + ret = ret.subs(ret.atoms(Dummy).pop(), n) + assert all_close(ret, sol) + + # issue #21296 / #17667 + assert solveset(tan(x)-sqrt(2), x, Interval(0, pi/2)) == FiniteSet(atan(sqrt(2))) + assert solveset(tan(x)-pi, x, Interval(0, pi/2)) == FiniteSet(atan(pi)) + + # issue #17667 + # not yet working properly: + # solveset(cos(x)-y, x, Interval(0, pi)) + assert solveset(cos(x)-y, x, S.Reals).dummy_eq( + ConditionSet(x,(S(-1) <= y) & (y <= S(1)), Union( + ImageSet(Lambda(n, 2*n*pi - acos(y)), S.Integers), + ImageSet(Lambda(n, 2*n*pi + acos(y)), S.Integers)))) + + # issue #17579 + # Valid result, but the intersection could potentially be simplified. + assert solveset(sin(log(x)), x, Interval(0,1, True, False)).dummy_eq( + Union(Intersection(ImageSet(Lambda(n, exp(2*n*pi)), S.Integers), Interval.Lopen(0, 1)), + Intersection(ImageSet(Lambda(n, exp(2*n*pi + pi)), S.Integers), Interval.Lopen(0, 1)))) + + # issue #17334 + assert solveset(sin(x) - sin(1), x, S.Reals).dummy_eq(Union( + ImageSet(Lambda(n, 2*n*pi + 1), S.Integers), + ImageSet(Lambda(n, 2*n*pi - 1 + pi), S.Integers))) + assert solveset(sin(x) - sqrt(5)/3, x, S.Reals).dummy_eq(Union( + ImageSet(Lambda(n, 2*n*pi + asin(sqrt(5)/3)), S.Integers), + ImageSet(Lambda(n, 2*n*pi - asin(sqrt(5)/3) + pi), S.Integers))) + assert solveset(sinh(x)-cosh(2), x, S.Reals) == FiniteSet(asinh(cosh(2))) + + # issue 9825 + assert solveset(Eq(tan(x), y), x, domain=S.Reals).dummy_eq( + ConditionSet(x, (-oo < y) & (y < oo), + ImageSet(Lambda(n, n*pi + atan(y)), S.Integers))) + r = Symbol('r', real=True) + assert solveset(Eq(tan(x), r), x, domain=S.Reals).dummy_eq( + ImageSet(Lambda(n, n*pi + atan(r)), S.Integers)) + + +def test_solve_hyperbolic(): + # actual solver: _solve_trig1 + n = Dummy('n') + assert solveset(sinh(x) + cosh(x), x) == S.EmptySet + assert solveset(sinh(x) + cos(x), x) == ConditionSet(x, + Eq(cos(x) + sinh(x), 0), S.Complexes) + assert solveset_real(sinh(x) + sech(x), x) == FiniteSet( + log(sqrt(sqrt(5) - 2))) + assert solveset_real(cosh(2*x) + 2*sinh(x) - 5, x) == FiniteSet( + log(-2 + sqrt(5)), log(1 + sqrt(2))) + assert solveset_real((coth(x) + sinh(2*x))/cosh(x) - 3, x) == FiniteSet( + log(S.Half + sqrt(5)/2), log(1 + sqrt(2))) + assert solveset_real(cosh(x)*sinh(x) - 2, x) == FiniteSet( + log(4 + sqrt(17))/2) + assert solveset_real(sinh(x) + tanh(x) - 1, x) == FiniteSet( + log(sqrt(2)/2 + sqrt(-S(1)/2 + sqrt(2)))) + + assert dumeq(solveset_complex(sinh(x) + sech(x), x), Union( + ImageSet(Lambda(n, 2*n*I*pi + log(sqrt(-2 + sqrt(5)))), S.Integers), + ImageSet(Lambda(n, I*(2*n*pi + pi/2) + log(sqrt(2 + sqrt(5)))), S.Integers), + ImageSet(Lambda(n, I*(2*n*pi + pi) + log(sqrt(-2 + sqrt(5)))), S.Integers), + ImageSet(Lambda(n, I*(2*n*pi - pi/2) + log(sqrt(2 + sqrt(5)))), S.Integers))) + + assert dumeq(solveset(cosh(x/15) + cosh(x/5)), Union( + ImageSet(Lambda(n, 15*I*(2*n*pi + pi/2)), S.Integers), + ImageSet(Lambda(n, 15*I*(2*n*pi - pi/2)), S.Integers), + ImageSet(Lambda(n, 15*I*(2*n*pi - 3*pi/4)), S.Integers), + ImageSet(Lambda(n, 15*I*(2*n*pi + 3*pi/4)), S.Integers), + ImageSet(Lambda(n, 15*I*(2*n*pi - pi/4)), S.Integers), + ImageSet(Lambda(n, 15*I*(2*n*pi + pi/4)), S.Integers))) + + assert dumeq(solveset(tanh(pi*x) - coth(pi/2*x)), Union( + ImageSet(Lambda(n, 2*I*(2*n*pi + pi/2)/pi), S.Integers), + ImageSet(Lambda(n, 2*I*(2*n*pi - pi/2)/pi), S.Integers))) + + # issues #18490 / #19489 + assert solveset(cosh(x) + cosh(3*x) - cosh(5*x), x, S.Reals + ).dummy_eq(ConditionSet(x, + Eq(cosh(x) + cosh(3*x) - cosh(5*x), 0), S.Reals)) + assert solveset(sinh(8*x) + coth(12*x)).dummy_eq( + ConditionSet(x, Eq(sinh(8*x) + coth(12*x), 0), S.Complexes)) + + +def test_solve_trig_hyp_symbolic(): + # actual solver: invert_trig_hyp + assert dumeq(solveset(sin(a*x), x), ConditionSet(x, Ne(a, 0), Union( + ImageSet(Lambda(n, (2*n*pi + pi)/a), S.Integers), + ImageSet(Lambda(n, 2*n*pi/a), S.Integers)))) + + assert dumeq(solveset(cosh(x/a), x), ConditionSet(x, Ne(a, 0), Union( + ImageSet(Lambda(n, a*(2*n*I*pi + I*pi/2)), S.Integers), + ImageSet(Lambda(n, a*(2*n*I*pi + 3*I*pi/2)), S.Integers)))) + + assert dumeq(solveset(sin(2*sqrt(3)/3*a**2/(b*pi)*x) + + cos(4*sqrt(3)/3*a**2/(b*pi)*x), x), + ConditionSet(x, Ne(b, 0) & Ne(a**2, 0), Union( + ImageSet(Lambda(n, sqrt(3)*pi*b*(2*n*pi + pi/2)/(2*a**2)), S.Integers), + ImageSet(Lambda(n, sqrt(3)*pi*b*(2*n*pi - 5*pi/6)/(2*a**2)), S.Integers), + ImageSet(Lambda(n, sqrt(3)*pi*b*(2*n*pi - pi/6)/(2*a**2)), S.Integers)))) + + assert dumeq(solveset(cosh((a**2 + 1)*x) - 3, x), ConditionSet( + x, Ne(a**2 + 1, 0), Union( + ImageSet(Lambda(n, (2*n*I*pi - acosh(3))/(a**2 + 1)), S.Integers), + ImageSet(Lambda(n, (2*n*I*pi + acosh(3))/(a**2 + 1)), S.Integers)))) + + ar = Symbol('ar', real=True) + assert solveset(cosh((ar**2 + 1)*x) - 2, x, S.Reals) == FiniteSet( + -acosh(2)/(ar**2 + 1), acosh(2)/(ar**2 + 1)) + + # actual solver: _solve_trig1 + assert dumeq(simplify(solveset(cot((1 + I)*x) - cot((3 + 3*I)*x), x)), Union( + ImageSet(Lambda(n, pi*(1 - I)*(4*n + 1)/4), S.Integers), + ImageSet(Lambda(n, pi*(1 - I)*(4*n - 1)/4), S.Integers))) + + +def test_issue_9616(): + assert dumeq(solveset(sinh(x) + tanh(x) - 1, x), Union( + ImageSet(Lambda(n, 2*n*I*pi + log(sqrt(2)/2 + sqrt(-S.Half + sqrt(2)))), S.Integers), + ImageSet(Lambda(n, I*(2*n*pi - atan(sqrt(2)*sqrt(S.Half + sqrt(2))) + pi) + + log(sqrt(1 + sqrt(2)))), S.Integers), + ImageSet(Lambda(n, I*(2*n*pi + pi) + log(-sqrt(2)/2 + sqrt(-S.Half + sqrt(2)))), S.Integers), + ImageSet(Lambda(n, I*(2*n*pi - pi + atan(sqrt(2)*sqrt(S.Half + sqrt(2)))) + + log(sqrt(1 + sqrt(2)))), S.Integers))) + f1 = (sinh(x)).rewrite(exp) + f2 = (tanh(x)).rewrite(exp) + assert dumeq(solveset(f1 + f2 - 1, x), Union( + Complement(ImageSet( + Lambda(n, I*(2*n*pi + pi) + log(-sqrt(2)/2 + sqrt(-S.Half + sqrt(2)))), S.Integers), + ImageSet(Lambda(n, I*(2*n*pi + pi)/2), S.Integers)), + Complement(ImageSet(Lambda(n, I*(2*n*pi - pi + atan(sqrt(2)*sqrt(S.Half + sqrt(2)))) + + log(sqrt(1 + sqrt(2)))), S.Integers), + ImageSet(Lambda(n, I*(2*n*pi + pi)/2), S.Integers)), + Complement(ImageSet(Lambda(n, I*(2*n*pi - atan(sqrt(2)*sqrt(S.Half + sqrt(2))) + pi) + + log(sqrt(1 + sqrt(2)))), S.Integers), + ImageSet(Lambda(n, I*(2*n*pi + pi)/2), S.Integers)), + Complement( + ImageSet(Lambda(n, 2*n*I*pi + log(sqrt(2)/2 + sqrt(-S.Half + sqrt(2)))), S.Integers), + ImageSet(Lambda(n, I*(2*n*pi + pi)/2), S.Integers)))) + + +def test_solve_invalid_sol(): + assert 0 not in solveset_real(sin(x)/x, x) + assert 0 not in solveset_complex((exp(x) - 1)/x, x) + + +@XFAIL +def test_solve_trig_simplified(): + n = Dummy('n') + assert dumeq(solveset_real(sin(x), x), + imageset(Lambda(n, n*pi), S.Integers)) + + assert dumeq(solveset_real(cos(x), x), + imageset(Lambda(n, n*pi + pi/2), S.Integers)) + + assert dumeq(solveset_real(cos(x) + sin(x), x), + imageset(Lambda(n, n*pi - pi/4), S.Integers)) + + +@XFAIL +def test_solve_lambert(): + assert solveset_real(x*exp(x) - 1, x) == FiniteSet(LambertW(1)) + assert solveset_real(exp(x) + x, x) == FiniteSet(-LambertW(1)) + assert solveset_real(x + 2**x, x) == \ + FiniteSet(-LambertW(log(2))/log(2)) + + # issue 4739 + ans = solveset_real(3*x + 5 + 2**(-5*x + 3), x) + assert ans == FiniteSet(Rational(-5, 3) + + LambertW(-10240*2**Rational(1, 3)*log(2)/3)/(5*log(2))) + + eq = 2*(3*x + 4)**5 - 6*7**(3*x + 9) + result = solveset_real(eq, x) + ans = FiniteSet((log(2401) + + 5*LambertW(-log(7**(7*3**Rational(1, 5)/5))))/(3*log(7))/-1) + assert result == ans + assert solveset_real(eq.expand(), x) == result + + assert solveset_real(5*x - 1 + 3*exp(2 - 7*x), x) == \ + FiniteSet(Rational(1, 5) + LambertW(-21*exp(Rational(3, 5))/5)/7) + + assert solveset_real(2*x + 5 + log(3*x - 2), x) == \ + FiniteSet(Rational(2, 3) + LambertW(2*exp(Rational(-19, 3))/3)/2) + + assert solveset_real(3*x + log(4*x), x) == \ + FiniteSet(LambertW(Rational(3, 4))/3) + + assert solveset_real(x**x - 2) == FiniteSet(exp(LambertW(log(2)))) + + a = Symbol('a') + assert solveset_real(-a*x + 2*x*log(x), x) == FiniteSet(exp(a/2)) + a = Symbol('a', real=True) + assert solveset_real(a/x + exp(x/2), x) == \ + FiniteSet(2*LambertW(-a/2)) + assert solveset_real((a/x + exp(x/2)).diff(x), x) == \ + FiniteSet(4*LambertW(sqrt(2)*sqrt(a)/4)) + + # coverage test + assert solveset_real(tanh(x + 3)*tanh(x - 3) - 1, x) is S.EmptySet + + assert solveset_real((x**2 - 2*x + 1).subs(x, log(x) + 3*x), x) == \ + FiniteSet(LambertW(3*S.Exp1)/3) + assert solveset_real((x**2 - 2*x + 1).subs(x, (log(x) + 3*x)**2 - 1), x) == \ + FiniteSet(LambertW(3*exp(-sqrt(2)))/3, LambertW(3*exp(sqrt(2)))/3) + assert solveset_real((x**2 - 2*x - 2).subs(x, log(x) + 3*x), x) == \ + FiniteSet(LambertW(3*exp(1 + sqrt(3)))/3, LambertW(3*exp(-sqrt(3) + 1))/3) + assert solveset_real(x*log(x) + 3*x + 1, x) == \ + FiniteSet(exp(-3 + LambertW(-exp(3)))) + eq = (x*exp(x) - 3).subs(x, x*exp(x)) + assert solveset_real(eq, x) == \ + FiniteSet(LambertW(3*exp(-LambertW(3)))) + + assert solveset_real(3*log(a**(3*x + 5)) + a**(3*x + 5), x) == \ + FiniteSet(-((log(a**5) + LambertW(Rational(1, 3)))/(3*log(a)))) + p = symbols('p', positive=True) + assert solveset_real(3*log(p**(3*x + 5)) + p**(3*x + 5), x) == \ + FiniteSet( + log((-3**Rational(1, 3) - 3**Rational(5, 6)*I)*LambertW(Rational(1, 3))**Rational(1, 3)/(2*p**Rational(5, 3)))/log(p), + log((-3**Rational(1, 3) + 3**Rational(5, 6)*I)*LambertW(Rational(1, 3))**Rational(1, 3)/(2*p**Rational(5, 3)))/log(p), + log((3*LambertW(Rational(1, 3))/p**5)**(1/(3*log(p)))),) # checked numerically + # check collection + b = Symbol('b') + eq = 3*log(a**(3*x + 5)) + b*log(a**(3*x + 5)) + a**(3*x + 5) + assert solveset_real(eq, x) == FiniteSet( + -((log(a**5) + LambertW(1/(b + 3)))/(3*log(a)))) + + # issue 4271 + assert solveset_real((a/x + exp(x/2)).diff(x, 2), x) == FiniteSet( + 6*LambertW((-1)**Rational(1, 3)*a**Rational(1, 3)/3)) + + assert solveset_real(x**3 - 3**x, x) == \ + FiniteSet(-3/log(3)*LambertW(-log(3)/3)) + assert solveset_real(3**cos(x) - cos(x)**3) == FiniteSet( + acos(-3*LambertW(-log(3)/3)/log(3))) + + assert solveset_real(x**2 - 2**x, x) == \ + solveset_real(-x**2 + 2**x, x) + + assert solveset_real(3*log(x) - x*log(3)) == FiniteSet( + -3*LambertW(-log(3)/3)/log(3), + -3*LambertW(-log(3)/3, -1)/log(3)) + + assert solveset_real(LambertW(2*x) - y) == FiniteSet( + y*exp(y)/2) + + +@XFAIL +def test_other_lambert(): + a = Rational(6, 5) + assert solveset_real(x**a - a**x, x) == FiniteSet( + a, -a*LambertW(-log(a)/a)/log(a)) + + +@_both_exp_pow +def test_solveset(): + f = Function('f') + raises(ValueError, lambda: solveset(x + y)) + assert solveset(x, 1) == S.EmptySet + assert solveset(f(1)**2 + y + 1, f(1) + ) == FiniteSet(-sqrt(-y - 1), sqrt(-y - 1)) + assert solveset(f(1)**2 - 1, f(1), S.Reals) == FiniteSet(-1, 1) + assert solveset(f(1)**2 + 1, f(1)) == FiniteSet(-I, I) + assert solveset(x - 1, 1) == FiniteSet(x) + assert solveset(sin(x) - cos(x), sin(x)) == FiniteSet(cos(x)) + + assert solveset(0, domain=S.Reals) == S.Reals + assert solveset(1) == S.EmptySet + assert solveset(True, domain=S.Reals) == S.Reals # issue 10197 + assert solveset(False, domain=S.Reals) == S.EmptySet + + assert solveset(exp(x) - 1, domain=S.Reals) == FiniteSet(0) + assert solveset(exp(x) - 1, x, S.Reals) == FiniteSet(0) + assert solveset(Eq(exp(x), 1), x, S.Reals) == FiniteSet(0) + assert solveset(exp(x) - 1, exp(x), S.Reals) == FiniteSet(1) + A = Indexed('A', x) + assert solveset(A - 1, A, S.Reals) == FiniteSet(1) + + assert solveset(x - 1 >= 0, x, S.Reals) == Interval(1, oo) + assert solveset(exp(x) - 1 >= 0, x, S.Reals) == Interval(0, oo) + + assert dumeq(solveset(exp(x) - 1, x), imageset(Lambda(n, 2*I*pi*n), S.Integers)) + assert dumeq(solveset(Eq(exp(x), 1), x), imageset(Lambda(n, 2*I*pi*n), + S.Integers)) + # issue 13825 + assert solveset(x**2 + f(0) + 1, x) == {-sqrt(-f(0) - 1), sqrt(-f(0) - 1)} + + # issue 19977 + assert solveset(atan(log(x)) > 0, x, domain=Interval.open(0, oo)) == Interval.open(1, oo) + + +@_both_exp_pow +def test_multi_exp(): + k1, k2, k3 = symbols('k1, k2, k3') + assert dumeq(solveset(exp(exp(x)) - 5, x),\ + imageset(Lambda(((k1, n),), I*(2*k1*pi + arg(2*n*I*pi + log(5))) + log(Abs(2*n*I*pi + log(5)))),\ + ProductSet(S.Integers, S.Integers))) + assert dumeq(solveset((d*exp(exp(a*x + b)) + c), x),\ + imageset(Lambda(x, (-b + x)/a), ImageSet(Lambda(((k1, n),), \ + I*(2*k1*pi + arg(I*(2*n*pi + arg(-c/d)) + log(Abs(c/d)))) + log(Abs(I*(2*n*pi + arg(-c/d)) + log(Abs(c/d))))), \ + ProductSet(S.Integers, S.Integers)))) + + assert dumeq(solveset((d*exp(exp(exp(a*x + b))) + c), x),\ + imageset(Lambda(x, (-b + x)/a), ImageSet(Lambda(((k2, k1, n),), \ + I*(2*k2*pi + arg(I*(2*k1*pi + arg(I*(2*n*pi + arg(-c/d)) + log(Abs(c/d)))) + \ + log(Abs(I*(2*n*pi + arg(-c/d)) + log(Abs(c/d)))))) + log(Abs(I*(2*k1*pi + arg(I*(2*n*pi + arg(-c/d)) + \ + log(Abs(c/d)))) + log(Abs(I*(2*n*pi + arg(-c/d)) + log(Abs(c/d))))))), \ + ProductSet(S.Integers, S.Integers, S.Integers)))) + + assert dumeq(solveset((d*exp(exp(exp(exp(a*x + b)))) + c), x),\ + ImageSet(Lambda(x, (-b + x)/a), ImageSet(Lambda(((k3, k2, k1, n),), \ + I*(2*k3*pi + arg(I*(2*k2*pi + arg(I*(2*k1*pi + arg(I*(2*n*pi + arg(-c/d)) + log(Abs(c/d)))) + \ + log(Abs(I*(2*n*pi + arg(-c/d)) + log(Abs(c/d)))))) + log(Abs(I*(2*k1*pi + arg(I*(2*n*pi + arg(-c/d)) + \ + log(Abs(c/d)))) + log(Abs(I*(2*n*pi + arg(-c/d)) + log(Abs(c/d)))))))) + log(Abs(I*(2*k2*pi + \ + arg(I*(2*k1*pi + arg(I*(2*n*pi + arg(-c/d)) + log(Abs(c/d)))) + log(Abs(I*(2*n*pi + arg(-c/d)) + log(Abs(c/d)))))) + \ + log(Abs(I*(2*k1*pi + arg(I*(2*n*pi + arg(-c/d)) + log(Abs(c/d)))) + log(Abs(I*(2*n*pi + arg(-c/d)) + log(Abs(c/d))))))))), \ + ProductSet(S.Integers, S.Integers, S.Integers, S.Integers)))) + + +def test__solveset_multi(): + from sympy.solvers.solveset import _solveset_multi + from sympy.sets import Reals + + # Basic univariate case: + assert _solveset_multi([x**2-1], [x], [S.Reals]) == FiniteSet((1,), (-1,)) + + # Linear systems of two equations + assert _solveset_multi([x+y, x+1], [x, y], [Reals, Reals]) == FiniteSet((-1, 1)) + assert _solveset_multi([x+y, x+1], [y, x], [Reals, Reals]) == FiniteSet((1, -1)) + assert _solveset_multi([x+y, x-y-1], [x, y], [Reals, Reals]) == FiniteSet((S(1)/2, -S(1)/2)) + assert _solveset_multi([x-1, y-2], [x, y], [Reals, Reals]) == FiniteSet((1, 2)) + # assert dumeq(_solveset_multi([x+y], [x, y], [Reals, Reals]), ImageSet(Lambda(x, (x, -x)), Reals)) + assert dumeq(_solveset_multi([x+y], [x, y], [Reals, Reals]), Union( + ImageSet(Lambda(((x,),), (x, -x)), ProductSet(Reals)), + ImageSet(Lambda(((y,),), (-y, y)), ProductSet(Reals)))) + assert _solveset_multi([x+y, x+y+1], [x, y], [Reals, Reals]) == S.EmptySet + assert _solveset_multi([x+y, x-y, x-1], [x, y], [Reals, Reals]) == S.EmptySet + assert _solveset_multi([x+y, x-y, x-1], [y, x], [Reals, Reals]) == S.EmptySet + + # Systems of three equations: + assert _solveset_multi([x+y+z-1, x+y-z-2, x-y-z-3], [x, y, z], [Reals, + Reals, Reals]) == FiniteSet((2, -S.Half, -S.Half)) + + # Nonlinear systems: + from sympy.abc import theta + assert _solveset_multi([x**2+y**2-2, x+y], [x, y], [Reals, Reals]) == FiniteSet((-1, 1), (1, -1)) + assert _solveset_multi([x**2-1, y], [x, y], [Reals, Reals]) == FiniteSet((1, 0), (-1, 0)) + #assert _solveset_multi([x**2-y**2], [x, y], [Reals, Reals]) == Union( + # ImageSet(Lambda(x, (x, -x)), Reals), ImageSet(Lambda(x, (x, x)), Reals)) + assert dumeq(_solveset_multi([x**2-y**2], [x, y], [Reals, Reals]), Union( + ImageSet(Lambda(((x,),), (x, -Abs(x))), ProductSet(Reals)), + ImageSet(Lambda(((x,),), (x, Abs(x))), ProductSet(Reals)), + ImageSet(Lambda(((y,),), (-Abs(y), y)), ProductSet(Reals)), + ImageSet(Lambda(((y,),), (Abs(y), y)), ProductSet(Reals)))) + assert _solveset_multi([r*cos(theta)-1, r*sin(theta)], [theta, r], + [Interval(0, pi), Interval(-1, 1)]) == FiniteSet((0, 1), (pi, -1)) + assert _solveset_multi([r*cos(theta)-1, r*sin(theta)], [r, theta], + [Interval(0, 1), Interval(0, pi)]) == FiniteSet((1, 0)) + assert _solveset_multi([r*cos(theta)-r, r*sin(theta)], [r, theta], + [Interval(0, 1), Interval(0, pi)]) == Union( + ImageSet(Lambda(((r,),), (r, 0)), + ImageSet(Lambda(r, (r,)), Interval(0, 1))), + ImageSet(Lambda(((theta,),), (0, theta)), + ImageSet(Lambda(theta, (theta,)), Interval(0, pi)))) + + +def test_conditionset(): + assert solveset(Eq(sin(x)**2 + cos(x)**2, 1), x, domain=S.Reals + ) is S.Reals + + assert solveset(Eq(x**2 + x*sin(x), 1), x, domain=S.Reals + ).dummy_eq(ConditionSet(x, Eq(x**2 + x*sin(x) - 1, 0), S.Reals)) + + assert dumeq(solveset(Eq(-I*(exp(I*x) - exp(-I*x))/2, 1), x + ), imageset(Lambda(n, 2*n*pi + pi/2), S.Integers)) + + assert solveset(x + sin(x) > 1, x, domain=S.Reals + ).dummy_eq(ConditionSet(x, x + sin(x) > 1, S.Reals)) + + assert solveset(Eq(sin(Abs(x)), x), x, domain=S.Reals + ).dummy_eq(ConditionSet(x, Eq(-x + sin(Abs(x)), 0), S.Reals)) + + assert solveset(y**x-z, x, S.Reals + ).dummy_eq(ConditionSet(x, Eq(y**x - z, 0), S.Reals)) + + +@XFAIL +def test_conditionset_equality(): + ''' Checking equality of different representations of ConditionSet''' + assert solveset(Eq(tan(x), y), x) == ConditionSet(x, Eq(tan(x), y), S.Complexes) + + +def test_solveset_domain(): + assert solveset(x**2 - x - 6, x, Interval(0, oo)) == FiniteSet(3) + assert solveset(x**2 - 1, x, Interval(0, oo)) == FiniteSet(1) + assert solveset(x**4 - 16, x, Interval(0, 10)) == FiniteSet(2) + + +def test_improve_coverage(): + solution = solveset(exp(x) + sin(x), x, S.Reals) + unsolved_object = ConditionSet(x, Eq(exp(x) + sin(x), 0), S.Reals) + assert solution.dummy_eq(unsolved_object) + + +def test_issue_9522(): + expr1 = Eq(1/(x**2 - 4) + x, 1/(x**2 - 4) + 2) + expr2 = Eq(1/x + x, 1/x) + + assert solveset(expr1, x, S.Reals) is S.EmptySet + assert solveset(expr2, x, S.Reals) is S.EmptySet + + +def test_solvify(): + assert solvify(x**2 + 10, x, S.Reals) == [] + assert solvify(x**3 + 1, x, S.Complexes) == [-1, S.Half - sqrt(3)*I/2, + S.Half + sqrt(3)*I/2] + assert solvify(log(x), x, S.Reals) == [1] + assert solvify(cos(x), x, S.Reals) == [pi/2, pi*Rational(3, 2)] + assert solvify(sin(x) + 1, x, S.Reals) == [pi*Rational(3, 2)] + raises(NotImplementedError, lambda: solvify(sin(exp(x)), x, S.Complexes)) + + +def test_solvify_piecewise(): + p1 = Piecewise((0, x < -1), (x**2, x <= 1), (log(x), True)) + p2 = Piecewise((0, x < -10), (x**2 + 5*x - 6, x >= -9)) + p3 = Piecewise((0, Eq(x, 0)), (x**2/Abs(x), True)) + p4 = Piecewise((0, Eq(x, pi)), ((x - pi)/sin(x), True)) + + # issue 21079 + assert solvify(p1, x, S.Reals) == [0] + assert solvify(p2, x, S.Reals) == [-6, 1] + assert solvify(p3, x, S.Reals) == [0] + assert solvify(p4, x, S.Reals) == [pi] + + +def test_abs_invert_solvify(): + + x = Symbol('x',positive=True) + assert solvify(sin(Abs(x)), x, S.Reals) == [0, pi] + x = Symbol('x') + assert solvify(sin(Abs(x)), x, S.Reals) is None + + +def test_linear_eq_to_matrix(): + assert linear_eq_to_matrix(0, x) == (Matrix([[0]]), Matrix([[0]])) + assert linear_eq_to_matrix(1, x) == (Matrix([[0]]), Matrix([[-1]])) + + # integer coefficients + eqns1 = [2*x + y - 2*z - 3, x - y - z, x + y + 3*z - 12] + eqns2 = [Eq(3*x + 2*y - z, 1), Eq(2*x - 2*y + 4*z, -2), -2*x + y - 2*z] + + A, B = linear_eq_to_matrix(eqns1, x, y, z) + assert A == Matrix([[2, 1, -2], [1, -1, -1], [1, 1, 3]]) + assert B == Matrix([[3], [0], [12]]) + + A, B = linear_eq_to_matrix(eqns2, x, y, z) + assert A == Matrix([[3, 2, -1], [2, -2, 4], [-2, 1, -2]]) + assert B == Matrix([[1], [-2], [0]]) + + # Pure symbolic coefficients + eqns3 = [a*b*x + b*y + c*z - d, e*x + d*x + f*y + g*z - h, i*x + j*y + k*z - l] + A, B = linear_eq_to_matrix(eqns3, x, y, z) + assert A == Matrix([[a*b, b, c], [d + e, f, g], [i, j, k]]) + assert B == Matrix([[d], [h], [l]]) + + # raise Errors if + # 1) no symbols are given + raises(ValueError, lambda: linear_eq_to_matrix(eqns3)) + # 2) there are duplicates + raises(ValueError, lambda: linear_eq_to_matrix(eqns3, [x, x, y])) + # 3) a nonlinear term is detected in the original expression + raises(NonlinearError, lambda: linear_eq_to_matrix(Eq(1/x + x, 1/x), [x])) + raises(NonlinearError, lambda: linear_eq_to_matrix([x**2], [x])) + raises(NonlinearError, lambda: linear_eq_to_matrix([x*y], [x, y])) + # 4) Eq being used to represent equations autoevaluates + # (use unevaluated Eq instead) + raises(ValueError, lambda: linear_eq_to_matrix(Eq(x, x), x)) + raises(ValueError, lambda: linear_eq_to_matrix(Eq(x, x + 1), x)) + + + # if non-symbols are passed, the user is responsible for interpreting + assert linear_eq_to_matrix([x], [1/x]) == (Matrix([[0]]), Matrix([[-x]])) + + # issue 15195 + assert linear_eq_to_matrix(x + y*(z*(3*x + 2) + 3), x) == ( + Matrix([[3*y*z + 1]]), Matrix([[-y*(2*z + 3)]])) + assert linear_eq_to_matrix(Matrix( + [[a*x + b*y - 7], [5*x + 6*y - c]]), x, y) == ( + Matrix([[a, b], [5, 6]]), Matrix([[7], [c]])) + + # issue 15312 + assert linear_eq_to_matrix(Eq(x + 2, 1), x) == ( + Matrix([[1]]), Matrix([[-1]])) + + # issue 25423 + raises(TypeError, lambda: linear_eq_to_matrix([], {x, y})) + raises(TypeError, lambda: linear_eq_to_matrix([x + y], {x, y})) + raises(ValueError, lambda: linear_eq_to_matrix({x + y}, (x, y))) + + +def test_issue_16577(): + assert linear_eq_to_matrix(Eq(a*(2*x + 3*y) + 4*y, 5), x, y) == ( + Matrix([[2*a, 3*a + 4]]), Matrix([[5]])) + + +def test_issue_10085(): + assert invert_real(exp(x),0,x) == (x, S.EmptySet) + + +def test_linsolve(): + x1, x2, x3, x4 = symbols('x1, x2, x3, x4') + + # Test for different input forms + + M = Matrix([[1, 2, 1, 1, 7], [1, 2, 2, -1, 12], [2, 4, 0, 6, 4]]) + system1 = A, B = M[:, :-1], M[:, -1] + Eqns = [x1 + 2*x2 + x3 + x4 - 7, x1 + 2*x2 + 2*x3 - x4 - 12, + 2*x1 + 4*x2 + 6*x4 - 4] + + sol = FiniteSet((-2*x2 - 3*x4 + 2, x2, 2*x4 + 5, x4)) + assert linsolve(Eqns, (x1, x2, x3, x4)) == sol + assert linsolve(Eqns, *(x1, x2, x3, x4)) == sol + assert linsolve(system1, (x1, x2, x3, x4)) == sol + assert linsolve(system1, *(x1, x2, x3, x4)) == sol + # issue 9667 - symbols can be Dummy symbols + x1, x2, x3, x4 = symbols('x:4', cls=Dummy) + assert linsolve(system1, x1, x2, x3, x4) == FiniteSet( + (-2*x2 - 3*x4 + 2, x2, 2*x4 + 5, x4)) + + # raise ValueError for garbage value + raises(ValueError, lambda: linsolve(Eqns)) + raises(ValueError, lambda: linsolve(x1)) + raises(ValueError, lambda: linsolve(x1, x2)) + raises(ValueError, lambda: linsolve((A,), x1, x2)) + raises(ValueError, lambda: linsolve(A, B, x1, x2)) + raises(ValueError, lambda: linsolve([x1], x1, x1)) + raises(ValueError, lambda: linsolve([x1], (i for i in (x1, x1)))) + + #raise ValueError if equations are non-linear in given variables + raises(NonlinearError, lambda: linsolve([x + y - 1, x ** 2 + y - 3], [x, y])) + raises(NonlinearError, lambda: linsolve([cos(x) + y, x + y], [x, y])) + assert linsolve([x + z - 1, x ** 2 + y - 3], [z, y]) == {(-x + 1, -x**2 + 3)} + + # Fully symbolic test + A = Matrix([[a, b], [c, d]]) + B = Matrix([[e], [g]]) + system2 = (A, B) + sol = FiniteSet(((-b*g + d*e)/(a*d - b*c), (a*g - c*e)/(a*d - b*c))) + assert linsolve(system2, [x, y]) == sol + + # No solution + A = Matrix([[1, 2, 3], [2, 4, 6], [3, 6, 9]]) + B = Matrix([0, 0, 1]) + assert linsolve((A, B), (x, y, z)) is S.EmptySet + + # Issue #10056 + A, B, J1, J2 = symbols('A B J1 J2') + Augmatrix = Matrix([ + [2*I*J1, 2*I*J2, -2/J1], + [-2*I*J2, -2*I*J1, 2/J2], + [0, 2, 2*I/(J1*J2)], + [2, 0, 0], + ]) + + assert linsolve(Augmatrix, A, B) == FiniteSet((0, I/(J1*J2))) + + # Issue #10121 - Assignment of free variables + Augmatrix = Matrix([[0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0]]) + assert linsolve(Augmatrix, a, b, c, d, e) == FiniteSet((a, 0, c, 0, e)) + #raises(IndexError, lambda: linsolve(Augmatrix, a, b, c)) + + x0, x1, x2, _x0 = symbols('tau0 tau1 tau2 _tau0') + assert linsolve(Matrix([[0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, _x0]]) + ) == FiniteSet((x0, 0, x1, _x0, x2)) + x0, x1, x2, _x0 = symbols('tau00 tau01 tau02 tau0') + assert linsolve(Matrix([[0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, _x0]]) + ) == FiniteSet((x0, 0, x1, _x0, x2)) + x0, x1, x2, _x0 = symbols('tau00 tau01 tau02 tau1') + assert linsolve(Matrix([[0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, _x0]]) + ) == FiniteSet((x0, 0, x1, _x0, x2)) + # symbols can be given as generators + x0, x2, x4 = symbols('x0, x2, x4') + assert linsolve(Augmatrix, numbered_symbols('x') + ) == FiniteSet((x0, 0, x2, 0, x4)) + Augmatrix[-1, -1] = x0 + # use Dummy to avoid clash; the names may clash but the symbols + # will not + Augmatrix[-1, -1] = symbols('_x0') + assert len(linsolve( + Augmatrix, numbered_symbols('x', cls=Dummy)).free_symbols) == 4 + + # Issue #12604 + f = Function('f') + assert linsolve([f(x) - 5], f(x)) == FiniteSet((5,)) + + # Issue #14860 + from sympy.physics.units import meter, newton, kilo + kN = kilo*newton + Eqns = [8*kN + x + y, 28*kN*meter + 3*x*meter] + assert linsolve(Eqns, x, y) == { + (kilo*newton*Rational(-28, 3), kN*Rational(4, 3))} + + # linsolve does not allow expansion (real or implemented) + # to remove singularities, but it will cancel linear terms + assert linsolve([Eq(x, x + y)], [x, y]) == {(x, 0)} + assert linsolve([Eq(x + x*y, 1 + y)], [x]) == {(1,)} + assert linsolve([Eq(1 + y, x + x*y)], [x]) == {(1,)} + raises(NonlinearError, lambda: + linsolve([Eq(x**2, x**2 + y)], [x, y])) + + # corner cases + # + # XXX: The case below should give the same as for [0] + # assert linsolve([], [x]) == {(x,)} + assert linsolve([], [x]) is S.EmptySet + assert linsolve([0], [x]) == {(x,)} + assert linsolve([x], [x, y]) == {(0, y)} + assert linsolve([x, 0], [x, y]) == {(0, y)} + + +def test_linsolve_large_sparse(): + # + # This is mainly a performance test + # + + def _mk_eqs_sol(n): + xs = symbols('x:{}'.format(n)) + ys = symbols('y:{}'.format(n)) + syms = xs + ys + eqs = [] + sol = (-S.Half,) * n + (S.Half,) * n + for xi, yi in zip(xs, ys): + eqs.extend([xi + yi, xi - yi + 1]) + return eqs, syms, FiniteSet(sol) + + n = 500 + eqs, syms, sol = _mk_eqs_sol(n) + assert linsolve(eqs, syms) == sol + + +def test_linsolve_immutable(): + A = ImmutableDenseMatrix([[1, 1, 2], [0, 1, 2], [0, 0, 1]]) + B = ImmutableDenseMatrix([2, 1, -1]) + assert linsolve([A, B], (x, y, z)) == FiniteSet((1, 3, -1)) + + A = ImmutableDenseMatrix([[1, 1, 7], [1, -1, 3]]) + assert linsolve(A) == FiniteSet((5, 2)) + + +def test_solve_decomposition(): + n = Dummy('n') + + f1 = exp(3*x) - 6*exp(2*x) + 11*exp(x) - 6 + f2 = sin(x)**2 - 2*sin(x) + 1 + f3 = sin(x)**2 - sin(x) + f4 = sin(x + 1) + f5 = exp(x + 2) - 1 + f6 = 1/log(x) + f7 = 1/x + + s1 = ImageSet(Lambda(n, 2*n*pi), S.Integers) + s2 = ImageSet(Lambda(n, 2*n*pi + pi), S.Integers) + s3 = ImageSet(Lambda(n, 2*n*pi + pi/2), S.Integers) + s4 = ImageSet(Lambda(n, 2*n*pi - 1), S.Integers) + s5 = ImageSet(Lambda(n, 2*n*pi - 1 + pi), S.Integers) + + assert solve_decomposition(f1, x, S.Reals) == FiniteSet(0, log(2), log(3)) + assert dumeq(solve_decomposition(f2, x, S.Reals), s3) + assert dumeq(solve_decomposition(f3, x, S.Reals), Union(s1, s2, s3)) + assert dumeq(solve_decomposition(f4, x, S.Reals), Union(s4, s5)) + assert solve_decomposition(f5, x, S.Reals) == FiniteSet(-2) + assert solve_decomposition(f6, x, S.Reals) == S.EmptySet + assert solve_decomposition(f7, x, S.Reals) == S.EmptySet + assert solve_decomposition(x, x, Interval(1, 2)) == S.EmptySet + + +# nonlinsolve testcases +def test_nonlinsolve_basic(): + assert nonlinsolve([],[]) == S.EmptySet + assert nonlinsolve([],[x, y]) == S.EmptySet + + system = [x, y - x - 5] + assert nonlinsolve([x],[x, y]) == FiniteSet((0, y)) + assert nonlinsolve(system, [y]) == S.EmptySet + soln = (ImageSet(Lambda(n, 2*n*pi + pi/2), S.Integers),) + assert dumeq(nonlinsolve([sin(x) - 1], [x]), FiniteSet(tuple(soln))) + soln = ((ImageSet(Lambda(n, 2*n*pi + pi), S.Integers), 1), + (ImageSet(Lambda(n, 2*n*pi), S.Integers), 1)) + assert dumeq(nonlinsolve([sin(x), y - 1], [x, y]), FiniteSet(*soln)) + assert nonlinsolve([x**2 - 1], [x]) == FiniteSet((-1,), (1,)) + + soln = FiniteSet((y, y)) + assert nonlinsolve([x - y, 0], x, y) == soln + assert nonlinsolve([0, x - y], x, y) == soln + assert nonlinsolve([x - y, x - y], x, y) == soln + assert nonlinsolve([x, 0], x, y) == FiniteSet((0, y)) + f = Function('f') + assert nonlinsolve([f(x), 0], f(x), y) == FiniteSet((0, y)) + assert nonlinsolve([f(x), 0], f(x), f(y)) == FiniteSet((0, f(y))) + A = Indexed('A', x) + assert nonlinsolve([A, 0], A, y) == FiniteSet((0, y)) + assert nonlinsolve([x**2 -1], [sin(x)]) == FiniteSet((S.EmptySet,)) + assert nonlinsolve([x**2 -1], sin(x)) == FiniteSet((S.EmptySet,)) + assert nonlinsolve([x**2 -1], 1) == FiniteSet((x**2,)) + assert nonlinsolve([x**2 -1], x + y) == FiniteSet((S.EmptySet,)) + assert nonlinsolve([Eq(1, x + y), Eq(1, -x + y - 1), Eq(1, -x + y - 1)], x, y) == FiniteSet( + (-S.Half, 3*S.Half)) + + +def test_nonlinsolve_abs(): + soln = FiniteSet((y, y), (-y, y)) + assert nonlinsolve([Abs(x) - y], x, y) == soln + + +def test_raise_exception_nonlinsolve(): + raises(IndexError, lambda: nonlinsolve([x**2 -1], [])) + raises(ValueError, lambda: nonlinsolve([x**2 -1])) + + +def test_trig_system(): + # TODO: add more simple testcases when solveset returns + # simplified soln for Trig eq + assert nonlinsolve([sin(x) - 1, cos(x) -1 ], x) == S.EmptySet + soln1 = (ImageSet(Lambda(n, 2*n*pi + pi/2), S.Integers),) + soln = FiniteSet(soln1) + assert dumeq(nonlinsolve([sin(x) - 1, cos(x)], x), soln) + + +@XFAIL +def test_trig_system_fail(): + # fails because solveset trig solver is not much smart. + sys = [x + y - pi/2, sin(x) + sin(y) - 1] + # solveset returns conditionset for sin(x) + sin(y) - 1 + soln_1 = (ImageSet(Lambda(n, n*pi + pi/2), S.Integers), + ImageSet(Lambda(n, n*pi), S.Integers)) + soln_1 = FiniteSet(soln_1) + soln_2 = (ImageSet(Lambda(n, n*pi), S.Integers), + ImageSet(Lambda(n, n*pi+ pi/2), S.Integers)) + soln_2 = FiniteSet(soln_2) + soln = soln_1 + soln_2 + assert dumeq(nonlinsolve(sys, [x, y]), soln) + + # Add more cases from here + # http://www.vitutor.com/geometry/trigonometry/equations_systems.html#uno + sys = [sin(x) + sin(y) - (sqrt(3)+1)/2, sin(x) - sin(y) - (sqrt(3) - 1)/2] + soln_x = Union(ImageSet(Lambda(n, 2*n*pi + pi/3), S.Integers), + ImageSet(Lambda(n, 2*n*pi + pi*Rational(2, 3)), S.Integers)) + soln_y = Union(ImageSet(Lambda(n, 2*n*pi + pi/6), S.Integers), + ImageSet(Lambda(n, 2*n*pi + pi*Rational(5, 6)), S.Integers)) + assert dumeq(nonlinsolve(sys, [x, y]), FiniteSet((soln_x, soln_y))) + + +def test_nonlinsolve_positive_dimensional(): + x, y, a, b, c, d = symbols('x, y, a, b, c, d', extended_real=True) + assert nonlinsolve([x*y, x*y - x], [x, y]) == FiniteSet((0, y)) + + system = [a**2 + a*c, a - b] + assert nonlinsolve(system, [a, b]) == FiniteSet((0, 0), (-c, -c)) + # here (a= 0, b = 0) is independent soln so both is printed. + # if symbols = [a, b, c] then only {a : -c ,b : -c} + + eq1 = a + b + c + d + eq2 = a*b + b*c + c*d + d*a + eq3 = a*b*c + b*c*d + c*d*a + d*a*b + eq4 = a*b*c*d - 1 + system = [eq1, eq2, eq3, eq4] + sol1 = (-1/d, -d, 1/d, FiniteSet(d) - FiniteSet(0)) + sol2 = (1/d, -d, -1/d, FiniteSet(d) - FiniteSet(0)) + soln = FiniteSet(sol1, sol2) + assert nonlinsolve(system, [a, b, c, d]) == soln + + assert nonlinsolve([x**4 - 3*x**2 + y*x, x*z**2, y*z - 1], [x, y, z]) == \ + {(0, 1/z, z)} + + +def test_nonlinsolve_polysys(): + x, y, z = symbols('x, y, z', real=True) + assert nonlinsolve([x**2 + y - 2, x**2 + y], [x, y]) == S.EmptySet + + s = (-y + 2, y) + assert nonlinsolve([(x + y)**2 - 4, x + y - 2], [x, y]) == FiniteSet(s) + + system = [x**2 - y**2] + soln_real = FiniteSet((-y, y), (y, y)) + soln_complex = FiniteSet((-Abs(y), y), (Abs(y), y)) + soln =soln_real + soln_complex + assert nonlinsolve(system, [x, y]) == soln + + system = [x**2 - y**2] + soln_real= FiniteSet((y, -y), (y, y)) + soln_complex = FiniteSet((y, -Abs(y)), (y, Abs(y))) + soln = soln_real + soln_complex + assert nonlinsolve(system, [y, x]) == soln + + system = [x**2 + y - 3, x - y - 4] + assert nonlinsolve(system, (x, y)) != nonlinsolve(system, (y, x)) + + assert nonlinsolve([-x**2 - y**2 + z, -2*x, -2*y, S.One], [x, y, z]) == S.EmptySet + assert nonlinsolve([x + y + z, S.One, S.One, S.One], [x, y, z]) == S.EmptySet + + system = [-x**2*z**2 + x*y*z + y**4, -2*x*z**2 + y*z, x*z + 4*y**3, -2*x**2*z + x*y] + assert nonlinsolve(system, [x, y, z]) == FiniteSet((0, 0, z), (x, 0, 0)) + + +def test_nonlinsolve_using_substitution(): + x, y, z, n = symbols('x, y, z, n', real = True) + system = [(x + y)*n - y**2 + 2] + s_x = (n*y - y**2 + 2)/n + soln = (-s_x, y) + assert nonlinsolve(system, [x, y]) == FiniteSet(soln) + + system = [z**2*x**2 - z**2*y**2/exp(x)] + soln_real_1 = (y, x, 0) + soln_real_2 = (-exp(x/2)*Abs(x), x, z) + soln_real_3 = (exp(x/2)*Abs(x), x, z) + soln_complex_1 = (-x*exp(x/2), x, z) + soln_complex_2 = (x*exp(x/2), x, z) + syms = [y, x, z] + soln = FiniteSet(soln_real_1, soln_complex_1, soln_complex_2,\ + soln_real_2, soln_real_3) + assert nonlinsolve(system,syms) == soln + + +def test_nonlinsolve_complex(): + n = Dummy('n') + assert dumeq(nonlinsolve([exp(x) - sin(y), 1/y - 3], [x, y]), { + (ImageSet(Lambda(n, 2*n*I*pi + log(sin(Rational(1, 3)))), S.Integers), Rational(1, 3))}) + + system = [exp(x) - sin(y), 1/exp(y) - 3] + assert dumeq(nonlinsolve(system, [x, y]), { + (ImageSet(Lambda(n, I*(2*n*pi + pi) + + log(sin(log(3)))), S.Integers), -log(3)), + (ImageSet(Lambda(n, I*(2*n*pi + arg(sin(2*n*I*pi - log(3)))) + + log(Abs(sin(2*n*I*pi - log(3))))), S.Integers), + ImageSet(Lambda(n, 2*n*I*pi - log(3)), S.Integers))}) + + system = [exp(x) - sin(y), y**2 - 4] + assert dumeq(nonlinsolve(system, [x, y]), { + (ImageSet(Lambda(n, I*(2*n*pi + pi) + log(sin(2))), S.Integers), -2), + (ImageSet(Lambda(n, 2*n*I*pi + log(sin(2))), S.Integers), 2)}) + + system = [exp(x) - 2, y ** 2 - 2] + assert dumeq(nonlinsolve(system, [x, y]), { + (log(2), -sqrt(2)), (log(2), sqrt(2)), + (ImageSet(Lambda(n, 2*n*I*pi + log(2)), S.Integers), -sqrt(2)), + (ImageSet(Lambda(n, 2 * n * I * pi + log(2)), S.Integers), sqrt(2))}) + + +def test_nonlinsolve_radical(): + assert nonlinsolve([sqrt(y) - x - z, y - 1], [x, y, z]) == {(1 - z, 1, z)} + + +def test_nonlinsolve_inexact(): + sol = [(-1.625, -1.375), (1.625, 1.375)] + res = nonlinsolve([(x + y)**2 - 9, x**2 - y**2 - 0.75], [x, y]) + assert all(abs(res.args[i][j]-sol[i][j]) < 1e-9 + for i in range(2) for j in range(2)) + + assert nonlinsolve([(x + y)**2 - 9, (x + y)**2 - 0.75], [x, y]) == S.EmptySet + + assert nonlinsolve([y**2 + (x - 0.5)**2 - 0.0625, 2*x - 1.0, 2*y], [x, y]) == \ + S.EmptySet + + res = nonlinsolve([x**2 + y - 0.5, (x + y)**2, log(z)], [x, y, z]) + sol = [(-0.366025403784439, 0.366025403784439, 1), + (-0.366025403784439, 0.366025403784439, 1), + (1.36602540378444, -1.36602540378444, 1)] + assert all(abs(res.args[i][j]-sol[i][j]) < 1e-9 + for i in range(3) for j in range(3)) + + res = nonlinsolve([y - x**2, x**5 - x + 1.0], [x, y]) + sol = [(-1.16730397826142, 1.36259857766493), + (-0.181232444469876 - 1.08395410131771*I, + -1.14211129483496 + 0.392895302949911*I), + (-0.181232444469876 + 1.08395410131771*I, + -1.14211129483496 - 0.392895302949911*I), + (0.764884433600585 - 0.352471546031726*I, + 0.460812006002492 - 0.539199997693599*I), + (0.764884433600585 + 0.352471546031726*I, + 0.460812006002492 + 0.539199997693599*I)] + assert all(abs(res.args[i][j] - sol[i][j]) < 1e-9 + for i in range(5) for j in range(2)) + +@XFAIL +def test_solve_nonlinear_trans(): + # After the transcendental equation solver these will work + x, y = symbols('x, y', real=True) + soln1 = FiniteSet((2*LambertW(y/2), y)) + soln2 = FiniteSet((-x*sqrt(exp(x)), y), (x*sqrt(exp(x)), y)) + soln3 = FiniteSet((x*exp(x/2), x)) + soln4 = FiniteSet(2*LambertW(y/2), y) + assert nonlinsolve([x**2 - y**2/exp(x)], [x, y]) == soln1 + assert nonlinsolve([x**2 - y**2/exp(x)], [y, x]) == soln2 + assert nonlinsolve([x**2 - y**2/exp(x)], [y, x]) == soln3 + assert nonlinsolve([x**2 - y**2/exp(x)], [x, y]) == soln4 + + +def test_nonlinsolve_issue_25182(): + a1, b1, c1, ca, cb, cg = symbols('a1, b1, c1, ca, cb, cg') + eq1 = a1*a1 + b1*b1 - 2.*a1*b1*cg - c1*c1 + eq2 = a1*a1 + c1*c1 - 2.*a1*c1*cb - b1*b1 + eq3 = b1*b1 + c1*c1 - 2.*b1*c1*ca - a1*a1 + assert nonlinsolve([eq1, eq2, eq3], [c1, cb, cg]) == FiniteSet( + (1.0*b1*ca - 1.0*sqrt(a1**2 + b1**2*ca**2 - b1**2), + -1.0*sqrt(a1**2 + b1**2*ca**2 - b1**2)/a1, + -1.0*b1*(ca - 1)*(ca + 1)/a1 + 1.0*ca*sqrt(a1**2 + b1**2*ca**2 - b1**2)/a1), + (1.0*b1*ca + 1.0*sqrt(a1**2 + b1**2*ca**2 - b1**2), + 1.0*sqrt(a1**2 + b1**2*ca**2 - b1**2)/a1, + -1.0*b1*(ca - 1)*(ca + 1)/a1 - 1.0*ca*sqrt(a1**2 + b1**2*ca**2 - b1**2)/a1)) + + +def test_issue_14642(): + x = Symbol('x') + n1 = 0.5*x**3+x**2+0.5+I #add I in the Polynomials + solution = solveset(n1, x) + assert abs(solution.args[0] - (-2.28267560928153 - 0.312325580497716*I)) <= 1e-9 + assert abs(solution.args[1] - (-0.297354141679308 + 1.01904778618762*I)) <= 1e-9 + assert abs(solution.args[2] - (0.580029750960839 - 0.706722205689907*I)) <= 1e-9 + + # Symbolic + n1 = S.Half*x**3+x**2+S.Half+I + res = FiniteSet(-((3*sqrt(3)*31985**(S(1)/4)*sin(atan(S(172)/49)/2)/2 + + S(43)/2)**2 + (27 + 3*sqrt(3)*31985**(S(1)/4)*cos(atan(S(172)/49) + /2)/2)**2)**(S(1)/6)*cos(atan((27 + 3*sqrt(3)*31985**(S(1)/4)* + cos(atan(S(172)/49)/2)/2)/(3*sqrt(3)*31985**(S(1)/4)*sin(atan( + S(172)/49)/2)/2 + S(43)/2))/3)/3 - S(2)/3 - 4*cos(atan((27 + + 3*sqrt(3)*31985**(S(1)/4)*cos(atan(S(172)/49)/2)/2)/(3*sqrt(3)* + 31985**(S(1)/4)*sin(atan(S(172)/49)/2)/2 + S(43)/2))/3)/(3*((3* + sqrt(3)*31985**(S(1)/4)*sin(atan(S(172)/49)/2)/2 + S(43)/2)**2 + + (27 + 3*sqrt(3)*31985**(S(1)/4)*cos(atan(S(172)/49)/2)/2)**2)**(S(1)/ + 6)) + I*(-((3*sqrt(3)*31985**(S(1)/4)*sin(atan(S(172)/49)/2)/2 + + S(43)/2)**2 + (27 + 3*sqrt(3)*31985**(S(1)/4)*cos(atan(S(172)/49)/ + 2)/2)**2)**(S(1)/6)*sin(atan((27 + 3*sqrt(3)*31985**(S(1)/4)*cos( + atan(S(172)/49)/2)/2)/(3*sqrt(3)*31985**(S(1)/4)*sin(atan(S(172)/49) + /2)/2 + S(43)/2))/3)/3 + 4*sin(atan((27 + 3*sqrt(3)*31985**(S(1)/4)* + cos(atan(S(172)/49)/2)/2)/(3*sqrt(3)*31985**(S(1)/4)*sin(atan(S(172) + /49)/2)/2 + S(43)/2))/3)/(3*((3*sqrt(3)*31985**(S(1)/4)*sin(atan( + S(172)/49)/2)/2 + S(43)/2)**2 + (27 + 3*sqrt(3)*31985**(S(1)/4)* + cos(atan(S(172)/49)/2)/2)**2)**(S(1)/6))), -S(2)/3 - sqrt(3)*((3* + sqrt(3)*31985**(S(1)/4)*sin(atan(S(172)/49)/2)/2 + S(43)/2)**2 + + (27 + 3*sqrt(3)*31985**(S(1)/4)*cos(atan(S(172)/49)/2)/2)**2)**(S(1) + /6)*sin(atan((27 + 3*sqrt(3)*31985**(S(1)/4)*cos(atan(S(172)/49)/2) + /2)/(3*sqrt(3)*31985**(S(1)/4)*sin(atan(S(172)/49)/2)/2 + S(43)/2)) + /3)/6 - 4*re(1/((-S(1)/2 - sqrt(3)*I/2)*(S(43)/2 + 27*I + sqrt(-256 + + (43 + 54*I)**2)/2)**(S(1)/3)))/3 + ((3*sqrt(3)*31985**(S(1)/4)*sin( + atan(S(172)/49)/2)/2 + S(43)/2)**2 + (27 + 3*sqrt(3)*31985**(S(1)/4)* + cos(atan(S(172)/49)/2)/2)**2)**(S(1)/6)*cos(atan((27 + 3*sqrt(3)* + 31985**(S(1)/4)*cos(atan(S(172)/49)/2)/2)/(3*sqrt(3)*31985**(S(1)/4)* + sin(atan(S(172)/49)/2)/2 + S(43)/2))/3)/6 + I*(-4*im(1/((-S(1)/2 - + sqrt(3)*I/2)*(S(43)/2 + 27*I + sqrt(-256 + (43 + 54*I)**2)/2)**(S(1)/ + 3)))/3 + ((3*sqrt(3)*31985**(S(1)/4)*sin(atan(S(172)/49)/2)/2 + + S(43)/2)**2 + (27 + 3*sqrt(3)*31985**(S(1)/4)*cos(atan(S(172)/49)/2) + /2)**2)**(S(1)/6)*sin(atan((27 + 3*sqrt(3)*31985**(S(1)/4)*cos(atan( + S(172)/49)/2)/2)/(3*sqrt(3)*31985**(S(1)/4)*sin(atan(S(172)/49)/2)/2 + + S(43)/2))/3)/6 + sqrt(3)*((3*sqrt(3)*31985**(S(1)/4)*sin(atan(S(172)/ + 49)/2)/2 + S(43)/2)**2 + (27 + 3*sqrt(3)*31985**(S(1)/4)*cos(atan( + S(172)/49)/2)/2)**2)**(S(1)/6)*cos(atan((27 + 3*sqrt(3)*31985**(S(1)/ + 4)*cos(atan(S(172)/49)/2)/2)/(3*sqrt(3)*31985**(S(1)/4)*sin(atan( + S(172)/49)/2)/2 + S(43)/2))/3)/6), -S(2)/3 - 4*re(1/((-S(1)/2 + + sqrt(3)*I/2)*(S(43)/2 + 27*I + sqrt(-256 + (43 + 54*I)**2)/2)**(S(1) + /3)))/3 + sqrt(3)*((3*sqrt(3)*31985**(S(1)/4)*sin(atan(S(172)/49)/2)/2 + + S(43)/2)**2 + (27 + 3*sqrt(3)*31985**(S(1)/4)*cos(atan(S(172)/49)/2) + /2)**2)**(S(1)/6)*sin(atan((27 + 3*sqrt(3)*31985**(S(1)/4)*cos(atan( + S(172)/49)/2)/2)/(3*sqrt(3)*31985**(S(1)/4)*sin(atan(S(172)/49)/2)/2 + + S(43)/2))/3)/6 + ((3*sqrt(3)*31985**(S(1)/4)*sin(atan(S(172)/49)/2)/2 + + S(43)/2)**2 + (27 + 3*sqrt(3)*31985**(S(1)/4)*cos(atan(S(172)/49)/2) + /2)**2)**(S(1)/6)*cos(atan((27 + 3*sqrt(3)*31985**(S(1)/4)*cos(atan( + S(172)/49)/2)/2)/(3*sqrt(3)*31985**(S(1)/4)*sin(atan(S(172)/49)/2)/2 + + S(43)/2))/3)/6 + I*(-sqrt(3)*((3*sqrt(3)*31985**(S(1)/4)*sin(atan( + S(172)/49)/2)/2 + S(43)/2)**2 + (27 + 3*sqrt(3)*31985**(S(1)/4)*cos( + atan(S(172)/49)/2)/2)**2)**(S(1)/6)*cos(atan((27 + 3*sqrt(3)*31985**( + S(1)/4)*cos(atan(S(172)/49)/2)/2)/(3*sqrt(3)*31985**(S(1)/4)*sin( + atan(S(172)/49)/2)/2 + S(43)/2))/3)/6 + ((3*sqrt(3)*31985**(S(1)/4)* + sin(atan(S(172)/49)/2)/2 + S(43)/2)**2 + (27 + 3*sqrt(3)*31985**(S(1)/4)* + cos(atan(S(172)/49)/2)/2)**2)**(S(1)/6)*sin(atan((27 + 3*sqrt(3)*31985**( + S(1)/4)*cos(atan(S(172)/49)/2)/2)/(3*sqrt(3)*31985**(S(1)/4)*sin( + atan(S(172)/49)/2)/2 + S(43)/2))/3)/6 - 4*im(1/((-S(1)/2 + sqrt(3)*I/2)* + (S(43)/2 + 27*I + sqrt(-256 + (43 + 54*I)**2)/2)**(S(1)/3)))/3)) + + assert solveset(n1, x) == res + + +def test_issue_13961(): + V = (ax, bx, cx, gx, jx, lx, mx, nx, q) = symbols('ax bx cx gx jx lx mx nx q') + S = (ax*q - lx*q - mx, ax - gx*q - lx, bx*q**2 + cx*q - jx*q - nx, q*(-ax*q + lx*q + mx), q*(-ax + gx*q + lx)) + + sol = FiniteSet((lx + mx/q, (-cx*q + jx*q + nx)/q**2, cx, mx/q**2, jx, lx, mx, nx, Complement({q}, {0})), + (lx + mx/q, (cx*q - jx*q - nx)/q**2*-1, cx, mx/q**2, jx, lx, mx, nx, Complement({q}, {0}))) + assert nonlinsolve(S, *V) == sol + # The two solutions are in fact identical, so even better if only one is returned + + +def test_issue_14541(): + solutions = solveset(sqrt(-x**2 - 2.0), x) + assert abs(solutions.args[0]+1.4142135623731*I) <= 1e-9 + assert abs(solutions.args[1]-1.4142135623731*I) <= 1e-9 + + +def test_issue_13396(): + expr = -2*y*exp(-x**2 - y**2)*Abs(x) + sol = FiniteSet(0) + + assert solveset(expr, y, domain=S.Reals) == sol + + # Related type of equation also solved here + assert solveset(atan(x**2 - y**2)-pi/2, y, S.Reals) is S.EmptySet + + +def test_issue_12032(): + sol = FiniteSet(-sqrt(-2/(3*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3))) + + 2*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3)))/2 + + sqrt(Abs(-2*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3)) + + 2/(3*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3))) + + 2/sqrt(-2/(3*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3))) + + 2*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3)))))/2, + -sqrt(Abs(-2*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3)) + + 2/(3*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3))) + + 2/sqrt(-2/(3*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3))) + + 2*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3)))))/2 - + sqrt(-2/(3*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3))) + + 2*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3)))/2, + sqrt(-2/(3*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3))) + + 2*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3)))/2 - + I*sqrt(Abs(-2/sqrt(-2/(3*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3))) + + 2*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3))) - + 2*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3)) + + 2/(3*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3)))))/2, + sqrt(-2/(3*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3))) + + 2*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3)))/2 + + I*sqrt(Abs(-2/sqrt(-2/(3*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3))) + + 2*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3))) - + 2*(Rational(1, 16) + sqrt(849)/144)**(Rational(1, 3)) + + 2/(3*(Rational(1, 16) + sqrt(849)/144)**(Rational(1,3)))))/2) + assert solveset(x**4 + x - 1, x) == sol + + +def test_issue_10876(): + assert solveset(1/sqrt(x), x) == S.EmptySet + + +def test_issue_19050(): + # test_issue_19050 --> TypeError removed + assert dumeq(nonlinsolve([x + y, sin(y)], [x, y]), + FiniteSet((ImageSet(Lambda(n, -2*n*pi), S.Integers), ImageSet(Lambda(n, 2*n*pi), S.Integers)),\ + (ImageSet(Lambda(n, -2*n*pi - pi), S.Integers), ImageSet(Lambda(n, 2*n*pi + pi), S.Integers)))) + assert dumeq(nonlinsolve([x + y, sin(y) + cos(y)], [x, y]), + FiniteSet((ImageSet(Lambda(n, -2*n*pi - 3*pi/4), S.Integers), ImageSet(Lambda(n, 2*n*pi + 3*pi/4), S.Integers)), \ + (ImageSet(Lambda(n, -2*n*pi - 7*pi/4), S.Integers), ImageSet(Lambda(n, 2*n*pi + 7*pi/4), S.Integers)))) + + +def test_issue_16618(): + eqn = [sin(x)*sin(y), cos(x)*cos(y) - 1] + # nonlinsolve's answer is still suspicious since it contains only three + # distinct Dummys instead of 4. (Both 'x' ImageSets share the same Dummy.) + ans = FiniteSet((ImageSet(Lambda(n, 2*n*pi), S.Integers), ImageSet(Lambda(n, 2*n*pi), S.Integers)), + (ImageSet(Lambda(n, 2*n*pi + pi), S.Integers), ImageSet(Lambda(n, 2*n*pi + pi), S.Integers))) + sol = nonlinsolve(eqn, [x, y]) + + for i0, j0 in zip(ordered(sol), ordered(ans)): + assert len(i0) == len(j0) == 2 + assert all(a.dummy_eq(b) for a, b in zip(i0, j0)) + assert len(sol) == len(ans) + + +def test_issue_17566(): + assert nonlinsolve([32*(2**x)/2**(-y) - 4**y, 27*(3**x) - S(1)/3**y], x, y) ==\ + FiniteSet((-log(81)/log(3), 1)) + + +def test_issue_16643(): + n = Dummy('n') + assert solveset(x**2*sin(x), x).dummy_eq(Union(ImageSet(Lambda(n, 2*n*pi + pi), S.Integers), + ImageSet(Lambda(n, 2*n*pi), S.Integers))) + + +def test_issue_19587(): + n,m = symbols('n m') + assert nonlinsolve([32*2**m*2**n - 4**n, 27*3**m - 3**(-n)], m, n) ==\ + FiniteSet((-log(81)/log(3), 1)) + + +def test_issue_5132_1(): + system = [sqrt(x**2 + y**2) - sqrt(10), x + y - 4] + assert nonlinsolve(system, [x, y]) == FiniteSet((1, 3), (3, 1)) + + n = Dummy('n') + eqs = [exp(x)**2 - sin(y) + z**2, 1/exp(y) - 3] + s_real_y = -log(3) + s_real_z = sqrt(-exp(2*x) - sin(log(3))) + soln_real = FiniteSet((s_real_y, s_real_z), (s_real_y, -s_real_z)) + lam = Lambda(n, 2*n*I*pi + -log(3)) + s_complex_y = ImageSet(lam, S.Integers) + lam = Lambda(n, sqrt(-exp(2*x) + sin(2*n*I*pi + -log(3)))) + s_complex_z_1 = ImageSet(lam, S.Integers) + lam = Lambda(n, -sqrt(-exp(2*x) + sin(2*n*I*pi + -log(3)))) + s_complex_z_2 = ImageSet(lam, S.Integers) + soln_complex = FiniteSet( + (s_complex_y, s_complex_z_1), + (s_complex_y, s_complex_z_2) + ) + soln = soln_real + soln_complex + assert dumeq(nonlinsolve(eqs, [y, z]), soln) + + +def test_issue_5132_2(): + x, y = symbols('x, y', real=True) + eqs = [exp(x)**2 - sin(y) + z**2] + n = Dummy('n') + soln_real = (log(-z**2 + sin(y))/2, z) + lam = Lambda( n, I*(2*n*pi + arg(-z**2 + sin(y)))/2 + log(Abs(z**2 - sin(y)))/2) + img = ImageSet(lam, S.Integers) + # not sure about the complex soln. But it looks correct. + soln_complex = (img, z) + soln = FiniteSet(soln_real, soln_complex) + assert dumeq(nonlinsolve(eqs, [x, z]), soln) + + system = [r - x**2 - y**2, tan(t) - y/x] + s_x = sqrt(r/(tan(t)**2 + 1)) + s_y = sqrt(r/(tan(t)**2 + 1))*tan(t) + soln = FiniteSet((s_x, s_y), (-s_x, -s_y)) + assert nonlinsolve(system, [x, y]) == soln + + +def test_issue_6752(): + a, b = symbols('a, b', real=True) + assert nonlinsolve([a**2 + a, a - b], [a, b]) == {(-1, -1), (0, 0)} + + +@SKIP("slow") +def test_issue_5114_solveset(): + # slow testcase + from sympy.abc import o, p + + # there is no 'a' in the equation set but this is how the + # problem was originally posed + syms = [a, b, c, f, h, k, n] + eqs = [b + r/d - c/d, + c*(1/d + 1/e + 1/g) - f/g - r/d, + f*(1/g + 1/i + 1/j) - c/g - h/i, + h*(1/i + 1/l + 1/m) - f/i - k/m, + k*(1/m + 1/o + 1/p) - h/m - n/p, + n*(1/p + 1/q) - k/p] + assert len(nonlinsolve(eqs, syms)) == 1 + + +@SKIP("Hangs") +def _test_issue_5335(): + # Not able to check zero dimensional system. + # is_zero_dimensional Hangs + lam, a0, conc = symbols('lam a0 conc') + eqs = [lam + 2*y - a0*(1 - x/2)*x - 0.005*x/2*x, + a0*(1 - x/2)*x - 1*y - 0.743436700916726*y, + x + y - conc] + sym = [x, y, a0] + # there are 4 solutions but only two are valid + assert len(nonlinsolve(eqs, sym)) == 2 + # float + eqs = [lam + 2*y - a0*(1 - x/2)*x - 0.005*x/2*x, + a0*(1 - x/2)*x - 1*y - 0.743436700916726*y, + x + y - conc] + sym = [x, y, a0] + assert len(nonlinsolve(eqs, sym)) == 2 + + +def test_issue_2777(): + # the equations represent two circles + x, y = symbols('x y', real=True) + e1, e2 = sqrt(x**2 + y**2) - 10, sqrt(y**2 + (-x + 10)**2) - 3 + a, b = Rational(191, 20), 3*sqrt(391)/20 + ans = {(a, -b), (a, b)} + assert nonlinsolve((e1, e2), (x, y)) == ans + assert nonlinsolve((e1, e2/(x - a)), (x, y)) == S.EmptySet + # make the 2nd circle's radius be -3 + e2 += 6 + assert nonlinsolve((e1, e2), (x, y)) == S.EmptySet + + +def test_issue_8828(): + x1 = 0 + y1 = -620 + r1 = 920 + x2 = 126 + y2 = 276 + x3 = 51 + y3 = 205 + r3 = 104 + v = [x, y, z] + + f1 = (x - x1)**2 + (y - y1)**2 - (r1 - z)**2 + f2 = (x2 - x)**2 + (y2 - y)**2 - z**2 + f3 = (x - x3)**2 + (y - y3)**2 - (r3 - z)**2 + F = [f1, f2, f3] + + g1 = sqrt((x - x1)**2 + (y - y1)**2) + z - r1 + g2 = f2 + g3 = sqrt((x - x3)**2 + (y - y3)**2) + z - r3 + G = [g1, g2, g3] + + # both soln same + A = nonlinsolve(F, v) + B = nonlinsolve(G, v) + assert A == B + + +def test_nonlinsolve_conditionset(): + # when solveset failed to solve all the eq + # return conditionset + f = Function('f') + f1 = f(x) - pi/2 + f2 = f(y) - pi*Rational(3, 2) + intermediate_system = Eq(2*f(x) - pi, 0) & Eq(2*f(y) - 3*pi, 0) + syms = Tuple(x, y) + soln = ConditionSet( + syms, + intermediate_system, + S.Complexes**2) + assert nonlinsolve([f1, f2], [x, y]) == soln + + +def test_substitution_basic(): + assert substitution([], [x, y]) == S.EmptySet + assert substitution([], []) == S.EmptySet + system = [2*x**2 + 3*y**2 - 30, 3*x**2 - 2*y**2 - 19] + soln = FiniteSet((-3, -2), (-3, 2), (3, -2), (3, 2)) + assert substitution(system, [x, y]) == soln + + soln = FiniteSet((-1, 1)) + assert substitution([x + y], [x], [{y: 1}], [y], set(), [x, y]) == soln + assert substitution( + [x + y], [x], [{y: 1}], [y], + {x + 1}, [y, x]) == S.EmptySet + + +def test_substitution_incorrect(): + # the solutions in the following two tests are incorrect. The + # correct result is EmptySet in both cases. + assert substitution([h - 1, k - 1, f - 2, f - 4, -2 * k], + [h, k, f]) == {(1, 1, f)} + assert substitution([x + y + z, S.One, S.One, S.One], [x, y, z]) == \ + {(-y - z, y, z)} + + # the correct result in the test below is {(-I, I, I, -I), + # (I, -I, -I, I)} + assert substitution([a - d, b + d, c + d, d**2 + 1], [a, b, c, d]) == \ + {(d, -d, -d, d)} + + # the result in the test below is incomplete. The complete result + # is {(0, b), (log(2), 2)} + assert substitution([a*(a - log(b)), a*(b - 2)], [a, b]) == \ + {(0, b)} + + # The system in the test below is zero-dimensional, so the result + # should have no free symbols + assert substitution([-k*y + 6*x - 4*y, -81*k + 49*y**2 - 270, + -3*k*z + k + z**3, k**2 - 2*k + 4], + [x, y, z, k]).free_symbols == {z} + + +def test_substitution_redundant(): + # the third and fourth solutions are redundant in the test below + assert substitution([x**2 - y**2, z - 1], [x, z]) == \ + {(-y, 1), (y, 1), (-sqrt(y**2), 1), (sqrt(y**2), 1)} + + # the system below has three solutions. Two of the solutions + # returned by substitution are redundant. + res = substitution([x - y, y**3 - 3*y**2 + 1], [x, y]) + assert len(res) == 5 + + +def test_issue_5132_substitution(): + x, y, z, r, t = symbols('x, y, z, r, t', real=True) + system = [r - x**2 - y**2, tan(t) - y/x] + s_x_1 = Complement(FiniteSet(-sqrt(r/(tan(t)**2 + 1))), FiniteSet(0)) + s_x_2 = Complement(FiniteSet(sqrt(r/(tan(t)**2 + 1))), FiniteSet(0)) + s_y = sqrt(r/(tan(t)**2 + 1))*tan(t) + soln = FiniteSet((s_x_2, s_y)) + FiniteSet((s_x_1, -s_y)) + assert substitution(system, [x, y]) == soln + + n = Dummy('n') + eqs = [exp(x)**2 - sin(y) + z**2, 1/exp(y) - 3] + s_real_y = -log(3) + s_real_z = sqrt(-exp(2*x) - sin(log(3))) + soln_real = FiniteSet((s_real_y, s_real_z), (s_real_y, -s_real_z)) + lam = Lambda(n, 2*n*I*pi + -log(3)) + s_complex_y = ImageSet(lam, S.Integers) + lam = Lambda(n, sqrt(-exp(2*x) + sin(2*n*I*pi + -log(3)))) + s_complex_z_1 = ImageSet(lam, S.Integers) + lam = Lambda(n, -sqrt(-exp(2*x) + sin(2*n*I*pi + -log(3)))) + s_complex_z_2 = ImageSet(lam, S.Integers) + soln_complex = FiniteSet( + (s_complex_y, s_complex_z_1), + (s_complex_y, s_complex_z_2)) + soln = soln_real + soln_complex + assert dumeq(substitution(eqs, [y, z]), soln) + + +def test_raises_substitution(): + raises(ValueError, lambda: substitution([x**2 -1], [])) + raises(TypeError, lambda: substitution([x**2 -1])) + raises(ValueError, lambda: substitution([x**2 -1], [sin(x)])) + raises(TypeError, lambda: substitution([x**2 -1], x)) + raises(TypeError, lambda: substitution([x**2 -1], 1)) + + +def test_issue_21022(): + from sympy.core.sympify import sympify + + eqs = [ + 'k-16', + 'p-8', + 'y*y+z*z-x*x', + 'd - x + p', + 'd*d+k*k-y*y', + 'z*z-p*p-k*k', + 'abc-efg', + ] + efg = Symbol('efg') + eqs = [sympify(x) for x in eqs] + + syb = list(ordered(set.union(*[x.free_symbols for x in eqs]))) + res = nonlinsolve(eqs, syb) + + ans = FiniteSet( + (efg, 32, efg, 16, 8, 40, -16*sqrt(5), -8*sqrt(5)), + (efg, 32, efg, 16, 8, 40, -16*sqrt(5), 8*sqrt(5)), + (efg, 32, efg, 16, 8, 40, 16*sqrt(5), -8*sqrt(5)), + (efg, 32, efg, 16, 8, 40, 16*sqrt(5), 8*sqrt(5)), + ) + assert len(res) == len(ans) == 4 + assert res == ans + for result in res.args: + assert len(result) == 8 + + +def test_issue_17940(): + n = Dummy('n') + k1 = Dummy('k1') + sol = ImageSet(Lambda(((k1, n),), I*(2*k1*pi + arg(2*n*I*pi + log(5))) + + log(Abs(2*n*I*pi + log(5)))), + ProductSet(S.Integers, S.Integers)) + assert solveset(exp(exp(x)) - 5, x).dummy_eq(sol) + + +def test_issue_17906(): + assert solveset(7**(x**2 - 80) - 49**x, x) == FiniteSet(-8, 10) + + +@XFAIL +def test_issue_17933(): + eq1 = x*sin(45) - y*cos(q) + eq2 = x*cos(45) - y*sin(q) + eq3 = 9*x*sin(45)/10 + y*cos(q) + eq4 = 9*x*cos(45)/10 + y*sin(z) - z + assert nonlinsolve([eq1, eq2, eq3, eq4], x, y, z, q) ==\ + FiniteSet((0, 0, 0, q)) + +def test_issue_17933_bis(): + # nonlinsolve's result depends on the 'default_sort_key' ordering of + # the unknowns. + eq1 = x*sin(45) - y*cos(q) + eq2 = x*cos(45) - y*sin(q) + eq3 = 9*x*sin(45)/10 + y*cos(q) + eq4 = 9*x*cos(45)/10 + y*sin(z) - z + zz = Symbol('zz') + eqs = [e.subs(q, zz) for e in (eq1, eq2, eq3, eq4)] + assert nonlinsolve(eqs, x, y, z, zz) == FiniteSet((0, 0, 0, zz)) + + +def test_issue_14565(): + # removed redundancy + assert dumeq(nonlinsolve([k + m, k + m*exp(-2*pi*k)], [k, m]) , + FiniteSet((-n*I, ImageSet(Lambda(n, n*I), S.Integers)))) + + +# end of tests for nonlinsolve + + +def test_issue_9556(): + b = Symbol('b', positive=True) + + assert solveset(Abs(x) + 1, x, S.Reals) is S.EmptySet + assert solveset(Abs(x) + b, x, S.Reals) is S.EmptySet + assert solveset(Eq(b, -1), b, S.Reals) is S.EmptySet + + +def test_issue_9611(): + assert solveset(Eq(x - x + a, a), x, S.Reals) == S.Reals + assert solveset(Eq(y - y + a, a), y) == S.Complexes + + +def test_issue_9557(): + assert solveset(x**2 + a, x, S.Reals) == Intersection(S.Reals, + FiniteSet(-sqrt(-a), sqrt(-a))) + + +def test_issue_9778(): + x = Symbol('x', real=True) + y = Symbol('y', real=True) + assert solveset(x**3 + 1, x, S.Reals) == FiniteSet(-1) + assert solveset(x**Rational(3, 5) + 1, x, S.Reals) == S.EmptySet + assert solveset(x**3 + y, x, S.Reals) == \ + FiniteSet(-Abs(y)**Rational(1, 3)*sign(y)) + + +def test_issue_10214(): + assert solveset(x**Rational(3, 2) + 4, x, S.Reals) == S.EmptySet + assert solveset(x**(Rational(-3, 2)) + 4, x, S.Reals) == S.EmptySet + + ans = FiniteSet(-2**Rational(2, 3)) + assert solveset(x**(S(3)) + 4, x, S.Reals) == ans + assert (x**(S(3)) + 4).subs(x,list(ans)[0]) == 0 # substituting ans and verifying the result. + assert (x**(S(3)) + 4).subs(x,-(-2)**Rational(2, 3)) == 0 + + +def test_issue_9849(): + assert solveset(Abs(sin(x)) + 1, x, S.Reals) == S.EmptySet + + +def test_issue_9953(): + assert linsolve([ ], x) == S.EmptySet + + +def test_issue_9913(): + assert solveset(2*x + 1/(x - 10)**2, x, S.Reals) == \ + FiniteSet(-(3*sqrt(24081)/4 + Rational(4027, 4))**Rational(1, 3)/3 - 100/ + (3*(3*sqrt(24081)/4 + Rational(4027, 4))**Rational(1, 3)) + Rational(20, 3)) + + +def test_issue_10397(): + assert solveset(sqrt(x), x, S.Complexes) == FiniteSet(0) + + +def test_issue_14987(): + raises(ValueError, lambda: linear_eq_to_matrix( + [x**2], x)) + raises(ValueError, lambda: linear_eq_to_matrix( + [x*(-3/x + 1) + 2*y - a], [x, y])) + raises(ValueError, lambda: linear_eq_to_matrix( + [(x**2 - 3*x)/(x - 3) - 3], x)) + raises(ValueError, lambda: linear_eq_to_matrix( + [(x + 1)**3 - x**3 - 3*x**2 + 7], x)) + raises(ValueError, lambda: linear_eq_to_matrix( + [x*(1/x + 1) + y], [x, y])) + raises(ValueError, lambda: linear_eq_to_matrix( + [(x + 1)*y], [x, y])) + raises(ValueError, lambda: linear_eq_to_matrix( + [Eq(1/x, 1/x + y)], [x, y])) + raises(ValueError, lambda: linear_eq_to_matrix( + [Eq(y/x, y/x + y)], [x, y])) + raises(ValueError, lambda: linear_eq_to_matrix( + [Eq(x*(x + 1), x**2 + y)], [x, y])) + + +def test_simplification(): + eq = x + (a - b)/(-2*a + 2*b) + assert solveset(eq, x) == FiniteSet(S.Half) + assert solveset(eq, x, S.Reals) == Intersection({-((a - b)/(-2*a + 2*b))}, S.Reals) + # So that ap - bn is not zero: + ap = Symbol('ap', positive=True) + bn = Symbol('bn', negative=True) + eq = x + (ap - bn)/(-2*ap + 2*bn) + assert solveset(eq, x) == FiniteSet(S.Half) + assert solveset(eq, x, S.Reals) == FiniteSet(S.Half) + + +def test_integer_domain_relational(): + eq1 = 2*x + 3 > 0 + eq2 = x**2 + 3*x - 2 >= 0 + eq3 = x + 1/x > -2 + 1/x + eq4 = x + sqrt(x**2 - 5) > 0 + eq = x + 1/x > -2 + 1/x + eq5 = eq.subs(x,log(x)) + eq6 = log(x)/x <= 0 + eq7 = log(x)/x < 0 + eq8 = x/(x-3) < 3 + eq9 = x/(x**2-3) < 3 + + assert solveset(eq1, x, S.Integers) == Range(-1, oo, 1) + assert solveset(eq2, x, S.Integers) == Union(Range(-oo, -3, 1), Range(1, oo, 1)) + assert solveset(eq3, x, S.Integers) == Union(Range(-1, 0, 1), Range(1, oo, 1)) + assert solveset(eq4, x, S.Integers) == Range(3, oo, 1) + assert solveset(eq5, x, S.Integers) == Range(2, oo, 1) + assert solveset(eq6, x, S.Integers) == Range(1, 2, 1) + assert solveset(eq7, x, S.Integers) == S.EmptySet + assert solveset(eq8, x, domain=Range(0,5)) == Range(0, 3, 1) + assert solveset(eq9, x, domain=Range(0,5)) == Union(Range(0, 2, 1), Range(2, 5, 1)) + + # test_issue_19794 + assert solveset(x + 2 < 0, x, S.Integers) == Range(-oo, -2, 1) + + +def test_issue_10555(): + f = Function('f') + g = Function('g') + assert solveset(f(x) - pi/2, x, S.Reals).dummy_eq( + ConditionSet(x, Eq(f(x) - pi/2, 0), S.Reals)) + assert solveset(f(g(x)) - pi/2, g(x), S.Reals).dummy_eq( + ConditionSet(g(x), Eq(f(g(x)) - pi/2, 0), S.Reals)) + + +def test_issue_8715(): + eq = x + 1/x > -2 + 1/x + assert solveset(eq, x, S.Reals) == \ + (Interval.open(-2, oo) - FiniteSet(0)) + assert solveset(eq.subs(x,log(x)), x, S.Reals) == \ + Interval.open(exp(-2), oo) - FiniteSet(1) + + +def test_issue_11174(): + eq = z**2 + exp(2*x) - sin(y) + soln = Intersection(S.Reals, FiniteSet(log(-z**2 + sin(y))/2)) + assert solveset(eq, x, S.Reals) == soln + + eq = sqrt(r)*Abs(tan(t))/sqrt(tan(t)**2 + 1) + x*tan(t) + s = -sqrt(r)*Abs(tan(t))/(sqrt(tan(t)**2 + 1)*tan(t)) + soln = Intersection(S.Reals, FiniteSet(s)) + assert solveset(eq, x, S.Reals) == soln + + +def test_issue_11534(): + # eq1 and eq2 should not have the same solutions because squaring both + # sides of the radical equation introduces a spurious solution branch. + # The equations have a symbolic parameter y and it is easy to see that for + # y != 0 the solution s1 will not be valid for eq1. + x = Symbol('x', real=True) + y = Symbol('y', real=True) + eq1 = -y + x/sqrt(-x**2 + 1) + eq2 = -y**2 + x**2/(-x**2 + 1) + + # We get a ConditionSet here because s1 works in eq1 if y is equal to zero + # although not for any other value of y. That case is redundant though + # because if y=0 then s1=s2 so the solution for eq1 could just be returned + # as s2 - {-1, 1}. In fact we have + # |y/sqrt(y**2 + 1)| < 1 + # So the complements are not needed either. The ideal output here would be + # sol1 = s2 + # sol2 = s1 | s2. + s1, s2 = FiniteSet(-y/sqrt(y**2 + 1)), FiniteSet(y/sqrt(y**2 + 1)) + cset = ConditionSet(x, Eq(eq1, 0), s1) + sol1 = (s2 - {-1, 1}) | (cset - {-1, 1}) + sol2 = (s1 | s2) - {-1, 1} + + assert solveset(eq1, x, S.Reals) == sol1 + assert solveset(eq2, x, S.Reals) == sol2 + + +def test_issue_10477(): + assert solveset((x**2 + 4*x - 3)/x < 2, x, S.Reals) == \ + Union(Interval.open(-oo, -3), Interval.open(0, 1)) + + +def test_issue_10671(): + assert solveset(sin(y), y, Interval(0, pi)) == FiniteSet(0, pi) + i = Interval(1, 10) + assert solveset((1/x).diff(x) < 0, x, i) == i + + +def test_issue_11064(): + eq = x + sqrt(x**2 - 5) + assert solveset(eq > 0, x, S.Reals) == \ + Interval(sqrt(5), oo) + assert solveset(eq < 0, x, S.Reals) == \ + Interval(-oo, -sqrt(5)) + assert solveset(eq > sqrt(5), x, S.Reals) == \ + Interval.Lopen(sqrt(5), oo) + + +def test_issue_12478(): + eq = sqrt(x - 2) + 2 + soln = solveset_real(eq, x) + assert soln is S.EmptySet + assert solveset(eq < 0, x, S.Reals) is S.EmptySet + assert solveset(eq > 0, x, S.Reals) == Interval(2, oo) + + +def test_issue_12429(): + eq = solveset(log(x)/x <= 0, x, S.Reals) + sol = Interval.Lopen(0, 1) + assert eq == sol + + +def test_issue_19506(): + eq = arg(x + I) + C = Dummy('C') + assert solveset(eq).dummy_eq(Intersection(ConditionSet(C, Eq(im(C) + 1, 0), S.Complexes), + ConditionSet(C, re(C) > 0, S.Complexes))) + + +def test_solveset_arg(): + assert solveset(arg(x), x, S.Reals) == Interval.open(0, oo) + assert solveset(arg(4*x -3), x, S.Reals) == Interval.open(Rational(3, 4), oo) + + +def test__is_finite_with_finite_vars(): + f = _is_finite_with_finite_vars + # issue 12482 + assert all(f(1/x) is None for x in ( + Dummy(), Dummy(real=True), Dummy(complex=True))) + assert f(1/Dummy(real=False)) is True # b/c it's finite but not 0 + + +def test_issue_13550(): + assert solveset(x**2 - 2*x - 15, symbol = x, domain = Interval(-oo, 0)) == FiniteSet(-3) + + +def test_issue_13849(): + assert nonlinsolve((t*(sqrt(5) + sqrt(2)) - sqrt(2), t), t) is S.EmptySet + + +def test_issue_14223(): + assert solveset((Abs(x + Min(x, 2)) - 2).rewrite(Piecewise), x, + S.Reals) == FiniteSet(-1, 1) + assert solveset((Abs(x + Min(x, 2)) - 2).rewrite(Piecewise), x, + Interval(0, 2)) == FiniteSet(1) + assert solveset(x, x, FiniteSet(1, 2)) is S.EmptySet + + +def test_issue_10158(): + dom = S.Reals + assert solveset(x*Max(x, 15) - 10, x, dom) == FiniteSet(Rational(2, 3)) + assert solveset(x*Min(x, 15) - 10, x, dom) == FiniteSet(-sqrt(10), sqrt(10)) + assert solveset(Max(Abs(x - 3) - 1, x + 2) - 3, x, dom) == FiniteSet(-1, 1) + assert solveset(Abs(x - 1) - Abs(y), x, dom) == FiniteSet(-Abs(y) + 1, Abs(y) + 1) + assert solveset(Abs(x + 4*Abs(x + 1)), x, dom) == FiniteSet(Rational(-4, 3), Rational(-4, 5)) + assert solveset(2*Abs(x + Abs(x + Max(3, x))) - 2, x, S.Reals) == FiniteSet(-1, -2) + dom = S.Complexes + raises(ValueError, lambda: solveset(x*Max(x, 15) - 10, x, dom)) + raises(ValueError, lambda: solveset(x*Min(x, 15) - 10, x, dom)) + raises(ValueError, lambda: solveset(Max(Abs(x - 3) - 1, x + 2) - 3, x, dom)) + raises(ValueError, lambda: solveset(Abs(x - 1) - Abs(y), x, dom)) + raises(ValueError, lambda: solveset(Abs(x + 4*Abs(x + 1)), x, dom)) + + +def test_issue_14300(): + f = 1 - exp(-18000000*x) - y + a1 = FiniteSet(-log(-y + 1)/18000000) + + assert solveset(f, x, S.Reals) == \ + Intersection(S.Reals, a1) + assert dumeq(solveset(f, x), + ImageSet(Lambda(n, -I*(2*n*pi + arg(-y + 1))/18000000 - + log(Abs(y - 1))/18000000), S.Integers)) + + +def test_issue_14454(): + number = CRootOf(x**4 + x - 1, 2) + raises(ValueError, lambda: invert_real(number, 0, x)) + assert invert_real(x**2, number, x) # no error + + +def test_issue_17882(): + assert solveset(-8*x**2/(9*(x**2 - 1)**(S(4)/3)) + 4/(3*(x**2 - 1)**(S(1)/3)), x, S.Complexes) == \ + FiniteSet(sqrt(3), -sqrt(3)) + + +def test_term_factors(): + assert list(_term_factors(3**x - 2)) == [-2, 3**x] + expr = 4**(x + 1) + 4**(x + 2) + 4**(x - 1) - 3**(x + 2) - 3**(x + 3) + assert set(_term_factors(expr)) == { + 3**(x + 2), 4**(x + 2), 3**(x + 3), 4**(x - 1), -1, 4**(x + 1)} + + +#################### tests for transolve and its helpers ############### + +def test_transolve(): + + assert _transolve(3**x, x, S.Reals) == S.EmptySet + assert _transolve(3**x - 9**(x + 5), x, S.Reals) == FiniteSet(-10) + + +def test_issue_21276(): + eq = (2*x*(y - z) - y*erf(y - z) - y + z*erf(y - z) + z)**2 + assert solveset(eq.expand(), y) == FiniteSet(z, z + erfinv(2*x - 1)) + + +# exponential tests +def test_exponential_real(): + from sympy.abc import y + + e1 = 3**(2*x) - 2**(x + 3) + e2 = 4**(5 - 9*x) - 8**(2 - x) + e3 = 2**x + 4**x + e4 = exp(log(5)*x) - 2**x + e5 = exp(x/y)*exp(-z/y) - 2 + e6 = 5**(x/2) - 2**(x/3) + e7 = 4**(x + 1) + 4**(x + 2) + 4**(x - 1) - 3**(x + 2) - 3**(x + 3) + e8 = -9*exp(-2*x + 5) + 4*exp(3*x + 1) + e9 = 2**x + 4**x + 8**x - 84 + e10 = 29*2**(x + 1)*615**(x) - 123*2726**(x) + + assert solveset(e1, x, S.Reals) == FiniteSet( + -3*log(2)/(-2*log(3) + log(2))) + assert solveset(e2, x, S.Reals) == FiniteSet(Rational(4, 15)) + assert solveset(e3, x, S.Reals) == S.EmptySet + assert solveset(e4, x, S.Reals) == FiniteSet(0) + assert solveset(e5, x, S.Reals) == Intersection( + S.Reals, FiniteSet(y*log(2*exp(z/y)))) + assert solveset(e6, x, S.Reals) == FiniteSet(0) + assert solveset(e7, x, S.Reals) == FiniteSet(2) + assert solveset(e8, x, S.Reals) == FiniteSet(-2*log(2)/5 + 2*log(3)/5 + Rational(4, 5)) + assert solveset(e9, x, S.Reals) == FiniteSet(2) + assert solveset(e10,x, S.Reals) == FiniteSet((-log(29) - log(2) + log(123))/(-log(2726) + log(2) + log(615))) + + assert solveset_real(-9*exp(-2*x + 5) + 2**(x + 1), x) == FiniteSet( + -((-5 - 2*log(3) + log(2))/(log(2) + 2))) + assert solveset_real(4**(x/2) - 2**(x/3), x) == FiniteSet(0) + b = sqrt(6)*sqrt(log(2))/sqrt(log(5)) + assert solveset_real(5**(x/2) - 2**(3/x), x) == FiniteSet(-b, b) + + # coverage test + C1, C2 = symbols('C1 C2') + f = Function('f') + assert solveset_real(C1 + C2/x**2 - exp(-f(x)), f(x)) == Intersection( + S.Reals, FiniteSet(-log(C1 + C2/x**2))) + y = symbols('y', positive=True) + assert solveset_real(x**2 - y**2/exp(x), y) == Intersection( + S.Reals, FiniteSet(-sqrt(x**2*exp(x)), sqrt(x**2*exp(x)))) + p = Symbol('p', positive=True) + assert solveset_real((1/p + 1)**(p + 1), p).dummy_eq( + ConditionSet(x, Eq((1 + 1/x)**(x + 1), 0), S.Reals)) + assert solveset(2**x - 4**x + 12, x, S.Reals) == {2} + assert solveset(2**x - 2**(2*x) + 12, x, S.Reals) == {2} + + +@XFAIL +def test_exponential_complex(): + n = Dummy('n') + + assert dumeq(solveset_complex(2**x + 4**x, x),imageset( + Lambda(n, I*(2*n*pi + pi)/log(2)), S.Integers)) + assert solveset_complex(x**z*y**z - 2, z) == FiniteSet( + log(2)/(log(x) + log(y))) + assert dumeq(solveset_complex(4**(x/2) - 2**(x/3), x), imageset( + Lambda(n, 3*n*I*pi/log(2)), S.Integers)) + assert dumeq(solveset(2**x + 32, x), imageset( + Lambda(n, (I*(2*n*pi + pi) + 5*log(2))/log(2)), S.Integers)) + + eq = (2**exp(y**2/x) + 2)/(x**2 + 15) + a = sqrt(x)*sqrt(-log(log(2)) + log(log(2) + 2*n*I*pi)) + assert solveset_complex(eq, y) == FiniteSet(-a, a) + + union1 = imageset(Lambda(n, I*(2*n*pi - pi*Rational(2, 3))/log(2)), S.Integers) + union2 = imageset(Lambda(n, I*(2*n*pi + pi*Rational(2, 3))/log(2)), S.Integers) + assert dumeq(solveset(2**x + 4**x + 8**x, x), Union(union1, union2)) + + eq = 4**(x + 1) + 4**(x + 2) + 4**(x - 1) - 3**(x + 2) - 3**(x + 3) + res = solveset(eq, x) + num = 2*n*I*pi - 4*log(2) + 2*log(3) + den = -2*log(2) + log(3) + ans = imageset(Lambda(n, num/den), S.Integers) + assert dumeq(res, ans) + + +def test_expo_conditionset(): + + f1 = (exp(x) + 1)**x - 2 + f2 = (x + 2)**y*x - 3 + f3 = 2**x - exp(x) - 3 + f4 = log(x) - exp(x) + f5 = 2**x + 3**x - 5**x + + assert solveset(f1, x, S.Reals).dummy_eq(ConditionSet( + x, Eq((exp(x) + 1)**x - 2, 0), S.Reals)) + assert solveset(f2, x, S.Reals).dummy_eq(ConditionSet( + x, Eq(x*(x + 2)**y - 3, 0), S.Reals)) + assert solveset(f3, x, S.Reals).dummy_eq(ConditionSet( + x, Eq(2**x - exp(x) - 3, 0), S.Reals)) + assert solveset(f4, x, S.Reals).dummy_eq(ConditionSet( + x, Eq(-exp(x) + log(x), 0), S.Reals)) + assert solveset(f5, x, S.Reals).dummy_eq(ConditionSet( + x, Eq(2**x + 3**x - 5**x, 0), S.Reals)) + + +def test_exponential_symbols(): + x, y, z = symbols('x y z', positive=True) + xr, zr = symbols('xr, zr', real=True) + + assert solveset(z**x - y, x, S.Reals) == Intersection( + S.Reals, FiniteSet(log(y)/log(z))) + + f1 = 2*x**w - 4*y**w + f2 = (x/y)**w - 2 + sol1 = Intersection({log(2)/(log(x) - log(y))}, S.Reals) + sol2 = Intersection({log(2)/log(x/y)}, S.Reals) + assert solveset(f1, w, S.Reals) == sol1, solveset(f1, w, S.Reals) + assert solveset(f2, w, S.Reals) == sol2, solveset(f2, w, S.Reals) + + assert solveset(x**x, x, Interval.Lopen(0,oo)).dummy_eq( + ConditionSet(w, Eq(w**w, 0), Interval.open(0, oo))) + assert solveset(x**y - 1, y, S.Reals) == FiniteSet(0) + assert solveset(exp(x/y)*exp(-z/y) - 2, y, S.Reals) == \ + Complement(ConditionSet(y, Eq(im(x)/y, 0) & Eq(im(z)/y, 0), \ + Complement(Intersection(FiniteSet((x - z)/log(2)), S.Reals), FiniteSet(0))), FiniteSet(0)) + assert solveset(exp(xr/y)*exp(-zr/y) - 2, y, S.Reals) == \ + Complement(FiniteSet((xr - zr)/log(2)), FiniteSet(0)) + + assert solveset(a**x - b**x, x).dummy_eq(ConditionSet( + w, Ne(a, 0) & Ne(b, 0), FiniteSet(0))) + + +def test_ignore_assumptions(): + # make sure assumptions are ignored + xpos = symbols('x', positive=True) + x = symbols('x') + assert solveset_complex(xpos**2 - 4, xpos + ) == solveset_complex(x**2 - 4, x) + + +@XFAIL +def test_issue_10864(): + assert solveset(x**(y*z) - x, x, S.Reals) == FiniteSet(1) + + +@XFAIL +def test_solve_only_exp_2(): + assert solveset_real(sqrt(exp(x)) + sqrt(exp(-x)) - 4, x) == \ + FiniteSet(2*log(-sqrt(3) + 2), 2*log(sqrt(3) + 2)) + + +def test_is_exponential(): + assert _is_exponential(y, x) is False + assert _is_exponential(3**x - 2, x) is True + assert _is_exponential(5**x - 7**(2 - x), x) is True + assert _is_exponential(sin(2**x) - 4*x, x) is False + assert _is_exponential(x**y - z, y) is True + assert _is_exponential(x**y - z, x) is False + assert _is_exponential(2**x + 4**x - 1, x) is True + assert _is_exponential(x**(y*z) - x, x) is False + assert _is_exponential(x**(2*x) - 3**x, x) is False + assert _is_exponential(x**y - y*z, y) is False + assert _is_exponential(x**y - x*z, y) is True + + +def test_solve_exponential(): + assert _solve_exponential(3**(2*x) - 2**(x + 3), 0, x, S.Reals) == \ + FiniteSet(-3*log(2)/(-2*log(3) + log(2))) + assert _solve_exponential(2**y + 4**y, 1, y, S.Reals) == \ + FiniteSet(log(Rational(-1, 2) + sqrt(5)/2)/log(2)) + assert _solve_exponential(2**y + 4**y, 0, y, S.Reals) == \ + S.EmptySet + assert _solve_exponential(2**x + 3**x - 5**x, 0, x, S.Reals) == \ + ConditionSet(x, Eq(2**x + 3**x - 5**x, 0), S.Reals) + +# end of exponential tests + + +# logarithmic tests +def test_logarithmic(): + assert solveset_real(log(x - 3) + log(x + 3), x) == FiniteSet( + -sqrt(10), sqrt(10)) + assert solveset_real(log(x + 1) - log(2*x - 1), x) == FiniteSet(2) + assert solveset_real(log(x + 3) + log(1 + 3/x) - 3, x) == FiniteSet( + -3 + sqrt(-12 + exp(3))*exp(Rational(3, 2))/2 + exp(3)/2, + -sqrt(-12 + exp(3))*exp(Rational(3, 2))/2 - 3 + exp(3)/2) + + eq = z - log(x) + log(y/(x*(-1 + y**2/x**2))) + assert solveset_real(eq, x) == \ + Intersection(S.Reals, FiniteSet(-sqrt(y**2 - y*exp(z)), + sqrt(y**2 - y*exp(z)))) - \ + Intersection(S.Reals, FiniteSet(-sqrt(y**2), sqrt(y**2))) + assert solveset_real( + log(3*x) - log(-x + 1) - log(4*x + 1), x) == FiniteSet(Rational(-1, 2), S.Half) + assert solveset(log(x**y) - y*log(x), x, S.Reals) == S.Reals + +@XFAIL +def test_uselogcombine_2(): + eq = log(exp(2*x) + 1) + log(-tanh(x) + 1) - log(2) + assert solveset_real(eq, x) is S.EmptySet + eq = log(8*x) - log(sqrt(x) + 1) - 2 + assert solveset_real(eq, x) is S.EmptySet + + +def test_is_logarithmic(): + assert _is_logarithmic(y, x) is False + assert _is_logarithmic(log(x), x) is True + assert _is_logarithmic(log(x) - 3, x) is True + assert _is_logarithmic(log(x)*log(y), x) is True + assert _is_logarithmic(log(x)**2, x) is False + assert _is_logarithmic(log(x - 3) + log(x + 3), x) is True + assert _is_logarithmic(log(x**y) - y*log(x), x) is True + assert _is_logarithmic(sin(log(x)), x) is False + assert _is_logarithmic(x + y, x) is False + assert _is_logarithmic(log(3*x) - log(1 - x) + 4, x) is True + assert _is_logarithmic(log(x) + log(y) + x, x) is False + assert _is_logarithmic(log(log(x - 3)) + log(x - 3), x) is True + assert _is_logarithmic(log(log(3) + x) + log(x), x) is True + assert _is_logarithmic(log(x)*(y + 3) + log(x), y) is False + + +def test_solve_logarithm(): + y = Symbol('y') + assert _solve_logarithm(log(x**y) - y*log(x), 0, x, S.Reals) == S.Reals + y = Symbol('y', positive=True) + assert _solve_logarithm(log(x)*log(y), 0, x, S.Reals) == FiniteSet(1) + +# end of logarithmic tests + + +# lambert tests +def test_is_lambert(): + a, b, c = symbols('a,b,c') + assert _is_lambert(x**2, x) is False + assert _is_lambert(a**x**2+b*x+c, x) is True + assert _is_lambert(E**2, x) is False + assert _is_lambert(x*E**2, x) is False + assert _is_lambert(3*log(x) - x*log(3), x) is True + assert _is_lambert(log(log(x - 3)) + log(x-3), x) is True + assert _is_lambert(5*x - 1 + 3*exp(2 - 7*x), x) is True + assert _is_lambert((a/x + exp(x/2)).diff(x, 2), x) is True + assert _is_lambert((x**2 - 2*x + 1).subs(x, (log(x) + 3*x)**2 - 1), x) is True + assert _is_lambert(x*sinh(x) - 1, x) is True + assert _is_lambert(x*cos(x) - 5, x) is True + assert _is_lambert(tanh(x) - 5*x, x) is True + assert _is_lambert(cosh(x) - sinh(x), x) is False + +# end of lambert tests + + +def test_linear_coeffs(): + from sympy.solvers.solveset import linear_coeffs + assert linear_coeffs(0, x) == [0, 0] + assert all(i is S.Zero for i in linear_coeffs(0, x)) + assert linear_coeffs(x + 2*y + 3, x, y) == [1, 2, 3] + assert linear_coeffs(x + 2*y + 3, y, x) == [2, 1, 3] + assert linear_coeffs(x + 2*x**2 + 3, x, x**2) == [1, 2, 3] + raises(ValueError, lambda: + linear_coeffs(x + 2*x**2 + x**3, x, x**2)) + raises(ValueError, lambda: + linear_coeffs(1/x*(x - 1) + 1/x, x)) + raises(ValueError, lambda: + linear_coeffs(x, x, x)) + assert linear_coeffs(a*(x + y), x, y) == [a, a, 0] + assert linear_coeffs(1.0, x, y) == [0, 0, 1.0] + # don't include coefficients of 0 + assert linear_coeffs(Eq(x, x + y), x, y, dict=True) == {y: -1} + assert linear_coeffs(0, x, y, dict=True) == {} + + +def test_is_modular(): + assert _is_modular(y, x) is False + assert _is_modular(Mod(x, 3) - 1, x) is True + assert _is_modular(Mod(x**3 - 3*x**2 - x + 1, 3) - 1, x) is True + assert _is_modular(Mod(exp(x + y), 3) - 2, x) is True + assert _is_modular(Mod(exp(x + y), 3) - log(x), x) is True + assert _is_modular(Mod(x, 3) - 1, y) is False + assert _is_modular(Mod(x, 3)**2 - 5, x) is False + assert _is_modular(Mod(x, 3)**2 - y, x) is False + assert _is_modular(exp(Mod(x, 3)) - 1, x) is False + assert _is_modular(Mod(3, y) - 1, y) is False + + +def test_invert_modular(): + n = Dummy('n', integer=True) + from sympy.solvers.solveset import _invert_modular as invert_modular + + # no solutions + assert invert_modular(Mod(x, 12), S(1)/2, n, x) == (x, S.EmptySet) + # non invertible cases + assert invert_modular(Mod(sin(x), 7), S(5), n, x) == (Mod(sin(x), 7), 5) + assert invert_modular(Mod(exp(x), 7), S(5), n, x) == (Mod(exp(x), 7), 5) + assert invert_modular(Mod(log(x), 7), S(5), n, x) == (Mod(log(x), 7), 5) + # a is symbol + assert dumeq(invert_modular(Mod(x, 7), S(5), n, x), + (x, ImageSet(Lambda(n, 7*n + 5), S.Integers))) + # a.is_Add + assert dumeq(invert_modular(Mod(x + 8, 7), S(5), n, x), + (x, ImageSet(Lambda(n, 7*n + 4), S.Integers))) + assert invert_modular(Mod(x**2 + x, 7), S(5), n, x) == \ + (Mod(x**2 + x, 7), 5) + # a.is_Mul + assert dumeq(invert_modular(Mod(3*x, 7), S(5), n, x), + (x, ImageSet(Lambda(n, 7*n + 4), S.Integers))) + assert invert_modular(Mod((x + 1)*(x + 2), 7), S(5), n, x) == \ + (Mod((x + 1)*(x + 2), 7), 5) + # a.is_Pow + assert invert_modular(Mod(x**4, 7), S(5), n, x) == \ + (x, S.EmptySet) + assert dumeq(invert_modular(Mod(3**x, 4), S(3), n, x), + (x, ImageSet(Lambda(n, 2*n + 1), S.Naturals0))) + assert dumeq(invert_modular(Mod(2**(x**2 + x + 1), 7), S(2), n, x), + (x**2 + x + 1, ImageSet(Lambda(n, 3*n + 1), S.Naturals0))) + assert invert_modular(Mod(sin(x)**4, 7), S(5), n, x) == (x, S.EmptySet) + + +def test_solve_modular(): + n = Dummy('n', integer=True) + # if rhs has symbol (need to be implemented in future). + assert solveset(Mod(x, 4) - x, x, S.Integers + ).dummy_eq( + ConditionSet(x, Eq(-x + Mod(x, 4), 0), + S.Integers)) + # when _invert_modular fails to invert + assert solveset(3 - Mod(sin(x), 7), x, S.Integers + ).dummy_eq( + ConditionSet(x, Eq(Mod(sin(x), 7) - 3, 0), S.Integers)) + assert solveset(3 - Mod(log(x), 7), x, S.Integers + ).dummy_eq( + ConditionSet(x, Eq(Mod(log(x), 7) - 3, 0), S.Integers)) + assert solveset(3 - Mod(exp(x), 7), x, S.Integers + ).dummy_eq(ConditionSet(x, Eq(Mod(exp(x), 7) - 3, 0), + S.Integers)) + # EmptySet solution definitely + assert solveset(7 - Mod(x, 5), x, S.Integers) is S.EmptySet + assert solveset(5 - Mod(x, 5), x, S.Integers) is S.EmptySet + # Negative m + assert dumeq(solveset(2 + Mod(x, -3), x, S.Integers), + ImageSet(Lambda(n, -3*n - 2), S.Integers)) + assert solveset(4 + Mod(x, -3), x, S.Integers) is S.EmptySet + # linear expression in Mod + assert dumeq(solveset(3 - Mod(x, 5), x, S.Integers), + ImageSet(Lambda(n, 5*n + 3), S.Integers)) + assert dumeq(solveset(3 - Mod(5*x - 8, 7), x, S.Integers), + ImageSet(Lambda(n, 7*n + 5), S.Integers)) + assert dumeq(solveset(3 - Mod(5*x, 7), x, S.Integers), + ImageSet(Lambda(n, 7*n + 2), S.Integers)) + # higher degree expression in Mod + assert dumeq(solveset(Mod(x**2, 160) - 9, x, S.Integers), + Union(ImageSet(Lambda(n, 160*n + 3), S.Integers), + ImageSet(Lambda(n, 160*n + 13), S.Integers), + ImageSet(Lambda(n, 160*n + 67), S.Integers), + ImageSet(Lambda(n, 160*n + 77), S.Integers), + ImageSet(Lambda(n, 160*n + 83), S.Integers), + ImageSet(Lambda(n, 160*n + 93), S.Integers), + ImageSet(Lambda(n, 160*n + 147), S.Integers), + ImageSet(Lambda(n, 160*n + 157), S.Integers))) + assert solveset(3 - Mod(x**4, 7), x, S.Integers) is S.EmptySet + assert dumeq(solveset(Mod(x**4, 17) - 13, x, S.Integers), + Union(ImageSet(Lambda(n, 17*n + 3), S.Integers), + ImageSet(Lambda(n, 17*n + 5), S.Integers), + ImageSet(Lambda(n, 17*n + 12), S.Integers), + ImageSet(Lambda(n, 17*n + 14), S.Integers))) + # a.is_Pow tests + assert dumeq(solveset(Mod(7**x, 41) - 15, x, S.Integers), + ImageSet(Lambda(n, 40*n + 3), S.Naturals0)) + assert dumeq(solveset(Mod(12**x, 21) - 18, x, S.Integers), + ImageSet(Lambda(n, 6*n + 2), S.Naturals0)) + assert dumeq(solveset(Mod(3**x, 4) - 3, x, S.Integers), + ImageSet(Lambda(n, 2*n + 1), S.Naturals0)) + assert dumeq(solveset(Mod(2**x, 7) - 2 , x, S.Integers), + ImageSet(Lambda(n, 3*n + 1), S.Naturals0)) + assert dumeq(solveset(Mod(3**(3**x), 4) - 3, x, S.Integers), + Intersection(ImageSet(Lambda(n, Intersection({log(2*n + 1)/log(3)}, + S.Integers)), S.Naturals0), S.Integers)) + # Implemented for m without primitive root + assert solveset(Mod(x**3, 7) - 2, x, S.Integers) is S.EmptySet + assert dumeq(solveset(Mod(x**3, 8) - 1, x, S.Integers), + ImageSet(Lambda(n, 8*n + 1), S.Integers)) + assert dumeq(solveset(Mod(x**4, 9) - 4, x, S.Integers), + Union(ImageSet(Lambda(n, 9*n + 4), S.Integers), + ImageSet(Lambda(n, 9*n + 5), S.Integers))) + # domain intersection + assert dumeq(solveset(3 - Mod(5*x - 8, 7), x, S.Naturals0), + Intersection(ImageSet(Lambda(n, 7*n + 5), S.Integers), S.Naturals0)) + # Complex args + assert solveset(Mod(x, 3) - I, x, S.Integers) == \ + S.EmptySet + assert solveset(Mod(I*x, 3) - 2, x, S.Integers + ).dummy_eq( + ConditionSet(x, Eq(Mod(I*x, 3) - 2, 0), S.Integers)) + assert solveset(Mod(I + x, 3) - 2, x, S.Integers + ).dummy_eq( + ConditionSet(x, Eq(Mod(x + I, 3) - 2, 0), S.Integers)) + + # issue 17373 (https://github.com/sympy/sympy/issues/17373) + assert dumeq(solveset(Mod(x**4, 14) - 11, x, S.Integers), + Union(ImageSet(Lambda(n, 14*n + 3), S.Integers), + ImageSet(Lambda(n, 14*n + 11), S.Integers))) + assert dumeq(solveset(Mod(x**31, 74) - 43, x, S.Integers), + ImageSet(Lambda(n, 74*n + 31), S.Integers)) + + # issue 13178 + n = symbols('n', integer=True) + a = 742938285 + b = 1898888478 + m = 2**31 - 1 + c = 20170816 + assert dumeq(solveset(c - Mod(a**n*b, m), n, S.Integers), + ImageSet(Lambda(n, 2147483646*n + 100), S.Naturals0)) + assert dumeq(solveset(c - Mod(a**n*b, m), n, S.Naturals0), + Intersection(ImageSet(Lambda(n, 2147483646*n + 100), S.Naturals0), + S.Naturals0)) + assert dumeq(solveset(c - Mod(a**(2*n)*b, m), n, S.Integers), + Intersection(ImageSet(Lambda(n, 1073741823*n + 50), S.Naturals0), + S.Integers)) + assert solveset(c - Mod(a**(2*n + 7)*b, m), n, S.Integers) is S.EmptySet + assert dumeq(solveset(c - Mod(a**(n - 4)*b, m), n, S.Integers), + Intersection(ImageSet(Lambda(n, 2147483646*n + 104), S.Naturals0), + S.Integers)) + +# end of modular tests + +def test_issue_17276(): + assert nonlinsolve([Eq(x, 5**(S(1)/5)), Eq(x*y, 25*sqrt(5))], x, y) == \ + FiniteSet((5**(S(1)/5), 25*5**(S(3)/10))) + + +def test_issue_10426(): + x = Dummy('x') + a = Symbol('a') + n = Dummy('n') + assert (solveset(sin(x + a) - sin(x), a)).dummy_eq(Dummy('x')) == (Union( + ImageSet(Lambda(n, 2*n*pi), S.Integers), + Intersection(S.Complexes, ImageSet(Lambda(n, -I*(I*(2*n*pi + arg(-exp(-2*I*x))) + 2*im(x))), + S.Integers)))).dummy_eq(Dummy('x,n')) + + +def test_solveset_conjugate(): + """Test solveset for simple conjugate functions""" + assert solveset(conjugate(x) -3 + I) == FiniteSet(3 + I) + + +def test_issue_18208(): + variables = symbols('x0:16') + symbols('y0:12') + x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15,\ + y0, y1, y2, y3, y4, y5, y6, y7, y8, y9, y10, y11 = variables + + eqs = [x0 + x1 + x2 + x3 - 51, + x0 + x1 + x4 + x5 - 46, + x2 + x3 + x6 + x7 - 39, + x0 + x3 + x4 + x7 - 50, + x1 + x2 + x5 + x6 - 35, + x4 + x5 + x6 + x7 - 34, + x4 + x5 + x8 + x9 - 46, + x10 + x11 + x6 + x7 - 23, + x11 + x4 + x7 + x8 - 25, + x10 + x5 + x6 + x9 - 44, + x10 + x11 + x8 + x9 - 35, + x12 + x13 + x8 + x9 - 35, + x10 + x11 + x14 + x15 - 29, + x11 + x12 + x15 + x8 - 35, + x10 + x13 + x14 + x9 - 29, + x12 + x13 + x14 + x15 - 29, + y0 + y1 + y2 + y3 - 55, + y0 + y1 + y4 + y5 - 53, + y2 + y3 + y6 + y7 - 56, + y0 + y3 + y4 + y7 - 57, + y1 + y2 + y5 + y6 - 52, + y4 + y5 + y6 + y7 - 54, + y4 + y5 + y8 + y9 - 48, + y10 + y11 + y6 + y7 - 60, + y11 + y4 + y7 + y8 - 51, + y10 + y5 + y6 + y9 - 57, + y10 + y11 + y8 + y9 - 54, + x10 - 2, + x11 - 5, + x12 - 1, + x13 - 6, + x14 - 1, + x15 - 21, + y0 - 12, + y1 - 20] + + expected = [38 - x3, x3 - 10, 23 - x3, x3, 12 - x7, x7 + 6, 16 - x7, x7, + 8, 20, 2, 5, 1, 6, 1, 21, 12, 20, -y11 + y9 + 2, y11 - y9 + 21, + -y11 - y7 + y9 + 24, y11 + y7 - y9 - 3, 33 - y7, y7, 27 - y9, y9, + 27 - y11, y11] + + A, b = linear_eq_to_matrix(eqs, variables) + + # solve + solve_expected = {v:eq for v, eq in zip(variables, expected) if v != eq} + + assert solve(eqs, variables) == solve_expected + + # linsolve + linsolve_expected = FiniteSet(Tuple(*expected)) + + assert linsolve(eqs, variables) == linsolve_expected + assert linsolve((A, b), variables) == linsolve_expected + + # gauss_jordan_solve + gj_solve, new_vars = A.gauss_jordan_solve(b) + gj_solve = list(gj_solve) + + gj_expected = linsolve_expected.subs(zip([x3, x7, y7, y9, y11], new_vars)) + + assert FiniteSet(Tuple(*gj_solve)) == gj_expected + + # nonlinsolve + # The solution set of nonlinsolve is currently equivalent to linsolve and is + # also correct. However, we would prefer to use the same symbols as parameters + # for the solution to the underdetermined system in all cases if possible. + # We want a solution that is not just equivalent but also given in the same form. + # This test may be changed should nonlinsolve be modified in this way. + + nonlinsolve_expected = FiniteSet((38 - x3, x3 - 10, 23 - x3, x3, 12 - x7, x7 + 6, + 16 - x7, x7, 8, 20, 2, 5, 1, 6, 1, 21, 12, 20, + -y5 + y7 - 1, y5 - y7 + 24, 21 - y5, y5, 33 - y7, + y7, 27 - y9, y9, -y5 + y7 - y9 + 24, y5 - y7 + y9 + 3)) + + assert nonlinsolve(eqs, variables) == nonlinsolve_expected + + +def test_substitution_with_infeasible_solution(): + a00, a01, a10, a11, l0, l1, l2, l3, m0, m1, m2, m3, m4, m5, m6, m7, c00, c01, c10, c11, p00, p01, p10, p11 = symbols( + 'a00, a01, a10, a11, l0, l1, l2, l3, m0, m1, m2, m3, m4, m5, m6, m7, c00, c01, c10, c11, p00, p01, p10, p11' + ) + solvefor = [p00, p01, p10, p11, c00, c01, c10, c11, m0, m1, m3, l0, l1, l2, l3] + system = [ + -l0 * c00 - l1 * c01 + m0 + c00 + c01, + -l0 * c10 - l1 * c11 + m1, + -l2 * c00 - l3 * c01 + c00 + c01, + -l2 * c10 - l3 * c11 + m3, + -l0 * p00 - l2 * p10 + p00 + p10, + -l1 * p00 - l3 * p10 + p00 + p10, + -l0 * p01 - l2 * p11, + -l1 * p01 - l3 * p11, + -a00 + c00 * p00 + c10 * p01, + -a01 + c01 * p00 + c11 * p01, + -a10 + c00 * p10 + c10 * p11, + -a11 + c01 * p10 + c11 * p11, + -m0 * p00, + -m1 * p01, + -m2 * p10, + -m3 * p11, + -m4 * c00, + -m5 * c01, + -m6 * c10, + -m7 * c11, + m2, + m4, + m5, + m6, + m7 + ] + sol = FiniteSet( + (0, Complement(FiniteSet(p01), FiniteSet(0)), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, l2, l3), + (p00, Complement(FiniteSet(p01), FiniteSet(0)), 0, p11, 0, 0, 0, 0, 0, 0, 0, 1, 1, -p01/p11, -p01/p11), + (0, Complement(FiniteSet(p01), FiniteSet(0)), 0, p11, 0, 0, 0, 0, 0, 0, 0, 1, -l3*p11/p01, -p01/p11, l3), + (0, Complement(FiniteSet(p01), FiniteSet(0)), 0, p11, 0, 0, 0, 0, 0, 0, 0, -l2*p11/p01, -l3*p11/p01, l2, l3), + ) + assert sol != nonlinsolve(system, solvefor) + + +def test_issue_20097(): + assert solveset(1/sqrt(x)) is S.EmptySet + + +def test_issue_15350(): + assert solveset(diff(sqrt(1/x+x))) == FiniteSet(-1, 1) + + +def test_issue_18359(): + c1 = Piecewise((0, x < 0), (Min(1, x)/2 - Min(2, x)/2 + Min(3, x)/2, True)) + c2 = Piecewise((Piecewise((0, x < 0), (Min(1, x)/2 - Min(2, x)/2 + Min(3, x)/2, True)), x >= 0), (0, True)) + correct_result = Interval(1, 2) + result1 = solveset(c1 - Rational(1, 2), x, Interval(0, 3)) + result2 = solveset(c2 - Rational(1, 2), x, Interval(0, 3)) + assert result1 == correct_result + assert result2 == correct_result + + +def test_issue_17604(): + lhs = -2**(3*x/11)*exp(x/11) + pi**(x/11) + assert _is_exponential(lhs, x) + assert _solve_exponential(lhs, 0, x, S.Complexes) == FiniteSet(0) + + +def test_issue_17580(): + assert solveset(1/(1 - x**3)**2, x, S.Reals) is S.EmptySet + + +def test_issue_17566_actual(): + sys = [2**x + 2**y - 3, 4**x + 9**y - 5] + # Not clear this is the correct result, but at least no recursion error + assert nonlinsolve(sys, x, y) == FiniteSet((log(3 - 2**y)/log(2), y)) + + +def test_issue_17565(): + eq = Ge(2*(x - 2)**2/(3*(x + 1)**(Integer(1)/3)) + 2*(x - 2)*(x + 1)**(Integer(2)/3), 0) + res = Union(Interval.Lopen(-1, -Rational(1, 4)), Interval(2, oo)) + assert solveset(eq, x, S.Reals) == res + + +def test_issue_15024(): + function = (x + 5)/sqrt(-x**2 - 10*x) + assert solveset(function, x, S.Reals) == FiniteSet(Integer(-5)) + + +def test_issue_16877(): + assert dumeq(nonlinsolve([x - 1, sin(y)], x, y), + FiniteSet((1, ImageSet(Lambda(n, 2*n*pi), S.Integers)), + (1, ImageSet(Lambda(n, 2*n*pi + pi), S.Integers)))) + # Even better if (1, ImageSet(Lambda(n, n*pi), S.Integers)) is obtained + + +def test_issue_16876(): + assert dumeq(nonlinsolve([sin(x), 2*x - 4*y], x, y), + FiniteSet((ImageSet(Lambda(n, 2*n*pi), S.Integers), + ImageSet(Lambda(n, n*pi), S.Integers)), + (ImageSet(Lambda(n, 2*n*pi + pi), S.Integers), + ImageSet(Lambda(n, n*pi + pi/2), S.Integers)))) + # Even better if (ImageSet(Lambda(n, n*pi), S.Integers), + # ImageSet(Lambda(n, n*pi/2), S.Integers)) is obtained + +def test_issue_21236(): + x, z = symbols("x z") + y = symbols('y', rational=True) + assert solveset(x**y - z, x, S.Reals) == ConditionSet(x, Eq(x**y - z, 0), S.Reals) + e1, e2 = symbols('e1 e2', even=True) + y = e1/e2 # don't know if num or den will be odd and the other even + assert solveset(x**y - z, x, S.Reals) == ConditionSet(x, Eq(x**y - z, 0), S.Reals) + + +def test_issue_21908(): + assert nonlinsolve([(x**2 + 2*x - y**2)*exp(x), -2*y*exp(x)], x, y + ) == {(-2, 0), (0, 0)} + + +def test_issue_19144(): + # test case 1 + expr1 = [x + y - 1, y**2 + 1] + eq1 = [Eq(i, 0) for i in expr1] + soln1 = {(1 - I, I), (1 + I, -I)} + soln_expr1 = nonlinsolve(expr1, [x, y]) + soln_eq1 = nonlinsolve(eq1, [x, y]) + assert soln_eq1 == soln_expr1 == soln1 + # test case 2 - with denoms + expr2 = [x/y - 1, y**2 + 1] + eq2 = [Eq(i, 0) for i in expr2] + soln2 = {(-I, -I), (I, I)} + soln_expr2 = nonlinsolve(expr2, [x, y]) + soln_eq2 = nonlinsolve(eq2, [x, y]) + assert soln_eq2 == soln_expr2 == soln2 + # denominators that cancel in expression + assert nonlinsolve([Eq(x + 1/x, 1/x)], [x]) == FiniteSet((S.EmptySet,)) + + +def test_issue_22413(): + res = nonlinsolve((4*y*(2*x + 2*exp(y) + 1)*exp(2*x), + 4*x*exp(2*x) + 4*y*exp(2*x + y) + 4*exp(2*x + y) + 1), + x, y) + # First solution is not correct, but the issue was an exception + sols = FiniteSet((x, S.Zero), (-exp(y) - S.Half, y)) + assert res == sols + + +def test_issue_23318(): + eqs_eq = [ + Eq(53.5780461486929, x * log(y / (5.0 - y) + 1) / y), + Eq(x, 0.0015 * z), + Eq(0.0015, 7845.32 * y / z), + ] + eqs_expr = [eq.lhs - eq.rhs for eq in eqs_eq] + + sol = {(266.97755814852, 0.0340301680681629, 177985.03876568)} + + assert_close_nl(nonlinsolve(eqs_eq, [x, y, z]), sol) + assert_close_nl(nonlinsolve(eqs_expr, [x, y, z]), sol) + + logterm = log(1.91196789933362e-7*z/(5.0 - 1.91196789933362e-7*z) + 1) + eq = -0.0015*z*logterm + 1.02439504345316e-5*z + assert_close_ss(solveset(eq, z), {0, 177985.038765679}) + + +def test_issue_19814(): + assert nonlinsolve([ 2**m - 2**(2*n), 4*2**m - 2**(4*n)], m, n + ) == FiniteSet((log(2**(2*n))/log(2), S.Complexes)) + + +def test_issue_22058(): + sol = solveset(-sqrt(t)*x**2 + 2*x + sqrt(t), x, S.Reals) + # doesn't fail (and following numerical check) + assert sol.xreplace({t: 1}) == {1 - sqrt(2), 1 + sqrt(2)}, sol.xreplace({t: 1}) + + +def test_issue_11184(): + assert solveset(20*sqrt(y**2 + (sqrt(-(y - 10)*(y + 10)) + 10)**2) - 60, y, S.Reals) is S.EmptySet + + +def test_issue_21890(): + e = S(2)/3 + assert nonlinsolve([4*x**3*y**4 - 2*y, 4*x**4*y**3 - 2*x], x, y) == { + (2**e/(2*y), y), ((-2**e/4 - 2**e*sqrt(3)*I/4)/y, y), + ((-2**e/4 + 2**e*sqrt(3)*I/4)/y, y)} + assert nonlinsolve([(1 - 4*x**2)*exp(-2*x**2 - 2*y**2), + -4*x*y*exp(-2*x**2)*exp(-2*y**2)], x, y) == {(-S(1)/2, 0), (S(1)/2, 0)} + rx, ry = symbols('x y', real=True) + sol = nonlinsolve([4*rx**3*ry**4 - 2*ry, 4*rx**4*ry**3 - 2*rx], rx, ry) + ans = {(2**(S(2)/3)/(2*ry), ry), + ((-2**(S(2)/3)/4 - 2**(S(2)/3)*sqrt(3)*I/4)/ry, ry), + ((-2**(S(2)/3)/4 + 2**(S(2)/3)*sqrt(3)*I/4)/ry, ry)} + assert sol == ans + + +def test_issue_22628(): + assert nonlinsolve([h - 1, k - 1, f - 2, f - 4, -2*k], h, k, f) == S.EmptySet + assert nonlinsolve([x**3 - 1, x + y, x**2 - 4], [x, y]) == S.EmptySet + + +def test_issue_25781(): + assert solve(sqrt(x/2) - x) == [0, S.Half] + + +def test_issue_26077(): + _n = Symbol('_n') + function = x*cot(5*x) + critical_points = stationary_points(function, x, S.Reals) + excluded_points = Union( + ImageSet(Lambda(_n, 2*_n*pi/5), S.Integers), + ImageSet(Lambda(_n, 2*_n*pi/5 + pi/5), S.Integers) + ) + solution = ConditionSet(x, + Eq(x*(-5*cot(5*x)**2 - 5) + cot(5*x), 0), + Complement(S.Reals, excluded_points) + ) + assert solution.as_dummy() == critical_points.as_dummy()