Initial import: grid-bot — grid trading bot for BTC-USDT on Cifra Markets

This commit is contained in:
Kolp
2026-09-24 13:22:23 +07:00
commit 642cc11a9f
18968 changed files with 5683248 additions and 0 deletions
@@ -0,0 +1,545 @@
"""
SymPy is a Python library for symbolic mathematics. It aims to become a
full-featured computer algebra system (CAS) while keeping the code as simple
as possible in order to be comprehensible and easily extensible. SymPy is
written entirely in Python. It depends on mpmath, and other external libraries
may be optionally for things like plotting support.
See the webpage for more information and documentation:
https://sympy.org
"""
# Keep this in sync with setup.py/pyproject.toml
import sys
if sys.version_info < (3, 9):
raise ImportError("Python version 3.9 or above is required for SymPy.")
del sys
try:
import mpmath
except ImportError:
raise ImportError("SymPy now depends on mpmath as an external library. "
"See https://docs.sympy.org/latest/install.html#mpmath for more information.")
del mpmath
from sympy.release import __version__
from sympy.core.cache import lazy_function
if 'dev' in __version__:
def enable_warnings():
import warnings
warnings.filterwarnings('default', '.*', DeprecationWarning, module='sympy.*')
del warnings
enable_warnings()
del enable_warnings
def __sympy_debug():
# helper function so we don't import os globally
import os
debug_str = os.getenv('SYMPY_DEBUG', 'False')
if debug_str in ('True', 'False'):
return eval(debug_str)
else:
raise RuntimeError("unrecognized value for SYMPY_DEBUG: %s" %
debug_str)
# Fails py2 test if using type hinting
SYMPY_DEBUG = __sympy_debug() # type: bool
from .core import (sympify, SympifyError, cacheit, Basic, Atom,
preorder_traversal, S, Expr, AtomicExpr, UnevaluatedExpr, Symbol,
Wild, Dummy, symbols, var, Number, Float, Rational, Integer,
NumberSymbol, RealNumber, igcd, ilcm, seterr, E, I, nan, oo, pi, zoo,
AlgebraicNumber, comp, mod_inverse, Pow, integer_nthroot, integer_log,
trailing, Mul, prod, Add, Mod, Rel, Eq, Ne, Lt, Le, Gt, Ge, Equality,
GreaterThan, LessThan, Unequality, StrictGreaterThan, StrictLessThan,
vectorize, Lambda, WildFunction, Derivative, diff, FunctionClass,
Function, Subs, expand, PoleError, count_ops, expand_mul, expand_log,
expand_func, expand_trig, expand_complex, expand_multinomial, nfloat,
expand_power_base, expand_power_exp, arity, PrecisionExhausted, N,
evalf, Tuple, Dict, gcd_terms, factor_terms, factor_nc, evaluate,
Catalan, EulerGamma, GoldenRatio, TribonacciConstant, bottom_up, use,
postorder_traversal, default_sort_key, ordered, num_digits)
from .logic import (to_cnf, to_dnf, to_nnf, And, Or, Not, Xor, Nand, Nor,
Implies, Equivalent, ITE, POSform, SOPform, simplify_logic, bool_map,
true, false, satisfiable)
from .assumptions import (AppliedPredicate, Predicate, AssumptionsContext,
assuming, Q, ask, register_handler, remove_handler, refine)
from .polys import (Poly, PurePoly, poly_from_expr, parallel_poly_from_expr,
degree, total_degree, degree_list, LC, LM, LT, pdiv, prem, pquo,
pexquo, div, rem, quo, exquo, half_gcdex, gcdex, invert,
subresultants, resultant, discriminant, cofactors, gcd_list, gcd,
lcm_list, lcm, terms_gcd, trunc, monic, content, primitive, compose,
decompose, sturm, gff_list, gff, sqf_norm, sqf_part, sqf_list, sqf,
factor_list, factor, intervals, refine_root, count_roots, all_roots,
real_roots, nroots, ground_roots, nth_power_roots_poly, cancel,
reduced, groebner, is_zero_dimensional, GroebnerBasis, poly,
symmetrize, horner, interpolate, rational_interpolate, viete, together,
BasePolynomialError, ExactQuotientFailed, PolynomialDivisionFailed,
OperationNotSupported, HeuristicGCDFailed, HomomorphismFailed,
IsomorphismFailed, ExtraneousFactors, EvaluationFailed,
RefinementFailed, CoercionFailed, NotInvertible, NotReversible,
NotAlgebraic, DomainError, PolynomialError, UnificationFailed,
GeneratorsError, GeneratorsNeeded, ComputationFailed,
UnivariatePolynomialError, MultivariatePolynomialError,
PolificationFailed, OptionError, FlagError, minpoly,
minimal_polynomial, primitive_element, field_isomorphism,
to_number_field, isolate, round_two, prime_decomp, prime_valuation,
galois_group, itermonomials, Monomial, lex, grlex,
grevlex, ilex, igrlex, igrevlex, CRootOf, rootof, RootOf,
ComplexRootOf, RootSum, roots, Domain, FiniteField, IntegerRing,
RationalField, RealField, ComplexField, PythonFiniteField,
GMPYFiniteField, PythonIntegerRing, GMPYIntegerRing, PythonRational,
GMPYRationalField, AlgebraicField, PolynomialRing, FractionField,
ExpressionDomain, FF_python, FF_gmpy, ZZ_python, ZZ_gmpy, QQ_python,
QQ_gmpy, GF, FF, ZZ, QQ, ZZ_I, QQ_I, RR, CC, EX, EXRAW,
construct_domain, swinnerton_dyer_poly, cyclotomic_poly,
symmetric_poly, random_poly, interpolating_poly, jacobi_poly,
chebyshevt_poly, chebyshevu_poly, hermite_poly, hermite_prob_poly,
legendre_poly, laguerre_poly, apart, apart_list, assemble_partfrac_list,
Options, ring, xring, vring, sring, field, xfield, vfield, sfield)
from .series import (Order, O, limit, Limit, gruntz, series, approximants,
residue, EmptySequence, SeqPer, SeqFormula, sequence, SeqAdd, SeqMul,
fourier_series, fps, difference_delta, limit_seq)
from .functions import (factorial, factorial2, rf, ff, binomial,
RisingFactorial, FallingFactorial, subfactorial, carmichael,
fibonacci, lucas, motzkin, tribonacci, harmonic, bernoulli, bell, euler,
catalan, genocchi, andre, partition, divisor_sigma, legendre_symbol,
jacobi_symbol, kronecker_symbol, mobius, primenu, primeomega,
totient, reduced_totient, primepi, sqrt, root, Min, Max, Id,
real_root, Rem, cbrt, re, im, sign, Abs, conjugate, arg, polar_lift,
periodic_argument, unbranched_argument, principal_branch, transpose,
adjoint, polarify, unpolarify, sin, cos, tan, sec, csc, cot, sinc,
asin, acos, atan, asec, acsc, acot, atan2, exp_polar, exp, ln, log,
LambertW, sinh, cosh, tanh, coth, sech, csch, asinh, acosh, atanh,
acoth, asech, acsch, floor, ceiling, frac, Piecewise, piecewise_fold,
piecewise_exclusive, erf, erfc, erfi, erf2, erfinv, erfcinv, erf2inv,
Ei, expint, E1, li, Li, Si, Ci, Shi, Chi, fresnels, fresnelc, gamma,
lowergamma, uppergamma, polygamma, loggamma, digamma, trigamma,
multigamma, dirichlet_eta, zeta, lerchphi, polylog, stieltjes, Eijk,
LeviCivita, KroneckerDelta, SingularityFunction, DiracDelta, Heaviside,
bspline_basis, bspline_basis_set, interpolating_spline, besselj,
bessely, besseli, besselk, hankel1, hankel2, jn, yn, jn_zeros, hn1,
hn2, airyai, airybi, airyaiprime, airybiprime, marcumq, hyper,
meijerg, appellf1, legendre, assoc_legendre, hermite, hermite_prob,
chebyshevt, chebyshevu, chebyshevu_root, chebyshevt_root, laguerre,
assoc_laguerre, gegenbauer, jacobi, jacobi_normalized, Ynm, Ynm_c,
Znm, elliptic_k, elliptic_f, elliptic_e, elliptic_pi, beta, mathieus,
mathieuc, mathieusprime, mathieucprime, riemann_xi, betainc, betainc_regularized)
from .ntheory import (nextprime, prevprime, prime, primerange,
randprime, Sieve, sieve, primorial, cycle_length, composite,
compositepi, isprime, divisors, proper_divisors, factorint,
multiplicity, perfect_power, factor_cache, pollard_pm1, pollard_rho, primefactors,
divisor_count, proper_divisor_count,
factorrat,
mersenne_prime_exponent, is_perfect, is_mersenne_prime, is_abundant,
is_deficient, is_amicable, is_carmichael, abundance, npartitions, is_primitive_root,
is_quad_residue, n_order, sqrt_mod,
quadratic_residues, primitive_root, nthroot_mod, is_nthpow_residue,
sqrt_mod_iter, discrete_log, quadratic_congruence,
binomial_coefficients, binomial_coefficients_list,
multinomial_coefficients, continued_fraction_periodic,
continued_fraction_iterator, continued_fraction_reduce,
continued_fraction_convergents, continued_fraction, egyptian_fraction)
from .concrete import product, Product, summation, Sum
from .discrete import (fft, ifft, ntt, intt, fwht, ifwht, mobius_transform,
inverse_mobius_transform, convolution, covering_product,
intersecting_product)
from .simplify import (simplify, hypersimp, hypersimilar, logcombine,
separatevars, posify, besselsimp, kroneckersimp, signsimp,
nsimplify, FU, fu, sqrtdenest, cse, epath, EPath, hyperexpand,
collect, rcollect, radsimp, collect_const, fraction, numer, denom,
trigsimp, exptrigsimp, powsimp, powdenest, combsimp, gammasimp,
ratsimp, ratsimpmodprime)
from .sets import (Set, Interval, Union, EmptySet, FiniteSet, ProductSet,
Intersection, DisjointUnion, imageset, Complement, SymmetricDifference, ImageSet,
Range, ComplexRegion, Complexes, Reals, Contains, ConditionSet, Ordinal,
OmegaPower, ord0, PowerSet, Naturals, Naturals0, UniversalSet,
Integers, Rationals)
from .solvers import (solve, solve_linear_system, solve_linear_system_LU,
solve_undetermined_coeffs, nsolve, solve_linear, checksol, det_quick,
inv_quick, check_assumptions, failing_assumptions, diophantine,
rsolve, rsolve_poly, rsolve_ratio, rsolve_hyper, checkodesol,
classify_ode, dsolve, homogeneous_order, solve_poly_system, factor_system,
solve_triangulated, pde_separate, pde_separate_add, pde_separate_mul,
pdsolve, classify_pde, checkpdesol, ode_order, reduce_inequalities,
reduce_abs_inequality, reduce_abs_inequalities, solve_poly_inequality,
solve_rational_inequalities, solve_univariate_inequality, decompogen,
solveset, linsolve, linear_eq_to_matrix, nonlinsolve, substitution)
from .matrices import (ShapeError, NonSquareMatrixError, GramSchmidt,
casoratian, diag, eye, hessian, jordan_cell, list2numpy, matrix2numpy,
matrix_multiply_elementwise, ones, randMatrix, rot_axis1, rot_axis2,
rot_axis3, symarray, wronskian, zeros, MutableDenseMatrix,
DeferredVector, MatrixBase, Matrix, MutableMatrix,
MutableSparseMatrix, banded, ImmutableDenseMatrix,
ImmutableSparseMatrix, ImmutableMatrix, SparseMatrix, MatrixSlice,
BlockDiagMatrix, BlockMatrix, FunctionMatrix, Identity, Inverse,
MatAdd, MatMul, MatPow, MatrixExpr, MatrixSymbol, Trace, Transpose,
ZeroMatrix, OneMatrix, blockcut, block_collapse, matrix_symbols,
Adjoint, hadamard_product, HadamardProduct, HadamardPower,
Determinant, det, diagonalize_vector, DiagMatrix, DiagonalMatrix,
DiagonalOf, trace, DotProduct, kronecker_product, KroneckerProduct,
PermutationMatrix, MatrixPermute, Permanent, per, rot_ccw_axis1,
rot_ccw_axis2, rot_ccw_axis3, rot_givens)
from .geometry import (Point, Point2D, Point3D, Line, Ray, Segment, Line2D,
Segment2D, Ray2D, Line3D, Segment3D, Ray3D, Plane, Ellipse, Circle,
Polygon, RegularPolygon, Triangle, rad, deg, are_similar, centroid,
convex_hull, idiff, intersection, closest_points, farthest_points,
GeometryError, Curve, Parabola)
from .utilities import (flatten, group, take, subsets, variations,
numbered_symbols, cartes, capture, dict_merge, prefixes, postfixes,
sift, topological_sort, unflatten, has_dups, has_variety, reshape,
rotations, filldedent, lambdify,
threaded, xthreaded, public, memoize_property, timed)
from .integrals import (integrate, Integral, line_integrate, mellin_transform,
inverse_mellin_transform, MellinTransform, InverseMellinTransform,
laplace_transform, laplace_correspondence, laplace_initial_conds,
inverse_laplace_transform, 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, singularityintegrate)
from .tensor import (IndexedBase, Idx, Indexed, get_contraction_structure,
get_indices, shape, MutableDenseNDimArray, ImmutableDenseNDimArray,
MutableSparseNDimArray, ImmutableSparseNDimArray, NDimArray,
tensorproduct, tensorcontraction, tensordiagonal, derive_by_array,
permutedims, Array, DenseNDimArray, SparseNDimArray)
from .parsing import parse_expr
from .calculus import (euler_equations, singularities, is_increasing,
is_strictly_increasing, is_decreasing, is_strictly_decreasing,
is_monotonic, finite_diff_weights, apply_finite_diff,
differentiate_finite, periodicity, not_empty_in, AccumBounds,
is_convex, stationary_points, minimum, maximum)
from .algebras import Quaternion
from .printing import (pager_print, pretty, pretty_print, pprint,
pprint_use_unicode, pprint_try_use_unicode, latex, print_latex,
multiline_latex, mathml, print_mathml, python, print_python, pycode,
ccode, print_ccode, smtlib_code, glsl_code, print_glsl, cxxcode, fcode,
print_fcode, rcode, print_rcode, jscode, print_jscode, julia_code,
mathematica_code, octave_code, rust_code, print_gtk, preview, srepr,
print_tree, StrPrinter, sstr, sstrrepr, TableForm, dotprint,
maple_code, print_maple_code)
test = lazy_function('sympy.testing.runtests_pytest', 'test')
doctest = lazy_function('sympy.testing.runtests', 'doctest')
# This module causes conflicts with other modules:
# from .stats import *
# Adds about .04-.05 seconds of import time
# from combinatorics import *
# This module is slow to import:
#from physics import units
from .plotting import plot, textplot, plot_backends, plot_implicit, plot_parametric
from .interactive import init_session, init_printing, interactive_traversal
evalf._create_evalf_table()
__all__ = [
'__version__',
# sympy.core
'sympify', 'SympifyError', 'cacheit', 'Basic', 'Atom',
'preorder_traversal', 'S', 'Expr', 'AtomicExpr', 'UnevaluatedExpr',
'Symbol', 'Wild', 'Dummy', 'symbols', 'var', 'Number', 'Float',
'Rational', 'Integer', 'NumberSymbol', 'RealNumber', 'igcd', 'ilcm',
'seterr', 'E', 'I', 'nan', 'oo', 'pi', 'zoo', 'AlgebraicNumber', 'comp',
'mod_inverse', 'Pow', 'integer_nthroot', 'integer_log', 'trailing', 'Mul', 'prod',
'Add', 'Mod', 'Rel', 'Eq', 'Ne', 'Lt', 'Le', 'Gt', 'Ge', 'Equality',
'GreaterThan', 'LessThan', 'Unequality', 'StrictGreaterThan',
'StrictLessThan', 'vectorize', 'Lambda', 'WildFunction', 'Derivative',
'diff', 'FunctionClass', 'Function', 'Subs', 'expand', 'PoleError',
'count_ops', 'expand_mul', 'expand_log', 'expand_func', 'expand_trig',
'expand_complex', 'expand_multinomial', 'nfloat', 'expand_power_base',
'expand_power_exp', 'arity', 'PrecisionExhausted', 'N', 'evalf', 'Tuple',
'Dict', 'gcd_terms', 'factor_terms', 'factor_nc', 'evaluate', 'Catalan',
'EulerGamma', 'GoldenRatio', 'TribonacciConstant', 'bottom_up', 'use',
'postorder_traversal', 'default_sort_key', 'ordered', 'num_digits',
# sympy.logic
'to_cnf', 'to_dnf', 'to_nnf', 'And', 'Or', 'Not', 'Xor', 'Nand', 'Nor',
'Implies', 'Equivalent', 'ITE', 'POSform', 'SOPform', 'simplify_logic',
'bool_map', 'true', 'false', 'satisfiable',
# sympy.assumptions
'AppliedPredicate', 'Predicate', 'AssumptionsContext', 'assuming', 'Q',
'ask', 'register_handler', 'remove_handler', 'refine',
# sympy.polys
'Poly', 'PurePoly', 'poly_from_expr', 'parallel_poly_from_expr', 'degree',
'total_degree', 'degree_list', 'LC', 'LM', 'LT', 'pdiv', 'prem', 'pquo',
'pexquo', 'div', 'rem', 'quo', 'exquo', 'half_gcdex', 'gcdex', 'invert',
'subresultants', 'resultant', 'discriminant', 'cofactors', 'gcd_list',
'gcd', 'lcm_list', 'lcm', 'terms_gcd', 'trunc', 'monic', 'content',
'primitive', 'compose', 'decompose', 'sturm', 'gff_list', 'gff',
'sqf_norm', 'sqf_part', 'sqf_list', 'sqf', 'factor_list', 'factor',
'intervals', 'refine_root', 'count_roots', 'all_roots', 'real_roots',
'nroots', 'ground_roots', 'nth_power_roots_poly', 'cancel', 'reduced',
'groebner', 'is_zero_dimensional', 'GroebnerBasis', 'poly', 'symmetrize',
'horner', 'interpolate', 'rational_interpolate', 'viete', 'together',
'BasePolynomialError', 'ExactQuotientFailed', 'PolynomialDivisionFailed',
'OperationNotSupported', 'HeuristicGCDFailed', 'HomomorphismFailed',
'IsomorphismFailed', 'ExtraneousFactors', 'EvaluationFailed',
'RefinementFailed', 'CoercionFailed', 'NotInvertible', 'NotReversible',
'NotAlgebraic', 'DomainError', 'PolynomialError', 'UnificationFailed',
'GeneratorsError', 'GeneratorsNeeded', 'ComputationFailed',
'UnivariatePolynomialError', 'MultivariatePolynomialError',
'PolificationFailed', 'OptionError', 'FlagError', 'minpoly',
'minimal_polynomial', 'primitive_element', 'field_isomorphism',
'to_number_field', 'isolate', 'round_two', 'prime_decomp',
'prime_valuation', 'galois_group', 'itermonomials', 'Monomial', 'lex', 'grlex',
'grevlex', 'ilex', 'igrlex', 'igrevlex', 'CRootOf', 'rootof', 'RootOf',
'ComplexRootOf', 'RootSum', 'roots', 'Domain', 'FiniteField',
'IntegerRing', 'RationalField', 'RealField', 'ComplexField',
'PythonFiniteField', 'GMPYFiniteField', 'PythonIntegerRing',
'GMPYIntegerRing', 'PythonRational', 'GMPYRationalField',
'AlgebraicField', 'PolynomialRing', 'FractionField', 'ExpressionDomain',
'FF_python', 'FF_gmpy', 'ZZ_python', 'ZZ_gmpy', 'QQ_python', 'QQ_gmpy',
'GF', 'FF', 'ZZ', 'QQ', 'ZZ_I', 'QQ_I', 'RR', 'CC', 'EX', 'EXRAW',
'construct_domain', 'swinnerton_dyer_poly', 'cyclotomic_poly',
'symmetric_poly', 'random_poly', 'interpolating_poly', 'jacobi_poly',
'chebyshevt_poly', 'chebyshevu_poly', 'hermite_poly', 'hermite_prob_poly',
'legendre_poly', 'laguerre_poly', 'apart', 'apart_list', 'assemble_partfrac_list',
'Options', 'ring', 'xring', 'vring', 'sring', 'field', 'xfield', 'vfield',
'sfield',
# sympy.series
'Order', 'O', 'limit', 'Limit', 'gruntz', 'series', 'approximants',
'residue', 'EmptySequence', 'SeqPer', 'SeqFormula', 'sequence', 'SeqAdd',
'SeqMul', 'fourier_series', 'fps', 'difference_delta', 'limit_seq',
# sympy.functions
'factorial', 'factorial2', 'rf', 'ff', 'binomial', 'RisingFactorial',
'FallingFactorial', 'subfactorial', 'carmichael', 'fibonacci', 'lucas',
'motzkin', 'tribonacci', 'harmonic', 'bernoulli', 'bell', 'euler', 'catalan',
'genocchi', 'andre', 'partition', 'divisor_sigma', 'legendre_symbol', 'jacobi_symbol',
'kronecker_symbol', 'mobius', 'primenu', 'primeomega', 'totient', 'primepi',
'reduced_totient', 'sqrt', 'root', 'Min', 'Max', 'Id', 'real_root',
'Rem', 'cbrt', 're', 'im', 'sign', 'Abs', 'conjugate', 'arg', 'polar_lift',
'periodic_argument', 'unbranched_argument', 'principal_branch',
'transpose', 'adjoint', 'polarify', 'unpolarify', 'sin', 'cos', 'tan',
'sec', 'csc', 'cot', 'sinc', 'asin', 'acos', 'atan', 'asec', 'acsc',
'acot', 'atan2', 'exp_polar', 'exp', 'ln', 'log', 'LambertW', 'sinh',
'cosh', 'tanh', 'coth', 'sech', 'csch', 'asinh', 'acosh', 'atanh',
'acoth', 'asech', 'acsch', 'floor', 'ceiling', 'frac', 'Piecewise',
'piecewise_fold', 'piecewise_exclusive', 'erf', 'erfc', 'erfi', 'erf2',
'erfinv', 'erfcinv', 'erf2inv', 'Ei', 'expint', 'E1', 'li', 'Li', 'Si',
'Ci', 'Shi', 'Chi', 'fresnels', 'fresnelc', 'gamma', 'lowergamma',
'uppergamma', 'polygamma', 'loggamma', 'digamma', 'trigamma', 'multigamma',
'dirichlet_eta', 'zeta', 'lerchphi', 'polylog', 'stieltjes', 'Eijk', 'LeviCivita',
'KroneckerDelta', 'SingularityFunction', 'DiracDelta', 'Heaviside',
'bspline_basis', 'bspline_basis_set', 'interpolating_spline', 'besselj',
'bessely', 'besseli', 'besselk', 'hankel1', 'hankel2', 'jn', 'yn',
'jn_zeros', 'hn1', 'hn2', 'airyai', 'airybi', 'airyaiprime',
'airybiprime', 'marcumq', 'hyper', 'meijerg', 'appellf1', 'legendre',
'assoc_legendre', 'hermite', 'hermite_prob', 'chebyshevt', 'chebyshevu',
'chebyshevu_root', 'chebyshevt_root', 'laguerre', 'assoc_laguerre',
'gegenbauer', 'jacobi', 'jacobi_normalized', 'Ynm', 'Ynm_c', 'Znm',
'elliptic_k', 'elliptic_f', 'elliptic_e', 'elliptic_pi', 'beta',
'mathieus', 'mathieuc', 'mathieusprime', 'mathieucprime', 'riemann_xi','betainc',
'betainc_regularized',
# sympy.ntheory
'nextprime', 'prevprime', 'prime', 'primerange', 'randprime',
'Sieve', 'sieve', 'primorial', 'cycle_length', 'composite', 'compositepi',
'isprime', 'divisors', 'proper_divisors', 'factorint', 'multiplicity',
'perfect_power', 'pollard_pm1', 'factor_cache', 'pollard_rho', 'primefactors',
'divisor_count', 'proper_divisor_count',
'factorrat',
'mersenne_prime_exponent', 'is_perfect', 'is_mersenne_prime',
'is_abundant', 'is_deficient', 'is_amicable', 'is_carmichael', 'abundance',
'npartitions',
'is_primitive_root', 'is_quad_residue',
'n_order', 'sqrt_mod', 'quadratic_residues',
'primitive_root', 'nthroot_mod', 'is_nthpow_residue', 'sqrt_mod_iter',
'discrete_log', 'quadratic_congruence', 'binomial_coefficients',
'binomial_coefficients_list', 'multinomial_coefficients',
'continued_fraction_periodic', 'continued_fraction_iterator',
'continued_fraction_reduce', 'continued_fraction_convergents',
'continued_fraction', 'egyptian_fraction',
# sympy.concrete
'product', 'Product', 'summation', 'Sum',
# sympy.discrete
'fft', 'ifft', 'ntt', 'intt', 'fwht', 'ifwht', 'mobius_transform',
'inverse_mobius_transform', 'convolution', 'covering_product',
'intersecting_product',
# sympy.simplify
'simplify', 'hypersimp', 'hypersimilar', 'logcombine', 'separatevars',
'posify', 'besselsimp', 'kroneckersimp', 'signsimp',
'nsimplify', 'FU', 'fu', 'sqrtdenest', 'cse', 'epath', 'EPath',
'hyperexpand', 'collect', 'rcollect', 'radsimp', 'collect_const',
'fraction', 'numer', 'denom', 'trigsimp', 'exptrigsimp', 'powsimp',
'powdenest', 'combsimp', 'gammasimp', 'ratsimp', 'ratsimpmodprime',
# sympy.sets
'Set', 'Interval', 'Union', 'EmptySet', 'FiniteSet', 'ProductSet',
'Intersection', 'imageset', 'DisjointUnion', 'Complement', 'SymmetricDifference',
'ImageSet', 'Range', 'ComplexRegion', 'Reals', 'Contains', 'ConditionSet',
'Ordinal', 'OmegaPower', 'ord0', 'PowerSet', 'Naturals',
'Naturals0', 'UniversalSet', 'Integers', 'Rationals', 'Complexes',
# sympy.solvers
'solve', 'solve_linear_system', 'solve_linear_system_LU',
'solve_undetermined_coeffs', 'nsolve', 'solve_linear', 'checksol',
'det_quick', 'inv_quick', 'check_assumptions', 'failing_assumptions',
'diophantine', 'rsolve', 'rsolve_poly', 'rsolve_ratio', 'rsolve_hyper',
'checkodesol', 'classify_ode', 'dsolve', 'homogeneous_order',
'solve_poly_system', 'factor_system', 'solve_triangulated', 'pde_separate',
'pde_separate_add', 'pde_separate_mul', 'pdsolve', 'classify_pde',
'checkpdesol', 'ode_order', 'reduce_inequalities',
'reduce_abs_inequality', 'reduce_abs_inequalities',
'solve_poly_inequality', 'solve_rational_inequalities',
'solve_univariate_inequality', 'decompogen', 'solveset', 'linsolve',
'linear_eq_to_matrix', 'nonlinsolve', 'substitution',
# sympy.matrices
'ShapeError', 'NonSquareMatrixError', 'GramSchmidt', 'casoratian', 'diag',
'eye', 'hessian', 'jordan_cell', 'list2numpy', 'matrix2numpy',
'matrix_multiply_elementwise', 'ones', 'randMatrix', 'rot_axis1',
'rot_axis2', 'rot_axis3', 'symarray', 'wronskian', 'zeros',
'MutableDenseMatrix', 'DeferredVector', 'MatrixBase', 'Matrix',
'MutableMatrix', 'MutableSparseMatrix', 'banded', 'ImmutableDenseMatrix',
'ImmutableSparseMatrix', 'ImmutableMatrix', 'SparseMatrix', 'MatrixSlice',
'BlockDiagMatrix', 'BlockMatrix', 'FunctionMatrix', 'Identity', 'Inverse',
'MatAdd', 'MatMul', 'MatPow', 'MatrixExpr', 'MatrixSymbol', 'Trace',
'Transpose', 'ZeroMatrix', 'OneMatrix', 'blockcut', 'block_collapse',
'matrix_symbols', 'Adjoint', 'hadamard_product', 'HadamardProduct',
'HadamardPower', 'Determinant', 'det', 'diagonalize_vector', 'DiagMatrix',
'DiagonalMatrix', 'DiagonalOf', 'trace', 'DotProduct',
'kronecker_product', 'KroneckerProduct', 'PermutationMatrix',
'MatrixPermute', 'Permanent', 'per', 'rot_ccw_axis1', 'rot_ccw_axis2',
'rot_ccw_axis3', 'rot_givens',
# sympy.geometry
'Point', 'Point2D', 'Point3D', 'Line', 'Ray', 'Segment', 'Line2D',
'Segment2D', 'Ray2D', 'Line3D', 'Segment3D', 'Ray3D', 'Plane', 'Ellipse',
'Circle', 'Polygon', 'RegularPolygon', 'Triangle', 'rad', 'deg',
'are_similar', 'centroid', 'convex_hull', 'idiff', 'intersection',
'closest_points', 'farthest_points', 'GeometryError', 'Curve', 'Parabola',
# sympy.utilities
'flatten', 'group', 'take', 'subsets', 'variations', 'numbered_symbols',
'cartes', 'capture', 'dict_merge', 'prefixes', 'postfixes', 'sift',
'topological_sort', 'unflatten', 'has_dups', 'has_variety', 'reshape',
'rotations', 'filldedent', 'lambdify', 'threaded', 'xthreaded',
'public', 'memoize_property', 'timed',
# sympy.integrals
'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',
# sympy.tensor
'IndexedBase', 'Idx', 'Indexed', 'get_contraction_structure',
'get_indices', 'shape', 'MutableDenseNDimArray', 'ImmutableDenseNDimArray',
'MutableSparseNDimArray', 'ImmutableSparseNDimArray', 'NDimArray',
'tensorproduct', 'tensorcontraction', 'tensordiagonal', 'derive_by_array',
'permutedims', 'Array', 'DenseNDimArray', 'SparseNDimArray',
# sympy.parsing
'parse_expr',
# sympy.calculus
'euler_equations', 'singularities', 'is_increasing',
'is_strictly_increasing', 'is_decreasing', 'is_strictly_decreasing',
'is_monotonic', 'finite_diff_weights', 'apply_finite_diff',
'differentiate_finite', 'periodicity', 'not_empty_in',
'AccumBounds', 'is_convex', 'stationary_points', 'minimum', 'maximum',
# sympy.algebras
'Quaternion',
# sympy.printing
'pager_print', 'pretty', 'pretty_print', 'pprint', 'pprint_use_unicode',
'pprint_try_use_unicode', 'latex', 'print_latex', 'multiline_latex',
'mathml', 'print_mathml', 'python', 'print_python', 'pycode', 'ccode',
'print_ccode', 'smtlib_code', 'glsl_code', 'print_glsl', 'cxxcode', 'fcode',
'print_fcode', 'rcode', 'print_rcode', 'jscode', 'print_jscode',
'julia_code', 'mathematica_code', 'octave_code', 'rust_code', 'print_gtk',
'preview', 'srepr', 'print_tree', 'StrPrinter', 'sstr', 'sstrrepr',
'TableForm', 'dotprint', 'maple_code', 'print_maple_code',
# sympy.plotting
'plot', 'textplot', 'plot_backends', 'plot_implicit', 'plot_parametric',
# sympy.interactive
'init_session', 'init_printing', 'interactive_traversal',
# sympy.testing
'test', 'doctest',
]
#===========================================================================#
# #
# XXX: The names below were importable before SymPy 1.6 using #
# #
# from sympy import * #
# #
# This happened implicitly because there was no __all__ defined in this #
# __init__.py file. Not every package is imported. The list matches what #
# would have been imported before. It is possible that these packages will #
# not be imported by a star-import from sympy in future. #
# #
#===========================================================================#
__all__.extend((
'algebras',
'assumptions',
'calculus',
'concrete',
'discrete',
'external',
'functions',
'geometry',
'interactive',
'multipledispatch',
'ntheory',
'parsing',
'plotting',
'polys',
'printing',
'release',
'strategies',
'tensor',
'utilities',
))
@@ -0,0 +1,111 @@
"""
This module exports all latin and greek letters as Symbols, so you can
conveniently do
>>> from sympy.abc import x, y
instead of the slightly more clunky-looking
>>> from sympy import symbols
>>> x, y = symbols('x y')
Caveats
=======
1. As of the time of writing this, the names ``O``, ``S``, ``I``, ``N``,
``E``, and ``Q`` are colliding with names defined in SymPy. If you import them
from both ``sympy.abc`` and ``sympy``, the second import will "win".
This is an issue only for * imports, which should only be used for short-lived
code such as interactive sessions and throwaway scripts that do not survive
until the next SymPy upgrade, where ``sympy`` may contain a different set of
names.
2. This module does not define symbol names on demand, i.e.
``from sympy.abc import foo`` will be reported as an error because
``sympy.abc`` does not contain the name ``foo``. To get a symbol named ``foo``,
you still need to use ``Symbol('foo')`` or ``symbols('foo')``.
You can freely mix usage of ``sympy.abc`` and ``Symbol``/``symbols``, though
sticking with one and only one way to get the symbols does tend to make the code
more readable.
The module also defines some special names to help detect which names clash
with the default SymPy namespace.
``_clash1`` defines all the single letter variables that clash with
SymPy objects; ``_clash2`` defines the multi-letter clashing symbols;
and ``_clash`` is the union of both. These can be passed for ``locals``
during sympification if one desires Symbols rather than the non-Symbol
objects for those names.
Examples
========
>>> from sympy import S
>>> from sympy.abc import _clash1, _clash2, _clash
>>> S("Q & C", locals=_clash1)
C & Q
>>> S('pi(x)', locals=_clash2)
pi(x)
>>> S('pi(C, Q)', locals=_clash)
pi(C, Q)
"""
from __future__ import annotations
from typing import Any
import string
from .core import Symbol, symbols
from .core.alphabets import greeks
from sympy.parsing.sympy_parser import null
##### Symbol definitions #####
# Implementation note: The easiest way to avoid typos in the symbols()
# parameter is to copy it from the left-hand side of the assignment.
a, b, c, d, e, f, g, h, i, j = symbols('a, b, c, d, e, f, g, h, i, j')
k, l, m, n, o, p, q, r, s, t = symbols('k, l, m, n, o, p, q, r, s, t')
u, v, w, x, y, z = symbols('u, v, w, x, y, z')
A, B, C, D, E, F, G, H, I, J = symbols('A, B, C, D, E, F, G, H, I, J')
K, L, M, N, O, P, Q, R, S, T = symbols('K, L, M, N, O, P, Q, R, S, T')
U, V, W, X, Y, Z = symbols('U, V, W, X, Y, Z')
alpha, beta, gamma, delta = symbols('alpha, beta, gamma, delta')
epsilon, zeta, eta, theta = symbols('epsilon, zeta, eta, theta')
iota, kappa, lamda, mu = symbols('iota, kappa, lamda, mu')
nu, xi, omicron, pi = symbols('nu, xi, omicron, pi')
rho, sigma, tau, upsilon = symbols('rho, sigma, tau, upsilon')
phi, chi, psi, omega = symbols('phi, chi, psi, omega')
##### Clashing-symbols diagnostics #####
# We want to know which names in SymPy collide with those in here.
# This is mostly for diagnosing SymPy's namespace during SymPy development.
_latin = list(string.ascii_letters)
# QOSINE should not be imported as they clash; gamma, pi and zeta clash, too
_greek = list(greeks) # make a copy, so we can mutate it
# Note: We import lamda since lambda is a reserved keyword in Python
_greek.remove("lambda")
_greek.append("lamda")
ns: dict[str, Any] = {}
exec('from sympy import *', ns)
_clash1: dict[str, Any] = {}
_clash2: dict[str, Any] = {}
while ns:
_k, _ = ns.popitem()
if _k in _greek:
_clash2[_k] = null
_greek.remove(_k)
elif _k in _latin:
_clash1[_k] = null
_latin.remove(_k)
_clash = {}
_clash.update(_clash1)
_clash.update(_clash2)
del _latin, _greek, Symbol, _k, null
@@ -0,0 +1,3 @@
from .quaternion import Quaternion
__all__ = ["Quaternion",]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,437 @@
from sympy.testing.pytest import slow
from sympy.core.function import diff
from sympy.core.function import expand
from sympy.core.numbers import (E, I, Rational, pi)
from sympy.core.singleton import S
from sympy.core.symbol import (Symbol, symbols)
from sympy.functions.elementary.complexes import (Abs, conjugate, im, re, sign)
from sympy.functions.elementary.exponential import log
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.elementary.trigonometric import (acos, asin, cos, sin, atan2, atan)
from sympy.integrals.integrals import integrate
from sympy.matrices.dense import Matrix
from sympy.simplify import simplify
from sympy.simplify.trigsimp import trigsimp
from sympy.algebras.quaternion import Quaternion
from sympy.testing.pytest import raises
import math
from itertools import permutations, product
w, x, y, z = symbols('w:z')
phi = symbols('phi')
def test_quaternion_construction():
q = Quaternion(w, x, y, z)
assert q + q == Quaternion(2*w, 2*x, 2*y, 2*z)
q2 = Quaternion.from_axis_angle((sqrt(3)/3, sqrt(3)/3, sqrt(3)/3),
pi*Rational(2, 3))
assert q2 == Quaternion(S.Half, S.Half,
S.Half, S.Half)
M = Matrix([[cos(phi), -sin(phi), 0], [sin(phi), cos(phi), 0], [0, 0, 1]])
q3 = trigsimp(Quaternion.from_rotation_matrix(M))
assert q3 == Quaternion(
sqrt(2)*sqrt(cos(phi) + 1)/2, 0, 0, sqrt(2 - 2*cos(phi))*sign(sin(phi))/2)
nc = Symbol('nc', commutative=False)
raises(ValueError, lambda: Quaternion(w, x, nc, z))
def test_quaternion_construction_norm():
q1 = Quaternion(*symbols('a:d'))
q2 = Quaternion(w, x, y, z)
assert expand((q1*q2).norm()**2 - (q1.norm()**2 * q2.norm()**2)) == 0
q3 = Quaternion(w, x, y, z, norm=1)
assert (q1 * q3).norm() == q1.norm()
def test_issue_25254():
# calculating the inverse cached the norm which caused problems
# when multiplying
p = Quaternion(1, 0, 0, 0)
q = Quaternion.from_axis_angle((1, 1, 1), 3 * math.pi/4)
qi = q.inverse() # this operation cached the norm
test = q * p * qi
assert ((test - p).norm() < 1E-10)
def test_to_and_from_Matrix():
q = Quaternion(w, x, y, z)
q_full = Quaternion.from_Matrix(q.to_Matrix())
q_vect = Quaternion.from_Matrix(q.to_Matrix(True))
assert (q - q_full).is_zero_quaternion()
assert (q.vector_part() - q_vect).is_zero_quaternion()
def test_product_matrices():
q1 = Quaternion(w, x, y, z)
q2 = Quaternion(*(symbols("a:d")))
assert (q1 * q2).to_Matrix() == q1.product_matrix_left * q2.to_Matrix()
assert (q1 * q2).to_Matrix() == q2.product_matrix_right * q1.to_Matrix()
R1 = (q1.product_matrix_left * q1.product_matrix_right.T)[1:, 1:]
R2 = simplify(q1.to_rotation_matrix()*q1.norm()**2)
assert R1 == R2
def test_quaternion_axis_angle():
test_data = [ # axis, angle, expected_quaternion
((1, 0, 0), 0, (1, 0, 0, 0)),
((1, 0, 0), pi/2, (sqrt(2)/2, sqrt(2)/2, 0, 0)),
((0, 1, 0), pi/2, (sqrt(2)/2, 0, sqrt(2)/2, 0)),
((0, 0, 1), pi/2, (sqrt(2)/2, 0, 0, sqrt(2)/2)),
((1, 0, 0), pi, (0, 1, 0, 0)),
((0, 1, 0), pi, (0, 0, 1, 0)),
((0, 0, 1), pi, (0, 0, 0, 1)),
((1, 1, 1), pi, (0, 1/sqrt(3),1/sqrt(3),1/sqrt(3))),
((sqrt(3)/3, sqrt(3)/3, sqrt(3)/3), pi*2/3, (S.Half, S.Half, S.Half, S.Half))
]
for axis, angle, expected in test_data:
assert Quaternion.from_axis_angle(axis, angle) == Quaternion(*expected)
def test_quaternion_axis_angle_simplification():
result = Quaternion.from_axis_angle((1, 2, 3), asin(4))
assert result.a == cos(asin(4)/2)
assert result.b == sqrt(14)*sin(asin(4)/2)/14
assert result.c == sqrt(14)*sin(asin(4)/2)/7
assert result.d == 3*sqrt(14)*sin(asin(4)/2)/14
def test_quaternion_complex_real_addition():
a = symbols("a", complex=True)
b = symbols("b", real=True)
# This symbol is not complex:
c = symbols("c", commutative=False)
q = Quaternion(w, x, y, z)
assert a + q == Quaternion(w + re(a), x + im(a), y, z)
assert 1 + q == Quaternion(1 + w, x, y, z)
assert I + q == Quaternion(w, 1 + x, y, z)
assert b + q == Quaternion(w + b, x, y, z)
raises(ValueError, lambda: c + q)
raises(ValueError, lambda: q * c)
raises(ValueError, lambda: c * q)
assert -q == Quaternion(-w, -x, -y, -z)
q1 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
q2 = Quaternion(1, 4, 7, 8)
assert q1 + (2 + 3*I) == Quaternion(5 + 7*I, 2 + 5*I, 0, 7 + 8*I)
assert q2 + (2 + 3*I) == Quaternion(3, 7, 7, 8)
assert q1 * (2 + 3*I) == \
Quaternion((2 + 3*I)*(3 + 4*I), (2 + 3*I)*(2 + 5*I), 0, (2 + 3*I)*(7 + 8*I))
assert q2 * (2 + 3*I) == Quaternion(-10, 11, 38, -5)
q1 = Quaternion(1, 2, 3, 4)
q0 = Quaternion(0, 0, 0, 0)
assert q1 + q0 == q1
assert q1 - q0 == q1
assert q1 - q1 == q0
def test_quaternion_subs():
q = Quaternion.from_axis_angle((0, 0, 1), phi)
assert q.subs(phi, 0) == Quaternion(1, 0, 0, 0)
def test_quaternion_evalf():
assert (Quaternion(sqrt(2), 0, 0, sqrt(3)).evalf() ==
Quaternion(sqrt(2).evalf(), 0, 0, sqrt(3).evalf()))
assert (Quaternion(1/sqrt(2), 0, 0, 1/sqrt(2)).evalf() ==
Quaternion((1/sqrt(2)).evalf(), 0, 0, (1/sqrt(2)).evalf()))
def test_quaternion_functions():
q = Quaternion(w, x, y, z)
q1 = Quaternion(1, 2, 3, 4)
q0 = Quaternion(0, 0, 0, 0)
assert conjugate(q) == Quaternion(w, -x, -y, -z)
assert q.norm() == sqrt(w**2 + x**2 + y**2 + z**2)
assert q.normalize() == Quaternion(w, x, y, z) / sqrt(w**2 + x**2 + y**2 + z**2)
assert q.inverse() == Quaternion(w, -x, -y, -z) / (w**2 + x**2 + y**2 + z**2)
assert q.inverse() == q.pow(-1)
raises(ValueError, lambda: q0.inverse())
assert q.pow(2) == Quaternion(w**2 - x**2 - y**2 - z**2, 2*w*x, 2*w*y, 2*w*z)
assert q**(2) == Quaternion(w**2 - x**2 - y**2 - z**2, 2*w*x, 2*w*y, 2*w*z)
assert q1.pow(-2) == Quaternion(
Rational(-7, 225), Rational(-1, 225), Rational(-1, 150), Rational(-2, 225))
assert q1**(-2) == Quaternion(
Rational(-7, 225), Rational(-1, 225), Rational(-1, 150), Rational(-2, 225))
assert q1.pow(-0.5) == NotImplemented
raises(TypeError, lambda: q1**(-0.5))
assert q1.exp() == \
Quaternion(E * cos(sqrt(29)),
2 * sqrt(29) * E * sin(sqrt(29)) / 29,
3 * sqrt(29) * E * sin(sqrt(29)) / 29,
4 * sqrt(29) * E * sin(sqrt(29)) / 29)
assert q1.log() == \
Quaternion(log(sqrt(30)),
2 * sqrt(29) * acos(sqrt(30)/30) / 29,
3 * sqrt(29) * acos(sqrt(30)/30) / 29,
4 * sqrt(29) * acos(sqrt(30)/30) / 29)
assert q1.pow_cos_sin(2) == \
Quaternion(30 * cos(2 * acos(sqrt(30)/30)),
60 * sqrt(29) * sin(2 * acos(sqrt(30)/30)) / 29,
90 * sqrt(29) * sin(2 * acos(sqrt(30)/30)) / 29,
120 * sqrt(29) * sin(2 * acos(sqrt(30)/30)) / 29)
assert diff(Quaternion(x, x, x, x), x) == Quaternion(1, 1, 1, 1)
assert integrate(Quaternion(x, x, x, x), x) == \
Quaternion(x**2 / 2, x**2 / 2, x**2 / 2, x**2 / 2)
assert Quaternion(1, x, x**2, x**3).integrate(x) == \
Quaternion(x, x**2/2, x**3/3, x**4/4)
assert Quaternion(sin(x), cos(x), sin(2*x), cos(2*x)).integrate(x) == \
Quaternion(-cos(x), sin(x), -cos(2*x)/2, sin(2*x)/2)
assert Quaternion(x**2, y**2, z**2, x*y*z).integrate(x, y) == \
Quaternion(x**3*y/3, x*y**3/3, x*y*z**2, x**2*y**2*z/4)
assert Quaternion.rotate_point((1, 1, 1), q1) == (S.One / 5, 1, S(7) / 5)
n = Symbol('n')
raises(TypeError, lambda: q1**n)
n = Symbol('n', integer=True)
raises(TypeError, lambda: q1**n)
assert Quaternion(22, 23, 55, 8).scalar_part() == 22
assert Quaternion(w, x, y, z).scalar_part() == w
assert Quaternion(22, 23, 55, 8).vector_part() == Quaternion(0, 23, 55, 8)
assert Quaternion(w, x, y, z).vector_part() == Quaternion(0, x, y, z)
assert q1.axis() == Quaternion(0, 2*sqrt(29)/29, 3*sqrt(29)/29, 4*sqrt(29)/29)
assert q1.axis().pow(2) == Quaternion(-1, 0, 0, 0)
assert q0.axis().scalar_part() == 0
assert (q.axis() == Quaternion(0,
x/sqrt(x**2 + y**2 + z**2),
y/sqrt(x**2 + y**2 + z**2),
z/sqrt(x**2 + y**2 + z**2)))
assert q0.is_pure() is True
assert q1.is_pure() is False
assert Quaternion(0, 0, 0, 3).is_pure() is True
assert Quaternion(0, 2, 10, 3).is_pure() is True
assert Quaternion(w, 2, 10, 3).is_pure() is None
assert q1.angle() == 2*atan(sqrt(29))
assert q.angle() == 2*atan2(sqrt(x**2 + y**2 + z**2), w)
assert Quaternion.arc_coplanar(q1, Quaternion(2, 4, 6, 8)) is True
assert Quaternion.arc_coplanar(q1, Quaternion(1, -2, -3, -4)) is True
assert Quaternion.arc_coplanar(q1, Quaternion(1, 8, 12, 16)) is True
assert Quaternion.arc_coplanar(q1, Quaternion(1, 2, 3, 4)) is True
assert Quaternion.arc_coplanar(q1, Quaternion(w, 4, 6, 8)) is True
assert Quaternion.arc_coplanar(q1, Quaternion(2, 7, 4, 1)) is False
assert Quaternion.arc_coplanar(q1, Quaternion(w, x, y, z)) is None
raises(ValueError, lambda: Quaternion.arc_coplanar(q1, q0))
assert Quaternion.vector_coplanar(
Quaternion(0, 8, 12, 16),
Quaternion(0, 4, 6, 8),
Quaternion(0, 2, 3, 4)) is True
assert Quaternion.vector_coplanar(
Quaternion(0, 0, 0, 0), Quaternion(0, 4, 6, 8), Quaternion(0, 2, 3, 4)) is True
assert Quaternion.vector_coplanar(
Quaternion(0, 8, 2, 6), Quaternion(0, 1, 6, 6), Quaternion(0, 0, 3, 4)) is False
assert Quaternion.vector_coplanar(
Quaternion(0, 1, 3, 4),
Quaternion(0, 4, w, 6),
Quaternion(0, 6, 8, 1)) is None
raises(ValueError, lambda:
Quaternion.vector_coplanar(q0, Quaternion(0, 4, 6, 8), q1))
assert Quaternion(0, 1, 2, 3).parallel(Quaternion(0, 2, 4, 6)) is True
assert Quaternion(0, 1, 2, 3).parallel(Quaternion(0, 2, 2, 6)) is False
assert Quaternion(0, 1, 2, 3).parallel(Quaternion(w, x, y, 6)) is None
raises(ValueError, lambda: q0.parallel(q1))
assert Quaternion(0, 1, 2, 3).orthogonal(Quaternion(0, -2, 1, 0)) is True
assert Quaternion(0, 2, 4, 7).orthogonal(Quaternion(0, 2, 2, 6)) is False
assert Quaternion(0, 2, 4, 7).orthogonal(Quaternion(w, x, y, 6)) is None
raises(ValueError, lambda: q0.orthogonal(q1))
assert q1.index_vector() == Quaternion(
0, 2*sqrt(870)/29,
3*sqrt(870)/29,
4*sqrt(870)/29)
assert Quaternion(0, 3, 9, 4).index_vector() == Quaternion(0, 3, 9, 4)
assert Quaternion(4, 3, 9, 4).mensor() == log(sqrt(122))
assert Quaternion(3, 3, 0, 2).mensor() == log(sqrt(22))
assert q0.is_zero_quaternion() is True
assert q1.is_zero_quaternion() is False
assert Quaternion(w, 0, 0, 0).is_zero_quaternion() is None
def test_quaternion_conversions():
q1 = Quaternion(1, 2, 3, 4)
assert q1.to_axis_angle() == ((2 * sqrt(29)/29,
3 * sqrt(29)/29,
4 * sqrt(29)/29),
2 * acos(sqrt(30)/30))
assert (q1.to_rotation_matrix() ==
Matrix([[Rational(-2, 3), Rational(2, 15), Rational(11, 15)],
[Rational(2, 3), Rational(-1, 3), Rational(2, 3)],
[Rational(1, 3), Rational(14, 15), Rational(2, 15)]]))
assert (q1.to_rotation_matrix((1, 1, 1)) ==
Matrix([
[Rational(-2, 3), Rational(2, 15), Rational(11, 15), Rational(4, 5)],
[Rational(2, 3), Rational(-1, 3), Rational(2, 3), S.Zero],
[Rational(1, 3), Rational(14, 15), Rational(2, 15), Rational(-2, 5)],
[S.Zero, S.Zero, S.Zero, S.One]]))
theta = symbols("theta", real=True)
q2 = Quaternion(cos(theta/2), 0, 0, sin(theta/2))
assert trigsimp(q2.to_rotation_matrix()) == Matrix([
[cos(theta), -sin(theta), 0],
[sin(theta), cos(theta), 0],
[0, 0, 1]])
assert q2.to_axis_angle() == ((0, 0, sin(theta/2)/Abs(sin(theta/2))),
2*acos(cos(theta/2)))
assert trigsimp(q2.to_rotation_matrix((1, 1, 1))) == Matrix([
[cos(theta), -sin(theta), 0, sin(theta) - cos(theta) + 1],
[sin(theta), cos(theta), 0, -sin(theta) - cos(theta) + 1],
[0, 0, 1, 0],
[0, 0, 0, 1]])
def test_rotation_matrix_homogeneous():
q = Quaternion(w, x, y, z)
R1 = q.to_rotation_matrix(homogeneous=True) * q.norm()**2
R2 = simplify(q.to_rotation_matrix(homogeneous=False) * q.norm()**2)
assert R1 == R2
def test_quaternion_rotation_iss1593():
"""
There was a sign mistake in the definition,
of the rotation matrix. This tests that particular sign mistake.
See issue 1593 for reference.
See wikipedia
https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix
for the correct definition
"""
q = Quaternion(cos(phi/2), sin(phi/2), 0, 0)
assert(trigsimp(q.to_rotation_matrix()) == Matrix([
[1, 0, 0],
[0, cos(phi), -sin(phi)],
[0, sin(phi), cos(phi)]]))
def test_quaternion_multiplication():
q1 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
q2 = Quaternion(1, 2, 3, 5)
q3 = Quaternion(1, 1, 1, y)
assert Quaternion._generic_mul(S(4), S.One) == 4
assert (Quaternion._generic_mul(S(4), q1) ==
Quaternion(12 + 16*I, 8 + 20*I, 0, 28 + 32*I))
assert q2.mul(2) == Quaternion(2, 4, 6, 10)
assert q2.mul(q3) == Quaternion(-5*y - 4, 3*y - 2, 9 - 2*y, y + 4)
assert q2.mul(q3) == q2*q3
z = symbols('z', complex=True)
z_quat = Quaternion(re(z), im(z), 0, 0)
q = Quaternion(*symbols('q:4', real=True))
assert z * q == z_quat * q
assert q * z == q * z_quat
def test_issue_16318():
#for rtruediv
q0 = Quaternion(0, 0, 0, 0)
raises(ValueError, lambda: 1/q0)
#for rotate_point
q = Quaternion(1, 2, 3, 4)
(axis, angle) = q.to_axis_angle()
assert Quaternion.rotate_point((1, 1, 1), (axis, angle)) == (S.One / 5, 1, S(7) / 5)
#test for to_axis_angle
q = Quaternion(-1, 1, 1, 1)
axis = (-sqrt(3)/3, -sqrt(3)/3, -sqrt(3)/3)
angle = 2*pi/3
assert (axis, angle) == q.to_axis_angle()
@slow
def test_to_euler():
q = Quaternion(w, x, y, z)
q_normalized = q.normalize()
seqs = ['zxy', 'zyx', 'zyz', 'zxz']
seqs += [seq.upper() for seq in seqs]
for seq in seqs:
euler_from_q = q.to_euler(seq)
q_back = simplify(Quaternion.from_euler(euler_from_q, seq))
assert q_back == q_normalized
def test_to_euler_iss24504():
"""
There was a mistake in the degenerate case testing
See issue 24504 for reference.
"""
q = Quaternion.from_euler((phi, 0, 0), 'zyz')
assert trigsimp(q.to_euler('zyz'), inverse=True) == (phi, 0, 0)
def test_to_euler_numerical_singilarities():
def test_one_case(angles, seq):
q = Quaternion.from_euler(angles, seq)
assert q.to_euler(seq) == angles
# symmetric
test_one_case((pi/2, 0, 0), 'zyz')
test_one_case((pi/2, 0, 0), 'ZYZ')
test_one_case((pi/2, pi, 0), 'zyz')
test_one_case((pi/2, pi, 0), 'ZYZ')
# asymmetric
test_one_case((pi/2, pi/2, 0), 'zyx')
test_one_case((pi/2, -pi/2, 0), 'zyx')
test_one_case((pi/2, pi/2, 0), 'ZYX')
test_one_case((pi/2, -pi/2, 0), 'ZYX')
@slow
def test_to_euler_options():
def test_one_case(q):
angles1 = Matrix(q.to_euler(seq, True, True))
angles2 = Matrix(q.to_euler(seq, False, False))
angle_errors = simplify(angles1-angles2).evalf()
for angle_error in angle_errors:
# forcing angles to set {-pi, pi}
angle_error = (angle_error + pi) % (2 * pi) - pi
assert angle_error < 10e-7
for xyz in ('xyz', 'XYZ'):
for seq_tuple in permutations(xyz):
for symmetric in (True, False):
if symmetric:
seq = ''.join([seq_tuple[0], seq_tuple[1], seq_tuple[0]])
else:
seq = ''.join(seq_tuple)
for elements in product([-1, 0, 1], repeat=4):
q = Quaternion(*elements)
if not q.is_zero_quaternion():
test_one_case(q)
@@ -0,0 +1,18 @@
"""
A module to implement logical predicates and assumption system.
"""
from .assume import (
AppliedPredicate, Predicate, AssumptionsContext, assuming,
global_assumptions
)
from .ask import Q, ask, register_handler, remove_handler
from .refine import refine
from .relation import BinaryRelation, AppliedBinaryRelation
__all__ = [
'AppliedPredicate', 'Predicate', 'AssumptionsContext', 'assuming',
'global_assumptions', 'Q', 'ask', 'register_handler', 'remove_handler',
'refine',
'BinaryRelation', 'AppliedBinaryRelation'
]
@@ -0,0 +1,651 @@
"""Module for querying SymPy objects about assumptions."""
from sympy.assumptions.assume import (global_assumptions, Predicate,
AppliedPredicate)
from sympy.assumptions.cnf import CNF, EncodedCNF, Literal
from sympy.core import sympify
from sympy.core.kind import BooleanKind
from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le
from sympy.logic.inference import satisfiable
from sympy.utilities.decorator import memoize_property
from sympy.utilities.exceptions import (sympy_deprecation_warning,
SymPyDeprecationWarning,
ignore_warnings)
# Memoization is necessary for the properties of AssumptionKeys to
# ensure that only one object of Predicate objects are created.
# This is because assumption handlers are registered on those objects.
class AssumptionKeys:
"""
This class contains all the supported keys by ``ask``.
It should be accessed via the instance ``sympy.Q``.
"""
# DO NOT add methods or properties other than predicate keys.
# SAT solver checks the properties of Q and use them to compute the
# fact system. Non-predicate attributes will break this.
@memoize_property
def hermitian(self):
from .handlers.sets import HermitianPredicate
return HermitianPredicate()
@memoize_property
def antihermitian(self):
from .handlers.sets import AntihermitianPredicate
return AntihermitianPredicate()
@memoize_property
def real(self):
from .handlers.sets import RealPredicate
return RealPredicate()
@memoize_property
def extended_real(self):
from .handlers.sets import ExtendedRealPredicate
return ExtendedRealPredicate()
@memoize_property
def imaginary(self):
from .handlers.sets import ImaginaryPredicate
return ImaginaryPredicate()
@memoize_property
def complex(self):
from .handlers.sets import ComplexPredicate
return ComplexPredicate()
@memoize_property
def algebraic(self):
from .handlers.sets import AlgebraicPredicate
return AlgebraicPredicate()
@memoize_property
def transcendental(self):
from .predicates.sets import TranscendentalPredicate
return TranscendentalPredicate()
@memoize_property
def integer(self):
from .handlers.sets import IntegerPredicate
return IntegerPredicate()
@memoize_property
def noninteger(self):
from .predicates.sets import NonIntegerPredicate
return NonIntegerPredicate()
@memoize_property
def rational(self):
from .handlers.sets import RationalPredicate
return RationalPredicate()
@memoize_property
def irrational(self):
from .handlers.sets import IrrationalPredicate
return IrrationalPredicate()
@memoize_property
def finite(self):
from .handlers.calculus import FinitePredicate
return FinitePredicate()
@memoize_property
def infinite(self):
from .handlers.calculus import InfinitePredicate
return InfinitePredicate()
@memoize_property
def positive_infinite(self):
from .handlers.calculus import PositiveInfinitePredicate
return PositiveInfinitePredicate()
@memoize_property
def negative_infinite(self):
from .handlers.calculus import NegativeInfinitePredicate
return NegativeInfinitePredicate()
@memoize_property
def positive(self):
from .handlers.order import PositivePredicate
return PositivePredicate()
@memoize_property
def negative(self):
from .handlers.order import NegativePredicate
return NegativePredicate()
@memoize_property
def zero(self):
from .handlers.order import ZeroPredicate
return ZeroPredicate()
@memoize_property
def extended_positive(self):
from .handlers.order import ExtendedPositivePredicate
return ExtendedPositivePredicate()
@memoize_property
def extended_negative(self):
from .handlers.order import ExtendedNegativePredicate
return ExtendedNegativePredicate()
@memoize_property
def nonzero(self):
from .handlers.order import NonZeroPredicate
return NonZeroPredicate()
@memoize_property
def nonpositive(self):
from .handlers.order import NonPositivePredicate
return NonPositivePredicate()
@memoize_property
def nonnegative(self):
from .handlers.order import NonNegativePredicate
return NonNegativePredicate()
@memoize_property
def extended_nonzero(self):
from .handlers.order import ExtendedNonZeroPredicate
return ExtendedNonZeroPredicate()
@memoize_property
def extended_nonpositive(self):
from .handlers.order import ExtendedNonPositivePredicate
return ExtendedNonPositivePredicate()
@memoize_property
def extended_nonnegative(self):
from .handlers.order import ExtendedNonNegativePredicate
return ExtendedNonNegativePredicate()
@memoize_property
def even(self):
from .handlers.ntheory import EvenPredicate
return EvenPredicate()
@memoize_property
def odd(self):
from .handlers.ntheory import OddPredicate
return OddPredicate()
@memoize_property
def prime(self):
from .handlers.ntheory import PrimePredicate
return PrimePredicate()
@memoize_property
def composite(self):
from .handlers.ntheory import CompositePredicate
return CompositePredicate()
@memoize_property
def commutative(self):
from .handlers.common import CommutativePredicate
return CommutativePredicate()
@memoize_property
def is_true(self):
from .handlers.common import IsTruePredicate
return IsTruePredicate()
@memoize_property
def symmetric(self):
from .handlers.matrices import SymmetricPredicate
return SymmetricPredicate()
@memoize_property
def invertible(self):
from .handlers.matrices import InvertiblePredicate
return InvertiblePredicate()
@memoize_property
def orthogonal(self):
from .handlers.matrices import OrthogonalPredicate
return OrthogonalPredicate()
@memoize_property
def unitary(self):
from .handlers.matrices import UnitaryPredicate
return UnitaryPredicate()
@memoize_property
def positive_definite(self):
from .handlers.matrices import PositiveDefinitePredicate
return PositiveDefinitePredicate()
@memoize_property
def upper_triangular(self):
from .handlers.matrices import UpperTriangularPredicate
return UpperTriangularPredicate()
@memoize_property
def lower_triangular(self):
from .handlers.matrices import LowerTriangularPredicate
return LowerTriangularPredicate()
@memoize_property
def diagonal(self):
from .handlers.matrices import DiagonalPredicate
return DiagonalPredicate()
@memoize_property
def fullrank(self):
from .handlers.matrices import FullRankPredicate
return FullRankPredicate()
@memoize_property
def square(self):
from .handlers.matrices import SquarePredicate
return SquarePredicate()
@memoize_property
def integer_elements(self):
from .handlers.matrices import IntegerElementsPredicate
return IntegerElementsPredicate()
@memoize_property
def real_elements(self):
from .handlers.matrices import RealElementsPredicate
return RealElementsPredicate()
@memoize_property
def complex_elements(self):
from .handlers.matrices import ComplexElementsPredicate
return ComplexElementsPredicate()
@memoize_property
def singular(self):
from .predicates.matrices import SingularPredicate
return SingularPredicate()
@memoize_property
def normal(self):
from .predicates.matrices import NormalPredicate
return NormalPredicate()
@memoize_property
def triangular(self):
from .predicates.matrices import TriangularPredicate
return TriangularPredicate()
@memoize_property
def unit_triangular(self):
from .predicates.matrices import UnitTriangularPredicate
return UnitTriangularPredicate()
@memoize_property
def eq(self):
from .relation.equality import EqualityPredicate
return EqualityPredicate()
@memoize_property
def ne(self):
from .relation.equality import UnequalityPredicate
return UnequalityPredicate()
@memoize_property
def gt(self):
from .relation.equality import StrictGreaterThanPredicate
return StrictGreaterThanPredicate()
@memoize_property
def ge(self):
from .relation.equality import GreaterThanPredicate
return GreaterThanPredicate()
@memoize_property
def lt(self):
from .relation.equality import StrictLessThanPredicate
return StrictLessThanPredicate()
@memoize_property
def le(self):
from .relation.equality import LessThanPredicate
return LessThanPredicate()
Q = AssumptionKeys()
def _extract_all_facts(assump, exprs):
"""
Extract all relevant assumptions from *assump* with respect to given *exprs*.
Parameters
==========
assump : sympy.assumptions.cnf.CNF
exprs : tuple of expressions
Returns
=======
sympy.assumptions.cnf.CNF
Examples
========
>>> from sympy import Q
>>> from sympy.assumptions.cnf import CNF
>>> from sympy.assumptions.ask import _extract_all_facts
>>> from sympy.abc import x, y
>>> assump = CNF.from_prop(Q.positive(x) & Q.integer(y))
>>> exprs = (x,)
>>> cnf = _extract_all_facts(assump, exprs)
>>> cnf.clauses
{frozenset({Literal(Q.positive, False)})}
"""
facts = set()
for clause in assump.clauses:
args = []
for literal in clause:
if isinstance(literal.lit, AppliedPredicate) and len(literal.lit.arguments) == 1:
if literal.lit.arg in exprs:
# Add literal if it has matching in it
args.append(Literal(literal.lit.function, literal.is_Not))
else:
# If any of the literals doesn't have matching expr don't add the whole clause.
break
else:
# If any of the literals aren't unary predicate don't add the whole clause.
break
else:
if args:
facts.add(frozenset(args))
return CNF(facts)
def ask(proposition, assumptions=True, context=global_assumptions):
"""
Function to evaluate the proposition with assumptions.
Explanation
===========
This function evaluates the proposition to ``True`` or ``False`` if
the truth value can be determined. If not, it returns ``None``.
It should be discerned from :func:`~.refine` which, when applied to a
proposition, simplifies the argument to symbolic ``Boolean`` instead of
Python built-in ``True``, ``False`` or ``None``.
**Syntax**
* ask(proposition)
Evaluate the *proposition* in global assumption context.
* ask(proposition, assumptions)
Evaluate the *proposition* with respect to *assumptions* in
global assumption context.
Parameters
==========
proposition : Boolean
Proposition which will be evaluated to boolean value. If this is
not ``AppliedPredicate``, it will be wrapped by ``Q.is_true``.
assumptions : Boolean, optional
Local assumptions to evaluate the *proposition*.
context : AssumptionsContext, optional
Default assumptions to evaluate the *proposition*. By default,
this is ``sympy.assumptions.global_assumptions`` variable.
Returns
=======
``True``, ``False``, or ``None``
Raises
======
TypeError : *proposition* or *assumptions* is not valid logical expression.
ValueError : assumptions are inconsistent.
Examples
========
>>> from sympy import ask, Q, pi
>>> from sympy.abc import x, y
>>> ask(Q.rational(pi))
False
>>> ask(Q.even(x*y), Q.even(x) & Q.integer(y))
True
>>> ask(Q.prime(4*x), Q.integer(x))
False
If the truth value cannot be determined, ``None`` will be returned.
>>> print(ask(Q.odd(3*x))) # cannot determine unless we know x
None
``ValueError`` is raised if assumptions are inconsistent.
>>> ask(Q.integer(x), Q.even(x) & Q.odd(x))
Traceback (most recent call last):
...
ValueError: inconsistent assumptions Q.even(x) & Q.odd(x)
Notes
=====
Relations in assumptions are not implemented (yet), so the following
will not give a meaningful result.
>>> ask(Q.positive(x), x > 0)
It is however a work in progress.
See Also
========
sympy.assumptions.refine.refine : Simplification using assumptions.
Proposition is not reduced to ``None`` if the truth value cannot
be determined.
"""
from sympy.assumptions.satask import satask
from sympy.assumptions.lra_satask import lra_satask
from sympy.logic.algorithms.lra_theory import UnhandledInput
proposition = sympify(proposition)
assumptions = sympify(assumptions)
if isinstance(proposition, Predicate) or proposition.kind is not BooleanKind:
raise TypeError("proposition must be a valid logical expression")
if isinstance(assumptions, Predicate) or assumptions.kind is not BooleanKind:
raise TypeError("assumptions must be a valid logical expression")
binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le}
if isinstance(proposition, AppliedPredicate):
key, args = proposition.function, proposition.arguments
elif proposition.func in binrelpreds:
key, args = binrelpreds[type(proposition)], proposition.args
else:
key, args = Q.is_true, (proposition,)
# convert local and global assumptions to CNF
assump_cnf = CNF.from_prop(assumptions)
assump_cnf.extend(context)
# extract the relevant facts from assumptions with respect to args
local_facts = _extract_all_facts(assump_cnf, args)
# convert default facts and assumed facts to encoded CNF
known_facts_cnf = get_all_known_facts()
enc_cnf = EncodedCNF()
enc_cnf.from_cnf(CNF(known_facts_cnf))
enc_cnf.add_from_cnf(local_facts)
# check the satisfiability of given assumptions
if local_facts.clauses and satisfiable(enc_cnf) is False:
raise ValueError("inconsistent assumptions %s" % assumptions)
# quick computation for single fact
res = _ask_single_fact(key, local_facts)
if res is not None:
return res
# direct resolution method, no logic
res = key(*args)._eval_ask(assumptions)
if res is not None:
return bool(res)
# using satask (still costly)
res = satask(proposition, assumptions=assumptions, context=context)
if res is not None:
return res
try:
res = lra_satask(proposition, assumptions=assumptions, context=context)
except UnhandledInput:
return None
return res
def _ask_single_fact(key, local_facts):
"""
Compute the truth value of single predicate using assumptions.
Parameters
==========
key : sympy.assumptions.assume.Predicate
Proposition predicate.
local_facts : sympy.assumptions.cnf.CNF
Local assumption in CNF form.
Returns
=======
``True``, ``False`` or ``None``
Examples
========
>>> from sympy import Q
>>> from sympy.assumptions.cnf import CNF
>>> from sympy.assumptions.ask import _ask_single_fact
If prerequisite of proposition is rejected by the assumption,
return ``False``.
>>> key, assump = Q.zero, ~Q.zero
>>> local_facts = CNF.from_prop(assump)
>>> _ask_single_fact(key, local_facts)
False
>>> key, assump = Q.zero, ~Q.even
>>> local_facts = CNF.from_prop(assump)
>>> _ask_single_fact(key, local_facts)
False
If assumption implies the proposition, return ``True``.
>>> key, assump = Q.even, Q.zero
>>> local_facts = CNF.from_prop(assump)
>>> _ask_single_fact(key, local_facts)
True
If proposition rejects the assumption, return ``False``.
>>> key, assump = Q.even, Q.odd
>>> local_facts = CNF.from_prop(assump)
>>> _ask_single_fact(key, local_facts)
False
"""
if local_facts.clauses:
known_facts_dict = get_known_facts_dict()
if len(local_facts.clauses) == 1:
cl, = local_facts.clauses
if len(cl) == 1:
f, = cl
prop_facts = known_facts_dict.get(key, None)
prop_req = prop_facts[0] if prop_facts is not None else set()
if f.is_Not and f.arg in prop_req:
# the prerequisite of proposition is rejected
return False
for clause in local_facts.clauses:
if len(clause) == 1:
f, = clause
prop_facts = known_facts_dict.get(f.arg, None) if not f.is_Not else None
if prop_facts is None:
continue
prop_req, prop_rej = prop_facts
if key in prop_req:
# assumption implies the proposition
return True
elif key in prop_rej:
# proposition rejects the assumption
return False
return None
def register_handler(key, handler):
"""
Register a handler in the ask system. key must be a string and handler a
class inheriting from AskHandler.
.. deprecated:: 1.8.
Use multipledispatch handler instead. See :obj:`~.Predicate`.
"""
sympy_deprecation_warning(
"""
The AskHandler system is deprecated. The register_handler() function
should be replaced with the multipledispatch handler of Predicate.
""",
deprecated_since_version="1.8",
active_deprecations_target='deprecated-askhandler',
)
if isinstance(key, Predicate):
key = key.name.name
Qkey = getattr(Q, key, None)
if Qkey is not None:
Qkey.add_handler(handler)
else:
setattr(Q, key, Predicate(key, handlers=[handler]))
def remove_handler(key, handler):
"""
Removes a handler from the ask system.
.. deprecated:: 1.8.
Use multipledispatch handler instead. See :obj:`~.Predicate`.
"""
sympy_deprecation_warning(
"""
The AskHandler system is deprecated. The remove_handler() function
should be replaced with the multipledispatch handler of Predicate.
""",
deprecated_since_version="1.8",
active_deprecations_target='deprecated-askhandler',
)
if isinstance(key, Predicate):
key = key.name.name
# Don't show the same warning again recursively
with ignore_warnings(SymPyDeprecationWarning):
getattr(Q, key).remove_handler(handler)
from sympy.assumptions.ask_generated import (get_all_known_facts,
get_known_facts_dict)
@@ -0,0 +1,352 @@
"""
Do NOT manually edit this file.
Instead, run ./bin/ask_update.py.
"""
from sympy.assumptions.ask import Q
from sympy.assumptions.cnf import Literal
from sympy.core.cache import cacheit
@cacheit
def get_all_known_facts():
"""
Known facts between unary predicates as CNF clauses.
"""
return {
frozenset((Literal(Q.algebraic, False), Literal(Q.imaginary, True), Literal(Q.transcendental, False))),
frozenset((Literal(Q.algebraic, False), Literal(Q.negative, True), Literal(Q.transcendental, False))),
frozenset((Literal(Q.algebraic, False), Literal(Q.positive, True), Literal(Q.transcendental, False))),
frozenset((Literal(Q.algebraic, False), Literal(Q.rational, True))),
frozenset((Literal(Q.algebraic, False), Literal(Q.transcendental, False), Literal(Q.zero, True))),
frozenset((Literal(Q.algebraic, True), Literal(Q.finite, False))),
frozenset((Literal(Q.algebraic, True), Literal(Q.transcendental, True))),
frozenset((Literal(Q.antihermitian, False), Literal(Q.hermitian, False), Literal(Q.zero, True))),
frozenset((Literal(Q.antihermitian, False), Literal(Q.imaginary, True))),
frozenset((Literal(Q.commutative, False), Literal(Q.finite, True))),
frozenset((Literal(Q.commutative, False), Literal(Q.infinite, True))),
frozenset((Literal(Q.complex_elements, False), Literal(Q.real_elements, True))),
frozenset((Literal(Q.composite, False), Literal(Q.even, True), Literal(Q.positive, True), Literal(Q.prime, False))),
frozenset((Literal(Q.composite, True), Literal(Q.even, False), Literal(Q.odd, False))),
frozenset((Literal(Q.composite, True), Literal(Q.positive, False))),
frozenset((Literal(Q.composite, True), Literal(Q.prime, True))),
frozenset((Literal(Q.diagonal, False), Literal(Q.lower_triangular, True), Literal(Q.upper_triangular, True))),
frozenset((Literal(Q.diagonal, True), Literal(Q.lower_triangular, False))),
frozenset((Literal(Q.diagonal, True), Literal(Q.normal, False))),
frozenset((Literal(Q.diagonal, True), Literal(Q.symmetric, False))),
frozenset((Literal(Q.diagonal, True), Literal(Q.upper_triangular, False))),
frozenset((Literal(Q.even, False), Literal(Q.odd, False), Literal(Q.prime, True))),
frozenset((Literal(Q.even, False), Literal(Q.zero, True))),
frozenset((Literal(Q.even, True), Literal(Q.odd, True))),
frozenset((Literal(Q.even, True), Literal(Q.rational, False))),
frozenset((Literal(Q.finite, False), Literal(Q.transcendental, True))),
frozenset((Literal(Q.finite, True), Literal(Q.infinite, True))),
frozenset((Literal(Q.fullrank, False), Literal(Q.invertible, True))),
frozenset((Literal(Q.fullrank, True), Literal(Q.invertible, False), Literal(Q.square, True))),
frozenset((Literal(Q.hermitian, False), Literal(Q.negative, True))),
frozenset((Literal(Q.hermitian, False), Literal(Q.positive, True))),
frozenset((Literal(Q.hermitian, False), Literal(Q.zero, True))),
frozenset((Literal(Q.imaginary, True), Literal(Q.negative, True))),
frozenset((Literal(Q.imaginary, True), Literal(Q.positive, True))),
frozenset((Literal(Q.imaginary, True), Literal(Q.zero, True))),
frozenset((Literal(Q.infinite, False), Literal(Q.negative_infinite, True))),
frozenset((Literal(Q.infinite, False), Literal(Q.positive_infinite, True))),
frozenset((Literal(Q.integer_elements, True), Literal(Q.real_elements, False))),
frozenset((Literal(Q.invertible, False), Literal(Q.positive_definite, True))),
frozenset((Literal(Q.invertible, False), Literal(Q.singular, False))),
frozenset((Literal(Q.invertible, False), Literal(Q.unitary, True))),
frozenset((Literal(Q.invertible, True), Literal(Q.singular, True))),
frozenset((Literal(Q.invertible, True), Literal(Q.square, False))),
frozenset((Literal(Q.irrational, False), Literal(Q.negative, True), Literal(Q.rational, False))),
frozenset((Literal(Q.irrational, False), Literal(Q.positive, True), Literal(Q.rational, False))),
frozenset((Literal(Q.irrational, False), Literal(Q.rational, False), Literal(Q.zero, True))),
frozenset((Literal(Q.irrational, True), Literal(Q.negative, False), Literal(Q.positive, False), Literal(Q.zero, False))),
frozenset((Literal(Q.irrational, True), Literal(Q.rational, True))),
frozenset((Literal(Q.lower_triangular, False), Literal(Q.triangular, True), Literal(Q.upper_triangular, False))),
frozenset((Literal(Q.lower_triangular, True), Literal(Q.triangular, False))),
frozenset((Literal(Q.negative, False), Literal(Q.positive, False), Literal(Q.rational, True), Literal(Q.zero, False))),
frozenset((Literal(Q.negative, True), Literal(Q.negative_infinite, True))),
frozenset((Literal(Q.negative, True), Literal(Q.positive, True))),
frozenset((Literal(Q.negative, True), Literal(Q.positive_infinite, True))),
frozenset((Literal(Q.negative, True), Literal(Q.zero, True))),
frozenset((Literal(Q.negative_infinite, True), Literal(Q.positive, True))),
frozenset((Literal(Q.negative_infinite, True), Literal(Q.positive_infinite, True))),
frozenset((Literal(Q.negative_infinite, True), Literal(Q.zero, True))),
frozenset((Literal(Q.normal, False), Literal(Q.unitary, True))),
frozenset((Literal(Q.normal, True), Literal(Q.square, False))),
frozenset((Literal(Q.odd, True), Literal(Q.rational, False))),
frozenset((Literal(Q.orthogonal, False), Literal(Q.real_elements, True), Literal(Q.unitary, True))),
frozenset((Literal(Q.orthogonal, True), Literal(Q.positive_definite, False))),
frozenset((Literal(Q.orthogonal, True), Literal(Q.unitary, False))),
frozenset((Literal(Q.positive, False), Literal(Q.prime, True))),
frozenset((Literal(Q.positive, True), Literal(Q.positive_infinite, True))),
frozenset((Literal(Q.positive, True), Literal(Q.zero, True))),
frozenset((Literal(Q.positive_infinite, True), Literal(Q.zero, True))),
frozenset((Literal(Q.square, False), Literal(Q.symmetric, True))),
frozenset((Literal(Q.triangular, False), Literal(Q.unit_triangular, True))),
frozenset((Literal(Q.triangular, False), Literal(Q.upper_triangular, True)))
}
@cacheit
def get_all_known_matrix_facts():
"""
Known facts between unary predicates for matrices as CNF clauses.
"""
return {
frozenset((Literal(Q.complex_elements, False), Literal(Q.real_elements, True))),
frozenset((Literal(Q.diagonal, False), Literal(Q.lower_triangular, True), Literal(Q.upper_triangular, True))),
frozenset((Literal(Q.diagonal, True), Literal(Q.lower_triangular, False))),
frozenset((Literal(Q.diagonal, True), Literal(Q.normal, False))),
frozenset((Literal(Q.diagonal, True), Literal(Q.symmetric, False))),
frozenset((Literal(Q.diagonal, True), Literal(Q.upper_triangular, False))),
frozenset((Literal(Q.fullrank, False), Literal(Q.invertible, True))),
frozenset((Literal(Q.fullrank, True), Literal(Q.invertible, False), Literal(Q.square, True))),
frozenset((Literal(Q.integer_elements, True), Literal(Q.real_elements, False))),
frozenset((Literal(Q.invertible, False), Literal(Q.positive_definite, True))),
frozenset((Literal(Q.invertible, False), Literal(Q.singular, False))),
frozenset((Literal(Q.invertible, False), Literal(Q.unitary, True))),
frozenset((Literal(Q.invertible, True), Literal(Q.singular, True))),
frozenset((Literal(Q.invertible, True), Literal(Q.square, False))),
frozenset((Literal(Q.lower_triangular, False), Literal(Q.triangular, True), Literal(Q.upper_triangular, False))),
frozenset((Literal(Q.lower_triangular, True), Literal(Q.triangular, False))),
frozenset((Literal(Q.normal, False), Literal(Q.unitary, True))),
frozenset((Literal(Q.normal, True), Literal(Q.square, False))),
frozenset((Literal(Q.orthogonal, False), Literal(Q.real_elements, True), Literal(Q.unitary, True))),
frozenset((Literal(Q.orthogonal, True), Literal(Q.positive_definite, False))),
frozenset((Literal(Q.orthogonal, True), Literal(Q.unitary, False))),
frozenset((Literal(Q.square, False), Literal(Q.symmetric, True))),
frozenset((Literal(Q.triangular, False), Literal(Q.unit_triangular, True))),
frozenset((Literal(Q.triangular, False), Literal(Q.upper_triangular, True)))
}
@cacheit
def get_all_known_number_facts():
"""
Known facts between unary predicates for numbers as CNF clauses.
"""
return {
frozenset((Literal(Q.algebraic, False), Literal(Q.imaginary, True), Literal(Q.transcendental, False))),
frozenset((Literal(Q.algebraic, False), Literal(Q.negative, True), Literal(Q.transcendental, False))),
frozenset((Literal(Q.algebraic, False), Literal(Q.positive, True), Literal(Q.transcendental, False))),
frozenset((Literal(Q.algebraic, False), Literal(Q.rational, True))),
frozenset((Literal(Q.algebraic, False), Literal(Q.transcendental, False), Literal(Q.zero, True))),
frozenset((Literal(Q.algebraic, True), Literal(Q.finite, False))),
frozenset((Literal(Q.algebraic, True), Literal(Q.transcendental, True))),
frozenset((Literal(Q.antihermitian, False), Literal(Q.hermitian, False), Literal(Q.zero, True))),
frozenset((Literal(Q.antihermitian, False), Literal(Q.imaginary, True))),
frozenset((Literal(Q.commutative, False), Literal(Q.finite, True))),
frozenset((Literal(Q.commutative, False), Literal(Q.infinite, True))),
frozenset((Literal(Q.composite, False), Literal(Q.even, True), Literal(Q.positive, True), Literal(Q.prime, False))),
frozenset((Literal(Q.composite, True), Literal(Q.even, False), Literal(Q.odd, False))),
frozenset((Literal(Q.composite, True), Literal(Q.positive, False))),
frozenset((Literal(Q.composite, True), Literal(Q.prime, True))),
frozenset((Literal(Q.even, False), Literal(Q.odd, False), Literal(Q.prime, True))),
frozenset((Literal(Q.even, False), Literal(Q.zero, True))),
frozenset((Literal(Q.even, True), Literal(Q.odd, True))),
frozenset((Literal(Q.even, True), Literal(Q.rational, False))),
frozenset((Literal(Q.finite, False), Literal(Q.transcendental, True))),
frozenset((Literal(Q.finite, True), Literal(Q.infinite, True))),
frozenset((Literal(Q.hermitian, False), Literal(Q.negative, True))),
frozenset((Literal(Q.hermitian, False), Literal(Q.positive, True))),
frozenset((Literal(Q.hermitian, False), Literal(Q.zero, True))),
frozenset((Literal(Q.imaginary, True), Literal(Q.negative, True))),
frozenset((Literal(Q.imaginary, True), Literal(Q.positive, True))),
frozenset((Literal(Q.imaginary, True), Literal(Q.zero, True))),
frozenset((Literal(Q.infinite, False), Literal(Q.negative_infinite, True))),
frozenset((Literal(Q.infinite, False), Literal(Q.positive_infinite, True))),
frozenset((Literal(Q.irrational, False), Literal(Q.negative, True), Literal(Q.rational, False))),
frozenset((Literal(Q.irrational, False), Literal(Q.positive, True), Literal(Q.rational, False))),
frozenset((Literal(Q.irrational, False), Literal(Q.rational, False), Literal(Q.zero, True))),
frozenset((Literal(Q.irrational, True), Literal(Q.negative, False), Literal(Q.positive, False), Literal(Q.zero, False))),
frozenset((Literal(Q.irrational, True), Literal(Q.rational, True))),
frozenset((Literal(Q.negative, False), Literal(Q.positive, False), Literal(Q.rational, True), Literal(Q.zero, False))),
frozenset((Literal(Q.negative, True), Literal(Q.negative_infinite, True))),
frozenset((Literal(Q.negative, True), Literal(Q.positive, True))),
frozenset((Literal(Q.negative, True), Literal(Q.positive_infinite, True))),
frozenset((Literal(Q.negative, True), Literal(Q.zero, True))),
frozenset((Literal(Q.negative_infinite, True), Literal(Q.positive, True))),
frozenset((Literal(Q.negative_infinite, True), Literal(Q.positive_infinite, True))),
frozenset((Literal(Q.negative_infinite, True), Literal(Q.zero, True))),
frozenset((Literal(Q.odd, True), Literal(Q.rational, False))),
frozenset((Literal(Q.positive, False), Literal(Q.prime, True))),
frozenset((Literal(Q.positive, True), Literal(Q.positive_infinite, True))),
frozenset((Literal(Q.positive, True), Literal(Q.zero, True))),
frozenset((Literal(Q.positive_infinite, True), Literal(Q.zero, True)))
}
@cacheit
def get_known_facts_dict():
"""
Logical relations between unary predicates as dictionary.
Each key is a predicate, and item is two groups of predicates.
First group contains the predicates which are implied by the key, and
second group contains the predicates which are rejected by the key.
"""
return {
Q.algebraic: (set([Q.algebraic, Q.commutative, Q.complex, Q.finite]),
set([Q.infinite, Q.negative_infinite, Q.positive_infinite,
Q.transcendental])),
Q.antihermitian: (set([Q.antihermitian]), set([])),
Q.commutative: (set([Q.commutative]), set([])),
Q.complex: (set([Q.commutative, Q.complex, Q.finite]),
set([Q.infinite, Q.negative_infinite, Q.positive_infinite])),
Q.complex_elements: (set([Q.complex_elements]), set([])),
Q.composite: (set([Q.algebraic, Q.commutative, Q.complex, Q.composite,
Q.extended_nonnegative, Q.extended_nonzero,
Q.extended_positive, Q.extended_real, Q.finite, Q.hermitian,
Q.integer, Q.nonnegative, Q.nonzero, Q.positive, Q.rational,
Q.real]), set([Q.extended_negative, Q.extended_nonpositive,
Q.imaginary, Q.infinite, Q.irrational, Q.negative,
Q.negative_infinite, Q.nonpositive, Q.positive_infinite,
Q.prime, Q.transcendental, Q.zero])),
Q.diagonal: (set([Q.diagonal, Q.lower_triangular, Q.normal, Q.square,
Q.symmetric, Q.triangular, Q.upper_triangular]), set([])),
Q.even: (set([Q.algebraic, Q.commutative, Q.complex, Q.even,
Q.extended_real, Q.finite, Q.hermitian, Q.integer, Q.rational,
Q.real]), set([Q.imaginary, Q.infinite, Q.irrational,
Q.negative_infinite, Q.odd, Q.positive_infinite,
Q.transcendental])),
Q.extended_negative: (set([Q.commutative, Q.extended_negative,
Q.extended_nonpositive, Q.extended_nonzero, Q.extended_real]),
set([Q.composite, Q.extended_nonnegative, Q.extended_positive,
Q.imaginary, Q.nonnegative, Q.positive, Q.positive_infinite,
Q.prime, Q.zero])),
Q.extended_nonnegative: (set([Q.commutative, Q.extended_nonnegative,
Q.extended_real]), set([Q.extended_negative, Q.imaginary,
Q.negative, Q.negative_infinite])),
Q.extended_nonpositive: (set([Q.commutative, Q.extended_nonpositive,
Q.extended_real]), set([Q.composite, Q.extended_positive,
Q.imaginary, Q.positive, Q.positive_infinite, Q.prime])),
Q.extended_nonzero: (set([Q.commutative, Q.extended_nonzero,
Q.extended_real]), set([Q.imaginary, Q.zero])),
Q.extended_positive: (set([Q.commutative, Q.extended_nonnegative,
Q.extended_nonzero, Q.extended_positive, Q.extended_real]),
set([Q.extended_negative, Q.extended_nonpositive, Q.imaginary,
Q.negative, Q.negative_infinite, Q.nonpositive, Q.zero])),
Q.extended_real: (set([Q.commutative, Q.extended_real]),
set([Q.imaginary])),
Q.finite: (set([Q.commutative, Q.finite]), set([Q.infinite,
Q.negative_infinite, Q.positive_infinite])),
Q.fullrank: (set([Q.fullrank]), set([])),
Q.hermitian: (set([Q.hermitian]), set([])),
Q.imaginary: (set([Q.antihermitian, Q.commutative, Q.complex,
Q.finite, Q.imaginary]), set([Q.composite, Q.even,
Q.extended_negative, Q.extended_nonnegative,
Q.extended_nonpositive, Q.extended_nonzero,
Q.extended_positive, Q.extended_real, Q.infinite, Q.integer,
Q.irrational, Q.negative, Q.negative_infinite, Q.nonnegative,
Q.nonpositive, Q.nonzero, Q.odd, Q.positive,
Q.positive_infinite, Q.prime, Q.rational, Q.real, Q.zero])),
Q.infinite: (set([Q.commutative, Q.infinite]), set([Q.algebraic,
Q.complex, Q.composite, Q.even, Q.finite, Q.imaginary,
Q.integer, Q.irrational, Q.negative, Q.nonnegative,
Q.nonpositive, Q.nonzero, Q.odd, Q.positive, Q.prime,
Q.rational, Q.real, Q.transcendental, Q.zero])),
Q.integer: (set([Q.algebraic, Q.commutative, Q.complex,
Q.extended_real, Q.finite, Q.hermitian, Q.integer, Q.rational,
Q.real]), set([Q.imaginary, Q.infinite, Q.irrational,
Q.negative_infinite, Q.positive_infinite, Q.transcendental])),
Q.integer_elements: (set([Q.complex_elements, Q.integer_elements,
Q.real_elements]), set([])),
Q.invertible: (set([Q.fullrank, Q.invertible, Q.square]),
set([Q.singular])),
Q.irrational: (set([Q.commutative, Q.complex, Q.extended_nonzero,
Q.extended_real, Q.finite, Q.hermitian, Q.irrational,
Q.nonzero, Q.real]), set([Q.composite, Q.even, Q.imaginary,
Q.infinite, Q.integer, Q.negative_infinite, Q.odd,
Q.positive_infinite, Q.prime, Q.rational, Q.zero])),
Q.is_true: (set([Q.is_true]), set([])),
Q.lower_triangular: (set([Q.lower_triangular, Q.triangular]), set([])),
Q.negative: (set([Q.commutative, Q.complex, Q.extended_negative,
Q.extended_nonpositive, Q.extended_nonzero, Q.extended_real,
Q.finite, Q.hermitian, Q.negative, Q.nonpositive, Q.nonzero,
Q.real]), set([Q.composite, Q.extended_nonnegative,
Q.extended_positive, Q.imaginary, Q.infinite,
Q.negative_infinite, Q.nonnegative, Q.positive,
Q.positive_infinite, Q.prime, Q.zero])),
Q.negative_infinite: (set([Q.commutative, Q.extended_negative,
Q.extended_nonpositive, Q.extended_nonzero, Q.extended_real,
Q.infinite, Q.negative_infinite]), set([Q.algebraic,
Q.complex, Q.composite, Q.even, Q.extended_nonnegative,
Q.extended_positive, Q.finite, Q.imaginary, Q.integer,
Q.irrational, Q.negative, Q.nonnegative, Q.nonpositive,
Q.nonzero, Q.odd, Q.positive, Q.positive_infinite, Q.prime,
Q.rational, Q.real, Q.transcendental, Q.zero])),
Q.noninteger: (set([Q.noninteger]), set([])),
Q.nonnegative: (set([Q.commutative, Q.complex, Q.extended_nonnegative,
Q.extended_real, Q.finite, Q.hermitian, Q.nonnegative,
Q.real]), set([Q.extended_negative, Q.imaginary, Q.infinite,
Q.negative, Q.negative_infinite, Q.positive_infinite])),
Q.nonpositive: (set([Q.commutative, Q.complex, Q.extended_nonpositive,
Q.extended_real, Q.finite, Q.hermitian, Q.nonpositive,
Q.real]), set([Q.composite, Q.extended_positive, Q.imaginary,
Q.infinite, Q.negative_infinite, Q.positive,
Q.positive_infinite, Q.prime])),
Q.nonzero: (set([Q.commutative, Q.complex, Q.extended_nonzero,
Q.extended_real, Q.finite, Q.hermitian, Q.nonzero, Q.real]),
set([Q.imaginary, Q.infinite, Q.negative_infinite,
Q.positive_infinite, Q.zero])),
Q.normal: (set([Q.normal, Q.square]), set([])),
Q.odd: (set([Q.algebraic, Q.commutative, Q.complex,
Q.extended_nonzero, Q.extended_real, Q.finite, Q.hermitian,
Q.integer, Q.nonzero, Q.odd, Q.rational, Q.real]),
set([Q.even, Q.imaginary, Q.infinite, Q.irrational,
Q.negative_infinite, Q.positive_infinite, Q.transcendental,
Q.zero])),
Q.orthogonal: (set([Q.fullrank, Q.invertible, Q.normal, Q.orthogonal,
Q.positive_definite, Q.square, Q.unitary]), set([Q.singular])),
Q.positive: (set([Q.commutative, Q.complex, Q.extended_nonnegative,
Q.extended_nonzero, Q.extended_positive, Q.extended_real,
Q.finite, Q.hermitian, Q.nonnegative, Q.nonzero, Q.positive,
Q.real]), set([Q.extended_negative, Q.extended_nonpositive,
Q.imaginary, Q.infinite, Q.negative, Q.negative_infinite,
Q.nonpositive, Q.positive_infinite, Q.zero])),
Q.positive_definite: (set([Q.fullrank, Q.invertible,
Q.positive_definite, Q.square]), set([Q.singular])),
Q.positive_infinite: (set([Q.commutative, Q.extended_nonnegative,
Q.extended_nonzero, Q.extended_positive, Q.extended_real,
Q.infinite, Q.positive_infinite]), set([Q.algebraic,
Q.complex, Q.composite, Q.even, Q.extended_negative,
Q.extended_nonpositive, Q.finite, Q.imaginary, Q.integer,
Q.irrational, Q.negative, Q.negative_infinite, Q.nonnegative,
Q.nonpositive, Q.nonzero, Q.odd, Q.positive, Q.prime,
Q.rational, Q.real, Q.transcendental, Q.zero])),
Q.prime: (set([Q.algebraic, Q.commutative, Q.complex,
Q.extended_nonnegative, Q.extended_nonzero,
Q.extended_positive, Q.extended_real, Q.finite, Q.hermitian,
Q.integer, Q.nonnegative, Q.nonzero, Q.positive, Q.prime,
Q.rational, Q.real]), set([Q.composite, Q.extended_negative,
Q.extended_nonpositive, Q.imaginary, Q.infinite, Q.irrational,
Q.negative, Q.negative_infinite, Q.nonpositive,
Q.positive_infinite, Q.transcendental, Q.zero])),
Q.rational: (set([Q.algebraic, Q.commutative, Q.complex,
Q.extended_real, Q.finite, Q.hermitian, Q.rational, Q.real]),
set([Q.imaginary, Q.infinite, Q.irrational,
Q.negative_infinite, Q.positive_infinite, Q.transcendental])),
Q.real: (set([Q.commutative, Q.complex, Q.extended_real, Q.finite,
Q.hermitian, Q.real]), set([Q.imaginary, Q.infinite,
Q.negative_infinite, Q.positive_infinite])),
Q.real_elements: (set([Q.complex_elements, Q.real_elements]), set([])),
Q.singular: (set([Q.singular]), set([Q.invertible, Q.orthogonal,
Q.positive_definite, Q.unitary])),
Q.square: (set([Q.square]), set([])),
Q.symmetric: (set([Q.square, Q.symmetric]), set([])),
Q.transcendental: (set([Q.commutative, Q.complex, Q.finite,
Q.transcendental]), set([Q.algebraic, Q.composite, Q.even,
Q.infinite, Q.integer, Q.negative_infinite, Q.odd,
Q.positive_infinite, Q.prime, Q.rational, Q.zero])),
Q.triangular: (set([Q.triangular]), set([])),
Q.unit_triangular: (set([Q.triangular, Q.unit_triangular]), set([])),
Q.unitary: (set([Q.fullrank, Q.invertible, Q.normal, Q.square,
Q.unitary]), set([Q.singular])),
Q.upper_triangular: (set([Q.triangular, Q.upper_triangular]), set([])),
Q.zero: (set([Q.algebraic, Q.commutative, Q.complex, Q.even,
Q.extended_nonnegative, Q.extended_nonpositive,
Q.extended_real, Q.finite, Q.hermitian, Q.integer,
Q.nonnegative, Q.nonpositive, Q.rational, Q.real, Q.zero]),
set([Q.composite, Q.extended_negative, Q.extended_nonzero,
Q.extended_positive, Q.imaginary, Q.infinite, Q.irrational,
Q.negative, Q.negative_infinite, Q.nonzero, Q.odd, Q.positive,
Q.positive_infinite, Q.prime, Q.transcendental])),
}
@@ -0,0 +1,485 @@
"""A module which implements predicates and assumption context."""
from contextlib import contextmanager
import inspect
from sympy.core.symbol import Str
from sympy.core.sympify import _sympify
from sympy.logic.boolalg import Boolean, false, true
from sympy.multipledispatch.dispatcher import Dispatcher, str_signature
from sympy.utilities.exceptions import sympy_deprecation_warning
from sympy.utilities.iterables import is_sequence
from sympy.utilities.source import get_class
class AssumptionsContext(set):
"""
Set containing default assumptions which are applied to the ``ask()``
function.
Explanation
===========
This is used to represent global assumptions, but you can also use this
class to create your own local assumptions contexts. It is basically a thin
wrapper to Python's set, so see its documentation for advanced usage.
Examples
========
The default assumption context is ``global_assumptions``, which is initially empty:
>>> from sympy import ask, Q
>>> from sympy.assumptions import global_assumptions
>>> global_assumptions
AssumptionsContext()
You can add default assumptions:
>>> from sympy.abc import x
>>> global_assumptions.add(Q.real(x))
>>> global_assumptions
AssumptionsContext({Q.real(x)})
>>> ask(Q.real(x))
True
And remove them:
>>> global_assumptions.remove(Q.real(x))
>>> print(ask(Q.real(x)))
None
The ``clear()`` method removes every assumption:
>>> global_assumptions.add(Q.positive(x))
>>> global_assumptions
AssumptionsContext({Q.positive(x)})
>>> global_assumptions.clear()
>>> global_assumptions
AssumptionsContext()
See Also
========
assuming
"""
def add(self, *assumptions):
"""Add assumptions."""
for a in assumptions:
super().add(a)
def _sympystr(self, printer):
if not self:
return "%s()" % self.__class__.__name__
return "{}({})".format(self.__class__.__name__, printer._print_set(self))
global_assumptions = AssumptionsContext()
class AppliedPredicate(Boolean):
"""
The class of expressions resulting from applying ``Predicate`` to
the arguments. ``AppliedPredicate`` merely wraps its argument and
remain unevaluated. To evaluate it, use the ``ask()`` function.
Examples
========
>>> from sympy import Q, ask
>>> Q.integer(1)
Q.integer(1)
The ``function`` attribute returns the predicate, and the ``arguments``
attribute returns the tuple of arguments.
>>> type(Q.integer(1))
<class 'sympy.assumptions.assume.AppliedPredicate'>
>>> Q.integer(1).function
Q.integer
>>> Q.integer(1).arguments
(1,)
Applied predicates can be evaluated to a boolean value with ``ask``:
>>> ask(Q.integer(1))
True
"""
__slots__ = ()
def __new__(cls, predicate, *args):
if not isinstance(predicate, Predicate):
raise TypeError("%s is not a Predicate." % predicate)
args = map(_sympify, args)
return super().__new__(cls, predicate, *args)
@property
def arg(self):
"""
Return the expression used by this assumption.
Examples
========
>>> from sympy import Q, Symbol
>>> x = Symbol('x')
>>> a = Q.integer(x + 1)
>>> a.arg
x + 1
"""
# Will be deprecated
args = self._args
if len(args) == 2:
# backwards compatibility
return args[1]
raise TypeError("'arg' property is allowed only for unary predicates.")
@property
def function(self):
"""
Return the predicate.
"""
# Will be changed to self.args[0] after args overriding is removed
return self._args[0]
@property
def arguments(self):
"""
Return the arguments which are applied to the predicate.
"""
# Will be changed to self.args[1:] after args overriding is removed
return self._args[1:]
def _eval_ask(self, assumptions):
return self.function.eval(self.arguments, assumptions)
@property
def binary_symbols(self):
from .ask import Q
if self.function == Q.is_true:
i = self.arguments[0]
if i.is_Boolean or i.is_Symbol:
return i.binary_symbols
if self.function in (Q.eq, Q.ne):
if true in self.arguments or false in self.arguments:
if self.arguments[0].is_Symbol:
return {self.arguments[0]}
elif self.arguments[1].is_Symbol:
return {self.arguments[1]}
return set()
class PredicateMeta(type):
def __new__(cls, clsname, bases, dct):
# If handler is not defined, assign empty dispatcher.
if "handler" not in dct:
name = f"Ask{clsname.capitalize()}Handler"
handler = Dispatcher(name, doc="Handler for key %s" % name)
dct["handler"] = handler
dct["_orig_doc"] = dct.get("__doc__", "")
return super().__new__(cls, clsname, bases, dct)
@property
def __doc__(cls):
handler = cls.handler
doc = cls._orig_doc
if cls is not Predicate and handler is not None:
doc += "Handler\n"
doc += " =======\n\n"
# Append the handler's doc without breaking sphinx documentation.
docs = [" Multiply dispatched method: %s" % handler.name]
if handler.doc:
for line in handler.doc.splitlines():
if not line:
continue
docs.append(" %s" % line)
other = []
for sig in handler.ordering[::-1]:
func = handler.funcs[sig]
if func.__doc__:
s = ' Inputs: <%s>' % str_signature(sig)
lines = []
for line in func.__doc__.splitlines():
lines.append(" %s" % line)
s += "\n".join(lines)
docs.append(s)
else:
other.append(str_signature(sig))
if other:
othersig = " Other signatures:"
for line in other:
othersig += "\n * %s" % line
docs.append(othersig)
doc += '\n\n'.join(docs)
return doc
class Predicate(Boolean, metaclass=PredicateMeta):
"""
Base class for mathematical predicates. It also serves as a
constructor for undefined predicate objects.
Explanation
===========
Predicate is a function that returns a boolean value [1].
Predicate function is object, and it is instance of predicate class.
When a predicate is applied to arguments, ``AppliedPredicate``
instance is returned. This merely wraps the argument and remain
unevaluated. To obtain the truth value of applied predicate, use the
function ``ask``.
Evaluation of predicate is done by multiple dispatching. You can
register new handler to the predicate to support new types.
Every predicate in SymPy can be accessed via the property of ``Q``.
For example, ``Q.even`` returns the predicate which checks if the
argument is even number.
To define a predicate which can be evaluated, you must subclass this
class, make an instance of it, and register it to ``Q``. After then,
dispatch the handler by argument types.
If you directly construct predicate using this class, you will get
``UndefinedPredicate`` which cannot be dispatched. This is useful
when you are building boolean expressions which do not need to be
evaluated.
Examples
========
Applying and evaluating to boolean value:
>>> from sympy import Q, ask
>>> ask(Q.prime(7))
True
You can define a new predicate by subclassing and dispatching. Here,
we define a predicate for sexy primes [2] as an example.
>>> from sympy import Predicate, Integer
>>> class SexyPrimePredicate(Predicate):
... name = "sexyprime"
>>> Q.sexyprime = SexyPrimePredicate()
>>> @Q.sexyprime.register(Integer, Integer)
... def _(int1, int2, assumptions):
... args = sorted([int1, int2])
... if not all(ask(Q.prime(a), assumptions) for a in args):
... return False
... return args[1] - args[0] == 6
>>> ask(Q.sexyprime(5, 11))
True
Direct constructing returns ``UndefinedPredicate``, which can be
applied but cannot be dispatched.
>>> from sympy import Predicate, Integer
>>> Q.P = Predicate("P")
>>> type(Q.P)
<class 'sympy.assumptions.assume.UndefinedPredicate'>
>>> Q.P(1)
Q.P(1)
>>> Q.P.register(Integer)(lambda expr, assump: True)
Traceback (most recent call last):
...
TypeError: <class 'sympy.assumptions.assume.UndefinedPredicate'> cannot be dispatched.
References
==========
.. [1] https://en.wikipedia.org/wiki/Predicate_%28mathematical_logic%29
.. [2] https://en.wikipedia.org/wiki/Sexy_prime
"""
is_Atom = True
def __new__(cls, *args, **kwargs):
if cls is Predicate:
return UndefinedPredicate(*args, **kwargs)
obj = super().__new__(cls, *args)
return obj
@property
def name(self):
# May be overridden
return type(self).__name__
@classmethod
def register(cls, *types, **kwargs):
"""
Register the signature to the handler.
"""
if cls.handler is None:
raise TypeError("%s cannot be dispatched." % type(cls))
return cls.handler.register(*types, **kwargs)
@classmethod
def register_many(cls, *types, **kwargs):
"""
Register multiple signatures to same handler.
"""
def _(func):
for t in types:
if not is_sequence(t):
t = (t,) # for convenience, allow passing `type` to mean `(type,)`
cls.register(*t, **kwargs)(func)
return _
def __call__(self, *args):
return AppliedPredicate(self, *args)
def eval(self, args, assumptions=True):
"""
Evaluate ``self(*args)`` under the given assumptions.
This uses only direct resolution methods, not logical inference.
"""
result = None
try:
result = self.handler(*args, assumptions=assumptions)
except NotImplementedError:
pass
return result
def _eval_refine(self, assumptions):
# When Predicate is no longer Boolean, delete this method
return self
class UndefinedPredicate(Predicate):
"""
Predicate without handler.
Explanation
===========
This predicate is generated by using ``Predicate`` directly for
construction. It does not have a handler, and evaluating this with
arguments is done by SAT solver.
Examples
========
>>> from sympy import Predicate, Q
>>> Q.P = Predicate('P')
>>> Q.P.func
<class 'sympy.assumptions.assume.UndefinedPredicate'>
>>> Q.P.name
Str('P')
"""
handler = None
def __new__(cls, name, handlers=None):
# "handlers" parameter supports old design
if not isinstance(name, Str):
name = Str(name)
obj = super(Boolean, cls).__new__(cls, name)
obj.handlers = handlers or []
return obj
@property
def name(self):
return self.args[0]
def _hashable_content(self):
return (self.name,)
def __getnewargs__(self):
return (self.name,)
def __call__(self, expr):
return AppliedPredicate(self, expr)
def add_handler(self, handler):
sympy_deprecation_warning(
"""
The AskHandler system is deprecated. Predicate.add_handler()
should be replaced with the multipledispatch handler of Predicate.
""",
deprecated_since_version="1.8",
active_deprecations_target='deprecated-askhandler',
)
self.handlers.append(handler)
def remove_handler(self, handler):
sympy_deprecation_warning(
"""
The AskHandler system is deprecated. Predicate.remove_handler()
should be replaced with the multipledispatch handler of Predicate.
""",
deprecated_since_version="1.8",
active_deprecations_target='deprecated-askhandler',
)
self.handlers.remove(handler)
def eval(self, args, assumptions=True):
# Support for deprecated design
# When old design is removed, this will always return None
sympy_deprecation_warning(
"""
The AskHandler system is deprecated. Evaluating UndefinedPredicate
objects should be replaced with the multipledispatch handler of
Predicate.
""",
deprecated_since_version="1.8",
active_deprecations_target='deprecated-askhandler',
stacklevel=5,
)
expr, = args
res, _res = None, None
mro = inspect.getmro(type(expr))
for handler in self.handlers:
cls = get_class(handler)
for subclass in mro:
eval_ = getattr(cls, subclass.__name__, None)
if eval_ is None:
continue
res = eval_(expr, assumptions)
# Do not stop if value returned is None
# Try to check for higher classes
if res is None:
continue
if _res is None:
_res = res
else:
# only check consistency if both resolutors have concluded
if _res != res:
raise ValueError('incompatible resolutors')
break
return res
@contextmanager
def assuming(*assumptions):
"""
Context manager for assumptions.
Examples
========
>>> from sympy import assuming, Q, ask
>>> from sympy.abc import x, y
>>> print(ask(Q.integer(x + y)))
None
>>> with assuming(Q.integer(x), Q.integer(y)):
... print(ask(Q.integer(x + y)))
True
"""
old_global_assumptions = global_assumptions.copy()
global_assumptions.update(assumptions)
try:
yield
finally:
global_assumptions.clear()
global_assumptions.update(old_global_assumptions)
@@ -0,0 +1,445 @@
"""
The classes used here are for the internal use of assumptions system
only and should not be used anywhere else as these do not possess the
signatures common to SymPy objects. For general use of logic constructs
please refer to sympy.logic classes And, Or, Not, etc.
"""
from itertools import combinations, product, zip_longest
from sympy.assumptions.assume import AppliedPredicate, Predicate
from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le
from sympy.core.singleton import S
from sympy.logic.boolalg import Or, And, Not, Xnor
from sympy.logic.boolalg import (Equivalent, ITE, Implies, Nand, Nor, Xor)
class Literal:
"""
The smallest element of a CNF object.
Parameters
==========
lit : Boolean expression
is_Not : bool
Examples
========
>>> from sympy import Q
>>> from sympy.assumptions.cnf import Literal
>>> from sympy.abc import x
>>> Literal(Q.even(x))
Literal(Q.even(x), False)
>>> Literal(~Q.even(x))
Literal(Q.even(x), True)
"""
def __new__(cls, lit, is_Not=False):
if isinstance(lit, Not):
lit = lit.args[0]
is_Not = True
elif isinstance(lit, (AND, OR, Literal)):
return ~lit if is_Not else lit
obj = super().__new__(cls)
obj.lit = lit
obj.is_Not = is_Not
return obj
@property
def arg(self):
return self.lit
def rcall(self, expr):
if callable(self.lit):
lit = self.lit(expr)
else:
lit = self.lit.apply(expr)
return type(self)(lit, self.is_Not)
def __invert__(self):
is_Not = not self.is_Not
return Literal(self.lit, is_Not)
def __str__(self):
return '{}({}, {})'.format(type(self).__name__, self.lit, self.is_Not)
__repr__ = __str__
def __eq__(self, other):
return self.arg == other.arg and self.is_Not == other.is_Not
def __hash__(self):
h = hash((type(self).__name__, self.arg, self.is_Not))
return h
class OR:
"""
A low-level implementation for Or
"""
def __init__(self, *args):
self._args = args
@property
def args(self):
return sorted(self._args, key=str)
def rcall(self, expr):
return type(self)(*[arg.rcall(expr)
for arg in self._args
])
def __invert__(self):
return AND(*[~arg for arg in self._args])
def __hash__(self):
return hash((type(self).__name__,) + tuple(self.args))
def __eq__(self, other):
return self.args == other.args
def __str__(self):
s = '(' + ' | '.join([str(arg) for arg in self.args]) + ')'
return s
__repr__ = __str__
class AND:
"""
A low-level implementation for And
"""
def __init__(self, *args):
self._args = args
def __invert__(self):
return OR(*[~arg for arg in self._args])
@property
def args(self):
return sorted(self._args, key=str)
def rcall(self, expr):
return type(self)(*[arg.rcall(expr)
for arg in self._args
])
def __hash__(self):
return hash((type(self).__name__,) + tuple(self.args))
def __eq__(self, other):
return self.args == other.args
def __str__(self):
s = '('+' & '.join([str(arg) for arg in self.args])+')'
return s
__repr__ = __str__
def to_NNF(expr, composite_map=None):
"""
Generates the Negation Normal Form of any boolean expression in terms
of AND, OR, and Literal objects.
Examples
========
>>> from sympy import Q, Eq
>>> from sympy.assumptions.cnf import to_NNF
>>> from sympy.abc import x, y
>>> expr = Q.even(x) & ~Q.positive(x)
>>> to_NNF(expr)
(Literal(Q.even(x), False) & Literal(Q.positive(x), True))
Supported boolean objects are converted to corresponding predicates.
>>> to_NNF(Eq(x, y))
Literal(Q.eq(x, y), False)
If ``composite_map`` argument is given, ``to_NNF`` decomposes the
specified predicate into a combination of primitive predicates.
>>> cmap = {Q.nonpositive: Q.negative | Q.zero}
>>> to_NNF(Q.nonpositive, cmap)
(Literal(Q.negative, False) | Literal(Q.zero, False))
>>> to_NNF(Q.nonpositive(x), cmap)
(Literal(Q.negative(x), False) | Literal(Q.zero(x), False))
"""
from sympy.assumptions.ask import Q
if composite_map is None:
composite_map = {}
binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le}
if type(expr) in binrelpreds:
pred = binrelpreds[type(expr)]
expr = pred(*expr.args)
if isinstance(expr, Not):
arg = expr.args[0]
tmp = to_NNF(arg, composite_map) # Strategy: negate the NNF of expr
return ~tmp
if isinstance(expr, Or):
return OR(*[to_NNF(x, composite_map) for x in Or.make_args(expr)])
if isinstance(expr, And):
return AND(*[to_NNF(x, composite_map) for x in And.make_args(expr)])
if isinstance(expr, Nand):
tmp = AND(*[to_NNF(x, composite_map) for x in expr.args])
return ~tmp
if isinstance(expr, Nor):
tmp = OR(*[to_NNF(x, composite_map) for x in expr.args])
return ~tmp
if isinstance(expr, Xor):
cnfs = []
for i in range(0, len(expr.args) + 1, 2):
for neg in combinations(expr.args, i):
clause = [~to_NNF(s, composite_map) if s in neg else to_NNF(s, composite_map)
for s in expr.args]
cnfs.append(OR(*clause))
return AND(*cnfs)
if isinstance(expr, Xnor):
cnfs = []
for i in range(0, len(expr.args) + 1, 2):
for neg in combinations(expr.args, i):
clause = [~to_NNF(s, composite_map) if s in neg else to_NNF(s, composite_map)
for s in expr.args]
cnfs.append(OR(*clause))
return ~AND(*cnfs)
if isinstance(expr, Implies):
L, R = to_NNF(expr.args[0], composite_map), to_NNF(expr.args[1], composite_map)
return OR(~L, R)
if isinstance(expr, Equivalent):
cnfs = []
for a, b in zip_longest(expr.args, expr.args[1:], fillvalue=expr.args[0]):
a = to_NNF(a, composite_map)
b = to_NNF(b, composite_map)
cnfs.append(OR(~a, b))
return AND(*cnfs)
if isinstance(expr, ITE):
L = to_NNF(expr.args[0], composite_map)
M = to_NNF(expr.args[1], composite_map)
R = to_NNF(expr.args[2], composite_map)
return AND(OR(~L, M), OR(L, R))
if isinstance(expr, AppliedPredicate):
pred, args = expr.function, expr.arguments
newpred = composite_map.get(pred, None)
if newpred is not None:
return to_NNF(newpred.rcall(*args), composite_map)
if isinstance(expr, Predicate):
newpred = composite_map.get(expr, None)
if newpred is not None:
return to_NNF(newpred, composite_map)
return Literal(expr)
def distribute_AND_over_OR(expr):
"""
Distributes AND over OR in the NNF expression.
Returns the result( Conjunctive Normal Form of expression)
as a CNF object.
"""
if not isinstance(expr, (AND, OR)):
tmp = set()
tmp.add(frozenset((expr,)))
return CNF(tmp)
if isinstance(expr, OR):
return CNF.all_or(*[distribute_AND_over_OR(arg)
for arg in expr._args])
if isinstance(expr, AND):
return CNF.all_and(*[distribute_AND_over_OR(arg)
for arg in expr._args])
class CNF:
"""
Class to represent CNF of a Boolean expression.
Consists of set of clauses, which themselves are stored as
frozenset of Literal objects.
Examples
========
>>> from sympy import Q
>>> from sympy.assumptions.cnf import CNF
>>> from sympy.abc import x
>>> cnf = CNF.from_prop(Q.real(x) & ~Q.zero(x))
>>> cnf.clauses
{frozenset({Literal(Q.zero(x), True)}),
frozenset({Literal(Q.negative(x), False),
Literal(Q.positive(x), False), Literal(Q.zero(x), False)})}
"""
def __init__(self, clauses=None):
if not clauses:
clauses = set()
self.clauses = clauses
def add(self, prop):
clauses = CNF.to_CNF(prop).clauses
self.add_clauses(clauses)
def __str__(self):
s = ' & '.join(
['(' + ' | '.join([str(lit) for lit in clause]) +')'
for clause in self.clauses]
)
return s
def extend(self, props):
for p in props:
self.add(p)
return self
def copy(self):
return CNF(set(self.clauses))
def add_clauses(self, clauses):
self.clauses |= clauses
@classmethod
def from_prop(cls, prop):
res = cls()
res.add(prop)
return res
def __iand__(self, other):
self.add_clauses(other.clauses)
return self
def all_predicates(self):
predicates = set()
for c in self.clauses:
predicates |= {arg.lit for arg in c}
return predicates
def _or(self, cnf):
clauses = set()
for a, b in product(self.clauses, cnf.clauses):
tmp = set(a)
tmp.update(b)
clauses.add(frozenset(tmp))
return CNF(clauses)
def _and(self, cnf):
clauses = self.clauses.union(cnf.clauses)
return CNF(clauses)
def _not(self):
clss = list(self.clauses)
ll = {frozenset((~x,)) for x in clss[-1]}
ll = CNF(ll)
for rest in clss[:-1]:
p = {frozenset((~x,)) for x in rest}
ll = ll._or(CNF(p))
return ll
def rcall(self, expr):
clause_list = []
for clause in self.clauses:
lits = [arg.rcall(expr) for arg in clause]
clause_list.append(OR(*lits))
expr = AND(*clause_list)
return distribute_AND_over_OR(expr)
@classmethod
def all_or(cls, *cnfs):
b = cnfs[0].copy()
for rest in cnfs[1:]:
b = b._or(rest)
return b
@classmethod
def all_and(cls, *cnfs):
b = cnfs[0].copy()
for rest in cnfs[1:]:
b = b._and(rest)
return b
@classmethod
def to_CNF(cls, expr):
from sympy.assumptions.facts import get_composite_predicates
expr = to_NNF(expr, get_composite_predicates())
expr = distribute_AND_over_OR(expr)
return expr
@classmethod
def CNF_to_cnf(cls, cnf):
"""
Converts CNF object to SymPy's boolean expression
retaining the form of expression.
"""
def remove_literal(arg):
return Not(arg.lit) if arg.is_Not else arg.lit
return And(*(Or(*(remove_literal(arg) for arg in clause)) for clause in cnf.clauses))
class EncodedCNF:
"""
Class for encoding the CNF expression.
"""
def __init__(self, data=None, encoding=None):
if not data and not encoding:
data = []
encoding = {}
self.data = data
self.encoding = encoding
self._symbols = list(encoding.keys())
def from_cnf(self, cnf):
self._symbols = list(cnf.all_predicates())
n = len(self._symbols)
self.encoding = dict(zip(self._symbols, range(1, n + 1)))
self.data = [self.encode(clause) for clause in cnf.clauses]
@property
def symbols(self):
return self._symbols
@property
def variables(self):
return range(1, len(self._symbols) + 1)
def copy(self):
new_data = [set(clause) for clause in self.data]
return EncodedCNF(new_data, dict(self.encoding))
def add_prop(self, prop):
cnf = CNF.from_prop(prop)
self.add_from_cnf(cnf)
def add_from_cnf(self, cnf):
clauses = [self.encode(clause) for clause in cnf.clauses]
self.data += clauses
def encode_arg(self, arg):
literal = arg.lit
value = self.encoding.get(literal, None)
if value is None:
n = len(self._symbols)
self._symbols.append(literal)
value = self.encoding[literal] = n + 1
if arg.is_Not:
return -value
else:
return value
def encode(self, clause):
return {self.encode_arg(arg) if not arg.lit == S.false else 0 for arg in clause}
@@ -0,0 +1,270 @@
"""
Known facts in assumptions module.
This module defines the facts between unary predicates in ``get_known_facts()``,
and supports functions to generate the contents in
``sympy.assumptions.ask_generated`` file.
"""
from sympy.assumptions.ask import Q
from sympy.assumptions.assume import AppliedPredicate
from sympy.core.cache import cacheit
from sympy.core.symbol import Symbol
from sympy.logic.boolalg import (to_cnf, And, Not, Implies, Equivalent,
Exclusive,)
from sympy.logic.inference import satisfiable
@cacheit
def get_composite_predicates():
# To reduce the complexity of sat solver, these predicates are
# transformed into the combination of primitive predicates.
return {
Q.real : Q.negative | Q.zero | Q.positive,
Q.integer : Q.even | Q.odd,
Q.nonpositive : Q.negative | Q.zero,
Q.nonzero : Q.negative | Q.positive,
Q.nonnegative : Q.zero | Q.positive,
Q.extended_real : Q.negative_infinite | Q.negative | Q.zero | Q.positive | Q.positive_infinite,
Q.extended_positive: Q.positive | Q.positive_infinite,
Q.extended_negative: Q.negative | Q.negative_infinite,
Q.extended_nonzero: Q.negative_infinite | Q.negative | Q.positive | Q.positive_infinite,
Q.extended_nonpositive: Q.negative_infinite | Q.negative | Q.zero,
Q.extended_nonnegative: Q.zero | Q.positive | Q.positive_infinite,
Q.complex : Q.algebraic | Q.transcendental
}
@cacheit
def get_known_facts(x=None):
"""
Facts between unary predicates.
Parameters
==========
x : Symbol, optional
Placeholder symbol for unary facts. Default is ``Symbol('x')``.
Returns
=======
fact : Known facts in conjugated normal form.
"""
if x is None:
x = Symbol('x')
fact = And(
get_number_facts(x),
get_matrix_facts(x)
)
return fact
@cacheit
def get_number_facts(x = None):
"""
Facts between unary number predicates.
Parameters
==========
x : Symbol, optional
Placeholder symbol for unary facts. Default is ``Symbol('x')``.
Returns
=======
fact : Known facts in conjugated normal form.
"""
if x is None:
x = Symbol('x')
fact = And(
# primitive predicates for extended real exclude each other.
Exclusive(Q.negative_infinite(x), Q.negative(x), Q.zero(x),
Q.positive(x), Q.positive_infinite(x)),
# build complex plane
Exclusive(Q.real(x), Q.imaginary(x)),
Implies(Q.real(x) | Q.imaginary(x), Q.complex(x)),
# other subsets of complex
Exclusive(Q.transcendental(x), Q.algebraic(x)),
Equivalent(Q.real(x), Q.rational(x) | Q.irrational(x)),
Exclusive(Q.irrational(x), Q.rational(x)),
Implies(Q.rational(x), Q.algebraic(x)),
# integers
Exclusive(Q.even(x), Q.odd(x)),
Implies(Q.integer(x), Q.rational(x)),
Implies(Q.zero(x), Q.even(x)),
Exclusive(Q.composite(x), Q.prime(x)),
Implies(Q.composite(x) | Q.prime(x), Q.integer(x) & Q.positive(x)),
Implies(Q.even(x) & Q.positive(x) & ~Q.prime(x), Q.composite(x)),
# hermitian and antihermitian
Implies(Q.real(x), Q.hermitian(x)),
Implies(Q.imaginary(x), Q.antihermitian(x)),
Implies(Q.zero(x), Q.hermitian(x) | Q.antihermitian(x)),
# define finity and infinity, and build extended real line
Exclusive(Q.infinite(x), Q.finite(x)),
Implies(Q.complex(x), Q.finite(x)),
Implies(Q.negative_infinite(x) | Q.positive_infinite(x), Q.infinite(x)),
# commutativity
Implies(Q.finite(x) | Q.infinite(x), Q.commutative(x)),
)
return fact
@cacheit
def get_matrix_facts(x = None):
"""
Facts between unary matrix predicates.
Parameters
==========
x : Symbol, optional
Placeholder symbol for unary facts. Default is ``Symbol('x')``.
Returns
=======
fact : Known facts in conjugated normal form.
"""
if x is None:
x = Symbol('x')
fact = And(
# matrices
Implies(Q.orthogonal(x), Q.positive_definite(x)),
Implies(Q.orthogonal(x), Q.unitary(x)),
Implies(Q.unitary(x) & Q.real_elements(x), Q.orthogonal(x)),
Implies(Q.unitary(x), Q.normal(x)),
Implies(Q.unitary(x), Q.invertible(x)),
Implies(Q.normal(x), Q.square(x)),
Implies(Q.diagonal(x), Q.normal(x)),
Implies(Q.positive_definite(x), Q.invertible(x)),
Implies(Q.diagonal(x), Q.upper_triangular(x)),
Implies(Q.diagonal(x), Q.lower_triangular(x)),
Implies(Q.lower_triangular(x), Q.triangular(x)),
Implies(Q.upper_triangular(x), Q.triangular(x)),
Implies(Q.triangular(x), Q.upper_triangular(x) | Q.lower_triangular(x)),
Implies(Q.upper_triangular(x) & Q.lower_triangular(x), Q.diagonal(x)),
Implies(Q.diagonal(x), Q.symmetric(x)),
Implies(Q.unit_triangular(x), Q.triangular(x)),
Implies(Q.invertible(x), Q.fullrank(x)),
Implies(Q.invertible(x), Q.square(x)),
Implies(Q.symmetric(x), Q.square(x)),
Implies(Q.fullrank(x) & Q.square(x), Q.invertible(x)),
Equivalent(Q.invertible(x), ~Q.singular(x)),
Implies(Q.integer_elements(x), Q.real_elements(x)),
Implies(Q.real_elements(x), Q.complex_elements(x)),
)
return fact
def generate_known_facts_dict(keys, fact):
"""
Computes and returns a dictionary which contains the relations between
unary predicates.
Each key is a predicate, and item is two groups of predicates.
First group contains the predicates which are implied by the key, and
second group contains the predicates which are rejected by the key.
All predicates in *keys* and *fact* must be unary and have same placeholder
symbol.
Parameters
==========
keys : list of AppliedPredicate instances.
fact : Fact between predicates in conjugated normal form.
Examples
========
>>> from sympy import Q, And, Implies
>>> from sympy.assumptions.facts import generate_known_facts_dict
>>> from sympy.abc import x
>>> keys = [Q.even(x), Q.odd(x), Q.zero(x)]
>>> fact = And(Implies(Q.even(x), ~Q.odd(x)),
... Implies(Q.zero(x), Q.even(x)))
>>> generate_known_facts_dict(keys, fact)
{Q.even: ({Q.even}, {Q.odd}),
Q.odd: ({Q.odd}, {Q.even, Q.zero}),
Q.zero: ({Q.even, Q.zero}, {Q.odd})}
"""
fact_cnf = to_cnf(fact)
mapping = single_fact_lookup(keys, fact_cnf)
ret = {}
for key, value in mapping.items():
implied = set()
rejected = set()
for expr in value:
if isinstance(expr, AppliedPredicate):
implied.add(expr.function)
elif isinstance(expr, Not):
pred = expr.args[0]
rejected.add(pred.function)
ret[key.function] = (implied, rejected)
return ret
@cacheit
def get_known_facts_keys():
"""
Return every unary predicates registered to ``Q``.
This function is used to generate the keys for
``generate_known_facts_dict``.
"""
# exclude polyadic predicates
exclude = {Q.eq, Q.ne, Q.gt, Q.lt, Q.ge, Q.le}
result = []
for attr in Q.__class__.__dict__:
if attr.startswith('__'):
continue
pred = getattr(Q, attr)
if pred in exclude:
continue
result.append(pred)
return result
def single_fact_lookup(known_facts_keys, known_facts_cnf):
# Return the dictionary for quick lookup of single fact
mapping = {}
for key in known_facts_keys:
mapping[key] = {key}
for other_key in known_facts_keys:
if other_key != key:
if ask_full_inference(other_key, key, known_facts_cnf):
mapping[key].add(other_key)
if ask_full_inference(~other_key, key, known_facts_cnf):
mapping[key].add(~other_key)
return mapping
def ask_full_inference(proposition, assumptions, known_facts_cnf):
"""
Method for inferring properties about objects.
"""
if not satisfiable(And(known_facts_cnf, assumptions, proposition)):
return False
if not satisfiable(And(known_facts_cnf, assumptions, Not(proposition))):
return True
return None
@@ -0,0 +1,13 @@
"""
Multipledispatch handlers for ``Predicate`` are implemented here.
Handlers in this module are not directly imported to other modules in
order to avoid circular import problem.
"""
from .common import (AskHandler, CommonHandler,
test_closed_group)
__all__ = [
'AskHandler', 'CommonHandler',
'test_closed_group'
]
@@ -0,0 +1,273 @@
"""
This module contains query handlers responsible for calculus queries:
infinitesimal, finite, etc.
"""
from sympy.assumptions import Q, ask
from sympy.core import Expr, Add, Mul, Pow, Symbol
from sympy.core.numbers import (NegativeInfinity, GoldenRatio,
Infinity, Exp1, ComplexInfinity, ImaginaryUnit, NaN, Number, Pi, E,
TribonacciConstant)
from sympy.functions import cos, exp, log, sign, sin
from sympy.logic.boolalg import conjuncts
from ..predicates.calculus import (FinitePredicate, InfinitePredicate,
PositiveInfinitePredicate, NegativeInfinitePredicate)
# FinitePredicate
@FinitePredicate.register(Symbol)
def _(expr, assumptions):
"""
Handles Symbol.
"""
if expr.is_finite is not None:
return expr.is_finite
if Q.finite(expr) in conjuncts(assumptions):
return True
return None
@FinitePredicate.register(Add)
def _(expr, assumptions):
"""
Return True if expr is bounded, False if not and None if unknown.
Truth Table:
+-------+-----+-----------+-----------+
| | | | |
| | B | U | ? |
| | | | |
+-------+-----+---+---+---+---+---+---+
| | | | | | | | |
| | |'+'|'-'|'x'|'+'|'-'|'x'|
| | | | | | | | |
+-------+-----+---+---+---+---+---+---+
| | | | |
| B | B | U | ? |
| | | | |
+---+---+-----+---+---+---+---+---+---+
| | | | | | | | | |
| |'+'| | U | ? | ? | U | ? | ? |
| | | | | | | | | |
| +---+-----+---+---+---+---+---+---+
| | | | | | | | | |
| U |'-'| | ? | U | ? | ? | U | ? |
| | | | | | | | | |
| +---+-----+---+---+---+---+---+---+
| | | | | |
| |'x'| | ? | ? |
| | | | | |
+---+---+-----+---+---+---+---+---+---+
| | | | |
| ? | | | ? |
| | | | |
+-------+-----+-----------+---+---+---+
* 'B' = Bounded
* 'U' = Unbounded
* '?' = unknown boundedness
* '+' = positive sign
* '-' = negative sign
* 'x' = sign unknown
* All Bounded -> True
* 1 Unbounded and the rest Bounded -> False
* >1 Unbounded, all with same known sign -> False
* Any Unknown and unknown sign -> None
* Else -> None
When the signs are not the same you can have an undefined
result as in oo - oo, hence 'bounded' is also undefined.
"""
sign = -1 # sign of unknown or infinite
result = True
for arg in expr.args:
_bounded = ask(Q.finite(arg), assumptions)
if _bounded:
continue
s = ask(Q.extended_positive(arg), assumptions)
# if there has been more than one sign or if the sign of this arg
# is None and Bounded is None or there was already
# an unknown sign, return None
if sign != -1 and s != sign or \
s is None and None in (_bounded, sign):
return None
else:
sign = s
# once False, do not change
if result is not False:
result = _bounded
return result
@FinitePredicate.register(Mul)
def _(expr, assumptions):
"""
Return True if expr is bounded, False if not and None if unknown.
Truth Table:
+---+---+---+--------+
| | | | |
| | B | U | ? |
| | | | |
+---+---+---+---+----+
| | | | | |
| | | | s | /s |
| | | | | |
+---+---+---+---+----+
| | | | |
| B | B | U | ? |
| | | | |
+---+---+---+---+----+
| | | | | |
| U | | U | U | ? |
| | | | | |
+---+---+---+---+----+
| | | | |
| ? | | | ? |
| | | | |
+---+---+---+---+----+
* B = Bounded
* U = Unbounded
* ? = unknown boundedness
* s = signed (hence nonzero)
* /s = not signed
"""
result = True
possible_zero = False
for arg in expr.args:
_bounded = ask(Q.finite(arg), assumptions)
if _bounded:
if ask(Q.zero(arg), assumptions) is not False:
if result is False:
return None
possible_zero = True
elif _bounded is None:
if result is None:
return None
if ask(Q.extended_nonzero(arg), assumptions) is None:
return None
if result is not False:
result = None
else:
if possible_zero:
return None
result = False
return result
@FinitePredicate.register(Pow)
def _(expr, assumptions):
"""
* Unbounded ** NonZero -> Unbounded
* Bounded ** Bounded -> Bounded
* Abs()<=1 ** Positive -> Bounded
* Abs()>=1 ** Negative -> Bounded
* Otherwise unknown
"""
if expr.base == E:
return ask(Q.finite(expr.exp), assumptions)
base_bounded = ask(Q.finite(expr.base), assumptions)
exp_bounded = ask(Q.finite(expr.exp), assumptions)
if base_bounded is None and exp_bounded is None: # Common Case
return None
if base_bounded is False and ask(Q.extended_nonzero(expr.exp), assumptions):
return False
if base_bounded and exp_bounded:
is_base_zero = ask(Q.zero(expr.base),assumptions)
is_exp_negative = ask(Q.negative(expr.exp),assumptions)
if is_base_zero is True and is_exp_negative is True:
return False
if is_base_zero is not False and is_exp_negative is not False:
return None
return True
if (abs(expr.base) <= 1) == True and ask(Q.extended_positive(expr.exp), assumptions):
return True
if (abs(expr.base) >= 1) == True and ask(Q.extended_negative(expr.exp), assumptions):
return True
if (abs(expr.base) >= 1) == True and exp_bounded is False:
return False
return None
@FinitePredicate.register(exp)
def _(expr, assumptions):
return ask(Q.finite(expr.exp), assumptions)
@FinitePredicate.register(log)
def _(expr, assumptions):
# After complex -> finite fact is registered to new assumption system,
# querying Q.infinite may be removed.
if ask(Q.infinite(expr.args[0]), assumptions):
return False
return ask(~Q.zero(expr.args[0]), assumptions)
@FinitePredicate.register_many(cos, sin, Number, Pi, Exp1, GoldenRatio,
TribonacciConstant, ImaginaryUnit, sign)
def _(expr, assumptions):
return True
@FinitePredicate.register_many(ComplexInfinity, Infinity, NegativeInfinity)
def _(expr, assumptions):
return False
@FinitePredicate.register(NaN)
def _(expr, assumptions):
return None
# InfinitePredicate
@InfinitePredicate.register(Expr)
def _(expr, assumptions):
is_finite = Q.finite(expr)._eval_ask(assumptions)
if is_finite is None:
return None
return not is_finite
# PositiveInfinitePredicate
@PositiveInfinitePredicate.register(Infinity)
def _(expr, assumptions):
return True
@PositiveInfinitePredicate.register_many(NegativeInfinity, ComplexInfinity)
def _(expr, assumptions):
return False
# NegativeInfinitePredicate
@NegativeInfinitePredicate.register(NegativeInfinity)
def _(expr, assumptions):
return True
@NegativeInfinitePredicate.register_many(Infinity, ComplexInfinity)
def _(expr, assumptions):
return False
@@ -0,0 +1,164 @@
"""
This module defines base class for handlers and some core handlers:
``Q.commutative`` and ``Q.is_true``.
"""
from sympy.assumptions import Q, ask, AppliedPredicate
from sympy.core import Basic, Symbol
from sympy.core.logic import _fuzzy_group, fuzzy_and, fuzzy_or
from sympy.core.numbers import NaN, Number
from sympy.logic.boolalg import (And, BooleanTrue, BooleanFalse, conjuncts,
Equivalent, Implies, Not, Or)
from sympy.utilities.exceptions import sympy_deprecation_warning
from ..predicates.common import CommutativePredicate, IsTruePredicate
class AskHandler:
"""Base class that all Ask Handlers must inherit."""
def __new__(cls, *args, **kwargs):
sympy_deprecation_warning(
"""
The AskHandler system is deprecated. The AskHandler class should
be replaced with the multipledispatch handler of Predicate
""",
deprecated_since_version="1.8",
active_deprecations_target='deprecated-askhandler',
)
return super().__new__(cls, *args, **kwargs)
class CommonHandler(AskHandler):
# Deprecated
"""Defines some useful methods common to most Handlers. """
@staticmethod
def AlwaysTrue(expr, assumptions):
return True
@staticmethod
def AlwaysFalse(expr, assumptions):
return False
@staticmethod
def AlwaysNone(expr, assumptions):
return None
NaN = AlwaysFalse
# CommutativePredicate
@CommutativePredicate.register(Symbol)
def _(expr, assumptions):
"""Objects are expected to be commutative unless otherwise stated"""
assumps = conjuncts(assumptions)
if expr.is_commutative is not None:
return expr.is_commutative and not ~Q.commutative(expr) in assumps
if Q.commutative(expr) in assumps:
return True
elif ~Q.commutative(expr) in assumps:
return False
return True
@CommutativePredicate.register(Basic)
def _(expr, assumptions):
for arg in expr.args:
if not ask(Q.commutative(arg), assumptions):
return False
return True
@CommutativePredicate.register(Number)
def _(expr, assumptions):
return True
@CommutativePredicate.register(NaN)
def _(expr, assumptions):
return True
# IsTruePredicate
@IsTruePredicate.register(bool)
def _(expr, assumptions):
return expr
@IsTruePredicate.register(BooleanTrue)
def _(expr, assumptions):
return True
@IsTruePredicate.register(BooleanFalse)
def _(expr, assumptions):
return False
@IsTruePredicate.register(AppliedPredicate)
def _(expr, assumptions):
return ask(expr, assumptions)
@IsTruePredicate.register(Not)
def _(expr, assumptions):
arg = expr.args[0]
if arg.is_Symbol:
# symbol used as abstract boolean object
return None
value = ask(arg, assumptions=assumptions)
if value in (True, False):
return not value
else:
return None
@IsTruePredicate.register(Or)
def _(expr, assumptions):
result = False
for arg in expr.args:
p = ask(arg, assumptions=assumptions)
if p is True:
return True
if p is None:
result = None
return result
@IsTruePredicate.register(And)
def _(expr, assumptions):
result = True
for arg in expr.args:
p = ask(arg, assumptions=assumptions)
if p is False:
return False
if p is None:
result = None
return result
@IsTruePredicate.register(Implies)
def _(expr, assumptions):
p, q = expr.args
return ask(~p | q, assumptions=assumptions)
@IsTruePredicate.register(Equivalent)
def _(expr, assumptions):
p, q = expr.args
pt = ask(p, assumptions=assumptions)
if pt is None:
return None
qt = ask(q, assumptions=assumptions)
if qt is None:
return None
return pt == qt
#### Helper methods
def test_closed_group(expr, assumptions, key):
"""
Test for membership in a group with respect
to the current operation.
"""
return _fuzzy_group(
(ask(key(a), assumptions) for a in expr.args), quick_exit=True)
def ask_all(*queries, assumptions):
return fuzzy_and(
(ask(query, assumptions) for query in queries))
def ask_any(*queries, assumptions):
return fuzzy_or(
(ask(query, assumptions) for query in queries))
@@ -0,0 +1,716 @@
"""
This module contains query handlers responsible for Matrices queries:
Square, Symmetric, Invertible etc.
"""
from sympy.logic.boolalg import conjuncts
from sympy.assumptions import Q, ask
from sympy.assumptions.handlers import test_closed_group
from sympy.matrices import MatrixBase
from sympy.matrices.expressions import (BlockMatrix, BlockDiagMatrix, Determinant,
DiagMatrix, DiagonalMatrix, HadamardProduct, Identity, Inverse, MatAdd, MatMul,
MatPow, MatrixExpr, MatrixSlice, MatrixSymbol, OneMatrix, Trace, Transpose,
ZeroMatrix)
from sympy.matrices.expressions.blockmatrix import reblock_2x2
from sympy.matrices.expressions.factorizations import Factorization
from sympy.matrices.expressions.fourier import DFT
from sympy.core.logic import fuzzy_and
from sympy.utilities.iterables import sift
from sympy.core import Basic
from ..predicates.matrices import (SquarePredicate, SymmetricPredicate,
InvertiblePredicate, OrthogonalPredicate, UnitaryPredicate,
FullRankPredicate, PositiveDefinitePredicate, UpperTriangularPredicate,
LowerTriangularPredicate, DiagonalPredicate, IntegerElementsPredicate,
RealElementsPredicate, ComplexElementsPredicate)
def _Factorization(predicate, expr, assumptions):
if predicate in expr.predicates:
return True
# SquarePredicate
@SquarePredicate.register(MatrixExpr)
def _(expr, assumptions):
return expr.shape[0] == expr.shape[1]
# SymmetricPredicate
@SymmetricPredicate.register(MatMul)
def _(expr, assumptions):
factor, mmul = expr.as_coeff_mmul()
if all(ask(Q.symmetric(arg), assumptions) for arg in mmul.args):
return True
# TODO: implement sathandlers system for the matrices.
# Now it duplicates the general fact: Implies(Q.diagonal, Q.symmetric).
if ask(Q.diagonal(expr), assumptions):
return True
if len(mmul.args) >= 2 and mmul.args[0] == mmul.args[-1].T:
if len(mmul.args) == 2:
return True
return ask(Q.symmetric(MatMul(*mmul.args[1:-1])), assumptions)
@SymmetricPredicate.register(MatPow)
def _(expr, assumptions):
# only for integer powers
base, exp = expr.args
int_exp = ask(Q.integer(exp), assumptions)
if not int_exp:
return None
non_negative = ask(~Q.negative(exp), assumptions)
if (non_negative or non_negative == False
and ask(Q.invertible(base), assumptions)):
return ask(Q.symmetric(base), assumptions)
return None
@SymmetricPredicate.register(MatAdd)
def _(expr, assumptions):
return all(ask(Q.symmetric(arg), assumptions) for arg in expr.args)
@SymmetricPredicate.register(MatrixSymbol)
def _(expr, assumptions):
if not expr.is_square:
return False
# TODO: implement sathandlers system for the matrices.
# Now it duplicates the general fact: Implies(Q.diagonal, Q.symmetric).
if ask(Q.diagonal(expr), assumptions):
return True
if Q.symmetric(expr) in conjuncts(assumptions):
return True
@SymmetricPredicate.register_many(OneMatrix, ZeroMatrix)
def _(expr, assumptions):
return ask(Q.square(expr), assumptions)
@SymmetricPredicate.register_many(Inverse, Transpose)
def _(expr, assumptions):
return ask(Q.symmetric(expr.arg), assumptions)
@SymmetricPredicate.register(MatrixSlice)
def _(expr, assumptions):
# TODO: implement sathandlers system for the matrices.
# Now it duplicates the general fact: Implies(Q.diagonal, Q.symmetric).
if ask(Q.diagonal(expr), assumptions):
return True
if not expr.on_diag:
return None
else:
return ask(Q.symmetric(expr.parent), assumptions)
@SymmetricPredicate.register(Identity)
def _(expr, assumptions):
return True
# InvertiblePredicate
@InvertiblePredicate.register(MatMul)
def _(expr, assumptions):
factor, mmul = expr.as_coeff_mmul()
if all(ask(Q.invertible(arg), assumptions) for arg in mmul.args):
return True
if any(ask(Q.invertible(arg), assumptions) is False
for arg in mmul.args):
return False
@InvertiblePredicate.register(MatPow)
def _(expr, assumptions):
# only for integer powers
base, exp = expr.args
int_exp = ask(Q.integer(exp), assumptions)
if not int_exp:
return None
if exp.is_negative == False:
return ask(Q.invertible(base), assumptions)
return None
@InvertiblePredicate.register(MatAdd)
def _(expr, assumptions):
return None
@InvertiblePredicate.register(MatrixSymbol)
def _(expr, assumptions):
if not expr.is_square:
return False
if Q.invertible(expr) in conjuncts(assumptions):
return True
@InvertiblePredicate.register_many(Identity, Inverse)
def _(expr, assumptions):
return True
@InvertiblePredicate.register(ZeroMatrix)
def _(expr, assumptions):
return False
@InvertiblePredicate.register(OneMatrix)
def _(expr, assumptions):
return expr.shape[0] == 1 and expr.shape[1] == 1
@InvertiblePredicate.register(Transpose)
def _(expr, assumptions):
return ask(Q.invertible(expr.arg), assumptions)
@InvertiblePredicate.register(MatrixSlice)
def _(expr, assumptions):
if not expr.on_diag:
return None
else:
return ask(Q.invertible(expr.parent), assumptions)
@InvertiblePredicate.register(MatrixBase)
def _(expr, assumptions):
if not expr.is_square:
return False
return expr.rank() == expr.rows
@InvertiblePredicate.register(MatrixExpr)
def _(expr, assumptions):
if not expr.is_square:
return False
return None
@InvertiblePredicate.register(BlockMatrix)
def _(expr, assumptions):
if not expr.is_square:
return False
if expr.blockshape == (1, 1):
return ask(Q.invertible(expr.blocks[0, 0]), assumptions)
expr = reblock_2x2(expr)
if expr.blockshape == (2, 2):
[[A, B], [C, D]] = expr.blocks.tolist()
if ask(Q.invertible(A), assumptions) == True:
invertible = ask(Q.invertible(D - C * A.I * B), assumptions)
if invertible is not None:
return invertible
if ask(Q.invertible(B), assumptions) == True:
invertible = ask(Q.invertible(C - D * B.I * A), assumptions)
if invertible is not None:
return invertible
if ask(Q.invertible(C), assumptions) == True:
invertible = ask(Q.invertible(B - A * C.I * D), assumptions)
if invertible is not None:
return invertible
if ask(Q.invertible(D), assumptions) == True:
invertible = ask(Q.invertible(A - B * D.I * C), assumptions)
if invertible is not None:
return invertible
return None
@InvertiblePredicate.register(BlockDiagMatrix)
def _(expr, assumptions):
if expr.rowblocksizes != expr.colblocksizes:
return None
return fuzzy_and([ask(Q.invertible(a), assumptions) for a in expr.diag])
# OrthogonalPredicate
@OrthogonalPredicate.register(MatMul)
def _(expr, assumptions):
factor, mmul = expr.as_coeff_mmul()
if (all(ask(Q.orthogonal(arg), assumptions) for arg in mmul.args) and
factor == 1):
return True
if any(ask(Q.invertible(arg), assumptions) is False
for arg in mmul.args):
return False
@OrthogonalPredicate.register(MatPow)
def _(expr, assumptions):
# only for integer powers
base, exp = expr.args
int_exp = ask(Q.integer(exp), assumptions)
if int_exp:
return ask(Q.orthogonal(base), assumptions)
return None
@OrthogonalPredicate.register(MatAdd)
def _(expr, assumptions):
if (len(expr.args) == 1 and
ask(Q.orthogonal(expr.args[0]), assumptions)):
return True
@OrthogonalPredicate.register(MatrixSymbol)
def _(expr, assumptions):
if (not expr.is_square or
ask(Q.invertible(expr), assumptions) is False):
return False
if Q.orthogonal(expr) in conjuncts(assumptions):
return True
@OrthogonalPredicate.register(Identity)
def _(expr, assumptions):
return True
@OrthogonalPredicate.register(ZeroMatrix)
def _(expr, assumptions):
return False
@OrthogonalPredicate.register_many(Inverse, Transpose)
def _(expr, assumptions):
return ask(Q.orthogonal(expr.arg), assumptions)
@OrthogonalPredicate.register(MatrixSlice)
def _(expr, assumptions):
if not expr.on_diag:
return None
else:
return ask(Q.orthogonal(expr.parent), assumptions)
@OrthogonalPredicate.register(Factorization)
def _(expr, assumptions):
return _Factorization(Q.orthogonal, expr, assumptions)
# UnitaryPredicate
@UnitaryPredicate.register(MatMul)
def _(expr, assumptions):
factor, mmul = expr.as_coeff_mmul()
if (all(ask(Q.unitary(arg), assumptions) for arg in mmul.args) and
abs(factor) == 1):
return True
if any(ask(Q.invertible(arg), assumptions) is False
for arg in mmul.args):
return False
@UnitaryPredicate.register(MatPow)
def _(expr, assumptions):
# only for integer powers
base, exp = expr.args
int_exp = ask(Q.integer(exp), assumptions)
if int_exp:
return ask(Q.unitary(base), assumptions)
return None
@UnitaryPredicate.register(MatrixSymbol)
def _(expr, assumptions):
if (not expr.is_square or
ask(Q.invertible(expr), assumptions) is False):
return False
if Q.unitary(expr) in conjuncts(assumptions):
return True
@UnitaryPredicate.register_many(Inverse, Transpose)
def _(expr, assumptions):
return ask(Q.unitary(expr.arg), assumptions)
@UnitaryPredicate.register(MatrixSlice)
def _(expr, assumptions):
if not expr.on_diag:
return None
else:
return ask(Q.unitary(expr.parent), assumptions)
@UnitaryPredicate.register_many(DFT, Identity)
def _(expr, assumptions):
return True
@UnitaryPredicate.register(ZeroMatrix)
def _(expr, assumptions):
return False
@UnitaryPredicate.register(Factorization)
def _(expr, assumptions):
return _Factorization(Q.unitary, expr, assumptions)
# FullRankPredicate
@FullRankPredicate.register(MatMul)
def _(expr, assumptions):
if all(ask(Q.fullrank(arg), assumptions) for arg in expr.args):
return True
@FullRankPredicate.register(MatPow)
def _(expr, assumptions):
# only for integer powers
base, exp = expr.args
int_exp = ask(Q.integer(exp), assumptions)
if int_exp and ask(~Q.negative(exp), assumptions):
return ask(Q.fullrank(base), assumptions)
return None
@FullRankPredicate.register(Identity)
def _(expr, assumptions):
return True
@FullRankPredicate.register(ZeroMatrix)
def _(expr, assumptions):
return False
@FullRankPredicate.register(OneMatrix)
def _(expr, assumptions):
return expr.shape[0] == 1 and expr.shape[1] == 1
@FullRankPredicate.register_many(Inverse, Transpose)
def _(expr, assumptions):
return ask(Q.fullrank(expr.arg), assumptions)
@FullRankPredicate.register(MatrixSlice)
def _(expr, assumptions):
if ask(Q.orthogonal(expr.parent), assumptions):
return True
# PositiveDefinitePredicate
@PositiveDefinitePredicate.register(MatMul)
def _(expr, assumptions):
factor, mmul = expr.as_coeff_mmul()
if (all(ask(Q.positive_definite(arg), assumptions)
for arg in mmul.args) and factor > 0):
return True
if (len(mmul.args) >= 2
and mmul.args[0] == mmul.args[-1].T
and ask(Q.fullrank(mmul.args[0]), assumptions)):
return ask(Q.positive_definite(
MatMul(*mmul.args[1:-1])), assumptions)
@PositiveDefinitePredicate.register(MatPow)
def _(expr, assumptions):
# a power of a positive definite matrix is positive definite
if ask(Q.positive_definite(expr.args[0]), assumptions):
return True
@PositiveDefinitePredicate.register(MatAdd)
def _(expr, assumptions):
if all(ask(Q.positive_definite(arg), assumptions)
for arg in expr.args):
return True
@PositiveDefinitePredicate.register(MatrixSymbol)
def _(expr, assumptions):
if not expr.is_square:
return False
if Q.positive_definite(expr) in conjuncts(assumptions):
return True
@PositiveDefinitePredicate.register(Identity)
def _(expr, assumptions):
return True
@PositiveDefinitePredicate.register(ZeroMatrix)
def _(expr, assumptions):
return False
@PositiveDefinitePredicate.register(OneMatrix)
def _(expr, assumptions):
return expr.shape[0] == 1 and expr.shape[1] == 1
@PositiveDefinitePredicate.register_many(Inverse, Transpose)
def _(expr, assumptions):
return ask(Q.positive_definite(expr.arg), assumptions)
@PositiveDefinitePredicate.register(MatrixSlice)
def _(expr, assumptions):
if not expr.on_diag:
return None
else:
return ask(Q.positive_definite(expr.parent), assumptions)
# UpperTriangularPredicate
@UpperTriangularPredicate.register(MatMul)
def _(expr, assumptions):
factor, matrices = expr.as_coeff_matrices()
if all(ask(Q.upper_triangular(m), assumptions) for m in matrices):
return True
@UpperTriangularPredicate.register(MatAdd)
def _(expr, assumptions):
if all(ask(Q.upper_triangular(arg), assumptions) for arg in expr.args):
return True
@UpperTriangularPredicate.register(MatPow)
def _(expr, assumptions):
# only for integer powers
base, exp = expr.args
int_exp = ask(Q.integer(exp), assumptions)
if not int_exp:
return None
non_negative = ask(~Q.negative(exp), assumptions)
if (non_negative or non_negative == False
and ask(Q.invertible(base), assumptions)):
return ask(Q.upper_triangular(base), assumptions)
return None
@UpperTriangularPredicate.register(MatrixSymbol)
def _(expr, assumptions):
if Q.upper_triangular(expr) in conjuncts(assumptions):
return True
@UpperTriangularPredicate.register_many(Identity, ZeroMatrix)
def _(expr, assumptions):
return True
@UpperTriangularPredicate.register(OneMatrix)
def _(expr, assumptions):
return expr.shape[0] == 1 and expr.shape[1] == 1
@UpperTriangularPredicate.register(Transpose)
def _(expr, assumptions):
return ask(Q.lower_triangular(expr.arg), assumptions)
@UpperTriangularPredicate.register(Inverse)
def _(expr, assumptions):
return ask(Q.upper_triangular(expr.arg), assumptions)
@UpperTriangularPredicate.register(MatrixSlice)
def _(expr, assumptions):
if not expr.on_diag:
return None
else:
return ask(Q.upper_triangular(expr.parent), assumptions)
@UpperTriangularPredicate.register(Factorization)
def _(expr, assumptions):
return _Factorization(Q.upper_triangular, expr, assumptions)
# LowerTriangularPredicate
@LowerTriangularPredicate.register(MatMul)
def _(expr, assumptions):
factor, matrices = expr.as_coeff_matrices()
if all(ask(Q.lower_triangular(m), assumptions) for m in matrices):
return True
@LowerTriangularPredicate.register(MatAdd)
def _(expr, assumptions):
if all(ask(Q.lower_triangular(arg), assumptions) for arg in expr.args):
return True
@LowerTriangularPredicate.register(MatPow)
def _(expr, assumptions):
# only for integer powers
base, exp = expr.args
int_exp = ask(Q.integer(exp), assumptions)
if not int_exp:
return None
non_negative = ask(~Q.negative(exp), assumptions)
if (non_negative or non_negative == False
and ask(Q.invertible(base), assumptions)):
return ask(Q.lower_triangular(base), assumptions)
return None
@LowerTriangularPredicate.register(MatrixSymbol)
def _(expr, assumptions):
if Q.lower_triangular(expr) in conjuncts(assumptions):
return True
@LowerTriangularPredicate.register_many(Identity, ZeroMatrix)
def _(expr, assumptions):
return True
@LowerTriangularPredicate.register(OneMatrix)
def _(expr, assumptions):
return expr.shape[0] == 1 and expr.shape[1] == 1
@LowerTriangularPredicate.register(Transpose)
def _(expr, assumptions):
return ask(Q.upper_triangular(expr.arg), assumptions)
@LowerTriangularPredicate.register(Inverse)
def _(expr, assumptions):
return ask(Q.lower_triangular(expr.arg), assumptions)
@LowerTriangularPredicate.register(MatrixSlice)
def _(expr, assumptions):
if not expr.on_diag:
return None
else:
return ask(Q.lower_triangular(expr.parent), assumptions)
@LowerTriangularPredicate.register(Factorization)
def _(expr, assumptions):
return _Factorization(Q.lower_triangular, expr, assumptions)
# DiagonalPredicate
def _is_empty_or_1x1(expr):
return expr.shape in ((0, 0), (1, 1))
@DiagonalPredicate.register(MatMul)
def _(expr, assumptions):
if _is_empty_or_1x1(expr):
return True
factor, matrices = expr.as_coeff_matrices()
if all(ask(Q.diagonal(m), assumptions) for m in matrices):
return True
@DiagonalPredicate.register(MatPow)
def _(expr, assumptions):
# only for integer powers
base, exp = expr.args
int_exp = ask(Q.integer(exp), assumptions)
if not int_exp:
return None
non_negative = ask(~Q.negative(exp), assumptions)
if (non_negative or non_negative == False
and ask(Q.invertible(base), assumptions)):
return ask(Q.diagonal(base), assumptions)
return None
@DiagonalPredicate.register(MatAdd)
def _(expr, assumptions):
if all(ask(Q.diagonal(arg), assumptions) for arg in expr.args):
return True
@DiagonalPredicate.register(MatrixSymbol)
def _(expr, assumptions):
if _is_empty_or_1x1(expr):
return True
if Q.diagonal(expr) in conjuncts(assumptions):
return True
@DiagonalPredicate.register(OneMatrix)
def _(expr, assumptions):
return expr.shape[0] == 1 and expr.shape[1] == 1
@DiagonalPredicate.register_many(Inverse, Transpose)
def _(expr, assumptions):
return ask(Q.diagonal(expr.arg), assumptions)
@DiagonalPredicate.register(MatrixSlice)
def _(expr, assumptions):
if _is_empty_or_1x1(expr):
return True
if not expr.on_diag:
return None
else:
return ask(Q.diagonal(expr.parent), assumptions)
@DiagonalPredicate.register_many(DiagonalMatrix, DiagMatrix, Identity, ZeroMatrix)
def _(expr, assumptions):
return True
@DiagonalPredicate.register(Factorization)
def _(expr, assumptions):
return _Factorization(Q.diagonal, expr, assumptions)
# IntegerElementsPredicate
def BM_elements(predicate, expr, assumptions):
""" Block Matrix elements. """
return all(ask(predicate(b), assumptions) for b in expr.blocks)
def MS_elements(predicate, expr, assumptions):
""" Matrix Slice elements. """
return ask(predicate(expr.parent), assumptions)
def MatMul_elements(matrix_predicate, scalar_predicate, expr, assumptions):
d = sift(expr.args, lambda x: isinstance(x, MatrixExpr))
factors, matrices = d[False], d[True]
return fuzzy_and([
test_closed_group(Basic(*factors), assumptions, scalar_predicate),
test_closed_group(Basic(*matrices), assumptions, matrix_predicate)])
@IntegerElementsPredicate.register_many(Determinant, HadamardProduct, MatAdd,
Trace, Transpose)
def _(expr, assumptions):
return test_closed_group(expr, assumptions, Q.integer_elements)
@IntegerElementsPredicate.register(MatPow)
def _(expr, assumptions):
# only for integer powers
base, exp = expr.args
int_exp = ask(Q.integer(exp), assumptions)
if not int_exp:
return None
if exp.is_negative == False:
return ask(Q.integer_elements(base), assumptions)
return None
@IntegerElementsPredicate.register_many(Identity, OneMatrix, ZeroMatrix)
def _(expr, assumptions):
return True
@IntegerElementsPredicate.register(MatMul)
def _(expr, assumptions):
return MatMul_elements(Q.integer_elements, Q.integer, expr, assumptions)
@IntegerElementsPredicate.register(MatrixSlice)
def _(expr, assumptions):
return MS_elements(Q.integer_elements, expr, assumptions)
@IntegerElementsPredicate.register(BlockMatrix)
def _(expr, assumptions):
return BM_elements(Q.integer_elements, expr, assumptions)
# RealElementsPredicate
@RealElementsPredicate.register_many(Determinant, Factorization, HadamardProduct,
MatAdd, Trace, Transpose)
def _(expr, assumptions):
return test_closed_group(expr, assumptions, Q.real_elements)
@RealElementsPredicate.register(MatPow)
def _(expr, assumptions):
# only for integer powers
base, exp = expr.args
int_exp = ask(Q.integer(exp), assumptions)
if not int_exp:
return None
non_negative = ask(~Q.negative(exp), assumptions)
if (non_negative or non_negative == False
and ask(Q.invertible(base), assumptions)):
return ask(Q.real_elements(base), assumptions)
return None
@RealElementsPredicate.register(MatMul)
def _(expr, assumptions):
return MatMul_elements(Q.real_elements, Q.real, expr, assumptions)
@RealElementsPredicate.register(MatrixSlice)
def _(expr, assumptions):
return MS_elements(Q.real_elements, expr, assumptions)
@RealElementsPredicate.register(BlockMatrix)
def _(expr, assumptions):
return BM_elements(Q.real_elements, expr, assumptions)
# ComplexElementsPredicate
@ComplexElementsPredicate.register_many(Determinant, Factorization, HadamardProduct,
Inverse, MatAdd, Trace, Transpose)
def _(expr, assumptions):
return test_closed_group(expr, assumptions, Q.complex_elements)
@ComplexElementsPredicate.register(MatPow)
def _(expr, assumptions):
# only for integer powers
base, exp = expr.args
int_exp = ask(Q.integer(exp), assumptions)
if not int_exp:
return None
non_negative = ask(~Q.negative(exp), assumptions)
if (non_negative or non_negative == False
and ask(Q.invertible(base), assumptions)):
return ask(Q.complex_elements(base), assumptions)
return None
@ComplexElementsPredicate.register(MatMul)
def _(expr, assumptions):
return MatMul_elements(Q.complex_elements, Q.complex, expr, assumptions)
@ComplexElementsPredicate.register(MatrixSlice)
def _(expr, assumptions):
return MS_elements(Q.complex_elements, expr, assumptions)
@ComplexElementsPredicate.register(BlockMatrix)
def _(expr, assumptions):
return BM_elements(Q.complex_elements, expr, assumptions)
@ComplexElementsPredicate.register(DFT)
def _(expr, assumptions):
return True
@@ -0,0 +1,279 @@
"""
Handlers for keys related to number theory: prime, even, odd, etc.
"""
from sympy.assumptions import Q, ask
from sympy.core import Add, Basic, Expr, Float, Mul, Pow, S
from sympy.core.numbers import (ImaginaryUnit, Infinity, Integer, NaN,
NegativeInfinity, NumberSymbol, Rational, int_valued)
from sympy.functions import Abs, im, re
from sympy.ntheory import isprime
from sympy.multipledispatch import MDNotImplementedError
from ..predicates.ntheory import (PrimePredicate, CompositePredicate,
EvenPredicate, OddPredicate)
# PrimePredicate
def _PrimePredicate_number(expr, assumptions):
# helper method
exact = not expr.atoms(Float)
try:
i = int(expr.round())
if (expr - i).equals(0) is False:
raise TypeError
except TypeError:
return False
if exact:
return isprime(i)
# when not exact, we won't give a True or False
# since the number represents an approximate value
@PrimePredicate.register(Expr)
def _(expr, assumptions):
ret = expr.is_prime
if ret is None:
raise MDNotImplementedError
return ret
@PrimePredicate.register(Basic)
def _(expr, assumptions):
if expr.is_number:
return _PrimePredicate_number(expr, assumptions)
@PrimePredicate.register(Mul)
def _(expr, assumptions):
if expr.is_number:
return _PrimePredicate_number(expr, assumptions)
for arg in expr.args:
if not ask(Q.integer(arg), assumptions):
return None
for arg in expr.args:
if arg.is_number and arg.is_composite:
return False
@PrimePredicate.register(Pow)
def _(expr, assumptions):
"""
Integer**Integer -> !Prime
"""
if expr.is_number:
return _PrimePredicate_number(expr, assumptions)
if ask(Q.integer(expr.exp), assumptions) and \
ask(Q.integer(expr.base), assumptions):
prime_base = ask(Q.prime(expr.base), assumptions)
if prime_base is False:
return False
is_exp_one = ask(Q.eq(expr.exp, 1), assumptions)
if is_exp_one is False:
return False
if prime_base is True and is_exp_one is True:
return True
@PrimePredicate.register(Integer)
def _(expr, assumptions):
return isprime(expr)
@PrimePredicate.register_many(Rational, Infinity, NegativeInfinity, ImaginaryUnit)
def _(expr, assumptions):
return False
@PrimePredicate.register(Float)
def _(expr, assumptions):
return _PrimePredicate_number(expr, assumptions)
@PrimePredicate.register(NumberSymbol)
def _(expr, assumptions):
return _PrimePredicate_number(expr, assumptions)
@PrimePredicate.register(NaN)
def _(expr, assumptions):
return None
# CompositePredicate
@CompositePredicate.register(Expr)
def _(expr, assumptions):
ret = expr.is_composite
if ret is None:
raise MDNotImplementedError
return ret
@CompositePredicate.register(Basic)
def _(expr, assumptions):
_positive = ask(Q.positive(expr), assumptions)
if _positive:
_integer = ask(Q.integer(expr), assumptions)
if _integer:
_prime = ask(Q.prime(expr), assumptions)
if _prime is None:
return
# Positive integer which is not prime is not
# necessarily composite
_is_one = ask(Q.eq(expr, 1), assumptions)
if _is_one:
return False
if _is_one is None:
return None
return not _prime
else:
return _integer
else:
return _positive
# EvenPredicate
def _EvenPredicate_number(expr, assumptions):
# helper method
if isinstance(expr, (float, Float)):
if int_valued(expr):
return None
return False
try:
i = int(expr.round())
except TypeError:
return False
if not (expr - i).equals(0):
return False
return i % 2 == 0
@EvenPredicate.register(Expr)
def _(expr, assumptions):
ret = expr.is_even
if ret is None:
raise MDNotImplementedError
return ret
@EvenPredicate.register(Basic)
def _(expr, assumptions):
if expr.is_number:
return _EvenPredicate_number(expr, assumptions)
@EvenPredicate.register(Mul)
def _(expr, assumptions):
"""
Even * Integer -> Even
Even * Odd -> Even
Integer * Odd -> ?
Odd * Odd -> Odd
Even * Even -> Even
Integer * Integer -> Even if Integer + Integer = Odd
otherwise -> ?
"""
if expr.is_number:
return _EvenPredicate_number(expr, assumptions)
even, odd, irrational, acc = False, 0, False, 1
for arg in expr.args:
# check for all integers and at least one even
if ask(Q.integer(arg), assumptions):
if ask(Q.even(arg), assumptions):
even = True
elif ask(Q.odd(arg), assumptions):
odd += 1
elif not even and acc != 1:
if ask(Q.odd(acc + arg), assumptions):
even = True
elif ask(Q.irrational(arg), assumptions):
# one irrational makes the result False
# two makes it undefined
if irrational:
break
irrational = True
else:
break
acc = arg
else:
if irrational:
return False
if even:
return True
if odd == len(expr.args):
return False
@EvenPredicate.register(Add)
def _(expr, assumptions):
"""
Even + Odd -> Odd
Even + Even -> Even
Odd + Odd -> Even
"""
if expr.is_number:
return _EvenPredicate_number(expr, assumptions)
_result = True
for arg in expr.args:
if ask(Q.even(arg), assumptions):
pass
elif ask(Q.odd(arg), assumptions):
_result = not _result
else:
break
else:
return _result
@EvenPredicate.register(Pow)
def _(expr, assumptions):
if expr.is_number:
return _EvenPredicate_number(expr, assumptions)
if ask(Q.integer(expr.exp), assumptions):
if ask(Q.positive(expr.exp), assumptions):
return ask(Q.even(expr.base), assumptions)
elif ask(~Q.negative(expr.exp) & Q.odd(expr.base), assumptions):
return False
elif expr.base is S.NegativeOne:
return False
@EvenPredicate.register(Integer)
def _(expr, assumptions):
return not bool(expr.p & 1)
@EvenPredicate.register_many(Rational, Infinity, NegativeInfinity, ImaginaryUnit)
def _(expr, assumptions):
return False
@EvenPredicate.register(NumberSymbol)
def _(expr, assumptions):
return _EvenPredicate_number(expr, assumptions)
@EvenPredicate.register(Abs)
def _(expr, assumptions):
if ask(Q.real(expr.args[0]), assumptions):
return ask(Q.even(expr.args[0]), assumptions)
@EvenPredicate.register(re)
def _(expr, assumptions):
if ask(Q.real(expr.args[0]), assumptions):
return ask(Q.even(expr.args[0]), assumptions)
@EvenPredicate.register(im)
def _(expr, assumptions):
if ask(Q.real(expr.args[0]), assumptions):
return True
@EvenPredicate.register(NaN)
def _(expr, assumptions):
return None
# OddPredicate
@OddPredicate.register(Expr)
def _(expr, assumptions):
ret = expr.is_odd
if ret is None:
raise MDNotImplementedError
return ret
@OddPredicate.register(Basic)
def _(expr, assumptions):
_integer = ask(Q.integer(expr), assumptions)
if _integer:
_even = ask(Q.even(expr), assumptions)
if _even is None:
return None
return not _even
return _integer
@@ -0,0 +1,440 @@
"""
Handlers related to order relations: positive, negative, etc.
"""
from sympy.assumptions import Q, ask
from sympy.core import Add, Basic, Expr, Mul, Pow, S
from sympy.core.logic import fuzzy_not, fuzzy_and, fuzzy_or
from sympy.core.numbers import E, ImaginaryUnit, NaN, I, pi
from sympy.functions import Abs, acos, acot, asin, atan, exp, factorial, log
from sympy.matrices import Determinant, Trace
from sympy.matrices.expressions.matexpr import MatrixElement
from sympy.multipledispatch import MDNotImplementedError
from ..predicates.order import (NegativePredicate, NonNegativePredicate,
NonZeroPredicate, ZeroPredicate, NonPositivePredicate, PositivePredicate,
ExtendedNegativePredicate, ExtendedNonNegativePredicate,
ExtendedNonPositivePredicate, ExtendedNonZeroPredicate,
ExtendedPositivePredicate,)
# NegativePredicate
def _NegativePredicate_number(expr, assumptions):
r, i = expr.as_real_imag()
if r == S.NaN or i == S.NaN:
return None
# If the imaginary part can symbolically be shown to be zero then
# we just evaluate the real part; otherwise we evaluate the imaginary
# part to see if it actually evaluates to zero and if it does then
# we make the comparison between the real part and zero.
if not i:
r = r.evalf(2)
if r._prec != 1:
return r < 0
else:
i = i.evalf(2)
if i._prec != 1:
if i != 0:
return False
r = r.evalf(2)
if r._prec != 1:
return r < 0
@NegativePredicate.register(Basic)
def _(expr, assumptions):
if expr.is_number:
return _NegativePredicate_number(expr, assumptions)
@NegativePredicate.register(Expr)
def _(expr, assumptions):
ret = expr.is_negative
if ret is None:
raise MDNotImplementedError
return ret
@NegativePredicate.register(Add)
def _(expr, assumptions):
"""
Positive + Positive -> Positive,
Negative + Negative -> Negative
"""
if expr.is_number:
return _NegativePredicate_number(expr, assumptions)
r = ask(Q.real(expr), assumptions)
if r is not True:
return r
nonpos = 0
for arg in expr.args:
if ask(Q.negative(arg), assumptions) is not True:
if ask(Q.positive(arg), assumptions) is False:
nonpos += 1
else:
break
else:
if nonpos < len(expr.args):
return True
@NegativePredicate.register(Mul)
def _(expr, assumptions):
if expr.is_number:
return _NegativePredicate_number(expr, assumptions)
result = None
for arg in expr.args:
if result is None:
result = False
if ask(Q.negative(arg), assumptions):
result = not result
elif ask(Q.positive(arg), assumptions):
pass
else:
return
return result
@NegativePredicate.register(Pow)
def _(expr, assumptions):
"""
Real ** Even -> NonNegative
Real ** Odd -> same_as_base
NonNegative ** Positive -> NonNegative
"""
if expr.base == E:
# Exponential is always positive:
if ask(Q.real(expr.exp), assumptions):
return False
return
if expr.is_number:
return _NegativePredicate_number(expr, assumptions)
if ask(Q.real(expr.base), assumptions):
if ask(Q.positive(expr.base), assumptions):
if ask(Q.real(expr.exp), assumptions):
return False
if ask(Q.even(expr.exp), assumptions):
return False
if ask(Q.odd(expr.exp), assumptions):
return ask(Q.negative(expr.base), assumptions)
@NegativePredicate.register_many(Abs, ImaginaryUnit)
def _(expr, assumptions):
return False
@NegativePredicate.register(exp)
def _(expr, assumptions):
if ask(Q.real(expr.exp), assumptions):
return False
raise MDNotImplementedError
# NonNegativePredicate
@NonNegativePredicate.register(Basic)
def _(expr, assumptions):
if expr.is_number:
notnegative = fuzzy_not(_NegativePredicate_number(expr, assumptions))
if notnegative:
return ask(Q.real(expr), assumptions)
else:
return notnegative
@NonNegativePredicate.register(Expr)
def _(expr, assumptions):
ret = expr.is_nonnegative
if ret is None:
raise MDNotImplementedError
return ret
# NonZeroPredicate
@NonZeroPredicate.register(Expr)
def _(expr, assumptions):
ret = expr.is_nonzero
if ret is None:
raise MDNotImplementedError
return ret
@NonZeroPredicate.register(Basic)
def _(expr, assumptions):
if ask(Q.real(expr)) is False:
return False
if expr.is_number:
# if there are no symbols just evalf
i = expr.evalf(2)
def nonz(i):
if i._prec != 1:
return i != 0
return fuzzy_or(nonz(i) for i in i.as_real_imag())
@NonZeroPredicate.register(Add)
def _(expr, assumptions):
if all(ask(Q.positive(x), assumptions) for x in expr.args) \
or all(ask(Q.negative(x), assumptions) for x in expr.args):
return True
@NonZeroPredicate.register(Mul)
def _(expr, assumptions):
for arg in expr.args:
result = ask(Q.nonzero(arg), assumptions)
if result:
continue
return result
return True
@NonZeroPredicate.register(Pow)
def _(expr, assumptions):
return ask(Q.nonzero(expr.base), assumptions)
@NonZeroPredicate.register(Abs)
def _(expr, assumptions):
return ask(Q.nonzero(expr.args[0]), assumptions)
@NonZeroPredicate.register(NaN)
def _(expr, assumptions):
return None
# ZeroPredicate
@ZeroPredicate.register(Expr)
def _(expr, assumptions):
ret = expr.is_zero
if ret is None:
raise MDNotImplementedError
return ret
@ZeroPredicate.register(Basic)
def _(expr, assumptions):
return fuzzy_and([fuzzy_not(ask(Q.nonzero(expr), assumptions)),
ask(Q.real(expr), assumptions)])
@ZeroPredicate.register(Mul)
def _(expr, assumptions):
# TODO: This should be deducible from the nonzero handler
return fuzzy_or(ask(Q.zero(arg), assumptions) for arg in expr.args)
# NonPositivePredicate
@NonPositivePredicate.register(Expr)
def _(expr, assumptions):
ret = expr.is_nonpositive
if ret is None:
raise MDNotImplementedError
return ret
@NonPositivePredicate.register(Basic)
def _(expr, assumptions):
if expr.is_number:
notpositive = fuzzy_not(_PositivePredicate_number(expr, assumptions))
if notpositive:
return ask(Q.real(expr), assumptions)
else:
return notpositive
# PositivePredicate
def _PositivePredicate_number(expr, assumptions):
r, i = expr.as_real_imag()
# If the imaginary part can symbolically be shown to be zero then
# we just evaluate the real part; otherwise we evaluate the imaginary
# part to see if it actually evaluates to zero and if it does then
# we make the comparison between the real part and zero.
if not i:
r = r.evalf(2)
if r._prec != 1:
return r > 0
else:
i = i.evalf(2)
if i._prec != 1:
if i != 0:
return False
r = r.evalf(2)
if r._prec != 1:
return r > 0
@PositivePredicate.register(Expr)
def _(expr, assumptions):
ret = expr.is_positive
if ret is None:
raise MDNotImplementedError
return ret
@PositivePredicate.register(Basic)
def _(expr, assumptions):
if expr.is_number:
return _PositivePredicate_number(expr, assumptions)
@PositivePredicate.register(Mul)
def _(expr, assumptions):
if expr.is_number:
return _PositivePredicate_number(expr, assumptions)
result = True
for arg in expr.args:
if ask(Q.positive(arg), assumptions):
continue
elif ask(Q.negative(arg), assumptions):
result = result ^ True
else:
return
return result
@PositivePredicate.register(Add)
def _(expr, assumptions):
if expr.is_number:
return _PositivePredicate_number(expr, assumptions)
r = ask(Q.real(expr), assumptions)
if r is not True:
return r
nonneg = 0
for arg in expr.args:
if ask(Q.positive(arg), assumptions) is not True:
if ask(Q.negative(arg), assumptions) is False:
nonneg += 1
else:
break
else:
if nonneg < len(expr.args):
return True
@PositivePredicate.register(Pow)
def _(expr, assumptions):
if expr.base == E:
if ask(Q.real(expr.exp), assumptions):
return True
if ask(Q.imaginary(expr.exp), assumptions):
return ask(Q.even(expr.exp/(I*pi)), assumptions)
return
if expr.is_number:
return _PositivePredicate_number(expr, assumptions)
if ask(Q.positive(expr.base), assumptions):
if ask(Q.real(expr.exp), assumptions):
return True
if ask(Q.negative(expr.base), assumptions):
if ask(Q.even(expr.exp), assumptions):
return True
if ask(Q.odd(expr.exp), assumptions):
return False
@PositivePredicate.register(exp)
def _(expr, assumptions):
if ask(Q.real(expr.exp), assumptions):
return True
if ask(Q.imaginary(expr.exp), assumptions):
return ask(Q.even(expr.exp/(I*pi)), assumptions)
@PositivePredicate.register(log)
def _(expr, assumptions):
r = ask(Q.real(expr.args[0]), assumptions)
if r is not True:
return r
if ask(Q.positive(expr.args[0] - 1), assumptions):
return True
if ask(Q.negative(expr.args[0] - 1), assumptions):
return False
@PositivePredicate.register(factorial)
def _(expr, assumptions):
x = expr.args[0]
if ask(Q.integer(x) & Q.positive(x), assumptions):
return True
@PositivePredicate.register(ImaginaryUnit)
def _(expr, assumptions):
return False
@PositivePredicate.register(Abs)
def _(expr, assumptions):
return ask(Q.nonzero(expr), assumptions)
@PositivePredicate.register(Trace)
def _(expr, assumptions):
if ask(Q.positive_definite(expr.arg), assumptions):
return True
@PositivePredicate.register(Determinant)
def _(expr, assumptions):
if ask(Q.positive_definite(expr.arg), assumptions):
return True
@PositivePredicate.register(MatrixElement)
def _(expr, assumptions):
if (expr.i == expr.j
and ask(Q.positive_definite(expr.parent), assumptions)):
return True
@PositivePredicate.register(atan)
def _(expr, assumptions):
return ask(Q.positive(expr.args[0]), assumptions)
@PositivePredicate.register(asin)
def _(expr, assumptions):
x = expr.args[0]
if ask(Q.positive(x) & Q.nonpositive(x - 1), assumptions):
return True
if ask(Q.negative(x) & Q.nonnegative(x + 1), assumptions):
return False
@PositivePredicate.register(acos)
def _(expr, assumptions):
x = expr.args[0]
if ask(Q.nonpositive(x - 1) & Q.nonnegative(x + 1), assumptions):
return True
@PositivePredicate.register(acot)
def _(expr, assumptions):
return ask(Q.real(expr.args[0]), assumptions)
@PositivePredicate.register(NaN)
def _(expr, assumptions):
return None
# ExtendedNegativePredicate
@ExtendedNegativePredicate.register(object)
def _(expr, assumptions):
return ask(Q.negative(expr) | Q.negative_infinite(expr), assumptions)
# ExtendedPositivePredicate
@ExtendedPositivePredicate.register(object)
def _(expr, assumptions):
return ask(Q.positive(expr) | Q.positive_infinite(expr), assumptions)
# ExtendedNonZeroPredicate
@ExtendedNonZeroPredicate.register(object)
def _(expr, assumptions):
return ask(
Q.negative_infinite(expr) | Q.negative(expr) | Q.positive(expr) | Q.positive_infinite(expr),
assumptions)
# ExtendedNonPositivePredicate
@ExtendedNonPositivePredicate.register(object)
def _(expr, assumptions):
return ask(
Q.negative_infinite(expr) | Q.negative(expr) | Q.zero(expr),
assumptions)
# ExtendedNonNegativePredicate
@ExtendedNonNegativePredicate.register(object)
def _(expr, assumptions):
return ask(
Q.zero(expr) | Q.positive(expr) | Q.positive_infinite(expr),
assumptions)
@@ -0,0 +1,816 @@
"""
Handlers for predicates related to set membership: integer, rational, etc.
"""
from sympy.assumptions import Q, ask
from sympy.core import Add, Basic, Expr, Mul, Pow, S
from sympy.core.numbers import (AlgebraicNumber, ComplexInfinity, Exp1, Float,
GoldenRatio, ImaginaryUnit, Infinity, Integer, NaN, NegativeInfinity,
Number, NumberSymbol, Pi, pi, Rational, TribonacciConstant, E)
from sympy.core.logic import fuzzy_bool
from sympy.functions import (Abs, acos, acot, asin, atan, cos, cot, exp, im,
log, re, sin, tan)
from sympy.core.numbers import I
from sympy.core.relational import Eq
from sympy.functions.elementary.complexes import conjugate
from sympy.matrices import Determinant, MatrixBase, Trace
from sympy.matrices.expressions.matexpr import MatrixElement
from sympy.multipledispatch import MDNotImplementedError
from .common import test_closed_group, ask_all, ask_any
from ..predicates.sets import (IntegerPredicate, RationalPredicate,
IrrationalPredicate, RealPredicate, ExtendedRealPredicate,
HermitianPredicate, ComplexPredicate, ImaginaryPredicate,
AntihermitianPredicate, AlgebraicPredicate)
# IntegerPredicate
def _IntegerPredicate_number(expr, assumptions):
# helper function
try:
i = int(expr.round())
if not (expr - i).equals(0):
raise TypeError
return True
except TypeError:
return False
@IntegerPredicate.register_many(int, Integer) # type:ignore
def _(expr, assumptions):
return True
@IntegerPredicate.register_many(Exp1, GoldenRatio, ImaginaryUnit, Infinity,
NegativeInfinity, Pi, Rational, TribonacciConstant)
def _(expr, assumptions):
return False
@IntegerPredicate.register(Expr)
def _(expr, assumptions):
ret = expr.is_integer
if ret is None:
raise MDNotImplementedError
return ret
@IntegerPredicate.register(Add)
def _(expr, assumptions):
"""
* Integer + Integer -> Integer
* Integer + !Integer -> !Integer
* !Integer + !Integer -> ?
"""
if expr.is_number:
return _IntegerPredicate_number(expr, assumptions)
return test_closed_group(expr, assumptions, Q.integer)
@IntegerPredicate.register(Pow)
def _(expr,assumptions):
if expr.is_number:
return _IntegerPredicate_number(expr, assumptions)
if ask_all(~Q.zero(expr.base), Q.finite(expr.base), Q.zero(expr.exp), assumptions=assumptions):
return True
if ask_all(Q.integer(expr.base), Q.integer(expr.exp), assumptions=assumptions):
if ask_any(Q.positive(expr.exp), Q.nonnegative(expr.exp) & ~Q.zero(expr.base), Q.zero(expr.base-1), Q.zero(expr.base+1), assumptions=assumptions):
return True
@IntegerPredicate.register(Mul)
def _(expr, assumptions):
"""
* Integer*Integer -> Integer
* Integer*Irrational -> !Integer
* Odd/Even -> !Integer
* Integer*Rational -> ?
"""
if expr.is_number:
return _IntegerPredicate_number(expr, assumptions)
_output = True
for arg in expr.args:
if not ask(Q.integer(arg), assumptions):
if arg.is_Rational:
if arg.q == 2:
return ask(Q.even(2*expr), assumptions)
if ~(arg.q & 1):
return None
elif ask(Q.irrational(arg), assumptions):
if _output:
_output = False
else:
return
else:
return
return _output
@IntegerPredicate.register(Abs)
def _(expr, assumptions):
if ask(Q.integer(expr.args[0]), assumptions):
return True
@IntegerPredicate.register_many(Determinant, MatrixElement, Trace)
def _(expr, assumptions):
return ask(Q.integer_elements(expr.args[0]), assumptions)
# RationalPredicate
@RationalPredicate.register(Rational)
def _(expr, assumptions):
return True
@RationalPredicate.register(Float)
def _(expr, assumptions):
return None
@RationalPredicate.register_many(Exp1, GoldenRatio, ImaginaryUnit, Infinity,
NegativeInfinity, Pi, TribonacciConstant)
def _(expr, assumptions):
return False
@RationalPredicate.register(Expr)
def _(expr, assumptions):
ret = expr.is_rational
if ret is None:
raise MDNotImplementedError
return ret
@RationalPredicate.register_many(Add, Mul)
def _(expr, assumptions):
"""
* Rational + Rational -> Rational
* Rational + !Rational -> !Rational
* !Rational + !Rational -> ?
"""
if expr.is_number:
if expr.as_real_imag()[1]:
return False
return test_closed_group(expr, assumptions, Q.rational)
@RationalPredicate.register(Pow)
def _(expr, assumptions):
"""
* Rational ** Integer -> Rational
* Irrational ** Rational -> Irrational
* Rational ** Irrational -> ?
"""
if expr.base == E:
x = expr.exp
if ask(Q.rational(x), assumptions):
return ask(Q.zero(x), assumptions)
return
is_exp_integer = ask(Q.integer(expr.exp), assumptions)
if is_exp_integer:
is_base_rational = ask(Q.rational(expr.base),assumptions)
if is_base_rational:
is_base_zero = ask(Q.zero(expr.base),assumptions)
if is_base_zero is False:
return True
if is_base_zero and ask(Q.positive(expr.exp)):
return True
if ask(Q.algebraic(expr.base),assumptions) is False:
return ask(Q.zero(expr.exp), assumptions)
if ask(Q.irrational(expr.base),assumptions) and ask(Q.eq(expr.exp,-1)):
return False
return
elif ask(Q.rational(expr.exp), assumptions):
if ask(Q.prime(expr.base), assumptions) and is_exp_integer is False:
return False
if ask(Q.zero(expr.base)) and ask(Q.positive(expr.exp)):
return True
if ask(Q.eq(expr.base,1)):
return True
@RationalPredicate.register_many(asin, atan, cos, sin, tan)
def _(expr, assumptions):
x = expr.args[0]
if ask(Q.rational(x), assumptions):
return ask(~Q.nonzero(x), assumptions)
@RationalPredicate.register(exp)
def _(expr, assumptions):
x = expr.exp
if ask(Q.rational(x), assumptions):
return ask(~Q.nonzero(x), assumptions)
@RationalPredicate.register_many(acot, cot)
def _(expr, assumptions):
x = expr.args[0]
if ask(Q.rational(x), assumptions):
return False
@RationalPredicate.register_many(acos, log)
def _(expr, assumptions):
x = expr.args[0]
if ask(Q.rational(x), assumptions):
return ask(~Q.nonzero(x - 1), assumptions)
# IrrationalPredicate
@IrrationalPredicate.register(Expr)
def _(expr, assumptions):
ret = expr.is_irrational
if ret is None:
raise MDNotImplementedError
return ret
@IrrationalPredicate.register(Basic)
def _(expr, assumptions):
_real = ask(Q.real(expr), assumptions)
if _real:
_rational = ask(Q.rational(expr), assumptions)
if _rational is None:
return None
return not _rational
else:
return _real
# RealPredicate
def _RealPredicate_number(expr, assumptions):
# let as_real_imag() work first since the expression may
# be simpler to evaluate
i = expr.as_real_imag()[1].evalf(2)
if i._prec != 1:
return not i
# allow None to be returned if we couldn't show for sure
# that i was 0
@RealPredicate.register_many(Abs, Exp1, Float, GoldenRatio, im, Pi, Rational,
re, TribonacciConstant)
def _(expr, assumptions):
return True
@RealPredicate.register_many(ImaginaryUnit, Infinity, NegativeInfinity)
def _(expr, assumptions):
return False
@RealPredicate.register(Expr)
def _(expr, assumptions):
ret = expr.is_real
if ret is None:
raise MDNotImplementedError
return ret
@RealPredicate.register(Add)
def _(expr, assumptions):
"""
* Real + Real -> Real
* Real + (Complex & !Real) -> !Real
"""
if expr.is_number:
return _RealPredicate_number(expr, assumptions)
return test_closed_group(expr, assumptions, Q.real)
@RealPredicate.register(Mul)
def _(expr, assumptions):
"""
* Real*Real -> Real
* Real*Imaginary -> !Real
* Imaginary*Imaginary -> Real
"""
if expr.is_number:
return _RealPredicate_number(expr, assumptions)
result = True
for arg in expr.args:
if ask(Q.real(arg), assumptions):
pass
elif ask(Q.imaginary(arg), assumptions):
result = result ^ True
else:
break
else:
return result
@RealPredicate.register(Pow)
def _(expr, assumptions):
"""
* Real**Integer -> Real
* Positive**Real -> Real
* Negative**Real -> ?
* Real**(Integer/Even) -> Real if base is nonnegative
* Real**(Integer/Odd) -> Real
* Imaginary**(Integer/Even) -> Real
* Imaginary**(Integer/Odd) -> not Real
* Imaginary**Real -> ? since Real could be 0 (giving real)
or 1 (giving imaginary)
* b**Imaginary -> Real if log(b) is imaginary and b != 0
and exponent != integer multiple of
I*pi/log(b)
* Real**Real -> ? e.g. sqrt(-1) is imaginary and
sqrt(2) is not
"""
if expr.is_number:
return _RealPredicate_number(expr, assumptions)
if expr.base == E:
return ask(
Q.integer(expr.exp/I/pi) | Q.real(expr.exp), assumptions
)
if expr.base.func == exp or (expr.base.is_Pow and expr.base.base == E):
if ask(Q.imaginary(expr.base.exp), assumptions):
if ask(Q.imaginary(expr.exp), assumptions):
return True
# If the i = (exp's arg)/(I*pi) is an integer or half-integer
# multiple of I*pi then 2*i will be an integer. In addition,
# exp(i*I*pi) = (-1)**i so the overall realness of the expr
# can be determined by replacing exp(i*I*pi) with (-1)**i.
i = expr.base.exp/I/pi
if ask(Q.integer(2*i), assumptions):
return ask(Q.real((S.NegativeOne**i)**expr.exp), assumptions)
return
if ask(Q.imaginary(expr.base), assumptions):
if ask(Q.integer(expr.exp), assumptions):
odd = ask(Q.odd(expr.exp), assumptions)
if odd is not None:
return not odd
return
if ask(Q.imaginary(expr.exp), assumptions):
imlog = ask(Q.imaginary(log(expr.base)), assumptions)
if imlog is not None:
# I**i -> real, log(I) is imag;
# (2*I)**i -> complex, log(2*I) is not imag
return imlog
if ask(Q.real(expr.base), assumptions):
if ask(Q.real(expr.exp), assumptions):
if ask(Q.zero(expr.base), assumptions) is not False:
if ask(Q.positive(expr.exp), assumptions):
return True
return
if expr.exp.is_Rational and \
ask(Q.even(expr.exp.q), assumptions):
return ask(Q.positive(expr.base), assumptions)
elif ask(Q.integer(expr.exp), assumptions):
return True
elif ask(Q.positive(expr.base), assumptions):
return True
@RealPredicate.register_many(cos, sin)
def _(expr, assumptions):
if ask(Q.real(expr.args[0]), assumptions):
return True
@RealPredicate.register(exp)
def _(expr, assumptions):
return ask(
Q.integer(expr.exp/I/pi) | Q.real(expr.exp), assumptions
)
@RealPredicate.register(log)
def _(expr, assumptions):
return ask(Q.positive(expr.args[0]), assumptions)
@RealPredicate.register_many(Determinant, MatrixElement, Trace)
def _(expr, assumptions):
return ask(Q.real_elements(expr.args[0]), assumptions)
# ExtendedRealPredicate
@ExtendedRealPredicate.register(object)
def _(expr, assumptions):
return ask(Q.negative_infinite(expr)
| Q.negative(expr)
| Q.zero(expr)
| Q.positive(expr)
| Q.positive_infinite(expr),
assumptions)
@ExtendedRealPredicate.register_many(Infinity, NegativeInfinity)
def _(expr, assumptions):
return True
@ExtendedRealPredicate.register_many(Add, Mul, Pow) # type:ignore
def _(expr, assumptions):
return test_closed_group(expr, assumptions, Q.extended_real)
# HermitianPredicate
@HermitianPredicate.register(object) # type:ignore
def _(expr, assumptions):
if isinstance(expr, MatrixBase):
return None
return ask(Q.real(expr), assumptions)
@HermitianPredicate.register(Add) # type:ignore
def _(expr, assumptions):
"""
* Hermitian + Hermitian -> Hermitian
* Hermitian + !Hermitian -> !Hermitian
"""
if expr.is_number:
raise MDNotImplementedError
return test_closed_group(expr, assumptions, Q.hermitian)
@HermitianPredicate.register(Mul) # type:ignore
def _(expr, assumptions):
"""
As long as there is at most only one noncommutative term:
* Hermitian*Hermitian -> Hermitian
* Hermitian*Antihermitian -> !Hermitian
* Antihermitian*Antihermitian -> Hermitian
"""
if expr.is_number:
raise MDNotImplementedError
nccount = 0
result = True
for arg in expr.args:
if ask(Q.antihermitian(arg), assumptions):
result = result ^ True
elif not ask(Q.hermitian(arg), assumptions):
break
if ask(~Q.commutative(arg), assumptions):
nccount += 1
if nccount > 1:
break
else:
return result
@HermitianPredicate.register(Pow) # type:ignore
def _(expr, assumptions):
"""
* Hermitian**Integer -> Hermitian
"""
if expr.is_number:
raise MDNotImplementedError
if expr.base == E:
if ask(Q.hermitian(expr.exp), assumptions):
return True
raise MDNotImplementedError
if ask(Q.hermitian(expr.base), assumptions):
if ask(Q.integer(expr.exp), assumptions):
return True
raise MDNotImplementedError
@HermitianPredicate.register_many(cos, sin) # type:ignore
def _(expr, assumptions):
if ask(Q.hermitian(expr.args[0]), assumptions):
return True
raise MDNotImplementedError
@HermitianPredicate.register(exp) # type:ignore
def _(expr, assumptions):
if ask(Q.hermitian(expr.exp), assumptions):
return True
raise MDNotImplementedError
@HermitianPredicate.register(MatrixBase) # type:ignore
def _(mat, assumptions):
rows, cols = mat.shape
ret_val = True
for i in range(rows):
for j in range(i, cols):
cond = fuzzy_bool(Eq(mat[i, j], conjugate(mat[j, i])))
if cond is None:
ret_val = None
if cond == False:
return False
if ret_val is None:
raise MDNotImplementedError
return ret_val
# ComplexPredicate
@ComplexPredicate.register_many(Abs, cos, exp, im, ImaginaryUnit, log, Number, # type:ignore
NumberSymbol, re, sin)
def _(expr, assumptions):
return True
@ComplexPredicate.register_many(Infinity, NegativeInfinity) # type:ignore
def _(expr, assumptions):
return False
@ComplexPredicate.register(Expr) # type:ignore
def _(expr, assumptions):
ret = expr.is_complex
if ret is None:
raise MDNotImplementedError
return ret
@ComplexPredicate.register_many(Add, Mul) # type:ignore
def _(expr, assumptions):
return test_closed_group(expr, assumptions, Q.complex)
@ComplexPredicate.register(Pow) # type:ignore
def _(expr, assumptions):
if expr.base == E:
return True
return test_closed_group(expr, assumptions, Q.complex)
@ComplexPredicate.register_many(Determinant, MatrixElement, Trace) # type:ignore
def _(expr, assumptions):
return ask(Q.complex_elements(expr.args[0]), assumptions)
@ComplexPredicate.register(NaN) # type:ignore
def _(expr, assumptions):
return None
# ImaginaryPredicate
def _Imaginary_number(expr, assumptions):
# let as_real_imag() work first since the expression may
# be simpler to evaluate
r = expr.as_real_imag()[0].evalf(2)
if r._prec != 1:
return not r
# allow None to be returned if we couldn't show for sure
# that r was 0
@ImaginaryPredicate.register(ImaginaryUnit) # type:ignore
def _(expr, assumptions):
return True
@ImaginaryPredicate.register(Expr) # type:ignore
def _(expr, assumptions):
ret = expr.is_imaginary
if ret is None:
raise MDNotImplementedError
return ret
@ImaginaryPredicate.register(Add) # type:ignore
def _(expr, assumptions):
"""
* Imaginary + Imaginary -> Imaginary
* Imaginary + Complex -> ?
* Imaginary + Real -> !Imaginary
"""
if expr.is_number:
return _Imaginary_number(expr, assumptions)
reals = 0
for arg in expr.args:
if ask(Q.imaginary(arg), assumptions):
pass
elif ask(Q.real(arg), assumptions):
reals += 1
else:
break
else:
if reals == 0:
return True
if reals in (1, len(expr.args)):
# two reals could sum 0 thus giving an imaginary
return False
@ImaginaryPredicate.register(Mul) # type:ignore
def _(expr, assumptions):
"""
* Real*Imaginary -> Imaginary
* Imaginary*Imaginary -> Real
"""
if expr.is_number:
return _Imaginary_number(expr, assumptions)
result = False
reals = 0
for arg in expr.args:
if ask(Q.imaginary(arg), assumptions):
result = result ^ True
elif not ask(Q.real(arg), assumptions):
break
else:
if reals == len(expr.args):
return False
return result
@ImaginaryPredicate.register(Pow) # type:ignore
def _(expr, assumptions):
"""
* Imaginary**Odd -> Imaginary
* Imaginary**Even -> Real
* b**Imaginary -> !Imaginary if exponent is an integer
multiple of I*pi/log(b)
* Imaginary**Real -> ?
* Positive**Real -> Real
* Negative**Integer -> Real
* Negative**(Integer/2) -> Imaginary
* Negative**Real -> not Imaginary if exponent is not Rational
"""
if expr.is_number:
return _Imaginary_number(expr, assumptions)
if expr.base == E:
a = expr.exp/I/pi
return ask(Q.integer(2*a) & ~Q.integer(a), assumptions)
if expr.base.func == exp or (expr.base.is_Pow and expr.base.base == E):
if ask(Q.imaginary(expr.base.exp), assumptions):
if ask(Q.imaginary(expr.exp), assumptions):
return False
i = expr.base.exp/I/pi
if ask(Q.integer(2*i), assumptions):
return ask(Q.imaginary((S.NegativeOne**i)**expr.exp), assumptions)
if ask(Q.imaginary(expr.base), assumptions):
if ask(Q.integer(expr.exp), assumptions):
odd = ask(Q.odd(expr.exp), assumptions)
if odd is not None:
return odd
return
if ask(Q.imaginary(expr.exp), assumptions):
imlog = ask(Q.imaginary(log(expr.base)), assumptions)
if imlog is not None:
# I**i -> real; (2*I)**i -> complex ==> not imaginary
return False
if ask(Q.real(expr.base) & Q.real(expr.exp), assumptions):
if ask(Q.positive(expr.base), assumptions):
return False
else:
rat = ask(Q.rational(expr.exp), assumptions)
if not rat:
return rat
if ask(Q.integer(expr.exp), assumptions):
return False
else:
half = ask(Q.integer(2*expr.exp), assumptions)
if half:
return ask(Q.negative(expr.base), assumptions)
return half
@ImaginaryPredicate.register(log) # type:ignore
def _(expr, assumptions):
if ask(Q.real(expr.args[0]), assumptions):
if ask(Q.positive(expr.args[0]), assumptions):
return False
return
# XXX it should be enough to do
# return ask(Q.nonpositive(expr.args[0]), assumptions)
# but ask(Q.nonpositive(exp(x)), Q.imaginary(x)) -> None;
# it should return True since exp(x) will be either 0 or complex
if expr.args[0].func == exp or (expr.args[0].is_Pow and expr.args[0].base == E):
if expr.args[0].exp in [I, -I]:
return True
im = ask(Q.imaginary(expr.args[0]), assumptions)
if im is False:
return False
@ImaginaryPredicate.register(exp) # type:ignore
def _(expr, assumptions):
a = expr.exp/I/pi
return ask(Q.integer(2*a) & ~Q.integer(a), assumptions)
@ImaginaryPredicate.register_many(Number, NumberSymbol) # type:ignore
def _(expr, assumptions):
return not (expr.as_real_imag()[1] == 0)
@ImaginaryPredicate.register(NaN) # type:ignore
def _(expr, assumptions):
return None
# AntihermitianPredicate
@AntihermitianPredicate.register(object) # type:ignore
def _(expr, assumptions):
if isinstance(expr, MatrixBase):
return None
if ask(Q.zero(expr), assumptions):
return True
return ask(Q.imaginary(expr), assumptions)
@AntihermitianPredicate.register(Add) # type:ignore
def _(expr, assumptions):
"""
* Antihermitian + Antihermitian -> Antihermitian
* Antihermitian + !Antihermitian -> !Antihermitian
"""
if expr.is_number:
raise MDNotImplementedError
return test_closed_group(expr, assumptions, Q.antihermitian)
@AntihermitianPredicate.register(Mul) # type:ignore
def _(expr, assumptions):
"""
As long as there is at most only one noncommutative term:
* Hermitian*Hermitian -> !Antihermitian
* Hermitian*Antihermitian -> Antihermitian
* Antihermitian*Antihermitian -> !Antihermitian
"""
if expr.is_number:
raise MDNotImplementedError
nccount = 0
result = False
for arg in expr.args:
if ask(Q.antihermitian(arg), assumptions):
result = result ^ True
elif not ask(Q.hermitian(arg), assumptions):
break
if ask(~Q.commutative(arg), assumptions):
nccount += 1
if nccount > 1:
break
else:
return result
@AntihermitianPredicate.register(Pow) # type:ignore
def _(expr, assumptions):
"""
* Hermitian**Integer -> !Antihermitian
* Antihermitian**Even -> !Antihermitian
* Antihermitian**Odd -> Antihermitian
"""
if expr.is_number:
raise MDNotImplementedError
if ask(Q.hermitian(expr.base), assumptions):
if ask(Q.integer(expr.exp), assumptions):
return False
elif ask(Q.antihermitian(expr.base), assumptions):
if ask(Q.even(expr.exp), assumptions):
return False
elif ask(Q.odd(expr.exp), assumptions):
return True
raise MDNotImplementedError
@AntihermitianPredicate.register(MatrixBase) # type:ignore
def _(mat, assumptions):
rows, cols = mat.shape
ret_val = True
for i in range(rows):
for j in range(i, cols):
cond = fuzzy_bool(Eq(mat[i, j], -conjugate(mat[j, i])))
if cond is None:
ret_val = None
if cond == False:
return False
if ret_val is None:
raise MDNotImplementedError
return ret_val
# AlgebraicPredicate
@AlgebraicPredicate.register_many(AlgebraicNumber, Float, GoldenRatio, # type:ignore
ImaginaryUnit, TribonacciConstant)
def _(expr, assumptions):
return True
@AlgebraicPredicate.register_many(ComplexInfinity, Exp1, Infinity, # type:ignore
NegativeInfinity, Pi)
def _(expr, assumptions):
return False
@AlgebraicPredicate.register_many(Add, Mul) # type:ignore
def _(expr, assumptions):
return test_closed_group(expr, assumptions, Q.algebraic)
@AlgebraicPredicate.register(Pow) # type:ignore
def _(expr, assumptions):
if expr.base == E:
if ask(Q.algebraic(expr.exp), assumptions):
return ask(~Q.nonzero(expr.exp), assumptions)
return
if expr.base == pi:
if ask(Q.integer(expr.exp), assumptions) and ask(Q.positive(expr.exp), assumptions):
return False
return
exp_rational = ask(Q.rational(expr.exp), assumptions)
base_algebraic = ask(Q.algebraic(expr.base), assumptions)
exp_algebraic = ask(Q.algebraic(expr.exp),assumptions)
if base_algebraic and exp_algebraic:
if exp_rational:
return True
# Check based on the Gelfond-Schneider theorem:
# If the base is algebraic and not equal to 0 or 1, and the exponent
# is irrational,then the result is transcendental.
if ask(Q.ne(expr.base,0) & Q.ne(expr.base,1)) and exp_rational is False:
return False
@AlgebraicPredicate.register(Rational) # type:ignore
def _(expr, assumptions):
return expr.q != 0
@AlgebraicPredicate.register_many(asin, atan, cos, sin, tan) # type:ignore
def _(expr, assumptions):
x = expr.args[0]
if ask(Q.algebraic(x), assumptions):
return ask(~Q.nonzero(x), assumptions)
@AlgebraicPredicate.register(exp) # type:ignore
def _(expr, assumptions):
x = expr.exp
if ask(Q.algebraic(x), assumptions):
return ask(~Q.nonzero(x), assumptions)
@AlgebraicPredicate.register_many(acot, cot) # type:ignore
def _(expr, assumptions):
x = expr.args[0]
if ask(Q.algebraic(x), assumptions):
return False
@AlgebraicPredicate.register_many(acos, log) # type:ignore
def _(expr, assumptions):
x = expr.args[0]
if ask(Q.algebraic(x), assumptions):
return ask(~Q.nonzero(x - 1), assumptions)
@@ -0,0 +1,286 @@
from sympy.assumptions.assume import global_assumptions
from sympy.assumptions.cnf import CNF, EncodedCNF
from sympy.assumptions.ask import Q
from sympy.logic.inference import satisfiable
from sympy.logic.algorithms.lra_theory import UnhandledInput, ALLOWED_PRED
from sympy.matrices.kind import MatrixKind
from sympy.core.kind import NumberKind
from sympy.assumptions.assume import AppliedPredicate
from sympy.core.mul import Mul
from sympy.core.singleton import S
def lra_satask(proposition, assumptions=True, context=global_assumptions):
"""
Function to evaluate the proposition with assumptions using SAT algorithm
in conjunction with an Linear Real Arithmetic theory solver.
Used to handle inequalities. Should eventually be depreciated and combined
into satask, but infinity handling and other things need to be implemented
before that can happen.
"""
props = CNF.from_prop(proposition)
_props = CNF.from_prop(~proposition)
cnf = CNF.from_prop(assumptions)
assumptions = EncodedCNF()
assumptions.from_cnf(cnf)
context_cnf = CNF()
if context:
context_cnf = context_cnf.extend(context)
assumptions.add_from_cnf(context_cnf)
return check_satisfiability(props, _props, assumptions)
# Some predicates such as Q.prime can't be handled by lra_satask.
# For example, (x > 0) & (x < 1) & Q.prime(x) is unsat but lra_satask would think it was sat.
# WHITE_LIST is a list of predicates that can always be handled.
WHITE_LIST = ALLOWED_PRED | {Q.positive, Q.negative, Q.zero, Q.nonzero, Q.nonpositive, Q.nonnegative,
Q.extended_positive, Q.extended_negative, Q.extended_nonpositive,
Q.extended_negative, Q.extended_nonzero, Q.negative_infinite,
Q.positive_infinite}
def check_satisfiability(prop, _prop, factbase):
sat_true = factbase.copy()
sat_false = factbase.copy()
sat_true.add_from_cnf(prop)
sat_false.add_from_cnf(_prop)
all_pred, all_exprs = get_all_pred_and_expr_from_enc_cnf(sat_true)
for pred in all_pred:
if pred.function not in WHITE_LIST and pred.function != Q.ne:
raise UnhandledInput(f"LRASolver: {pred} is an unhandled predicate")
for expr in all_exprs:
if expr.kind == MatrixKind(NumberKind):
raise UnhandledInput(f"LRASolver: {expr} is of MatrixKind")
if expr == S.NaN:
raise UnhandledInput("LRASolver: nan")
# convert old assumptions into predicates and add them to sat_true and sat_false
# also check for unhandled predicates
for assm in extract_pred_from_old_assum(all_exprs):
n = len(sat_true.encoding)
if assm not in sat_true.encoding:
sat_true.encoding[assm] = n+1
sat_true.data.append([sat_true.encoding[assm]])
n = len(sat_false.encoding)
if assm not in sat_false.encoding:
sat_false.encoding[assm] = n+1
sat_false.data.append([sat_false.encoding[assm]])
sat_true = _preprocess(sat_true)
sat_false = _preprocess(sat_false)
can_be_true = satisfiable(sat_true, use_lra_theory=True) is not False
can_be_false = satisfiable(sat_false, use_lra_theory=True) is not False
if can_be_true and can_be_false:
return None
if can_be_true and not can_be_false:
return True
if not can_be_true and can_be_false:
return False
if not can_be_true and not can_be_false:
raise ValueError("Inconsistent assumptions")
def _preprocess(enc_cnf):
"""
Returns an encoded cnf with only Q.eq, Q.gt, Q.lt,
Q.ge, and Q.le predicate.
Converts every unequality into a disjunction of strict
inequalities. For example, x != 3 would become
x < 3 OR x > 3.
Also converts all negated Q.ne predicates into
equalities.
"""
# loops through each literal in each clause
# to construct a new, preprocessed encodedCNF
enc_cnf = enc_cnf.copy()
cur_enc = 1
rev_encoding = {value: key for key, value in enc_cnf.encoding.items()}
new_encoding = {}
new_data = []
for clause in enc_cnf.data:
new_clause = []
for lit in clause:
if lit == 0:
new_clause.append(lit)
new_encoding[lit] = False
continue
prop = rev_encoding[abs(lit)]
negated = lit < 0
sign = (lit > 0) - (lit < 0)
prop = _pred_to_binrel(prop)
if not isinstance(prop, AppliedPredicate):
if prop not in new_encoding:
new_encoding[prop] = cur_enc
cur_enc += 1
lit = new_encoding[prop]
new_clause.append(sign*lit)
continue
if negated and prop.function == Q.eq:
negated = False
prop = Q.ne(*prop.arguments)
if prop.function == Q.ne:
arg1, arg2 = prop.arguments
if negated:
new_prop = Q.eq(arg1, arg2)
if new_prop not in new_encoding:
new_encoding[new_prop] = cur_enc
cur_enc += 1
new_enc = new_encoding[new_prop]
new_clause.append(new_enc)
continue
else:
new_props = (Q.gt(arg1, arg2), Q.lt(arg1, arg2))
for new_prop in new_props:
if new_prop not in new_encoding:
new_encoding[new_prop] = cur_enc
cur_enc += 1
new_enc = new_encoding[new_prop]
new_clause.append(new_enc)
continue
if prop.function == Q.eq and negated:
assert False
if prop not in new_encoding:
new_encoding[prop] = cur_enc
cur_enc += 1
new_clause.append(new_encoding[prop]*sign)
new_data.append(new_clause)
assert len(new_encoding) >= cur_enc - 1
enc_cnf = EncodedCNF(new_data, new_encoding)
return enc_cnf
def _pred_to_binrel(pred):
if not isinstance(pred, AppliedPredicate):
return pred
if pred.function in pred_to_pos_neg_zero:
f = pred_to_pos_neg_zero[pred.function]
if f is False:
return False
pred = f(pred.arguments[0])
if pred.function == Q.positive:
pred = Q.gt(pred.arguments[0], 0)
elif pred.function == Q.negative:
pred = Q.lt(pred.arguments[0], 0)
elif pred.function == Q.zero:
pred = Q.eq(pred.arguments[0], 0)
elif pred.function == Q.nonpositive:
pred = Q.le(pred.arguments[0], 0)
elif pred.function == Q.nonnegative:
pred = Q.ge(pred.arguments[0], 0)
elif pred.function == Q.nonzero:
pred = Q.ne(pred.arguments[0], 0)
return pred
pred_to_pos_neg_zero = {
Q.extended_positive: Q.positive,
Q.extended_negative: Q.negative,
Q.extended_nonpositive: Q.nonpositive,
Q.extended_negative: Q.negative,
Q.extended_nonzero: Q.nonzero,
Q.negative_infinite: False,
Q.positive_infinite: False
}
def get_all_pred_and_expr_from_enc_cnf(enc_cnf):
all_exprs = set()
all_pred = set()
for pred in enc_cnf.encoding.keys():
if isinstance(pred, AppliedPredicate):
all_pred.add(pred)
all_exprs.update(pred.arguments)
return all_pred, all_exprs
def extract_pred_from_old_assum(all_exprs):
"""
Returns a list of relevant new assumption predicate
based on any old assumptions.
Raises an UnhandledInput exception if any of the assumptions are
unhandled.
Ignored predicate:
- commutative
- complex
- algebraic
- transcendental
- extended_real
- real
- all matrix predicate
- rational
- irrational
Example
=======
>>> from sympy.assumptions.lra_satask import extract_pred_from_old_assum
>>> from sympy import symbols
>>> x, y = symbols("x y", positive=True)
>>> extract_pred_from_old_assum([x, y, 2])
[Q.positive(x), Q.positive(y)]
"""
ret = []
for expr in all_exprs:
if not hasattr(expr, "free_symbols"):
continue
if len(expr.free_symbols) == 0:
continue
if expr.is_real is not True:
raise UnhandledInput(f"LRASolver: {expr} must be real")
# test for I times imaginary variable; such expressions are considered real
if isinstance(expr, Mul) and any(arg.is_real is not True for arg in expr.args):
raise UnhandledInput(f"LRASolver: {expr} must be real")
if expr.is_integer == True and expr.is_zero != True:
raise UnhandledInput(f"LRASolver: {expr} is an integer")
if expr.is_integer == False:
raise UnhandledInput(f"LRASolver: {expr} can't be an integer")
if expr.is_rational == False:
raise UnhandledInput(f"LRASolver: {expr} is irational")
if expr.is_zero:
ret.append(Q.zero(expr))
elif expr.is_positive:
ret.append(Q.positive(expr))
elif expr.is_negative:
ret.append(Q.negative(expr))
elif expr.is_nonzero:
ret.append(Q.nonzero(expr))
elif expr.is_nonpositive:
ret.append(Q.nonpositive(expr))
elif expr.is_nonnegative:
ret.append(Q.nonnegative(expr))
return ret
@@ -0,0 +1,5 @@
"""
Module to implement predicate classes.
Class of every predicate registered to ``Q`` is defined here.
"""
@@ -0,0 +1,82 @@
from sympy.assumptions import Predicate
from sympy.multipledispatch import Dispatcher
class FinitePredicate(Predicate):
"""
Finite number predicate.
Explanation
===========
``Q.finite(x)`` is true if ``x`` is a number but neither an infinity
nor a ``NaN``. In other words, ``ask(Q.finite(x))`` is true for all
numerical ``x`` having a bounded absolute value.
Examples
========
>>> from sympy import Q, ask, S, oo, I, zoo
>>> from sympy.abc import x
>>> ask(Q.finite(oo))
False
>>> ask(Q.finite(-oo))
False
>>> ask(Q.finite(zoo))
False
>>> ask(Q.finite(1))
True
>>> ask(Q.finite(2 + 3*I))
True
>>> ask(Q.finite(x), Q.positive(x))
True
>>> print(ask(Q.finite(S.NaN)))
None
References
==========
.. [1] https://en.wikipedia.org/wiki/Finite
"""
name = 'finite'
handler = Dispatcher(
"FiniteHandler",
doc=("Handler for Q.finite. Test that an expression is bounded respect"
" to all its variables.")
)
class InfinitePredicate(Predicate):
"""
Infinite number predicate.
``Q.infinite(x)`` is true iff the absolute value of ``x`` is
infinity.
"""
# TODO: Add examples
name = 'infinite'
handler = Dispatcher(
"InfiniteHandler",
doc="""Handler for Q.infinite key."""
)
class PositiveInfinitePredicate(Predicate):
"""
Positive infinity predicate.
``Q.positive_infinite(x)`` is true iff ``x`` is positive infinity ``oo``.
"""
name = 'positive_infinite'
handler = Dispatcher("PositiveInfiniteHandler")
class NegativeInfinitePredicate(Predicate):
"""
Negative infinity predicate.
``Q.negative_infinite(x)`` is true iff ``x`` is negative infinity ``-oo``.
"""
name = 'negative_infinite'
handler = Dispatcher("NegativeInfiniteHandler")
@@ -0,0 +1,81 @@
from sympy.assumptions import Predicate, AppliedPredicate, Q
from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le
from sympy.multipledispatch import Dispatcher
class CommutativePredicate(Predicate):
"""
Commutative predicate.
Explanation
===========
``ask(Q.commutative(x))`` is true iff ``x`` commutes with any other
object with respect to multiplication operation.
"""
# TODO: Add examples
name = 'commutative'
handler = Dispatcher("CommutativeHandler", doc="Handler for key 'commutative'.")
binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le}
class IsTruePredicate(Predicate):
"""
Generic predicate.
Explanation
===========
``ask(Q.is_true(x))`` is true iff ``x`` is true. This only makes
sense if ``x`` is a boolean object.
Examples
========
>>> from sympy import ask, Q
>>> from sympy.abc import x, y
>>> ask(Q.is_true(True))
True
Wrapping another applied predicate just returns the applied predicate.
>>> Q.is_true(Q.even(x))
Q.even(x)
Wrapping binary relation classes in SymPy core returns applied binary
relational predicates.
>>> from sympy import Eq, Gt
>>> Q.is_true(Eq(x, y))
Q.eq(x, y)
>>> Q.is_true(Gt(x, y))
Q.gt(x, y)
Notes
=====
This class is designed to wrap the boolean objects so that they can
behave as if they are applied predicates. Consequently, wrapping another
applied predicate is unnecessary and thus it just returns the argument.
Also, binary relation classes in SymPy core have binary predicates to
represent themselves and thus wrapping them with ``Q.is_true`` converts them
to these applied predicates.
"""
name = 'is_true'
handler = Dispatcher(
"IsTrueHandler",
doc="Wrapper allowing to query the truth value of a boolean expression."
)
def __call__(self, arg):
# No need to wrap another predicate
if isinstance(arg, AppliedPredicate):
return arg
# Convert relational predicates instead of wrapping them
if getattr(arg, "is_Relational", False):
pred = binrelpreds[type(arg)]
return pred(*arg.args)
return super().__call__(arg)
@@ -0,0 +1,511 @@
from sympy.assumptions import Predicate
from sympy.multipledispatch import Dispatcher
class SquarePredicate(Predicate):
"""
Square matrix predicate.
Explanation
===========
``Q.square(x)`` is true iff ``x`` is a square matrix. A square matrix
is a matrix with the same number of rows and columns.
Examples
========
>>> from sympy import Q, ask, MatrixSymbol, ZeroMatrix, Identity
>>> X = MatrixSymbol('X', 2, 2)
>>> Y = MatrixSymbol('X', 2, 3)
>>> ask(Q.square(X))
True
>>> ask(Q.square(Y))
False
>>> ask(Q.square(ZeroMatrix(3, 3)))
True
>>> ask(Q.square(Identity(3)))
True
References
==========
.. [1] https://en.wikipedia.org/wiki/Square_matrix
"""
name = 'square'
handler = Dispatcher("SquareHandler", doc="Handler for Q.square.")
class SymmetricPredicate(Predicate):
"""
Symmetric matrix predicate.
Explanation
===========
``Q.symmetric(x)`` is true iff ``x`` is a square matrix and is equal to
its transpose. Every square diagonal matrix is a symmetric matrix.
Examples
========
>>> from sympy import Q, ask, MatrixSymbol
>>> X = MatrixSymbol('X', 2, 2)
>>> Y = MatrixSymbol('Y', 2, 3)
>>> Z = MatrixSymbol('Z', 2, 2)
>>> ask(Q.symmetric(X*Z), Q.symmetric(X) & Q.symmetric(Z))
True
>>> ask(Q.symmetric(X + Z), Q.symmetric(X) & Q.symmetric(Z))
True
>>> ask(Q.symmetric(Y))
False
References
==========
.. [1] https://en.wikipedia.org/wiki/Symmetric_matrix
"""
# TODO: Add handlers to make these keys work with
# actual matrices and add more examples in the docstring.
name = 'symmetric'
handler = Dispatcher("SymmetricHandler", doc="Handler for Q.symmetric.")
class InvertiblePredicate(Predicate):
"""
Invertible matrix predicate.
Explanation
===========
``Q.invertible(x)`` is true iff ``x`` is an invertible matrix.
A square matrix is called invertible only if its determinant is 0.
Examples
========
>>> from sympy import Q, ask, MatrixSymbol
>>> X = MatrixSymbol('X', 2, 2)
>>> Y = MatrixSymbol('Y', 2, 3)
>>> Z = MatrixSymbol('Z', 2, 2)
>>> ask(Q.invertible(X*Y), Q.invertible(X))
False
>>> ask(Q.invertible(X*Z), Q.invertible(X) & Q.invertible(Z))
True
>>> ask(Q.invertible(X), Q.fullrank(X) & Q.square(X))
True
References
==========
.. [1] https://en.wikipedia.org/wiki/Invertible_matrix
"""
name = 'invertible'
handler = Dispatcher("InvertibleHandler", doc="Handler for Q.invertible.")
class OrthogonalPredicate(Predicate):
"""
Orthogonal matrix predicate.
Explanation
===========
``Q.orthogonal(x)`` is true iff ``x`` is an orthogonal matrix.
A square matrix ``M`` is an orthogonal matrix if it satisfies
``M^TM = MM^T = I`` where ``M^T`` is the transpose matrix of
``M`` and ``I`` is an identity matrix. Note that an orthogonal
matrix is necessarily invertible.
Examples
========
>>> from sympy import Q, ask, MatrixSymbol, Identity
>>> X = MatrixSymbol('X', 2, 2)
>>> Y = MatrixSymbol('Y', 2, 3)
>>> Z = MatrixSymbol('Z', 2, 2)
>>> ask(Q.orthogonal(Y))
False
>>> ask(Q.orthogonal(X*Z*X), Q.orthogonal(X) & Q.orthogonal(Z))
True
>>> ask(Q.orthogonal(Identity(3)))
True
>>> ask(Q.invertible(X), Q.orthogonal(X))
True
References
==========
.. [1] https://en.wikipedia.org/wiki/Orthogonal_matrix
"""
name = 'orthogonal'
handler = Dispatcher("OrthogonalHandler", doc="Handler for key 'orthogonal'.")
class UnitaryPredicate(Predicate):
"""
Unitary matrix predicate.
Explanation
===========
``Q.unitary(x)`` is true iff ``x`` is a unitary matrix.
Unitary matrix is an analogue to orthogonal matrix. A square
matrix ``M`` with complex elements is unitary if :math:``M^TM = MM^T= I``
where :math:``M^T`` is the conjugate transpose matrix of ``M``.
Examples
========
>>> from sympy import Q, ask, MatrixSymbol, Identity
>>> X = MatrixSymbol('X', 2, 2)
>>> Y = MatrixSymbol('Y', 2, 3)
>>> Z = MatrixSymbol('Z', 2, 2)
>>> ask(Q.unitary(Y))
False
>>> ask(Q.unitary(X*Z*X), Q.unitary(X) & Q.unitary(Z))
True
>>> ask(Q.unitary(Identity(3)))
True
References
==========
.. [1] https://en.wikipedia.org/wiki/Unitary_matrix
"""
name = 'unitary'
handler = Dispatcher("UnitaryHandler", doc="Handler for key 'unitary'.")
class FullRankPredicate(Predicate):
"""
Fullrank matrix predicate.
Explanation
===========
``Q.fullrank(x)`` is true iff ``x`` is a full rank matrix.
A matrix is full rank if all rows and columns of the matrix
are linearly independent. A square matrix is full rank iff
its determinant is nonzero.
Examples
========
>>> from sympy import Q, ask, MatrixSymbol, ZeroMatrix, Identity
>>> X = MatrixSymbol('X', 2, 2)
>>> ask(Q.fullrank(X.T), Q.fullrank(X))
True
>>> ask(Q.fullrank(ZeroMatrix(3, 3)))
False
>>> ask(Q.fullrank(Identity(3)))
True
"""
name = 'fullrank'
handler = Dispatcher("FullRankHandler", doc="Handler for key 'fullrank'.")
class PositiveDefinitePredicate(Predicate):
r"""
Positive definite matrix predicate.
Explanation
===========
If $M$ is a :math:`n \times n` symmetric real matrix, it is said
to be positive definite if :math:`Z^TMZ` is positive for
every non-zero column vector $Z$ of $n$ real numbers.
Examples
========
>>> from sympy import Q, ask, MatrixSymbol, Identity
>>> X = MatrixSymbol('X', 2, 2)
>>> Y = MatrixSymbol('Y', 2, 3)
>>> Z = MatrixSymbol('Z', 2, 2)
>>> ask(Q.positive_definite(Y))
False
>>> ask(Q.positive_definite(Identity(3)))
True
>>> ask(Q.positive_definite(X + Z), Q.positive_definite(X) &
... Q.positive_definite(Z))
True
References
==========
.. [1] https://en.wikipedia.org/wiki/Positive-definite_matrix
"""
name = "positive_definite"
handler = Dispatcher("PositiveDefiniteHandler", doc="Handler for key 'positive_definite'.")
class UpperTriangularPredicate(Predicate):
"""
Upper triangular matrix predicate.
Explanation
===========
A matrix $M$ is called upper triangular matrix if :math:`M_{ij}=0`
for :math:`i<j`.
Examples
========
>>> from sympy import Q, ask, ZeroMatrix, Identity
>>> ask(Q.upper_triangular(Identity(3)))
True
>>> ask(Q.upper_triangular(ZeroMatrix(3, 3)))
True
References
==========
.. [1] https://mathworld.wolfram.com/UpperTriangularMatrix.html
"""
name = "upper_triangular"
handler = Dispatcher("UpperTriangularHandler", doc="Handler for key 'upper_triangular'.")
class LowerTriangularPredicate(Predicate):
"""
Lower triangular matrix predicate.
Explanation
===========
A matrix $M$ is called lower triangular matrix if :math:`M_{ij}=0`
for :math:`i>j`.
Examples
========
>>> from sympy import Q, ask, ZeroMatrix, Identity
>>> ask(Q.lower_triangular(Identity(3)))
True
>>> ask(Q.lower_triangular(ZeroMatrix(3, 3)))
True
References
==========
.. [1] https://mathworld.wolfram.com/LowerTriangularMatrix.html
"""
name = "lower_triangular"
handler = Dispatcher("LowerTriangularHandler", doc="Handler for key 'lower_triangular'.")
class DiagonalPredicate(Predicate):
"""
Diagonal matrix predicate.
Explanation
===========
``Q.diagonal(x)`` is true iff ``x`` is a diagonal matrix. A diagonal
matrix is a matrix in which the entries outside the main diagonal
are all zero.
Examples
========
>>> from sympy import Q, ask, MatrixSymbol, ZeroMatrix
>>> X = MatrixSymbol('X', 2, 2)
>>> ask(Q.diagonal(ZeroMatrix(3, 3)))
True
>>> ask(Q.diagonal(X), Q.lower_triangular(X) &
... Q.upper_triangular(X))
True
References
==========
.. [1] https://en.wikipedia.org/wiki/Diagonal_matrix
"""
name = "diagonal"
handler = Dispatcher("DiagonalHandler", doc="Handler for key 'diagonal'.")
class IntegerElementsPredicate(Predicate):
"""
Integer elements matrix predicate.
Explanation
===========
``Q.integer_elements(x)`` is true iff all the elements of ``x``
are integers.
Examples
========
>>> from sympy import Q, ask, MatrixSymbol
>>> X = MatrixSymbol('X', 4, 4)
>>> ask(Q.integer(X[1, 2]), Q.integer_elements(X))
True
"""
name = "integer_elements"
handler = Dispatcher("IntegerElementsHandler", doc="Handler for key 'integer_elements'.")
class RealElementsPredicate(Predicate):
"""
Real elements matrix predicate.
Explanation
===========
``Q.real_elements(x)`` is true iff all the elements of ``x``
are real numbers.
Examples
========
>>> from sympy import Q, ask, MatrixSymbol
>>> X = MatrixSymbol('X', 4, 4)
>>> ask(Q.real(X[1, 2]), Q.real_elements(X))
True
"""
name = "real_elements"
handler = Dispatcher("RealElementsHandler", doc="Handler for key 'real_elements'.")
class ComplexElementsPredicate(Predicate):
"""
Complex elements matrix predicate.
Explanation
===========
``Q.complex_elements(x)`` is true iff all the elements of ``x``
are complex numbers.
Examples
========
>>> from sympy import Q, ask, MatrixSymbol
>>> X = MatrixSymbol('X', 4, 4)
>>> ask(Q.complex(X[1, 2]), Q.complex_elements(X))
True
>>> ask(Q.complex_elements(X), Q.integer_elements(X))
True
"""
name = "complex_elements"
handler = Dispatcher("ComplexElementsHandler", doc="Handler for key 'complex_elements'.")
class SingularPredicate(Predicate):
"""
Singular matrix predicate.
A matrix is singular iff the value of its determinant is 0.
Examples
========
>>> from sympy import Q, ask, MatrixSymbol
>>> X = MatrixSymbol('X', 4, 4)
>>> ask(Q.singular(X), Q.invertible(X))
False
>>> ask(Q.singular(X), ~Q.invertible(X))
True
References
==========
.. [1] https://mathworld.wolfram.com/SingularMatrix.html
"""
name = "singular"
handler = Dispatcher("SingularHandler", doc="Predicate fore key 'singular'.")
class NormalPredicate(Predicate):
"""
Normal matrix predicate.
A matrix is normal if it commutes with its conjugate transpose.
Examples
========
>>> from sympy import Q, ask, MatrixSymbol
>>> X = MatrixSymbol('X', 4, 4)
>>> ask(Q.normal(X), Q.unitary(X))
True
References
==========
.. [1] https://en.wikipedia.org/wiki/Normal_matrix
"""
name = "normal"
handler = Dispatcher("NormalHandler", doc="Predicate fore key 'normal'.")
class TriangularPredicate(Predicate):
"""
Triangular matrix predicate.
Explanation
===========
``Q.triangular(X)`` is true if ``X`` is one that is either lower
triangular or upper triangular.
Examples
========
>>> from sympy import Q, ask, MatrixSymbol
>>> X = MatrixSymbol('X', 4, 4)
>>> ask(Q.triangular(X), Q.upper_triangular(X))
True
>>> ask(Q.triangular(X), Q.lower_triangular(X))
True
References
==========
.. [1] https://en.wikipedia.org/wiki/Triangular_matrix
"""
name = "triangular"
handler = Dispatcher("TriangularHandler", doc="Predicate fore key 'triangular'.")
class UnitTriangularPredicate(Predicate):
"""
Unit triangular matrix predicate.
Explanation
===========
A unit triangular matrix is a triangular matrix with 1s
on the diagonal.
Examples
========
>>> from sympy import Q, ask, MatrixSymbol
>>> X = MatrixSymbol('X', 4, 4)
>>> ask(Q.triangular(X), Q.unit_triangular(X))
True
"""
name = "unit_triangular"
handler = Dispatcher("UnitTriangularHandler", doc="Predicate fore key 'unit_triangular'.")
@@ -0,0 +1,126 @@
from sympy.assumptions import Predicate
from sympy.multipledispatch import Dispatcher
class PrimePredicate(Predicate):
"""
Prime number predicate.
Explanation
===========
``ask(Q.prime(x))`` is true iff ``x`` is a natural number greater
than 1 that has no positive divisors other than ``1`` and the
number itself.
Examples
========
>>> from sympy import Q, ask
>>> ask(Q.prime(0))
False
>>> ask(Q.prime(1))
False
>>> ask(Q.prime(2))
True
>>> ask(Q.prime(20))
False
>>> ask(Q.prime(-3))
False
"""
name = 'prime'
handler = Dispatcher(
"PrimeHandler",
doc=("Handler for key 'prime'. Test that an expression represents a prime"
" number. When the expression is an exact number, the result (when True)"
" is subject to the limitations of isprime() which is used to return the "
"result.")
)
class CompositePredicate(Predicate):
"""
Composite number predicate.
Explanation
===========
``ask(Q.composite(x))`` is true iff ``x`` is a positive integer and has
at least one positive divisor other than ``1`` and the number itself.
Examples
========
>>> from sympy import Q, ask
>>> ask(Q.composite(0))
False
>>> ask(Q.composite(1))
False
>>> ask(Q.composite(2))
False
>>> ask(Q.composite(20))
True
"""
name = 'composite'
handler = Dispatcher("CompositeHandler", doc="Handler for key 'composite'.")
class EvenPredicate(Predicate):
"""
Even number predicate.
Explanation
===========
``ask(Q.even(x))`` is true iff ``x`` belongs to the set of even
integers.
Examples
========
>>> from sympy import Q, ask, pi
>>> ask(Q.even(0))
True
>>> ask(Q.even(2))
True
>>> ask(Q.even(3))
False
>>> ask(Q.even(pi))
False
"""
name = 'even'
handler = Dispatcher("EvenHandler", doc="Handler for key 'even'.")
class OddPredicate(Predicate):
"""
Odd number predicate.
Explanation
===========
``ask(Q.odd(x))`` is true iff ``x`` belongs to the set of odd numbers.
Examples
========
>>> from sympy import Q, ask, pi
>>> ask(Q.odd(0))
False
>>> ask(Q.odd(2))
False
>>> ask(Q.odd(3))
True
>>> ask(Q.odd(pi))
False
"""
name = 'odd'
handler = Dispatcher(
"OddHandler",
doc=("Handler for key 'odd'. Test that an expression represents an odd"
" number.")
)
@@ -0,0 +1,390 @@
from sympy.assumptions import Predicate
from sympy.multipledispatch import Dispatcher
class NegativePredicate(Predicate):
r"""
Negative number predicate.
Explanation
===========
``Q.negative(x)`` is true iff ``x`` is a real number and :math:`x < 0`, that is,
it is in the interval :math:`(-\infty, 0)`. Note in particular that negative
infinity is not negative.
A few important facts about negative numbers:
- Note that ``Q.nonnegative`` and ``~Q.negative`` are *not* the same
thing. ``~Q.negative(x)`` simply means that ``x`` is not negative,
whereas ``Q.nonnegative(x)`` means that ``x`` is real and not
negative, i.e., ``Q.nonnegative(x)`` is logically equivalent to
``Q.zero(x) | Q.positive(x)``. So for example, ``~Q.negative(I)`` is
true, whereas ``Q.nonnegative(I)`` is false.
- See the documentation of ``Q.real`` for more information about
related facts.
Examples
========
>>> from sympy import Q, ask, symbols, I
>>> x = symbols('x')
>>> ask(Q.negative(x), Q.real(x) & ~Q.positive(x) & ~Q.zero(x))
True
>>> ask(Q.negative(-1))
True
>>> ask(Q.nonnegative(I))
False
>>> ask(~Q.negative(I))
True
"""
name = 'negative'
handler = Dispatcher(
"NegativeHandler",
doc=("Handler for Q.negative. Test that an expression is strictly less"
" than zero.")
)
class NonNegativePredicate(Predicate):
"""
Nonnegative real number predicate.
Explanation
===========
``ask(Q.nonnegative(x))`` is true iff ``x`` belongs to the set of
positive numbers including zero.
- Note that ``Q.nonnegative`` and ``~Q.negative`` are *not* the same
thing. ``~Q.negative(x)`` simply means that ``x`` is not negative,
whereas ``Q.nonnegative(x)`` means that ``x`` is real and not
negative, i.e., ``Q.nonnegative(x)`` is logically equivalent to
``Q.zero(x) | Q.positive(x)``. So for example, ``~Q.negative(I)`` is
true, whereas ``Q.nonnegative(I)`` is false.
Examples
========
>>> from sympy import Q, ask, I
>>> ask(Q.nonnegative(1))
True
>>> ask(Q.nonnegative(0))
True
>>> ask(Q.nonnegative(-1))
False
>>> ask(Q.nonnegative(I))
False
>>> ask(Q.nonnegative(-I))
False
"""
name = 'nonnegative'
handler = Dispatcher(
"NonNegativeHandler",
doc=("Handler for Q.nonnegative.")
)
class NonZeroPredicate(Predicate):
"""
Nonzero real number predicate.
Explanation
===========
``ask(Q.nonzero(x))`` is true iff ``x`` is real and ``x`` is not zero. Note in
particular that ``Q.nonzero(x)`` is false if ``x`` is not real. Use
``~Q.zero(x)`` if you want the negation of being zero without any real
assumptions.
A few important facts about nonzero numbers:
- ``Q.nonzero`` is logically equivalent to ``Q.positive | Q.negative``.
- See the documentation of ``Q.real`` for more information about
related facts.
Examples
========
>>> from sympy import Q, ask, symbols, I, oo
>>> x = symbols('x')
>>> print(ask(Q.nonzero(x), ~Q.zero(x)))
None
>>> ask(Q.nonzero(x), Q.positive(x))
True
>>> ask(Q.nonzero(x), Q.zero(x))
False
>>> ask(Q.nonzero(0))
False
>>> ask(Q.nonzero(I))
False
>>> ask(~Q.zero(I))
True
>>> ask(Q.nonzero(oo))
False
"""
name = 'nonzero'
handler = Dispatcher(
"NonZeroHandler",
doc=("Handler for key 'nonzero'. Test that an expression is not identically"
" zero.")
)
class ZeroPredicate(Predicate):
"""
Zero number predicate.
Explanation
===========
``ask(Q.zero(x))`` is true iff the value of ``x`` is zero.
Examples
========
>>> from sympy import ask, Q, oo, symbols
>>> x, y = symbols('x, y')
>>> ask(Q.zero(0))
True
>>> ask(Q.zero(1/oo))
True
>>> print(ask(Q.zero(0*oo)))
None
>>> ask(Q.zero(1))
False
>>> ask(Q.zero(x*y), Q.zero(x) | Q.zero(y))
True
"""
name = 'zero'
handler = Dispatcher(
"ZeroHandler",
doc="Handler for key 'zero'."
)
class NonPositivePredicate(Predicate):
"""
Nonpositive real number predicate.
Explanation
===========
``ask(Q.nonpositive(x))`` is true iff ``x`` belongs to the set of
negative numbers including zero.
- Note that ``Q.nonpositive`` and ``~Q.positive`` are *not* the same
thing. ``~Q.positive(x)`` simply means that ``x`` is not positive,
whereas ``Q.nonpositive(x)`` means that ``x`` is real and not
positive, i.e., ``Q.nonpositive(x)`` is logically equivalent to
`Q.negative(x) | Q.zero(x)``. So for example, ``~Q.positive(I)`` is
true, whereas ``Q.nonpositive(I)`` is false.
Examples
========
>>> from sympy import Q, ask, I
>>> ask(Q.nonpositive(-1))
True
>>> ask(Q.nonpositive(0))
True
>>> ask(Q.nonpositive(1))
False
>>> ask(Q.nonpositive(I))
False
>>> ask(Q.nonpositive(-I))
False
"""
name = 'nonpositive'
handler = Dispatcher(
"NonPositiveHandler",
doc="Handler for key 'nonpositive'."
)
class PositivePredicate(Predicate):
r"""
Positive real number predicate.
Explanation
===========
``Q.positive(x)`` is true iff ``x`` is real and `x > 0`, that is if ``x``
is in the interval `(0, \infty)`. In particular, infinity is not
positive.
A few important facts about positive numbers:
- Note that ``Q.nonpositive`` and ``~Q.positive`` are *not* the same
thing. ``~Q.positive(x)`` simply means that ``x`` is not positive,
whereas ``Q.nonpositive(x)`` means that ``x`` is real and not
positive, i.e., ``Q.nonpositive(x)`` is logically equivalent to
`Q.negative(x) | Q.zero(x)``. So for example, ``~Q.positive(I)`` is
true, whereas ``Q.nonpositive(I)`` is false.
- See the documentation of ``Q.real`` for more information about
related facts.
Examples
========
>>> from sympy import Q, ask, symbols, I
>>> x = symbols('x')
>>> ask(Q.positive(x), Q.real(x) & ~Q.negative(x) & ~Q.zero(x))
True
>>> ask(Q.positive(1))
True
>>> ask(Q.nonpositive(I))
False
>>> ask(~Q.positive(I))
True
"""
name = 'positive'
handler = Dispatcher(
"PositiveHandler",
doc=("Handler for key 'positive'. Test that an expression is strictly"
" greater than zero.")
)
class ExtendedPositivePredicate(Predicate):
r"""
Positive extended real number predicate.
Explanation
===========
``Q.extended_positive(x)`` is true iff ``x`` is extended real and
`x > 0`, that is if ``x`` is in the interval `(0, \infty]`.
Examples
========
>>> from sympy import ask, I, oo, Q
>>> ask(Q.extended_positive(1))
True
>>> ask(Q.extended_positive(oo))
True
>>> ask(Q.extended_positive(I))
False
"""
name = 'extended_positive'
handler = Dispatcher("ExtendedPositiveHandler")
class ExtendedNegativePredicate(Predicate):
r"""
Negative extended real number predicate.
Explanation
===========
``Q.extended_negative(x)`` is true iff ``x`` is extended real and
`x < 0`, that is if ``x`` is in the interval `[-\infty, 0)`.
Examples
========
>>> from sympy import ask, I, oo, Q
>>> ask(Q.extended_negative(-1))
True
>>> ask(Q.extended_negative(-oo))
True
>>> ask(Q.extended_negative(-I))
False
"""
name = 'extended_negative'
handler = Dispatcher("ExtendedNegativeHandler")
class ExtendedNonZeroPredicate(Predicate):
"""
Nonzero extended real number predicate.
Explanation
===========
``ask(Q.extended_nonzero(x))`` is true iff ``x`` is extended real and
``x`` is not zero.
Examples
========
>>> from sympy import ask, I, oo, Q
>>> ask(Q.extended_nonzero(-1))
True
>>> ask(Q.extended_nonzero(oo))
True
>>> ask(Q.extended_nonzero(I))
False
"""
name = 'extended_nonzero'
handler = Dispatcher("ExtendedNonZeroHandler")
class ExtendedNonPositivePredicate(Predicate):
"""
Nonpositive extended real number predicate.
Explanation
===========
``ask(Q.extended_nonpositive(x))`` is true iff ``x`` is extended real and
``x`` is not positive.
Examples
========
>>> from sympy import ask, I, oo, Q
>>> ask(Q.extended_nonpositive(-1))
True
>>> ask(Q.extended_nonpositive(oo))
False
>>> ask(Q.extended_nonpositive(0))
True
>>> ask(Q.extended_nonpositive(I))
False
"""
name = 'extended_nonpositive'
handler = Dispatcher("ExtendedNonPositiveHandler")
class ExtendedNonNegativePredicate(Predicate):
"""
Nonnegative extended real number predicate.
Explanation
===========
``ask(Q.extended_nonnegative(x))`` is true iff ``x`` is extended real and
``x`` is not negative.
Examples
========
>>> from sympy import ask, I, oo, Q
>>> ask(Q.extended_nonnegative(-1))
False
>>> ask(Q.extended_nonnegative(oo))
True
>>> ask(Q.extended_nonnegative(0))
True
>>> ask(Q.extended_nonnegative(I))
False
"""
name = 'extended_nonnegative'
handler = Dispatcher("ExtendedNonNegativeHandler")
@@ -0,0 +1,399 @@
from sympy.assumptions import Predicate
from sympy.multipledispatch import Dispatcher
class IntegerPredicate(Predicate):
"""
Integer predicate.
Explanation
===========
``Q.integer(x)`` is true iff ``x`` belongs to the set of integer
numbers.
Examples
========
>>> from sympy import Q, ask, S
>>> ask(Q.integer(5))
True
>>> ask(Q.integer(S(1)/2))
False
References
==========
.. [1] https://en.wikipedia.org/wiki/Integer
"""
name = 'integer'
handler = Dispatcher(
"IntegerHandler",
doc=("Handler for Q.integer.\n\n"
"Test that an expression belongs to the field of integer numbers.")
)
class NonIntegerPredicate(Predicate):
"""
Non-integer extended real predicate.
"""
name = 'noninteger'
handler = Dispatcher(
"NonIntegerHandler",
doc=("Handler for Q.noninteger.\n\n"
"Test that an expression is a non-integer extended real number.")
)
class RationalPredicate(Predicate):
"""
Rational number predicate.
Explanation
===========
``Q.rational(x)`` is true iff ``x`` belongs to the set of
rational numbers.
Examples
========
>>> from sympy import ask, Q, pi, S
>>> ask(Q.rational(0))
True
>>> ask(Q.rational(S(1)/2))
True
>>> ask(Q.rational(pi))
False
References
==========
.. [1] https://en.wikipedia.org/wiki/Rational_number
"""
name = 'rational'
handler = Dispatcher(
"RationalHandler",
doc=("Handler for Q.rational.\n\n"
"Test that an expression belongs to the field of rational numbers.")
)
class IrrationalPredicate(Predicate):
"""
Irrational number predicate.
Explanation
===========
``Q.irrational(x)`` is true iff ``x`` is any real number that
cannot be expressed as a ratio of integers.
Examples
========
>>> from sympy import ask, Q, pi, S, I
>>> ask(Q.irrational(0))
False
>>> ask(Q.irrational(S(1)/2))
False
>>> ask(Q.irrational(pi))
True
>>> ask(Q.irrational(I))
False
References
==========
.. [1] https://en.wikipedia.org/wiki/Irrational_number
"""
name = 'irrational'
handler = Dispatcher(
"IrrationalHandler",
doc=("Handler for Q.irrational.\n\n"
"Test that an expression is irrational numbers.")
)
class RealPredicate(Predicate):
r"""
Real number predicate.
Explanation
===========
``Q.real(x)`` is true iff ``x`` is a real number, i.e., it is in the
interval `(-\infty, \infty)`. Note that, in particular the
infinities are not real. Use ``Q.extended_real`` if you want to
consider those as well.
A few important facts about reals:
- Every real number is positive, negative, or zero. Furthermore,
because these sets are pairwise disjoint, each real number is
exactly one of those three.
- Every real number is also complex.
- Every real number is finite.
- Every real number is either rational or irrational.
- Every real number is either algebraic or transcendental.
- The facts ``Q.negative``, ``Q.zero``, ``Q.positive``,
``Q.nonnegative``, ``Q.nonpositive``, ``Q.nonzero``,
``Q.integer``, ``Q.rational``, and ``Q.irrational`` all imply
``Q.real``, as do all facts that imply those facts.
- The facts ``Q.algebraic``, and ``Q.transcendental`` do not imply
``Q.real``; they imply ``Q.complex``. An algebraic or
transcendental number may or may not be real.
- The "non" facts (i.e., ``Q.nonnegative``, ``Q.nonzero``,
``Q.nonpositive`` and ``Q.noninteger``) are not equivalent to
not the fact, but rather, not the fact *and* ``Q.real``.
For example, ``Q.nonnegative`` means ``~Q.negative & Q.real``.
So for example, ``I`` is not nonnegative, nonzero, or
nonpositive.
Examples
========
>>> from sympy import Q, ask, symbols
>>> x = symbols('x')
>>> ask(Q.real(x), Q.positive(x))
True
>>> ask(Q.real(0))
True
References
==========
.. [1] https://en.wikipedia.org/wiki/Real_number
"""
name = 'real'
handler = Dispatcher(
"RealHandler",
doc=("Handler for Q.real.\n\n"
"Test that an expression belongs to the field of real numbers.")
)
class ExtendedRealPredicate(Predicate):
r"""
Extended real predicate.
Explanation
===========
``Q.extended_real(x)`` is true iff ``x`` is a real number or
`\{-\infty, \infty\}`.
See documentation of ``Q.real`` for more information about related
facts.
Examples
========
>>> from sympy import ask, Q, oo, I
>>> ask(Q.extended_real(1))
True
>>> ask(Q.extended_real(I))
False
>>> ask(Q.extended_real(oo))
True
"""
name = 'extended_real'
handler = Dispatcher(
"ExtendedRealHandler",
doc=("Handler for Q.extended_real.\n\n"
"Test that an expression belongs to the field of extended real\n"
"numbers, that is real numbers union {Infinity, -Infinity}.")
)
class HermitianPredicate(Predicate):
"""
Hermitian predicate.
Explanation
===========
``ask(Q.hermitian(x))`` is true iff ``x`` belongs to the set of
Hermitian operators.
References
==========
.. [1] https://mathworld.wolfram.com/HermitianOperator.html
"""
# TODO: Add examples
name = 'hermitian'
handler = Dispatcher(
"HermitianHandler",
doc=("Handler for Q.hermitian.\n\n"
"Test that an expression belongs to the field of Hermitian operators.")
)
class ComplexPredicate(Predicate):
"""
Complex number predicate.
Explanation
===========
``Q.complex(x)`` is true iff ``x`` belongs to the set of complex
numbers. Note that every complex number is finite.
Examples
========
>>> from sympy import Q, Symbol, ask, I, oo
>>> x = Symbol('x')
>>> ask(Q.complex(0))
True
>>> ask(Q.complex(2 + 3*I))
True
>>> ask(Q.complex(oo))
False
References
==========
.. [1] https://en.wikipedia.org/wiki/Complex_number
"""
name = 'complex'
handler = Dispatcher(
"ComplexHandler",
doc=("Handler for Q.complex.\n\n"
"Test that an expression belongs to the field of complex numbers.")
)
class ImaginaryPredicate(Predicate):
"""
Imaginary number predicate.
Explanation
===========
``Q.imaginary(x)`` is true iff ``x`` can be written as a real
number multiplied by the imaginary unit ``I``. Please note that ``0``
is not considered to be an imaginary number.
Examples
========
>>> from sympy import Q, ask, I
>>> ask(Q.imaginary(3*I))
True
>>> ask(Q.imaginary(2 + 3*I))
False
>>> ask(Q.imaginary(0))
False
References
==========
.. [1] https://en.wikipedia.org/wiki/Imaginary_number
"""
name = 'imaginary'
handler = Dispatcher(
"ImaginaryHandler",
doc=("Handler for Q.imaginary.\n\n"
"Test that an expression belongs to the field of imaginary numbers,\n"
"that is, numbers in the form x*I, where x is real.")
)
class AntihermitianPredicate(Predicate):
"""
Antihermitian predicate.
Explanation
===========
``Q.antihermitian(x)`` is true iff ``x`` belongs to the field of
antihermitian operators, i.e., operators in the form ``x*I``, where
``x`` is Hermitian.
References
==========
.. [1] https://mathworld.wolfram.com/HermitianOperator.html
"""
# TODO: Add examples
name = 'antihermitian'
handler = Dispatcher(
"AntiHermitianHandler",
doc=("Handler for Q.antihermitian.\n\n"
"Test that an expression belongs to the field of anti-Hermitian\n"
"operators, that is, operators in the form x*I, where x is Hermitian.")
)
class AlgebraicPredicate(Predicate):
r"""
Algebraic number predicate.
Explanation
===========
``Q.algebraic(x)`` is true iff ``x`` belongs to the set of
algebraic numbers. ``x`` is algebraic if there is some polynomial
in ``p(x)\in \mathbb\{Q\}[x]`` such that ``p(x) = 0``.
Examples
========
>>> from sympy import ask, Q, sqrt, I, pi
>>> ask(Q.algebraic(sqrt(2)))
True
>>> ask(Q.algebraic(I))
True
>>> ask(Q.algebraic(pi))
False
References
==========
.. [1] https://en.wikipedia.org/wiki/Algebraic_number
"""
name = 'algebraic'
AlgebraicHandler = Dispatcher(
"AlgebraicHandler",
doc="""Handler for Q.algebraic key."""
)
class TranscendentalPredicate(Predicate):
"""
Transcedental number predicate.
Explanation
===========
``Q.transcendental(x)`` is true iff ``x`` belongs to the set of
transcendental numbers. A transcendental number is a real
or complex number that is not algebraic.
"""
# TODO: Add examples
name = 'transcendental'
handler = Dispatcher(
"Transcendental",
doc="""Handler for Q.transcendental key."""
)
@@ -0,0 +1,405 @@
from __future__ import annotations
from typing import Callable
from sympy.core import S, Add, Expr, Basic, Mul, Pow, Rational
from sympy.core.logic import fuzzy_not
from sympy.logic.boolalg import Boolean
from sympy.assumptions import ask, Q # type: ignore
def refine(expr, assumptions=True):
"""
Simplify an expression using assumptions.
Explanation
===========
Unlike :func:`~.simplify` which performs structural simplification
without any assumption, this function transforms the expression into
the form which is only valid under certain assumptions. Note that
``simplify()`` is generally not done in refining process.
Refining boolean expression involves reducing it to ``S.true`` or
``S.false``. Unlike :func:`~.ask`, the expression will not be reduced
if the truth value cannot be determined.
Examples
========
>>> from sympy import refine, sqrt, Q
>>> from sympy.abc import x
>>> refine(sqrt(x**2), Q.real(x))
Abs(x)
>>> refine(sqrt(x**2), Q.positive(x))
x
>>> refine(Q.real(x), Q.positive(x))
True
>>> refine(Q.positive(x), Q.real(x))
Q.positive(x)
See Also
========
sympy.simplify.simplify.simplify : Structural simplification without assumptions.
sympy.assumptions.ask.ask : Query for boolean expressions using assumptions.
"""
if not isinstance(expr, Basic):
return expr
if not expr.is_Atom:
args = [refine(arg, assumptions) for arg in expr.args]
# TODO: this will probably not work with Integral or Polynomial
expr = expr.func(*args)
if hasattr(expr, '_eval_refine'):
ref_expr = expr._eval_refine(assumptions)
if ref_expr is not None:
return ref_expr
name = expr.__class__.__name__
handler = handlers_dict.get(name, None)
if handler is None:
return expr
new_expr = handler(expr, assumptions)
if (new_expr is None) or (expr == new_expr):
return expr
if not isinstance(new_expr, Expr):
return new_expr
return refine(new_expr, assumptions)
def refine_abs(expr, assumptions):
"""
Handler for the absolute value.
Examples
========
>>> from sympy import Q, Abs
>>> from sympy.assumptions.refine import refine_abs
>>> from sympy.abc import x
>>> refine_abs(Abs(x), Q.real(x))
>>> refine_abs(Abs(x), Q.positive(x))
x
>>> refine_abs(Abs(x), Q.negative(x))
-x
"""
from sympy.functions.elementary.complexes import Abs
arg = expr.args[0]
if ask(Q.real(arg), assumptions) and \
fuzzy_not(ask(Q.negative(arg), assumptions)):
# if it's nonnegative
return arg
if ask(Q.negative(arg), assumptions):
return -arg
# arg is Mul
if isinstance(arg, Mul):
r = [refine(abs(a), assumptions) for a in arg.args]
non_abs = []
in_abs = []
for i in r:
if isinstance(i, Abs):
in_abs.append(i.args[0])
else:
non_abs.append(i)
return Mul(*non_abs) * Abs(Mul(*in_abs))
def refine_Pow(expr, assumptions):
"""
Handler for instances of Pow.
Examples
========
>>> from sympy import Q
>>> from sympy.assumptions.refine import refine_Pow
>>> from sympy.abc import x,y,z
>>> refine_Pow((-1)**x, Q.real(x))
>>> refine_Pow((-1)**x, Q.even(x))
1
>>> refine_Pow((-1)**x, Q.odd(x))
-1
For powers of -1, even parts of the exponent can be simplified:
>>> refine_Pow((-1)**(x+y), Q.even(x))
(-1)**y
>>> refine_Pow((-1)**(x+y+z), Q.odd(x) & Q.odd(z))
(-1)**y
>>> refine_Pow((-1)**(x+y+2), Q.odd(x))
(-1)**(y + 1)
>>> refine_Pow((-1)**(x+3), True)
(-1)**(x + 1)
"""
from sympy.functions.elementary.complexes import Abs
from sympy.functions import sign
if isinstance(expr.base, Abs):
if ask(Q.real(expr.base.args[0]), assumptions) and \
ask(Q.even(expr.exp), assumptions):
return expr.base.args[0] ** expr.exp
if ask(Q.real(expr.base), assumptions):
if expr.base.is_number:
if ask(Q.even(expr.exp), assumptions):
return abs(expr.base) ** expr.exp
if ask(Q.odd(expr.exp), assumptions):
return sign(expr.base) * abs(expr.base) ** expr.exp
if isinstance(expr.exp, Rational):
if isinstance(expr.base, Pow):
return abs(expr.base.base) ** (expr.base.exp * expr.exp)
if expr.base is S.NegativeOne:
if expr.exp.is_Add:
old = expr
# For powers of (-1) we can remove
# - even terms
# - pairs of odd terms
# - a single odd term + 1
# - A numerical constant N can be replaced with mod(N,2)
coeff, terms = expr.exp.as_coeff_add()
terms = set(terms)
even_terms = set()
odd_terms = set()
initial_number_of_terms = len(terms)
for t in terms:
if ask(Q.even(t), assumptions):
even_terms.add(t)
elif ask(Q.odd(t), assumptions):
odd_terms.add(t)
terms -= even_terms
if len(odd_terms) % 2:
terms -= odd_terms
new_coeff = (coeff + S.One) % 2
else:
terms -= odd_terms
new_coeff = coeff % 2
if new_coeff != coeff or len(terms) < initial_number_of_terms:
terms.add(new_coeff)
expr = expr.base**(Add(*terms))
# Handle (-1)**((-1)**n/2 + m/2)
e2 = 2*expr.exp
if ask(Q.even(e2), assumptions):
if e2.could_extract_minus_sign():
e2 *= expr.base
if e2.is_Add:
i, p = e2.as_two_terms()
if p.is_Pow and p.base is S.NegativeOne:
if ask(Q.integer(p.exp), assumptions):
i = (i + 1)/2
if ask(Q.even(i), assumptions):
return expr.base**p.exp
elif ask(Q.odd(i), assumptions):
return expr.base**(p.exp + 1)
else:
return expr.base**(p.exp + i)
if old != expr:
return expr
def refine_atan2(expr, assumptions):
"""
Handler for the atan2 function.
Examples
========
>>> from sympy import Q, atan2
>>> from sympy.assumptions.refine import refine_atan2
>>> from sympy.abc import x, y
>>> refine_atan2(atan2(y,x), Q.real(y) & Q.positive(x))
atan(y/x)
>>> refine_atan2(atan2(y,x), Q.negative(y) & Q.negative(x))
atan(y/x) - pi
>>> refine_atan2(atan2(y,x), Q.positive(y) & Q.negative(x))
atan(y/x) + pi
>>> refine_atan2(atan2(y,x), Q.zero(y) & Q.negative(x))
pi
>>> refine_atan2(atan2(y,x), Q.positive(y) & Q.zero(x))
pi/2
>>> refine_atan2(atan2(y,x), Q.negative(y) & Q.zero(x))
-pi/2
>>> refine_atan2(atan2(y,x), Q.zero(y) & Q.zero(x))
nan
"""
from sympy.functions.elementary.trigonometric import atan
y, x = expr.args
if ask(Q.real(y) & Q.positive(x), assumptions):
return atan(y / x)
elif ask(Q.negative(y) & Q.negative(x), assumptions):
return atan(y / x) - S.Pi
elif ask(Q.positive(y) & Q.negative(x), assumptions):
return atan(y / x) + S.Pi
elif ask(Q.zero(y) & Q.negative(x), assumptions):
return S.Pi
elif ask(Q.positive(y) & Q.zero(x), assumptions):
return S.Pi/2
elif ask(Q.negative(y) & Q.zero(x), assumptions):
return -S.Pi/2
elif ask(Q.zero(y) & Q.zero(x), assumptions):
return S.NaN
else:
return expr
def refine_re(expr, assumptions):
"""
Handler for real part.
Examples
========
>>> from sympy.assumptions.refine import refine_re
>>> from sympy import Q, re
>>> from sympy.abc import x
>>> refine_re(re(x), Q.real(x))
x
>>> refine_re(re(x), Q.imaginary(x))
0
"""
arg = expr.args[0]
if ask(Q.real(arg), assumptions):
return arg
if ask(Q.imaginary(arg), assumptions):
return S.Zero
return _refine_reim(expr, assumptions)
def refine_im(expr, assumptions):
"""
Handler for imaginary part.
Explanation
===========
>>> from sympy.assumptions.refine import refine_im
>>> from sympy import Q, im
>>> from sympy.abc import x
>>> refine_im(im(x), Q.real(x))
0
>>> refine_im(im(x), Q.imaginary(x))
-I*x
"""
arg = expr.args[0]
if ask(Q.real(arg), assumptions):
return S.Zero
if ask(Q.imaginary(arg), assumptions):
return - S.ImaginaryUnit * arg
return _refine_reim(expr, assumptions)
def refine_arg(expr, assumptions):
"""
Handler for complex argument
Explanation
===========
>>> from sympy.assumptions.refine import refine_arg
>>> from sympy import Q, arg
>>> from sympy.abc import x
>>> refine_arg(arg(x), Q.positive(x))
0
>>> refine_arg(arg(x), Q.negative(x))
pi
"""
rg = expr.args[0]
if ask(Q.positive(rg), assumptions):
return S.Zero
if ask(Q.negative(rg), assumptions):
return S.Pi
return None
def _refine_reim(expr, assumptions):
# Helper function for refine_re & refine_im
expanded = expr.expand(complex = True)
if expanded != expr:
refined = refine(expanded, assumptions)
if refined != expanded:
return refined
# Best to leave the expression as is
return None
def refine_sign(expr, assumptions):
"""
Handler for sign.
Examples
========
>>> from sympy.assumptions.refine import refine_sign
>>> from sympy import Symbol, Q, sign, im
>>> x = Symbol('x', real = True)
>>> expr = sign(x)
>>> refine_sign(expr, Q.positive(x) & Q.nonzero(x))
1
>>> refine_sign(expr, Q.negative(x) & Q.nonzero(x))
-1
>>> refine_sign(expr, Q.zero(x))
0
>>> y = Symbol('y', imaginary = True)
>>> expr = sign(y)
>>> refine_sign(expr, Q.positive(im(y)))
I
>>> refine_sign(expr, Q.negative(im(y)))
-I
"""
arg = expr.args[0]
if ask(Q.zero(arg), assumptions):
return S.Zero
if ask(Q.real(arg)):
if ask(Q.positive(arg), assumptions):
return S.One
if ask(Q.negative(arg), assumptions):
return S.NegativeOne
if ask(Q.imaginary(arg)):
arg_re, arg_im = arg.as_real_imag()
if ask(Q.positive(arg_im), assumptions):
return S.ImaginaryUnit
if ask(Q.negative(arg_im), assumptions):
return -S.ImaginaryUnit
return expr
def refine_matrixelement(expr, assumptions):
"""
Handler for symmetric part.
Examples
========
>>> from sympy.assumptions.refine import refine_matrixelement
>>> from sympy import MatrixSymbol, Q
>>> X = MatrixSymbol('X', 3, 3)
>>> refine_matrixelement(X[0, 1], Q.symmetric(X))
X[0, 1]
>>> refine_matrixelement(X[1, 0], Q.symmetric(X))
X[0, 1]
"""
from sympy.matrices.expressions.matexpr import MatrixElement
matrix, i, j = expr.args
if ask(Q.symmetric(matrix), assumptions):
if (i - j).could_extract_minus_sign():
return expr
return MatrixElement(matrix, j, i)
handlers_dict: dict[str, Callable[[Expr, Boolean], Expr]] = {
'Abs': refine_abs,
'Pow': refine_Pow,
'atan2': refine_atan2,
're': refine_re,
'im': refine_im,
'arg': refine_arg,
'sign': refine_sign,
'MatrixElement': refine_matrixelement
}
@@ -0,0 +1,13 @@
"""
A module to implement finitary relations [1] as predicate.
References
==========
.. [1] https://en.wikipedia.org/wiki/Finitary_relation
"""
__all__ = ['BinaryRelation', 'AppliedBinaryRelation']
from .binrel import BinaryRelation, AppliedBinaryRelation
@@ -0,0 +1,212 @@
"""
General binary relations.
"""
from typing import Optional
from sympy.core.singleton import S
from sympy.assumptions import AppliedPredicate, ask, Predicate, Q # type: ignore
from sympy.core.kind import BooleanKind
from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le
from sympy.logic.boolalg import conjuncts, Not
__all__ = ["BinaryRelation", "AppliedBinaryRelation"]
class BinaryRelation(Predicate):
"""
Base class for all binary relational predicates.
Explanation
===========
Binary relation takes two arguments and returns ``AppliedBinaryRelation``
instance. To evaluate it to boolean value, use :obj:`~.ask()` or
:obj:`~.refine()` function.
You can add support for new types by registering the handler to dispatcher.
See :obj:`~.Predicate()` for more information about predicate dispatching.
Examples
========
Applying and evaluating to boolean value:
>>> from sympy import Q, ask, sin, cos
>>> from sympy.abc import x
>>> Q.eq(sin(x)**2+cos(x)**2, 1)
Q.eq(sin(x)**2 + cos(x)**2, 1)
>>> ask(_)
True
You can define a new binary relation by subclassing and dispatching.
Here, we define a relation $R$ such that $x R y$ returns true if
$x = y + 1$.
>>> from sympy import ask, Number, Q
>>> from sympy.assumptions import BinaryRelation
>>> class MyRel(BinaryRelation):
... name = "R"
... is_reflexive = False
>>> Q.R = MyRel()
>>> @Q.R.register(Number, Number)
... def _(n1, n2, assumptions):
... return ask(Q.zero(n1 - n2 - 1), assumptions)
>>> Q.R(2, 1)
Q.R(2, 1)
Now, we can use ``ask()`` to evaluate it to boolean value.
>>> ask(Q.R(2, 1))
True
>>> ask(Q.R(1, 2))
False
``Q.R`` returns ``False`` with minimum cost if two arguments have same
structure because it is antireflexive relation [1] by
``is_reflexive = False``.
>>> ask(Q.R(x, x))
False
References
==========
.. [1] https://en.wikipedia.org/wiki/Reflexive_relation
"""
is_reflexive: Optional[bool] = None
is_symmetric: Optional[bool] = None
def __call__(self, *args):
if not len(args) == 2:
raise ValueError("Binary relation takes two arguments, but got %s." % len(args))
return AppliedBinaryRelation(self, *args)
@property
def reversed(self):
if self.is_symmetric:
return self
return None
@property
def negated(self):
return None
def _compare_reflexive(self, lhs, rhs):
# quick exit for structurally same arguments
# do not check != here because it cannot catch the
# equivalent arguments with different structures.
# reflexivity does not hold to NaN
if lhs is S.NaN or rhs is S.NaN:
return None
reflexive = self.is_reflexive
if reflexive is None:
pass
elif reflexive and (lhs == rhs):
return True
elif not reflexive and (lhs == rhs):
return False
return None
def eval(self, args, assumptions=True):
# quick exit for structurally same arguments
ret = self._compare_reflexive(*args)
if ret is not None:
return ret
# don't perform simplify on args here. (done by AppliedBinaryRelation._eval_ask)
# evaluate by multipledispatch
lhs, rhs = args
ret = self.handler(lhs, rhs, assumptions=assumptions)
if ret is not None:
return ret
# check reversed order if the relation is reflexive
if self.is_reflexive:
types = (type(lhs), type(rhs))
if self.handler.dispatch(*types) is not self.handler.dispatch(*reversed(types)):
ret = self.handler(rhs, lhs, assumptions=assumptions)
return ret
class AppliedBinaryRelation(AppliedPredicate):
"""
The class of expressions resulting from applying ``BinaryRelation``
to the arguments.
"""
@property
def lhs(self):
"""The left-hand side of the relation."""
return self.arguments[0]
@property
def rhs(self):
"""The right-hand side of the relation."""
return self.arguments[1]
@property
def reversed(self):
"""
Try to return the relationship with sides reversed.
"""
revfunc = self.function.reversed
if revfunc is None:
return self
return revfunc(self.rhs, self.lhs)
@property
def reversedsign(self):
"""
Try to return the relationship with signs reversed.
"""
revfunc = self.function.reversed
if revfunc is None:
return self
if not any(side.kind is BooleanKind for side in self.arguments):
return revfunc(-self.lhs, -self.rhs)
return self
@property
def negated(self):
neg_rel = self.function.negated
if neg_rel is None:
return Not(self, evaluate=False)
return neg_rel(*self.arguments)
def _eval_ask(self, assumptions):
conj_assumps = set()
binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le}
for a in conjuncts(assumptions):
if a.func in binrelpreds:
conj_assumps.add(binrelpreds[type(a)](*a.args))
else:
conj_assumps.add(a)
# After CNF in assumptions module is modified to take polyadic
# predicate, this will be removed
if any(rel in conj_assumps for rel in (self, self.reversed)):
return True
neg_rels = (self.negated, self.reversed.negated, Not(self, evaluate=False),
Not(self.reversed, evaluate=False))
if any(rel in conj_assumps for rel in neg_rels):
return False
# evaluation using multipledispatching
ret = self.function.eval(self.arguments, assumptions)
if ret is not None:
return ret
# simplify the args and try again
args = tuple(a.simplify() for a in self.arguments)
return self.function.eval(args, assumptions)
def __bool__(self):
ret = ask(self)
if ret is None:
raise TypeError("Cannot determine truth value of %s" % self)
return ret
@@ -0,0 +1,302 @@
"""
Module for mathematical equality [1] and inequalities [2].
The purpose of this module is to provide the instances which represent the
binary predicates in order to combine the relationals into logical inference
system. Objects such as ``Q.eq``, ``Q.lt`` should remain internal to
assumptions module, and user must use the classes such as :obj:`~.Eq()`,
:obj:`~.Lt()` instead to construct the relational expressions.
References
==========
.. [1] https://en.wikipedia.org/wiki/Equality_(mathematics)
.. [2] https://en.wikipedia.org/wiki/Inequality_(mathematics)
"""
from sympy.assumptions import Q
from sympy.core.relational import is_eq, is_neq, is_gt, is_ge, is_lt, is_le
from .binrel import BinaryRelation
__all__ = ['EqualityPredicate', 'UnequalityPredicate', 'StrictGreaterThanPredicate',
'GreaterThanPredicate', 'StrictLessThanPredicate', 'LessThanPredicate']
class EqualityPredicate(BinaryRelation):
"""
Binary predicate for $=$.
The purpose of this class is to provide the instance which represent
the equality predicate in order to allow the logical inference.
This class must remain internal to assumptions module and user must
use :obj:`~.Eq()` instead to construct the equality expression.
Evaluating this predicate to ``True`` or ``False`` is done by
:func:`~.core.relational.is_eq`
Examples
========
>>> from sympy import ask, Q
>>> Q.eq(0, 0)
Q.eq(0, 0)
>>> ask(_)
True
See Also
========
sympy.core.relational.Eq
"""
is_reflexive = True
is_symmetric = True
name = 'eq'
handler = None # Do not allow dispatching by this predicate
@property
def negated(self):
return Q.ne
def eval(self, args, assumptions=True):
if assumptions == True:
# default assumptions for is_eq is None
assumptions = None
return is_eq(*args, assumptions)
class UnequalityPredicate(BinaryRelation):
r"""
Binary predicate for $\neq$.
The purpose of this class is to provide the instance which represent
the inequation predicate in order to allow the logical inference.
This class must remain internal to assumptions module and user must
use :obj:`~.Ne()` instead to construct the inequation expression.
Evaluating this predicate to ``True`` or ``False`` is done by
:func:`~.core.relational.is_neq`
Examples
========
>>> from sympy import ask, Q
>>> Q.ne(0, 0)
Q.ne(0, 0)
>>> ask(_)
False
See Also
========
sympy.core.relational.Ne
"""
is_reflexive = False
is_symmetric = True
name = 'ne'
handler = None
@property
def negated(self):
return Q.eq
def eval(self, args, assumptions=True):
if assumptions == True:
# default assumptions for is_neq is None
assumptions = None
return is_neq(*args, assumptions)
class StrictGreaterThanPredicate(BinaryRelation):
"""
Binary predicate for $>$.
The purpose of this class is to provide the instance which represent
the ">" predicate in order to allow the logical inference.
This class must remain internal to assumptions module and user must
use :obj:`~.Gt()` instead to construct the equality expression.
Evaluating this predicate to ``True`` or ``False`` is done by
:func:`~.core.relational.is_gt`
Examples
========
>>> from sympy import ask, Q
>>> Q.gt(0, 0)
Q.gt(0, 0)
>>> ask(_)
False
See Also
========
sympy.core.relational.Gt
"""
is_reflexive = False
is_symmetric = False
name = 'gt'
handler = None
@property
def reversed(self):
return Q.lt
@property
def negated(self):
return Q.le
def eval(self, args, assumptions=True):
if assumptions == True:
# default assumptions for is_gt is None
assumptions = None
return is_gt(*args, assumptions)
class GreaterThanPredicate(BinaryRelation):
"""
Binary predicate for $>=$.
The purpose of this class is to provide the instance which represent
the ">=" predicate in order to allow the logical inference.
This class must remain internal to assumptions module and user must
use :obj:`~.Ge()` instead to construct the equality expression.
Evaluating this predicate to ``True`` or ``False`` is done by
:func:`~.core.relational.is_ge`
Examples
========
>>> from sympy import ask, Q
>>> Q.ge(0, 0)
Q.ge(0, 0)
>>> ask(_)
True
See Also
========
sympy.core.relational.Ge
"""
is_reflexive = True
is_symmetric = False
name = 'ge'
handler = None
@property
def reversed(self):
return Q.le
@property
def negated(self):
return Q.lt
def eval(self, args, assumptions=True):
if assumptions == True:
# default assumptions for is_ge is None
assumptions = None
return is_ge(*args, assumptions)
class StrictLessThanPredicate(BinaryRelation):
"""
Binary predicate for $<$.
The purpose of this class is to provide the instance which represent
the "<" predicate in order to allow the logical inference.
This class must remain internal to assumptions module and user must
use :obj:`~.Lt()` instead to construct the equality expression.
Evaluating this predicate to ``True`` or ``False`` is done by
:func:`~.core.relational.is_lt`
Examples
========
>>> from sympy import ask, Q
>>> Q.lt(0, 0)
Q.lt(0, 0)
>>> ask(_)
False
See Also
========
sympy.core.relational.Lt
"""
is_reflexive = False
is_symmetric = False
name = 'lt'
handler = None
@property
def reversed(self):
return Q.gt
@property
def negated(self):
return Q.ge
def eval(self, args, assumptions=True):
if assumptions == True:
# default assumptions for is_lt is None
assumptions = None
return is_lt(*args, assumptions)
class LessThanPredicate(BinaryRelation):
"""
Binary predicate for $<=$.
The purpose of this class is to provide the instance which represent
the "<=" predicate in order to allow the logical inference.
This class must remain internal to assumptions module and user must
use :obj:`~.Le()` instead to construct the equality expression.
Evaluating this predicate to ``True`` or ``False`` is done by
:func:`~.core.relational.is_le`
Examples
========
>>> from sympy import ask, Q
>>> Q.le(0, 0)
Q.le(0, 0)
>>> ask(_)
True
See Also
========
sympy.core.relational.Le
"""
is_reflexive = True
is_symmetric = False
name = 'le'
handler = None
@property
def reversed(self):
return Q.ge
@property
def negated(self):
return Q.gt
def eval(self, args, assumptions=True):
if assumptions == True:
# default assumptions for is_le is None
assumptions = None
return is_le(*args, assumptions)
@@ -0,0 +1,369 @@
"""
Module to evaluate the proposition with assumptions using SAT algorithm.
"""
from sympy.core.singleton import S
from sympy.core.symbol import Symbol
from sympy.core.kind import NumberKind, UndefinedKind
from sympy.assumptions.ask_generated import get_all_known_matrix_facts, get_all_known_number_facts
from sympy.assumptions.assume import global_assumptions, AppliedPredicate
from sympy.assumptions.sathandlers import class_fact_registry
from sympy.core import oo
from sympy.logic.inference import satisfiable
from sympy.assumptions.cnf import CNF, EncodedCNF
from sympy.matrices.kind import MatrixKind
def satask(proposition, assumptions=True, context=global_assumptions,
use_known_facts=True, iterations=oo):
"""
Function to evaluate the proposition with assumptions using SAT algorithm.
This function extracts every fact relevant to the expressions composing
proposition and assumptions. For example, if a predicate containing
``Abs(x)`` is proposed, then ``Q.zero(Abs(x)) | Q.positive(Abs(x))``
will be found and passed to SAT solver because ``Q.nonnegative`` is
registered as a fact for ``Abs``.
Proposition is evaluated to ``True`` or ``False`` if the truth value can be
determined. If not, ``None`` is returned.
Parameters
==========
proposition : Any boolean expression.
Proposition which will be evaluated to boolean value.
assumptions : Any boolean expression, optional.
Local assumptions to evaluate the *proposition*.
context : AssumptionsContext, optional.
Default assumptions to evaluate the *proposition*. By default,
this is ``sympy.assumptions.global_assumptions`` variable.
use_known_facts : bool, optional.
If ``True``, facts from ``sympy.assumptions.ask_generated``
module are passed to SAT solver as well.
iterations : int, optional.
Number of times that relevant facts are recursively extracted.
Default is infinite times until no new fact is found.
Returns
=======
``True``, ``False``, or ``None``
Examples
========
>>> from sympy import Abs, Q
>>> from sympy.assumptions.satask import satask
>>> from sympy.abc import x
>>> satask(Q.zero(Abs(x)), Q.zero(x))
True
"""
props = CNF.from_prop(proposition)
_props = CNF.from_prop(~proposition)
assumptions = CNF.from_prop(assumptions)
context_cnf = CNF()
if context:
context_cnf = context_cnf.extend(context)
sat = get_all_relevant_facts(props, assumptions, context_cnf,
use_known_facts=use_known_facts, iterations=iterations)
sat.add_from_cnf(assumptions)
if context:
sat.add_from_cnf(context_cnf)
return check_satisfiability(props, _props, sat)
def check_satisfiability(prop, _prop, factbase):
sat_true = factbase.copy()
sat_false = factbase.copy()
sat_true.add_from_cnf(prop)
sat_false.add_from_cnf(_prop)
can_be_true = satisfiable(sat_true)
can_be_false = satisfiable(sat_false)
if can_be_true and can_be_false:
return None
if can_be_true and not can_be_false:
return True
if not can_be_true and can_be_false:
return False
if not can_be_true and not can_be_false:
# TODO: Run additional checks to see which combination of the
# assumptions, global_assumptions, and relevant_facts are
# inconsistent.
raise ValueError("Inconsistent assumptions")
def extract_predargs(proposition, assumptions=None, context=None):
"""
Extract every expression in the argument of predicates from *proposition*,
*assumptions* and *context*.
Parameters
==========
proposition : sympy.assumptions.cnf.CNF
assumptions : sympy.assumptions.cnf.CNF, optional.
context : sympy.assumptions.cnf.CNF, optional.
CNF generated from assumptions context.
Examples
========
>>> from sympy import Q, Abs
>>> from sympy.assumptions.cnf import CNF
>>> from sympy.assumptions.satask import extract_predargs
>>> from sympy.abc import x, y
>>> props = CNF.from_prop(Q.zero(Abs(x*y)))
>>> assump = CNF.from_prop(Q.zero(x) & Q.zero(y))
>>> extract_predargs(props, assump)
{x, y, Abs(x*y)}
"""
req_keys = find_symbols(proposition)
keys = proposition.all_predicates()
# XXX: We need this since True/False are not Basic
lkeys = set()
if assumptions:
lkeys |= assumptions.all_predicates()
if context:
lkeys |= context.all_predicates()
lkeys = lkeys - {S.true, S.false}
tmp_keys = None
while tmp_keys != set():
tmp = set()
for l in lkeys:
syms = find_symbols(l)
if (syms & req_keys) != set():
tmp |= syms
tmp_keys = tmp - req_keys
req_keys |= tmp_keys
keys |= {l for l in lkeys if find_symbols(l) & req_keys != set()}
exprs = set()
for key in keys:
if isinstance(key, AppliedPredicate):
exprs |= set(key.arguments)
else:
exprs.add(key)
return exprs
def find_symbols(pred):
"""
Find every :obj:`~.Symbol` in *pred*.
Parameters
==========
pred : sympy.assumptions.cnf.CNF, or any Expr.
"""
if isinstance(pred, CNF):
symbols = set()
for a in pred.all_predicates():
symbols |= find_symbols(a)
return symbols
return pred.atoms(Symbol)
def get_relevant_clsfacts(exprs, relevant_facts=None):
"""
Extract relevant facts from the items in *exprs*. Facts are defined in
``assumptions.sathandlers`` module.
This function is recursively called by ``get_all_relevant_facts()``.
Parameters
==========
exprs : set
Expressions whose relevant facts are searched.
relevant_facts : sympy.assumptions.cnf.CNF, optional.
Pre-discovered relevant facts.
Returns
=======
exprs : set
Candidates for next relevant fact searching.
relevant_facts : sympy.assumptions.cnf.CNF
Updated relevant facts.
Examples
========
Here, we will see how facts relevant to ``Abs(x*y)`` are recursively
extracted. On the first run, set containing the expression is passed
without pre-discovered relevant facts. The result is a set containing
candidates for next run, and ``CNF()`` instance containing facts
which are relevant to ``Abs`` and its argument.
>>> from sympy import Abs
>>> from sympy.assumptions.satask import get_relevant_clsfacts
>>> from sympy.abc import x, y
>>> exprs = {Abs(x*y)}
>>> exprs, facts = get_relevant_clsfacts(exprs)
>>> exprs
{x*y}
>>> facts.clauses #doctest: +SKIP
{frozenset({Literal(Q.odd(Abs(x*y)), False), Literal(Q.odd(x*y), True)}),
frozenset({Literal(Q.zero(Abs(x*y)), False), Literal(Q.zero(x*y), True)}),
frozenset({Literal(Q.even(Abs(x*y)), False), Literal(Q.even(x*y), True)}),
frozenset({Literal(Q.zero(Abs(x*y)), True), Literal(Q.zero(x*y), False)}),
frozenset({Literal(Q.even(Abs(x*y)), False),
Literal(Q.odd(Abs(x*y)), False),
Literal(Q.odd(x*y), True)}),
frozenset({Literal(Q.even(Abs(x*y)), False),
Literal(Q.even(x*y), True),
Literal(Q.odd(Abs(x*y)), False)}),
frozenset({Literal(Q.positive(Abs(x*y)), False),
Literal(Q.zero(Abs(x*y)), False)})}
We pass the first run's results to the second run, and get the expressions
for next run and updated facts.
>>> exprs, facts = get_relevant_clsfacts(exprs, relevant_facts=facts)
>>> exprs
{x, y}
On final run, no more candidate is returned thus we know that all
relevant facts are successfully retrieved.
>>> exprs, facts = get_relevant_clsfacts(exprs, relevant_facts=facts)
>>> exprs
set()
"""
if not relevant_facts:
relevant_facts = CNF()
newexprs = set()
for expr in exprs:
for fact in class_fact_registry(expr):
newfact = CNF.to_CNF(fact)
relevant_facts = relevant_facts._and(newfact)
for key in newfact.all_predicates():
if isinstance(key, AppliedPredicate):
newexprs |= set(key.arguments)
return newexprs - exprs, relevant_facts
def get_all_relevant_facts(proposition, assumptions, context,
use_known_facts=True, iterations=oo):
"""
Extract all relevant facts from *proposition* and *assumptions*.
This function extracts the facts by recursively calling
``get_relevant_clsfacts()``. Extracted facts are converted to
``EncodedCNF`` and returned.
Parameters
==========
proposition : sympy.assumptions.cnf.CNF
CNF generated from proposition expression.
assumptions : sympy.assumptions.cnf.CNF
CNF generated from assumption expression.
context : sympy.assumptions.cnf.CNF
CNF generated from assumptions context.
use_known_facts : bool, optional.
If ``True``, facts from ``sympy.assumptions.ask_generated``
module are encoded as well.
iterations : int, optional.
Number of times that relevant facts are recursively extracted.
Default is infinite times until no new fact is found.
Returns
=======
sympy.assumptions.cnf.EncodedCNF
Examples
========
>>> from sympy import Q
>>> from sympy.assumptions.cnf import CNF
>>> from sympy.assumptions.satask import get_all_relevant_facts
>>> from sympy.abc import x, y
>>> props = CNF.from_prop(Q.nonzero(x*y))
>>> assump = CNF.from_prop(Q.nonzero(x))
>>> context = CNF.from_prop(Q.nonzero(y))
>>> get_all_relevant_facts(props, assump, context) #doctest: +SKIP
<sympy.assumptions.cnf.EncodedCNF at 0x7f09faa6ccd0>
"""
# The relevant facts might introduce new keys, e.g., Q.zero(x*y) will
# introduce the keys Q.zero(x) and Q.zero(y), so we need to run it until
# we stop getting new things. Hopefully this strategy won't lead to an
# infinite loop in the future.
i = 0
relevant_facts = CNF()
all_exprs = set()
while True:
if i == 0:
exprs = extract_predargs(proposition, assumptions, context)
all_exprs |= exprs
exprs, relevant_facts = get_relevant_clsfacts(exprs, relevant_facts)
i += 1
if i >= iterations:
break
if not exprs:
break
if use_known_facts:
known_facts_CNF = CNF()
if any(expr.kind == MatrixKind(NumberKind) for expr in all_exprs):
known_facts_CNF.add_clauses(get_all_known_matrix_facts())
# check for undefinedKind since kind system isn't fully implemented
if any(((expr.kind == NumberKind) or (expr.kind == UndefinedKind)) for expr in all_exprs):
known_facts_CNF.add_clauses(get_all_known_number_facts())
kf_encoded = EncodedCNF()
kf_encoded.from_cnf(known_facts_CNF)
def translate_literal(lit, delta):
if lit > 0:
return lit + delta
else:
return lit - delta
def translate_data(data, delta):
return [{translate_literal(i, delta) for i in clause} for clause in data]
data = []
symbols = []
n_lit = len(kf_encoded.symbols)
for i, expr in enumerate(all_exprs):
symbols += [pred(expr) for pred in kf_encoded.symbols]
data += translate_data(kf_encoded.data, i * n_lit)
encoding = dict(list(zip(symbols, range(1, len(symbols)+1))))
ctx = EncodedCNF(data, encoding)
else:
ctx = EncodedCNF()
ctx.add_from_cnf(relevant_facts)
return ctx
@@ -0,0 +1,322 @@
from collections import defaultdict
from sympy.assumptions.ask import Q
from sympy.core import (Add, Mul, Pow, Number, NumberSymbol, Symbol)
from sympy.core.numbers import ImaginaryUnit
from sympy.functions.elementary.complexes import Abs
from sympy.logic.boolalg import (Equivalent, And, Or, Implies)
from sympy.matrices.expressions import MatMul
# APIs here may be subject to change
### Helper functions ###
def allargs(symbol, fact, expr):
"""
Apply all arguments of the expression to the fact structure.
Parameters
==========
symbol : Symbol
A placeholder symbol.
fact : Boolean
Resulting ``Boolean`` expression.
expr : Expr
Examples
========
>>> from sympy import Q
>>> from sympy.assumptions.sathandlers import allargs
>>> from sympy.abc import x, y
>>> allargs(x, Q.negative(x) | Q.positive(x), x*y)
(Q.negative(x) | Q.positive(x)) & (Q.negative(y) | Q.positive(y))
"""
return And(*[fact.subs(symbol, arg) for arg in expr.args])
def anyarg(symbol, fact, expr):
"""
Apply any argument of the expression to the fact structure.
Parameters
==========
symbol : Symbol
A placeholder symbol.
fact : Boolean
Resulting ``Boolean`` expression.
expr : Expr
Examples
========
>>> from sympy import Q
>>> from sympy.assumptions.sathandlers import anyarg
>>> from sympy.abc import x, y
>>> anyarg(x, Q.negative(x) & Q.positive(x), x*y)
(Q.negative(x) & Q.positive(x)) | (Q.negative(y) & Q.positive(y))
"""
return Or(*[fact.subs(symbol, arg) for arg in expr.args])
def exactlyonearg(symbol, fact, expr):
"""
Apply exactly one argument of the expression to the fact structure.
Parameters
==========
symbol : Symbol
A placeholder symbol.
fact : Boolean
Resulting ``Boolean`` expression.
expr : Expr
Examples
========
>>> from sympy import Q
>>> from sympy.assumptions.sathandlers import exactlyonearg
>>> from sympy.abc import x, y
>>> exactlyonearg(x, Q.positive(x), x*y)
(Q.positive(x) & ~Q.positive(y)) | (Q.positive(y) & ~Q.positive(x))
"""
pred_args = [fact.subs(symbol, arg) for arg in expr.args]
res = Or(*[And(pred_args[i], *[~lit for lit in pred_args[:i] +
pred_args[i+1:]]) for i in range(len(pred_args))])
return res
### Fact registry ###
class ClassFactRegistry:
"""
Register handlers against classes.
Explanation
===========
``register`` method registers the handler function for a class. Here,
handler function should return a single fact. ``multiregister`` method
registers the handler function for multiple classes. Here, handler function
should return a container of multiple facts.
``registry(expr)`` returns a set of facts for *expr*.
Examples
========
Here, we register the facts for ``Abs``.
>>> from sympy import Abs, Equivalent, Q
>>> from sympy.assumptions.sathandlers import ClassFactRegistry
>>> reg = ClassFactRegistry()
>>> @reg.register(Abs)
... def f1(expr):
... return Q.nonnegative(expr)
>>> @reg.register(Abs)
... def f2(expr):
... arg = expr.args[0]
... return Equivalent(~Q.zero(arg), ~Q.zero(expr))
Calling the registry with expression returns the defined facts for the
expression.
>>> from sympy.abc import x
>>> reg(Abs(x))
{Q.nonnegative(Abs(x)), Equivalent(~Q.zero(x), ~Q.zero(Abs(x)))}
Multiple facts can be registered at once by ``multiregister`` method.
>>> reg2 = ClassFactRegistry()
>>> @reg2.multiregister(Abs)
... def _(expr):
... arg = expr.args[0]
... return [Q.even(arg) >> Q.even(expr), Q.odd(arg) >> Q.odd(expr)]
>>> reg2(Abs(x))
{Implies(Q.even(x), Q.even(Abs(x))), Implies(Q.odd(x), Q.odd(Abs(x)))}
"""
def __init__(self):
self.singlefacts = defaultdict(frozenset)
self.multifacts = defaultdict(frozenset)
def register(self, cls):
def _(func):
self.singlefacts[cls] |= {func}
return func
return _
def multiregister(self, *classes):
def _(func):
for cls in classes:
self.multifacts[cls] |= {func}
return func
return _
def __getitem__(self, key):
ret1 = self.singlefacts[key]
for k in self.singlefacts:
if issubclass(key, k):
ret1 |= self.singlefacts[k]
ret2 = self.multifacts[key]
for k in self.multifacts:
if issubclass(key, k):
ret2 |= self.multifacts[k]
return ret1, ret2
def __call__(self, expr):
ret = set()
handlers1, handlers2 = self[type(expr)]
ret.update(h(expr) for h in handlers1)
for h in handlers2:
ret.update(h(expr))
return ret
class_fact_registry = ClassFactRegistry()
### Class fact registration ###
x = Symbol('x')
## Abs ##
@class_fact_registry.multiregister(Abs)
def _(expr):
arg = expr.args[0]
return [Q.nonnegative(expr),
Equivalent(~Q.zero(arg), ~Q.zero(expr)),
Q.even(arg) >> Q.even(expr),
Q.odd(arg) >> Q.odd(expr),
Q.integer(arg) >> Q.integer(expr),
]
### Add ##
@class_fact_registry.multiregister(Add)
def _(expr):
return [allargs(x, Q.positive(x), expr) >> Q.positive(expr),
allargs(x, Q.negative(x), expr) >> Q.negative(expr),
allargs(x, Q.real(x), expr) >> Q.real(expr),
allargs(x, Q.rational(x), expr) >> Q.rational(expr),
allargs(x, Q.integer(x), expr) >> Q.integer(expr),
exactlyonearg(x, ~Q.integer(x), expr) >> ~Q.integer(expr),
]
@class_fact_registry.register(Add)
def _(expr):
allargs_real = allargs(x, Q.real(x), expr)
onearg_irrational = exactlyonearg(x, Q.irrational(x), expr)
return Implies(allargs_real, Implies(onearg_irrational, Q.irrational(expr)))
### Mul ###
@class_fact_registry.multiregister(Mul)
def _(expr):
return [Equivalent(Q.zero(expr), anyarg(x, Q.zero(x), expr)),
allargs(x, Q.positive(x), expr) >> Q.positive(expr),
allargs(x, Q.real(x), expr) >> Q.real(expr),
allargs(x, Q.rational(x), expr) >> Q.rational(expr),
allargs(x, Q.integer(x), expr) >> Q.integer(expr),
exactlyonearg(x, ~Q.rational(x), expr) >> ~Q.integer(expr),
allargs(x, Q.commutative(x), expr) >> Q.commutative(expr),
]
@class_fact_registry.register(Mul)
def _(expr):
# Implicitly assumes Mul has more than one arg
# Would be allargs(x, Q.prime(x) | Q.composite(x)) except 1 is composite
# More advanced prime assumptions will require inequalities, as 1 provides
# a corner case.
allargs_prime = allargs(x, Q.prime(x), expr)
return Implies(allargs_prime, ~Q.prime(expr))
@class_fact_registry.register(Mul)
def _(expr):
# General Case: Odd number of imaginary args implies mul is imaginary(To be implemented)
allargs_imag_or_real = allargs(x, Q.imaginary(x) | Q.real(x), expr)
onearg_imaginary = exactlyonearg(x, Q.imaginary(x), expr)
return Implies(allargs_imag_or_real, Implies(onearg_imaginary, Q.imaginary(expr)))
@class_fact_registry.register(Mul)
def _(expr):
allargs_real = allargs(x, Q.real(x), expr)
onearg_irrational = exactlyonearg(x, Q.irrational(x), expr)
return Implies(allargs_real, Implies(onearg_irrational, Q.irrational(expr)))
@class_fact_registry.register(Mul)
def _(expr):
# Including the integer qualification means we don't need to add any facts
# for odd, since the assumptions already know that every integer is
# exactly one of even or odd.
allargs_integer = allargs(x, Q.integer(x), expr)
anyarg_even = anyarg(x, Q.even(x), expr)
return Implies(allargs_integer, Equivalent(anyarg_even, Q.even(expr)))
### MatMul ###
@class_fact_registry.register(MatMul)
def _(expr):
allargs_square = allargs(x, Q.square(x), expr)
allargs_invertible = allargs(x, Q.invertible(x), expr)
return Implies(allargs_square, Equivalent(Q.invertible(expr), allargs_invertible))
### Pow ###
@class_fact_registry.multiregister(Pow)
def _(expr):
base, exp = expr.base, expr.exp
return [
(Q.real(base) & Q.even(exp) & Q.nonnegative(exp)) >> Q.nonnegative(expr),
(Q.nonnegative(base) & Q.odd(exp) & Q.nonnegative(exp)) >> Q.nonnegative(expr),
(Q.nonpositive(base) & Q.odd(exp) & Q.nonnegative(exp)) >> Q.nonpositive(expr),
Equivalent(Q.zero(expr), Q.zero(base) & Q.positive(exp))
]
### Numbers ###
_old_assump_getters = {
Q.positive: lambda o: o.is_positive,
Q.zero: lambda o: o.is_zero,
Q.negative: lambda o: o.is_negative,
Q.rational: lambda o: o.is_rational,
Q.irrational: lambda o: o.is_irrational,
Q.even: lambda o: o.is_even,
Q.odd: lambda o: o.is_odd,
Q.imaginary: lambda o: o.is_imaginary,
Q.prime: lambda o: o.is_prime,
Q.composite: lambda o: o.is_composite,
}
@class_fact_registry.multiregister(Number, NumberSymbol, ImaginaryUnit)
def _(expr):
ret = []
for p, getter in _old_assump_getters.items():
pred = p(expr)
prop = getter(expr)
if prop is not None:
ret.append(Equivalent(pred, prop))
return ret
@@ -0,0 +1,35 @@
"""
rename this to test_assumptions.py when the old assumptions system is deleted
"""
from sympy.abc import x, y
from sympy.assumptions.assume import global_assumptions
from sympy.assumptions.ask import Q
from sympy.printing import pretty
def test_equal():
"""Test for equality"""
assert Q.positive(x) == Q.positive(x)
assert Q.positive(x) != ~Q.positive(x)
assert ~Q.positive(x) == ~Q.positive(x)
def test_pretty():
assert pretty(Q.positive(x)) == "Q.positive(x)"
assert pretty(
{Q.positive, Q.integer}) == "{Q.integer, Q.positive}"
def test_global():
"""Test for global assumptions"""
global_assumptions.add(x > 0)
assert (x > 0) in global_assumptions
global_assumptions.remove(x > 0)
assert not (x > 0) in global_assumptions
# same with multiple of assumptions
global_assumptions.add(x > 0, y > 0)
assert (x > 0) in global_assumptions
assert (y > 0) in global_assumptions
global_assumptions.clear()
assert not (x > 0) in global_assumptions
assert not (y > 0) in global_assumptions
@@ -0,0 +1,39 @@
from sympy.assumptions import ask, Q
from sympy.assumptions.assume import assuming, global_assumptions
from sympy.abc import x, y
def test_assuming():
with assuming(Q.integer(x)):
assert ask(Q.integer(x))
assert not ask(Q.integer(x))
def test_assuming_nested():
assert not ask(Q.integer(x))
assert not ask(Q.integer(y))
with assuming(Q.integer(x)):
assert ask(Q.integer(x))
assert not ask(Q.integer(y))
with assuming(Q.integer(y)):
assert ask(Q.integer(x))
assert ask(Q.integer(y))
assert ask(Q.integer(x))
assert not ask(Q.integer(y))
assert not ask(Q.integer(x))
assert not ask(Q.integer(y))
def test_finally():
try:
with assuming(Q.integer(x)):
1/0
except ZeroDivisionError:
pass
assert not ask(Q.integer(x))
def test_remove_safe():
global_assumptions.add(Q.integer(x))
with assuming():
assert ask(Q.integer(x))
global_assumptions.remove(Q.integer(x))
assert not ask(Q.integer(x))
assert ask(Q.integer(x))
global_assumptions.clear() # for the benefit of other tests
@@ -0,0 +1,283 @@
from sympy.assumptions.ask import (Q, ask)
from sympy.core.symbol import Symbol
from sympy.matrices.expressions.diagonal import (DiagMatrix, DiagonalMatrix)
from sympy.matrices.dense import Matrix
from sympy.matrices.expressions import (MatrixSymbol, Identity, ZeroMatrix,
OneMatrix, Trace, MatrixSlice, Determinant, BlockMatrix, BlockDiagMatrix)
from sympy.matrices.expressions.factorizations import LofLU
from sympy.testing.pytest import XFAIL
X = MatrixSymbol('X', 2, 2)
Y = MatrixSymbol('Y', 2, 3)
Z = MatrixSymbol('Z', 2, 2)
A1x1 = MatrixSymbol('A1x1', 1, 1)
B1x1 = MatrixSymbol('B1x1', 1, 1)
C0x0 = MatrixSymbol('C0x0', 0, 0)
V1 = MatrixSymbol('V1', 2, 1)
V2 = MatrixSymbol('V2', 2, 1)
def test_square():
assert ask(Q.square(X))
assert not ask(Q.square(Y))
assert ask(Q.square(Y*Y.T))
def test_invertible():
assert ask(Q.invertible(X), Q.invertible(X))
assert ask(Q.invertible(Y)) is False
assert ask(Q.invertible(X*Y), Q.invertible(X)) is False
assert ask(Q.invertible(X*Z), Q.invertible(X)) is None
assert ask(Q.invertible(X*Z), Q.invertible(X) & Q.invertible(Z)) is True
assert ask(Q.invertible(X.T)) is None
assert ask(Q.invertible(X.T), Q.invertible(X)) is True
assert ask(Q.invertible(X.I)) is True
assert ask(Q.invertible(Identity(3))) is True
assert ask(Q.invertible(ZeroMatrix(3, 3))) is False
assert ask(Q.invertible(OneMatrix(1, 1))) is True
assert ask(Q.invertible(OneMatrix(3, 3))) is False
assert ask(Q.invertible(X), Q.fullrank(X) & Q.square(X))
def test_singular():
assert ask(Q.singular(X)) is None
assert ask(Q.singular(X), Q.invertible(X)) is False
assert ask(Q.singular(X), ~Q.invertible(X)) is True
@XFAIL
def test_invertible_fullrank():
assert ask(Q.invertible(X), Q.fullrank(X)) is True
def test_invertible_BlockMatrix():
assert ask(Q.invertible(BlockMatrix([Identity(3)]))) == True
assert ask(Q.invertible(BlockMatrix([ZeroMatrix(3, 3)]))) == False
X = Matrix([[1, 2, 3], [3, 5, 4]])
Y = Matrix([[4, 2, 7], [2, 3, 5]])
# non-invertible A block
assert ask(Q.invertible(BlockMatrix([
[Matrix.ones(3, 3), Y.T],
[X, Matrix.eye(2)],
]))) == True
# non-invertible B block
assert ask(Q.invertible(BlockMatrix([
[Y.T, Matrix.ones(3, 3)],
[Matrix.eye(2), X],
]))) == True
# non-invertible C block
assert ask(Q.invertible(BlockMatrix([
[X, Matrix.eye(2)],
[Matrix.ones(3, 3), Y.T],
]))) == True
# non-invertible D block
assert ask(Q.invertible(BlockMatrix([
[Matrix.eye(2), X],
[Y.T, Matrix.ones(3, 3)],
]))) == True
def test_invertible_BlockDiagMatrix():
assert ask(Q.invertible(BlockDiagMatrix(Identity(3), Identity(5)))) == True
assert ask(Q.invertible(BlockDiagMatrix(ZeroMatrix(3, 3), Identity(5)))) == False
assert ask(Q.invertible(BlockDiagMatrix(Identity(3), OneMatrix(5, 5)))) == False
def test_symmetric():
assert ask(Q.symmetric(X), Q.symmetric(X))
assert ask(Q.symmetric(X*Z), Q.symmetric(X)) is None
assert ask(Q.symmetric(X*Z), Q.symmetric(X) & Q.symmetric(Z)) is True
assert ask(Q.symmetric(X + Z), Q.symmetric(X) & Q.symmetric(Z)) is True
assert ask(Q.symmetric(Y)) is False
assert ask(Q.symmetric(Y*Y.T)) is True
assert ask(Q.symmetric(Y.T*X*Y)) is None
assert ask(Q.symmetric(Y.T*X*Y), Q.symmetric(X)) is True
assert ask(Q.symmetric(X**10), Q.symmetric(X)) is True
assert ask(Q.symmetric(A1x1)) is True
assert ask(Q.symmetric(A1x1 + B1x1)) is True
assert ask(Q.symmetric(A1x1 * B1x1)) is True
assert ask(Q.symmetric(V1.T*V1)) is True
assert ask(Q.symmetric(V1.T*(V1 + V2))) is True
assert ask(Q.symmetric(V1.T*(V1 + V2) + A1x1)) is True
assert ask(Q.symmetric(MatrixSlice(Y, (0, 1), (1, 2)))) is True
assert ask(Q.symmetric(Identity(3))) is True
assert ask(Q.symmetric(ZeroMatrix(3, 3))) is True
assert ask(Q.symmetric(OneMatrix(3, 3))) is True
def _test_orthogonal_unitary(predicate):
assert ask(predicate(X), predicate(X))
assert ask(predicate(X.T), predicate(X)) is True
assert ask(predicate(X.I), predicate(X)) is True
assert ask(predicate(X**2), predicate(X))
assert ask(predicate(Y)) is False
assert ask(predicate(X)) is None
assert ask(predicate(X), ~Q.invertible(X)) is False
assert ask(predicate(X*Z*X), predicate(X) & predicate(Z)) is True
assert ask(predicate(Identity(3))) is True
assert ask(predicate(ZeroMatrix(3, 3))) is False
assert ask(Q.invertible(X), predicate(X))
assert not ask(predicate(X + Z), predicate(X) & predicate(Z))
def test_orthogonal():
_test_orthogonal_unitary(Q.orthogonal)
def test_unitary():
_test_orthogonal_unitary(Q.unitary)
assert ask(Q.unitary(X), Q.orthogonal(X))
def test_fullrank():
assert ask(Q.fullrank(X), Q.fullrank(X))
assert ask(Q.fullrank(X**2), Q.fullrank(X))
assert ask(Q.fullrank(X.T), Q.fullrank(X)) is True
assert ask(Q.fullrank(X)) is None
assert ask(Q.fullrank(Y)) is None
assert ask(Q.fullrank(X*Z), Q.fullrank(X) & Q.fullrank(Z)) is True
assert ask(Q.fullrank(Identity(3))) is True
assert ask(Q.fullrank(ZeroMatrix(3, 3))) is False
assert ask(Q.fullrank(OneMatrix(1, 1))) is True
assert ask(Q.fullrank(OneMatrix(3, 3))) is False
assert ask(Q.invertible(X), ~Q.fullrank(X)) == False
def test_positive_definite():
assert ask(Q.positive_definite(X), Q.positive_definite(X))
assert ask(Q.positive_definite(X.T), Q.positive_definite(X)) is True
assert ask(Q.positive_definite(X.I), Q.positive_definite(X)) is True
assert ask(Q.positive_definite(Y)) is False
assert ask(Q.positive_definite(X)) is None
assert ask(Q.positive_definite(X**3), Q.positive_definite(X))
assert ask(Q.positive_definite(X*Z*X),
Q.positive_definite(X) & Q.positive_definite(Z)) is True
assert ask(Q.positive_definite(X), Q.orthogonal(X))
assert ask(Q.positive_definite(Y.T*X*Y),
Q.positive_definite(X) & Q.fullrank(Y)) is True
assert not ask(Q.positive_definite(Y.T*X*Y), Q.positive_definite(X))
assert ask(Q.positive_definite(Identity(3))) is True
assert ask(Q.positive_definite(ZeroMatrix(3, 3))) is False
assert ask(Q.positive_definite(OneMatrix(1, 1))) is True
assert ask(Q.positive_definite(OneMatrix(3, 3))) is False
assert ask(Q.positive_definite(X + Z), Q.positive_definite(X) &
Q.positive_definite(Z)) is True
assert not ask(Q.positive_definite(-X), Q.positive_definite(X))
assert ask(Q.positive(X[1, 1]), Q.positive_definite(X))
def test_triangular():
assert ask(Q.upper_triangular(X + Z.T + Identity(2)), Q.upper_triangular(X) &
Q.lower_triangular(Z)) is True
assert ask(Q.upper_triangular(X*Z.T), Q.upper_triangular(X) &
Q.lower_triangular(Z)) is True
assert ask(Q.lower_triangular(Identity(3))) is True
assert ask(Q.lower_triangular(ZeroMatrix(3, 3))) is True
assert ask(Q.upper_triangular(ZeroMatrix(3, 3))) is True
assert ask(Q.lower_triangular(OneMatrix(1, 1))) is True
assert ask(Q.upper_triangular(OneMatrix(1, 1))) is True
assert ask(Q.lower_triangular(OneMatrix(3, 3))) is False
assert ask(Q.upper_triangular(OneMatrix(3, 3))) is False
assert ask(Q.triangular(X), Q.unit_triangular(X))
assert ask(Q.upper_triangular(X**3), Q.upper_triangular(X))
assert ask(Q.lower_triangular(X**3), Q.lower_triangular(X))
def test_diagonal():
assert ask(Q.diagonal(X + Z.T + Identity(2)), Q.diagonal(X) &
Q.diagonal(Z)) is True
assert ask(Q.diagonal(ZeroMatrix(3, 3)))
assert ask(Q.diagonal(OneMatrix(1, 1))) is True
assert ask(Q.diagonal(OneMatrix(3, 3))) is False
assert ask(Q.lower_triangular(X) & Q.upper_triangular(X), Q.diagonal(X))
assert ask(Q.diagonal(X), Q.lower_triangular(X) & Q.upper_triangular(X))
assert ask(Q.symmetric(X), Q.diagonal(X))
assert ask(Q.triangular(X), Q.diagonal(X))
assert ask(Q.diagonal(C0x0))
assert ask(Q.diagonal(A1x1))
assert ask(Q.diagonal(A1x1 + B1x1))
assert ask(Q.diagonal(A1x1*B1x1))
assert ask(Q.diagonal(V1.T*V2))
assert ask(Q.diagonal(V1.T*(X + Z)*V1))
assert ask(Q.diagonal(MatrixSlice(Y, (0, 1), (1, 2)))) is True
assert ask(Q.diagonal(V1.T*(V1 + V2))) is True
assert ask(Q.diagonal(X**3), Q.diagonal(X))
assert ask(Q.diagonal(Identity(3)))
assert ask(Q.diagonal(DiagMatrix(V1)))
assert ask(Q.diagonal(DiagonalMatrix(X)))
def test_non_atoms():
assert ask(Q.real(Trace(X)), Q.positive(Trace(X)))
@XFAIL
def test_non_trivial_implies():
X = MatrixSymbol('X', 3, 3)
Y = MatrixSymbol('Y', 3, 3)
assert ask(Q.lower_triangular(X+Y), Q.lower_triangular(X) &
Q.lower_triangular(Y)) is True
assert ask(Q.triangular(X), Q.lower_triangular(X)) is True
assert ask(Q.triangular(X+Y), Q.lower_triangular(X) &
Q.lower_triangular(Y)) is True
def test_MatrixSlice():
X = MatrixSymbol('X', 4, 4)
B = MatrixSlice(X, (1, 3), (1, 3))
C = MatrixSlice(X, (0, 3), (1, 3))
assert ask(Q.symmetric(B), Q.symmetric(X))
assert ask(Q.invertible(B), Q.invertible(X))
assert ask(Q.diagonal(B), Q.diagonal(X))
assert ask(Q.orthogonal(B), Q.orthogonal(X))
assert ask(Q.upper_triangular(B), Q.upper_triangular(X))
assert not ask(Q.symmetric(C), Q.symmetric(X))
assert not ask(Q.invertible(C), Q.invertible(X))
assert not ask(Q.diagonal(C), Q.diagonal(X))
assert not ask(Q.orthogonal(C), Q.orthogonal(X))
assert not ask(Q.upper_triangular(C), Q.upper_triangular(X))
def test_det_trace_positive():
X = MatrixSymbol('X', 4, 4)
assert ask(Q.positive(Trace(X)), Q.positive_definite(X))
assert ask(Q.positive(Determinant(X)), Q.positive_definite(X))
def test_field_assumptions():
X = MatrixSymbol('X', 4, 4)
Y = MatrixSymbol('Y', 4, 4)
assert ask(Q.real_elements(X), Q.real_elements(X))
assert not ask(Q.integer_elements(X), Q.real_elements(X))
assert ask(Q.complex_elements(X), Q.real_elements(X))
assert ask(Q.complex_elements(X**2), Q.real_elements(X))
assert ask(Q.real_elements(X**2), Q.integer_elements(X))
assert ask(Q.real_elements(X+Y), Q.real_elements(X)) is None
assert ask(Q.real_elements(X+Y), Q.real_elements(X) & Q.real_elements(Y))
from sympy.matrices.expressions.hadamard import HadamardProduct
assert ask(Q.real_elements(HadamardProduct(X, Y)),
Q.real_elements(X) & Q.real_elements(Y))
assert ask(Q.complex_elements(X+Y), Q.real_elements(X) & Q.complex_elements(Y))
assert ask(Q.real_elements(X.T), Q.real_elements(X))
assert ask(Q.real_elements(X.I), Q.real_elements(X) & Q.invertible(X))
assert ask(Q.real_elements(Trace(X)), Q.real_elements(X))
assert ask(Q.integer_elements(Determinant(X)), Q.integer_elements(X))
assert not ask(Q.integer_elements(X.I), Q.integer_elements(X))
alpha = Symbol('alpha')
assert ask(Q.real_elements(alpha*X), Q.real_elements(X) & Q.real(alpha))
assert ask(Q.real_elements(LofLU(X)), Q.real_elements(X))
e = Symbol('e', integer=True, negative=True)
assert ask(Q.real_elements(X**e), Q.real_elements(X) & Q.invertible(X))
assert ask(Q.real_elements(X**e), Q.real_elements(X)) is None
def test_matrix_element_sets():
X = MatrixSymbol('X', 4, 4)
assert ask(Q.real(X[1, 2]), Q.real_elements(X))
assert ask(Q.integer(X[1, 2]), Q.integer_elements(X))
assert ask(Q.complex(X[1, 2]), Q.complex_elements(X))
assert ask(Q.integer_elements(Identity(3)))
assert ask(Q.integer_elements(ZeroMatrix(3, 3)))
assert ask(Q.integer_elements(OneMatrix(3, 3)))
from sympy.matrices.expressions.fourier import DFT
assert ask(Q.complex_elements(DFT(3)))
def test_matrix_element_sets_slices_blocks():
X = MatrixSymbol('X', 4, 4)
assert ask(Q.integer_elements(X[:, 3]), Q.integer_elements(X))
assert ask(Q.integer_elements(BlockMatrix([[X], [X]])),
Q.integer_elements(X))
def test_matrix_element_sets_determinant_trace():
assert ask(Q.integer(Determinant(X)), Q.integer_elements(X))
assert ask(Q.integer(Trace(X)), Q.integer_elements(X))
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,227 @@
from sympy.assumptions.ask import Q
from sympy.assumptions.refine import refine
from sympy.core.expr import Expr
from sympy.core.numbers import (I, Rational, nan, pi)
from sympy.core.singleton import S
from sympy.core.symbol import Symbol
from sympy.functions.elementary.complexes import (Abs, arg, im, re, sign)
from sympy.functions.elementary.exponential import exp
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.elementary.trigonometric import (atan, atan2)
from sympy.abc import w, x, y, z
from sympy.core.relational import Eq, Ne
from sympy.functions.elementary.piecewise import Piecewise
from sympy.matrices.expressions.matexpr import MatrixSymbol
def test_Abs():
assert refine(Abs(x), Q.positive(x)) == x
assert refine(1 + Abs(x), Q.positive(x)) == 1 + x
assert refine(Abs(x), Q.negative(x)) == -x
assert refine(1 + Abs(x), Q.negative(x)) == 1 - x
assert refine(Abs(x**2)) != x**2
assert refine(Abs(x**2), Q.real(x)) == x**2
def test_pow1():
assert refine((-1)**x, Q.even(x)) == 1
assert refine((-1)**x, Q.odd(x)) == -1
assert refine((-2)**x, Q.even(x)) == 2**x
# nested powers
assert refine(sqrt(x**2)) != Abs(x)
assert refine(sqrt(x**2), Q.complex(x)) != Abs(x)
assert refine(sqrt(x**2), Q.real(x)) == Abs(x)
assert refine(sqrt(x**2), Q.positive(x)) == x
assert refine((x**3)**Rational(1, 3)) != x
assert refine((x**3)**Rational(1, 3), Q.real(x)) != x
assert refine((x**3)**Rational(1, 3), Q.positive(x)) == x
assert refine(sqrt(1/x), Q.real(x)) != 1/sqrt(x)
assert refine(sqrt(1/x), Q.positive(x)) == 1/sqrt(x)
# powers of (-1)
assert refine((-1)**(x + y), Q.even(x)) == (-1)**y
assert refine((-1)**(x + y + z), Q.odd(x) & Q.odd(z)) == (-1)**y
assert refine((-1)**(x + y + 1), Q.odd(x)) == (-1)**y
assert refine((-1)**(x + y + 2), Q.odd(x)) == (-1)**(y + 1)
assert refine((-1)**(x + 3)) == (-1)**(x + 1)
# continuation
assert refine((-1)**((-1)**x/2 - S.Half), Q.integer(x)) == (-1)**x
assert refine((-1)**((-1)**x/2 + S.Half), Q.integer(x)) == (-1)**(x + 1)
assert refine((-1)**((-1)**x/2 + 5*S.Half), Q.integer(x)) == (-1)**(x + 1)
def test_pow2():
assert refine((-1)**((-1)**x/2 - 7*S.Half), Q.integer(x)) == (-1)**(x + 1)
assert refine((-1)**((-1)**x/2 - 9*S.Half), Q.integer(x)) == (-1)**x
# powers of Abs
assert refine(Abs(x)**2, Q.real(x)) == x**2
assert refine(Abs(x)**3, Q.real(x)) == Abs(x)**3
assert refine(Abs(x)**2) == Abs(x)**2
def test_exp():
x = Symbol('x', integer=True)
assert refine(exp(pi*I*2*x)) == 1
assert refine(exp(pi*I*2*(x + S.Half))) == -1
assert refine(exp(pi*I*2*(x + Rational(1, 4)))) == I
assert refine(exp(pi*I*2*(x + Rational(3, 4)))) == -I
def test_Piecewise():
assert refine(Piecewise((1, x < 0), (3, True)), (x < 0)) == 1
assert refine(Piecewise((1, x < 0), (3, True)), ~(x < 0)) == 3
assert refine(Piecewise((1, x < 0), (3, True)), (y < 0)) == \
Piecewise((1, x < 0), (3, True))
assert refine(Piecewise((1, x > 0), (3, True)), (x > 0)) == 1
assert refine(Piecewise((1, x > 0), (3, True)), ~(x > 0)) == 3
assert refine(Piecewise((1, x > 0), (3, True)), (y > 0)) == \
Piecewise((1, x > 0), (3, True))
assert refine(Piecewise((1, x <= 0), (3, True)), (x <= 0)) == 1
assert refine(Piecewise((1, x <= 0), (3, True)), ~(x <= 0)) == 3
assert refine(Piecewise((1, x <= 0), (3, True)), (y <= 0)) == \
Piecewise((1, x <= 0), (3, True))
assert refine(Piecewise((1, x >= 0), (3, True)), (x >= 0)) == 1
assert refine(Piecewise((1, x >= 0), (3, True)), ~(x >= 0)) == 3
assert refine(Piecewise((1, x >= 0), (3, True)), (y >= 0)) == \
Piecewise((1, x >= 0), (3, True))
assert refine(Piecewise((1, Eq(x, 0)), (3, True)), (Eq(x, 0)))\
== 1
assert refine(Piecewise((1, Eq(x, 0)), (3, True)), (Eq(0, x)))\
== 1
assert refine(Piecewise((1, Eq(x, 0)), (3, True)), ~(Eq(x, 0)))\
== 3
assert refine(Piecewise((1, Eq(x, 0)), (3, True)), ~(Eq(0, x)))\
== 3
assert refine(Piecewise((1, Eq(x, 0)), (3, True)), (Eq(y, 0)))\
== Piecewise((1, Eq(x, 0)), (3, True))
assert refine(Piecewise((1, Ne(x, 0)), (3, True)), (Ne(x, 0)))\
== 1
assert refine(Piecewise((1, Ne(x, 0)), (3, True)), ~(Ne(x, 0)))\
== 3
assert refine(Piecewise((1, Ne(x, 0)), (3, True)), (Ne(y, 0)))\
== Piecewise((1, Ne(x, 0)), (3, True))
def test_atan2():
assert refine(atan2(y, x), Q.real(y) & Q.positive(x)) == atan(y/x)
assert refine(atan2(y, x), Q.negative(y) & Q.positive(x)) == atan(y/x)
assert refine(atan2(y, x), Q.negative(y) & Q.negative(x)) == atan(y/x) - pi
assert refine(atan2(y, x), Q.positive(y) & Q.negative(x)) == atan(y/x) + pi
assert refine(atan2(y, x), Q.zero(y) & Q.negative(x)) == pi
assert refine(atan2(y, x), Q.positive(y) & Q.zero(x)) == pi/2
assert refine(atan2(y, x), Q.negative(y) & Q.zero(x)) == -pi/2
assert refine(atan2(y, x), Q.zero(y) & Q.zero(x)) is nan
def test_re():
assert refine(re(x), Q.real(x)) == x
assert refine(re(x), Q.imaginary(x)) is S.Zero
assert refine(re(x+y), Q.real(x) & Q.real(y)) == x + y
assert refine(re(x+y), Q.real(x) & Q.imaginary(y)) == x
assert refine(re(x*y), Q.real(x) & Q.real(y)) == x * y
assert refine(re(x*y), Q.real(x) & Q.imaginary(y)) == 0
assert refine(re(x*y*z), Q.real(x) & Q.real(y) & Q.real(z)) == x * y * z
def test_im():
assert refine(im(x), Q.imaginary(x)) == -I*x
assert refine(im(x), Q.real(x)) is S.Zero
assert refine(im(x+y), Q.imaginary(x) & Q.imaginary(y)) == -I*x - I*y
assert refine(im(x+y), Q.real(x) & Q.imaginary(y)) == -I*y
assert refine(im(x*y), Q.imaginary(x) & Q.real(y)) == -I*x*y
assert refine(im(x*y), Q.imaginary(x) & Q.imaginary(y)) == 0
assert refine(im(1/x), Q.imaginary(x)) == -I/x
assert refine(im(x*y*z), Q.imaginary(x) & Q.imaginary(y)
& Q.imaginary(z)) == -I*x*y*z
def test_complex():
assert refine(re(1/(x + I*y)), Q.real(x) & Q.real(y)) == \
x/(x**2 + y**2)
assert refine(im(1/(x + I*y)), Q.real(x) & Q.real(y)) == \
-y/(x**2 + y**2)
assert refine(re((w + I*x) * (y + I*z)), Q.real(w) & Q.real(x) & Q.real(y)
& Q.real(z)) == w*y - x*z
assert refine(im((w + I*x) * (y + I*z)), Q.real(w) & Q.real(x) & Q.real(y)
& Q.real(z)) == w*z + x*y
def test_sign():
x = Symbol('x', real = True)
assert refine(sign(x), Q.positive(x)) == 1
assert refine(sign(x), Q.negative(x)) == -1
assert refine(sign(x), Q.zero(x)) == 0
assert refine(sign(x), True) == sign(x)
assert refine(sign(Abs(x)), Q.nonzero(x)) == 1
x = Symbol('x', imaginary=True)
assert refine(sign(x), Q.positive(im(x))) == S.ImaginaryUnit
assert refine(sign(x), Q.negative(im(x))) == -S.ImaginaryUnit
assert refine(sign(x), True) == sign(x)
x = Symbol('x', complex=True)
assert refine(sign(x), Q.zero(x)) == 0
def test_arg():
x = Symbol('x', complex = True)
assert refine(arg(x), Q.positive(x)) == 0
assert refine(arg(x), Q.negative(x)) == pi
def test_func_args():
class MyClass(Expr):
# A class with nontrivial .func
def __init__(self, *args):
self.my_member = ""
@property
def func(self):
def my_func(*args):
obj = MyClass(*args)
obj.my_member = self.my_member
return obj
return my_func
x = MyClass()
x.my_member = "A very important value"
assert x.my_member == refine(x).my_member
def test_issue_refine_9384():
assert refine(Piecewise((1, x < 0), (0, True)), Q.positive(x)) == 0
assert refine(Piecewise((1, x < 0), (0, True)), Q.negative(x)) == 1
assert refine(Piecewise((1, x > 0), (0, True)), Q.positive(x)) == 1
assert refine(Piecewise((1, x > 0), (0, True)), Q.negative(x)) == 0
def test_eval_refine():
class MockExpr(Expr):
def _eval_refine(self, assumptions):
return True
mock_obj = MockExpr()
assert refine(mock_obj)
def test_refine_issue_12724():
expr1 = refine(Abs(x * y), Q.positive(x))
expr2 = refine(Abs(x * y * z), Q.positive(x))
assert expr1 == x * Abs(y)
assert expr2 == x * Abs(y * z)
y1 = Symbol('y1', real = True)
expr3 = refine(Abs(x * y1**2 * z), Q.positive(x))
assert expr3 == x * y1**2 * Abs(z)
def test_matrixelement():
x = MatrixSymbol('x', 3, 3)
i = Symbol('i', positive = True)
j = Symbol('j', positive = True)
assert refine(x[0, 1], Q.symmetric(x)) == x[0, 1]
assert refine(x[1, 0], Q.symmetric(x)) == x[0, 1]
assert refine(x[i, j], Q.symmetric(x)) == x[j, i]
assert refine(x[j, i], Q.symmetric(x)) == x[j, i]
@@ -0,0 +1,172 @@
from sympy.assumptions.lra_satask import lra_satask
from sympy.logic.algorithms.lra_theory import UnhandledInput
from sympy.assumptions.ask import Q, ask
from sympy.core import symbols, Symbol
from sympy.matrices.expressions.matexpr import MatrixSymbol
from sympy.core.numbers import I
from sympy.testing.pytest import raises, XFAIL
x, y, z = symbols("x y z", real=True)
def test_lra_satask():
im = Symbol('im', imaginary=True)
# test preprocessing of unequalities is working correctly
assert lra_satask(Q.eq(x, 1), ~Q.ne(x, 0)) is False
assert lra_satask(Q.eq(x, 0), ~Q.ne(x, 0)) is True
assert lra_satask(~Q.ne(x, 0), Q.eq(x, 0)) is True
assert lra_satask(~Q.eq(x, 0), Q.eq(x, 0)) is False
assert lra_satask(Q.ne(x, 0), Q.eq(x, 0)) is False
# basic tests
assert lra_satask(Q.ne(x, x)) is False
assert lra_satask(Q.eq(x, x)) is True
assert lra_satask(Q.gt(x, 0), Q.gt(x, 1)) is True
# check that True/False are handled
assert lra_satask(Q.gt(x, 0), True) is None
assert raises(ValueError, lambda: lra_satask(Q.gt(x, 0), False))
# check imaginary numbers are correctly handled
# (im * I).is_real returns True so this is an edge case
raises(UnhandledInput, lambda: lra_satask(Q.gt(im * I, 0), Q.gt(im * I, 0)))
# check matrix inputs
X = MatrixSymbol("X", 2, 2)
raises(UnhandledInput, lambda: lra_satask(Q.lt(X, 2) & Q.gt(X, 3)))
def test_old_assumptions():
# test unhandled old assumptions
w = symbols("w")
raises(UnhandledInput, lambda: lra_satask(Q.lt(w, 2) & Q.gt(w, 3)))
w = symbols("w", rational=False, real=True)
raises(UnhandledInput, lambda: lra_satask(Q.lt(w, 2) & Q.gt(w, 3)))
w = symbols("w", odd=True, real=True)
raises(UnhandledInput, lambda: lra_satask(Q.lt(w, 2) & Q.gt(w, 3)))
w = symbols("w", even=True, real=True)
raises(UnhandledInput, lambda: lra_satask(Q.lt(w, 2) & Q.gt(w, 3)))
w = symbols("w", prime=True, real=True)
raises(UnhandledInput, lambda: lra_satask(Q.lt(w, 2) & Q.gt(w, 3)))
w = symbols("w", composite=True, real=True)
raises(UnhandledInput, lambda: lra_satask(Q.lt(w, 2) & Q.gt(w, 3)))
w = symbols("w", integer=True, real=True)
raises(UnhandledInput, lambda: lra_satask(Q.lt(w, 2) & Q.gt(w, 3)))
w = symbols("w", integer=False, real=True)
raises(UnhandledInput, lambda: lra_satask(Q.lt(w, 2) & Q.gt(w, 3)))
# test handled
w = symbols("w", positive=True, real=True)
assert lra_satask(Q.le(w, 0)) is False
assert lra_satask(Q.gt(w, 0)) is True
w = symbols("w", negative=True, real=True)
assert lra_satask(Q.lt(w, 0)) is True
assert lra_satask(Q.ge(w, 0)) is False
w = symbols("w", zero=True, real=True)
assert lra_satask(Q.eq(w, 0)) is True
assert lra_satask(Q.ne(w, 0)) is False
w = symbols("w", nonzero=True, real=True)
assert lra_satask(Q.ne(w, 0)) is True
assert lra_satask(Q.eq(w, 1)) is None
w = symbols("w", nonpositive=True, real=True)
assert lra_satask(Q.le(w, 0)) is True
assert lra_satask(Q.gt(w, 0)) is False
w = symbols("w", nonnegative=True, real=True)
assert lra_satask(Q.ge(w, 0)) is True
assert lra_satask(Q.lt(w, 0)) is False
def test_rel_queries():
assert ask(Q.lt(x, 2) & Q.gt(x, 3)) is False
assert ask(Q.positive(x - z), (x > y) & (y > z)) is True
assert ask(x + y > 2, (x < 0) & (y <0)) is False
assert ask(x > z, (x > y) & (y > z)) is True
def test_unhandled_queries():
X = MatrixSymbol("X", 2, 2)
assert ask(Q.lt(X, 2) & Q.gt(X, 3)) is None
def test_all_pred():
# test usable pred
assert lra_satask(Q.extended_positive(x), (x > 2)) is True
assert lra_satask(Q.positive_infinite(x)) is False
assert lra_satask(Q.negative_infinite(x)) is False
# test disallowed pred
raises(UnhandledInput, lambda: lra_satask((x > 0), (x > 2) & Q.prime(x)))
raises(UnhandledInput, lambda: lra_satask((x > 0), (x > 2) & Q.composite(x)))
raises(UnhandledInput, lambda: lra_satask((x > 0), (x > 2) & Q.odd(x)))
raises(UnhandledInput, lambda: lra_satask((x > 0), (x > 2) & Q.even(x)))
raises(UnhandledInput, lambda: lra_satask((x > 0), (x > 2) & Q.integer(x)))
def test_number_line_properties():
# From:
# https://en.wikipedia.org/wiki/Inequality_(mathematics)#Properties_on_the_number_line
a, b, c = symbols("a b c", real=True)
# Transitivity
# If a <= b and b <= c, then a <= c.
assert ask(a <= c, (a <= b) & (b <= c)) is True
# If a <= b and b < c, then a < c.
assert ask(a < c, (a <= b) & (b < c)) is True
# If a < b and b <= c, then a < c.
assert ask(a < c, (a < b) & (b <= c)) is True
# Addition and subtraction
# If a <= b, then a + c <= b + c and a - c <= b - c.
assert ask(a + c <= b + c, a <= b) is True
assert ask(a - c <= b - c, a <= b) is True
@XFAIL
def test_failing_number_line_properties():
# From:
# https://en.wikipedia.org/wiki/Inequality_(mathematics)#Properties_on_the_number_line
a, b, c = symbols("a b c", real=True)
# Multiplication and division
# If a <= b and c > 0, then ac <= bc and a/c <= b/c. (True for non-zero c)
assert ask(a*c <= b*c, (a <= b) & (c > 0) & ~ Q.zero(c)) is True
assert ask(a/c <= b/c, (a <= b) & (c > 0) & ~ Q.zero(c)) is True
# If a <= b and c < 0, then ac >= bc and a/c >= b/c. (True for non-zero c)
assert ask(a*c >= b*c, (a <= b) & (c < 0) & ~ Q.zero(c)) is True
assert ask(a/c >= b/c, (a <= b) & (c < 0) & ~ Q.zero(c)) is True
# Additive inverse
# If a <= b, then -a >= -b.
assert ask(-a >= -b, a <= b) is True
# Multiplicative inverse
# For a, b that are both negative or both positive:
# If a <= b, then 1/a >= 1/b .
assert ask(1/a >= 1/b, (a <= b) & Q.positive(x) & Q.positive(b)) is True
assert ask(1/a >= 1/b, (a <= b) & Q.negative(x) & Q.negative(b)) is True
def test_equality():
# test symmetry and reflexivity
assert ask(Q.eq(x, x)) is True
assert ask(Q.eq(y, x), Q.eq(x, y)) is True
assert ask(Q.eq(y, x), ~Q.eq(z, z) | Q.eq(x, y)) is True
# test transitivity
assert ask(Q.eq(x,z), Q.eq(x,y) & Q.eq(y,z)) is True
@XFAIL
def test_equality_failing():
# Note that implementing the substitution property of equality
# most likely requires a redesign of the new assumptions.
# See issue #25485 for why this is the case and general ideas
# about how things could be redesigned.
# test substitution property
assert ask(Q.prime(x), Q.eq(x, y) & Q.prime(y)) is True
assert ask(Q.real(x), Q.eq(x, y) & Q.real(y)) is True
assert ask(Q.imaginary(x), Q.eq(x, y) & Q.imaginary(y)) is True
@@ -0,0 +1,378 @@
from sympy.assumptions.ask import Q
from sympy.assumptions.assume import assuming
from sympy.core.numbers import (I, pi)
from sympy.core.relational import (Eq, Gt)
from sympy.core.singleton import S
from sympy.core.symbol import symbols
from sympy.functions.elementary.complexes import Abs
from sympy.logic.boolalg import Implies
from sympy.matrices.expressions.matexpr import MatrixSymbol
from sympy.assumptions.cnf import CNF, Literal
from sympy.assumptions.satask import (satask, extract_predargs,
get_relevant_clsfacts)
from sympy.testing.pytest import raises, XFAIL
x, y, z = symbols('x y z')
def test_satask():
# No relevant facts
assert satask(Q.real(x), Q.real(x)) is True
assert satask(Q.real(x), ~Q.real(x)) is False
assert satask(Q.real(x)) is None
assert satask(Q.real(x), Q.positive(x)) is True
assert satask(Q.positive(x), Q.real(x)) is None
assert satask(Q.real(x), ~Q.positive(x)) is None
assert satask(Q.positive(x), ~Q.real(x)) is False
raises(ValueError, lambda: satask(Q.real(x), Q.real(x) & ~Q.real(x)))
with assuming(Q.positive(x)):
assert satask(Q.real(x)) is True
assert satask(~Q.positive(x)) is False
raises(ValueError, lambda: satask(Q.real(x), ~Q.positive(x)))
assert satask(Q.zero(x), Q.nonzero(x)) is False
assert satask(Q.positive(x), Q.zero(x)) is False
assert satask(Q.real(x), Q.zero(x)) is True
assert satask(Q.zero(x), Q.zero(x*y)) is None
assert satask(Q.zero(x*y), Q.zero(x))
def test_zero():
"""
Everything in this test doesn't work with the ask handlers, and most
things would be very difficult or impossible to make work under that
model.
"""
assert satask(Q.zero(x) | Q.zero(y), Q.zero(x*y)) is True
assert satask(Q.zero(x*y), Q.zero(x) | Q.zero(y)) is True
assert satask(Implies(Q.zero(x), Q.zero(x*y))) is True
# This one in particular requires computing the fixed-point of the
# relevant facts, because going from Q.nonzero(x*y) -> ~Q.zero(x*y) and
# Q.zero(x*y) -> Equivalent(Q.zero(x*y), Q.zero(x) | Q.zero(y)) takes two
# steps.
assert satask(Q.zero(x) | Q.zero(y), Q.nonzero(x*y)) is False
assert satask(Q.zero(x), Q.zero(x**2)) is True
def test_zero_positive():
assert satask(Q.zero(x + y), Q.positive(x) & Q.positive(y)) is False
assert satask(Q.positive(x) & Q.positive(y), Q.zero(x + y)) is False
assert satask(Q.nonzero(x + y), Q.positive(x) & Q.positive(y)) is True
assert satask(Q.positive(x) & Q.positive(y), Q.nonzero(x + y)) is None
# This one requires several levels of forward chaining
assert satask(Q.zero(x*(x + y)), Q.positive(x) & Q.positive(y)) is False
assert satask(Q.positive(pi*x*y + 1), Q.positive(x) & Q.positive(y)) is True
assert satask(Q.positive(pi*x*y - 5), Q.positive(x) & Q.positive(y)) is None
def test_zero_pow():
assert satask(Q.zero(x**y), Q.zero(x) & Q.positive(y)) is True
assert satask(Q.zero(x**y), Q.nonzero(x) & Q.zero(y)) is False
assert satask(Q.zero(x), Q.zero(x**y)) is True
assert satask(Q.zero(x**y), Q.zero(x)) is None
@XFAIL
# Requires correct Q.square calculation first
def test_invertible():
A = MatrixSymbol('A', 5, 5)
B = MatrixSymbol('B', 5, 5)
assert satask(Q.invertible(A*B), Q.invertible(A) & Q.invertible(B)) is True
assert satask(Q.invertible(A), Q.invertible(A*B)) is True
assert satask(Q.invertible(A) & Q.invertible(B), Q.invertible(A*B)) is True
def test_prime():
assert satask(Q.prime(5)) is True
assert satask(Q.prime(6)) is False
assert satask(Q.prime(-5)) is False
assert satask(Q.prime(x*y), Q.integer(x) & Q.integer(y)) is None
assert satask(Q.prime(x*y), Q.prime(x) & Q.prime(y)) is False
def test_old_assump():
assert satask(Q.positive(1)) is True
assert satask(Q.positive(-1)) is False
assert satask(Q.positive(0)) is False
assert satask(Q.positive(I)) is False
assert satask(Q.positive(pi)) is True
assert satask(Q.negative(1)) is False
assert satask(Q.negative(-1)) is True
assert satask(Q.negative(0)) is False
assert satask(Q.negative(I)) is False
assert satask(Q.negative(pi)) is False
assert satask(Q.zero(1)) is False
assert satask(Q.zero(-1)) is False
assert satask(Q.zero(0)) is True
assert satask(Q.zero(I)) is False
assert satask(Q.zero(pi)) is False
assert satask(Q.nonzero(1)) is True
assert satask(Q.nonzero(-1)) is True
assert satask(Q.nonzero(0)) is False
assert satask(Q.nonzero(I)) is False
assert satask(Q.nonzero(pi)) is True
assert satask(Q.nonpositive(1)) is False
assert satask(Q.nonpositive(-1)) is True
assert satask(Q.nonpositive(0)) is True
assert satask(Q.nonpositive(I)) is False
assert satask(Q.nonpositive(pi)) is False
assert satask(Q.nonnegative(1)) is True
assert satask(Q.nonnegative(-1)) is False
assert satask(Q.nonnegative(0)) is True
assert satask(Q.nonnegative(I)) is False
assert satask(Q.nonnegative(pi)) is True
def test_rational_irrational():
assert satask(Q.irrational(2)) is False
assert satask(Q.rational(2)) is True
assert satask(Q.irrational(pi)) is True
assert satask(Q.rational(pi)) is False
assert satask(Q.irrational(I)) is False
assert satask(Q.rational(I)) is False
assert satask(Q.irrational(x*y*z), Q.irrational(x) & Q.irrational(y) &
Q.rational(z)) is None
assert satask(Q.irrational(x*y*z), Q.irrational(x) & Q.rational(y) &
Q.rational(z)) is True
assert satask(Q.irrational(pi*x*y), Q.rational(x) & Q.rational(y)) is True
assert satask(Q.irrational(x + y + z), Q.irrational(x) & Q.irrational(y) &
Q.rational(z)) is None
assert satask(Q.irrational(x + y + z), Q.irrational(x) & Q.rational(y) &
Q.rational(z)) is True
assert satask(Q.irrational(pi + x + y), Q.rational(x) & Q.rational(y)) is True
assert satask(Q.irrational(x*y*z), Q.rational(x) & Q.rational(y) &
Q.rational(z)) is False
assert satask(Q.rational(x*y*z), Q.rational(x) & Q.rational(y) &
Q.rational(z)) is True
assert satask(Q.irrational(x + y + z), Q.rational(x) & Q.rational(y) &
Q.rational(z)) is False
assert satask(Q.rational(x + y + z), Q.rational(x) & Q.rational(y) &
Q.rational(z)) is True
def test_even_satask():
assert satask(Q.even(2)) is True
assert satask(Q.even(3)) is False
assert satask(Q.even(x*y), Q.even(x) & Q.odd(y)) is True
assert satask(Q.even(x*y), Q.even(x) & Q.integer(y)) is True
assert satask(Q.even(x*y), Q.even(x) & Q.even(y)) is True
assert satask(Q.even(x*y), Q.odd(x) & Q.odd(y)) is False
assert satask(Q.even(x*y), Q.even(x)) is None
assert satask(Q.even(x*y), Q.odd(x) & Q.integer(y)) is None
assert satask(Q.even(x*y), Q.odd(x) & Q.odd(y)) is False
assert satask(Q.even(abs(x)), Q.even(x)) is True
assert satask(Q.even(abs(x)), Q.odd(x)) is False
assert satask(Q.even(x), Q.even(abs(x))) is None # x could be complex
def test_odd_satask():
assert satask(Q.odd(2)) is False
assert satask(Q.odd(3)) is True
assert satask(Q.odd(x*y), Q.even(x) & Q.odd(y)) is False
assert satask(Q.odd(x*y), Q.even(x) & Q.integer(y)) is False
assert satask(Q.odd(x*y), Q.even(x) & Q.even(y)) is False
assert satask(Q.odd(x*y), Q.odd(x) & Q.odd(y)) is True
assert satask(Q.odd(x*y), Q.even(x)) is None
assert satask(Q.odd(x*y), Q.odd(x) & Q.integer(y)) is None
assert satask(Q.odd(x*y), Q.odd(x) & Q.odd(y)) is True
assert satask(Q.odd(abs(x)), Q.even(x)) is False
assert satask(Q.odd(abs(x)), Q.odd(x)) is True
assert satask(Q.odd(x), Q.odd(abs(x))) is None # x could be complex
def test_integer():
assert satask(Q.integer(1)) is True
assert satask(Q.integer(S.Half)) is False
assert satask(Q.integer(x + y), Q.integer(x) & Q.integer(y)) is True
assert satask(Q.integer(x + y), Q.integer(x)) is None
assert satask(Q.integer(x + y), Q.integer(x) & ~Q.integer(y)) is False
assert satask(Q.integer(x + y + z), Q.integer(x) & Q.integer(y) &
~Q.integer(z)) is False
assert satask(Q.integer(x + y + z), Q.integer(x) & ~Q.integer(y) &
~Q.integer(z)) is None
assert satask(Q.integer(x + y + z), Q.integer(x) & ~Q.integer(y)) is None
assert satask(Q.integer(x + y), Q.integer(x) & Q.irrational(y)) is False
assert satask(Q.integer(x*y), Q.integer(x) & Q.integer(y)) is True
assert satask(Q.integer(x*y), Q.integer(x)) is None
assert satask(Q.integer(x*y), Q.integer(x) & ~Q.integer(y)) is None
assert satask(Q.integer(x*y), Q.integer(x) & ~Q.rational(y)) is False
assert satask(Q.integer(x*y*z), Q.integer(x) & Q.integer(y) &
~Q.rational(z)) is False
assert satask(Q.integer(x*y*z), Q.integer(x) & ~Q.rational(y) &
~Q.rational(z)) is None
assert satask(Q.integer(x*y*z), Q.integer(x) & ~Q.rational(y)) is None
assert satask(Q.integer(x*y), Q.integer(x) & Q.irrational(y)) is False
def test_abs():
assert satask(Q.nonnegative(abs(x))) is True
assert satask(Q.positive(abs(x)), ~Q.zero(x)) is True
assert satask(Q.zero(x), ~Q.zero(abs(x))) is False
assert satask(Q.zero(x), Q.zero(abs(x))) is True
assert satask(Q.nonzero(x), ~Q.zero(abs(x))) is None # x could be complex
assert satask(Q.zero(abs(x)), Q.zero(x)) is True
def test_imaginary():
assert satask(Q.imaginary(2*I)) is True
assert satask(Q.imaginary(x*y), Q.imaginary(x)) is None
assert satask(Q.imaginary(x*y), Q.imaginary(x) & Q.real(y)) is True
assert satask(Q.imaginary(x), Q.real(x)) is False
assert satask(Q.imaginary(1)) is False
assert satask(Q.imaginary(x*y), Q.real(x) & Q.real(y)) is False
assert satask(Q.imaginary(x + y), Q.real(x) & Q.real(y)) is False
def test_real():
assert satask(Q.real(x*y), Q.real(x) & Q.real(y)) is True
assert satask(Q.real(x + y), Q.real(x) & Q.real(y)) is True
assert satask(Q.real(x*y*z), Q.real(x) & Q.real(y) & Q.real(z)) is True
assert satask(Q.real(x*y*z), Q.real(x) & Q.real(y)) is None
assert satask(Q.real(x*y*z), Q.real(x) & Q.real(y) & Q.imaginary(z)) is False
assert satask(Q.real(x + y + z), Q.real(x) & Q.real(y) & Q.real(z)) is True
assert satask(Q.real(x + y + z), Q.real(x) & Q.real(y)) is None
def test_pos_neg():
assert satask(~Q.positive(x), Q.negative(x)) is True
assert satask(~Q.negative(x), Q.positive(x)) is True
assert satask(Q.positive(x + y), Q.positive(x) & Q.positive(y)) is True
assert satask(Q.negative(x + y), Q.negative(x) & Q.negative(y)) is True
assert satask(Q.positive(x + y), Q.negative(x) & Q.negative(y)) is False
assert satask(Q.negative(x + y), Q.positive(x) & Q.positive(y)) is False
def test_pow_pos_neg():
assert satask(Q.nonnegative(x**2), Q.positive(x)) is True
assert satask(Q.nonpositive(x**2), Q.positive(x)) is False
assert satask(Q.positive(x**2), Q.positive(x)) is True
assert satask(Q.negative(x**2), Q.positive(x)) is False
assert satask(Q.real(x**2), Q.positive(x)) is True
assert satask(Q.nonnegative(x**2), Q.negative(x)) is True
assert satask(Q.nonpositive(x**2), Q.negative(x)) is False
assert satask(Q.positive(x**2), Q.negative(x)) is True
assert satask(Q.negative(x**2), Q.negative(x)) is False
assert satask(Q.real(x**2), Q.negative(x)) is True
assert satask(Q.nonnegative(x**2), Q.nonnegative(x)) is True
assert satask(Q.nonpositive(x**2), Q.nonnegative(x)) is None
assert satask(Q.positive(x**2), Q.nonnegative(x)) is None
assert satask(Q.negative(x**2), Q.nonnegative(x)) is False
assert satask(Q.real(x**2), Q.nonnegative(x)) is True
assert satask(Q.nonnegative(x**2), Q.nonpositive(x)) is True
assert satask(Q.nonpositive(x**2), Q.nonpositive(x)) is None
assert satask(Q.positive(x**2), Q.nonpositive(x)) is None
assert satask(Q.negative(x**2), Q.nonpositive(x)) is False
assert satask(Q.real(x**2), Q.nonpositive(x)) is True
assert satask(Q.nonnegative(x**3), Q.positive(x)) is True
assert satask(Q.nonpositive(x**3), Q.positive(x)) is False
assert satask(Q.positive(x**3), Q.positive(x)) is True
assert satask(Q.negative(x**3), Q.positive(x)) is False
assert satask(Q.real(x**3), Q.positive(x)) is True
assert satask(Q.nonnegative(x**3), Q.negative(x)) is False
assert satask(Q.nonpositive(x**3), Q.negative(x)) is True
assert satask(Q.positive(x**3), Q.negative(x)) is False
assert satask(Q.negative(x**3), Q.negative(x)) is True
assert satask(Q.real(x**3), Q.negative(x)) is True
assert satask(Q.nonnegative(x**3), Q.nonnegative(x)) is True
assert satask(Q.nonpositive(x**3), Q.nonnegative(x)) is None
assert satask(Q.positive(x**3), Q.nonnegative(x)) is None
assert satask(Q.negative(x**3), Q.nonnegative(x)) is False
assert satask(Q.real(x**3), Q.nonnegative(x)) is True
assert satask(Q.nonnegative(x**3), Q.nonpositive(x)) is None
assert satask(Q.nonpositive(x**3), Q.nonpositive(x)) is True
assert satask(Q.positive(x**3), Q.nonpositive(x)) is False
assert satask(Q.negative(x**3), Q.nonpositive(x)) is None
assert satask(Q.real(x**3), Q.nonpositive(x)) is True
# If x is zero, x**negative is not real.
assert satask(Q.nonnegative(x**-2), Q.nonpositive(x)) is None
assert satask(Q.nonpositive(x**-2), Q.nonpositive(x)) is None
assert satask(Q.positive(x**-2), Q.nonpositive(x)) is None
assert satask(Q.negative(x**-2), Q.nonpositive(x)) is None
assert satask(Q.real(x**-2), Q.nonpositive(x)) is None
# We could deduce things for negative powers if x is nonzero, but it
# isn't implemented yet.
def test_prime_composite():
assert satask(Q.prime(x), Q.composite(x)) is False
assert satask(Q.composite(x), Q.prime(x)) is False
assert satask(Q.composite(x), ~Q.prime(x)) is None
assert satask(Q.prime(x), ~Q.composite(x)) is None
# since 1 is neither prime nor composite the following should hold
assert satask(Q.prime(x), Q.integer(x) & Q.positive(x) & ~Q.composite(x)) is None
assert satask(Q.prime(2)) is True
assert satask(Q.prime(4)) is False
assert satask(Q.prime(1)) is False
assert satask(Q.composite(1)) is False
def test_extract_predargs():
props = CNF.from_prop(Q.zero(Abs(x*y)) & Q.zero(x*y))
assump = CNF.from_prop(Q.zero(x))
context = CNF.from_prop(Q.zero(y))
assert extract_predargs(props) == {Abs(x*y), x*y}
assert extract_predargs(props, assump) == {Abs(x*y), x*y, x}
assert extract_predargs(props, assump, context) == {Abs(x*y), x*y, x, y}
props = CNF.from_prop(Eq(x, y))
assump = CNF.from_prop(Gt(y, z))
assert extract_predargs(props, assump) == {x, y, z}
def test_get_relevant_clsfacts():
exprs = {Abs(x*y)}
exprs, facts = get_relevant_clsfacts(exprs)
assert exprs == {x*y}
assert facts.clauses == \
{frozenset({Literal(Q.odd(Abs(x*y)), False), Literal(Q.odd(x*y), True)}),
frozenset({Literal(Q.zero(Abs(x*y)), False), Literal(Q.zero(x*y), True)}),
frozenset({Literal(Q.even(Abs(x*y)), False), Literal(Q.even(x*y), True)}),
frozenset({Literal(Q.zero(Abs(x*y)), True), Literal(Q.zero(x*y), False)}),
frozenset({Literal(Q.even(Abs(x*y)), False),
Literal(Q.odd(Abs(x*y)), False),
Literal(Q.odd(x*y), True)}),
frozenset({Literal(Q.even(Abs(x*y)), False),
Literal(Q.even(x*y), True),
Literal(Q.odd(Abs(x*y)), False)}),
frozenset({Literal(Q.positive(Abs(x*y)), False),
Literal(Q.zero(Abs(x*y)), False)})}
@@ -0,0 +1,50 @@
from sympy.assumptions.ask import Q
from sympy.core.basic import Basic
from sympy.core.expr import Expr
from sympy.core.mul import Mul
from sympy.core.symbol import symbols
from sympy.logic.boolalg import (And, Or)
from sympy.assumptions.sathandlers import (ClassFactRegistry, allargs,
anyarg, exactlyonearg,)
x, y, z = symbols('x y z')
def test_class_handler_registry():
my_handler_registry = ClassFactRegistry()
# The predicate doesn't matter here, so just pass
@my_handler_registry.register(Mul)
def fact1(expr):
pass
@my_handler_registry.multiregister(Expr)
def fact2(expr):
pass
assert my_handler_registry[Basic] == (frozenset(), frozenset())
assert my_handler_registry[Expr] == (frozenset(), frozenset({fact2}))
assert my_handler_registry[Mul] == (frozenset({fact1}), frozenset({fact2}))
def test_allargs():
assert allargs(x, Q.zero(x), x*y) == And(Q.zero(x), Q.zero(y))
assert allargs(x, Q.positive(x) | Q.negative(x), x*y) == And(Q.positive(x) | Q.negative(x), Q.positive(y) | Q.negative(y))
def test_anyarg():
assert anyarg(x, Q.zero(x), x*y) == Or(Q.zero(x), Q.zero(y))
assert anyarg(x, Q.positive(x) & Q.negative(x), x*y) == \
Or(Q.positive(x) & Q.negative(x), Q.positive(y) & Q.negative(y))
def test_exactlyonearg():
assert exactlyonearg(x, Q.zero(x), x*y) == \
Or(Q.zero(x) & ~Q.zero(y), Q.zero(y) & ~Q.zero(x))
assert exactlyonearg(x, Q.zero(x), x*y*z) == \
Or(Q.zero(x) & ~Q.zero(y) & ~Q.zero(z), Q.zero(y)
& ~Q.zero(x) & ~Q.zero(z), Q.zero(z) & ~Q.zero(x) & ~Q.zero(y))
assert exactlyonearg(x, Q.positive(x) | Q.negative(x), x*y) == \
Or((Q.positive(x) | Q.negative(x)) &
~(Q.positive(y) | Q.negative(y)), (Q.positive(y) | Q.negative(y)) &
~(Q.positive(x) | Q.negative(x)))
@@ -0,0 +1,39 @@
from sympy.assumptions.ask import Q
from sympy.assumptions.wrapper import (AssumptionsWrapper, is_infinite,
is_extended_real)
from sympy.core.symbol import Symbol
from sympy.core.assumptions import _assume_defined
def test_all_predicates():
for fact in _assume_defined:
method_name = f'_eval_is_{fact}'
assert hasattr(AssumptionsWrapper, method_name)
def test_AssumptionsWrapper():
x = Symbol('x', positive=True)
y = Symbol('y')
assert AssumptionsWrapper(x).is_positive
assert AssumptionsWrapper(y).is_positive is None
assert AssumptionsWrapper(y, Q.positive(y)).is_positive
def test_is_infinite():
x = Symbol('x', infinite=True)
y = Symbol('y', infinite=False)
z = Symbol('z')
assert is_infinite(x)
assert not is_infinite(y)
assert is_infinite(z) is None
assert is_infinite(z, Q.infinite(z))
def test_is_extended_real():
x = Symbol('x', extended_real=True)
y = Symbol('y', extended_real=False)
z = Symbol('z')
assert is_extended_real(x)
assert not is_extended_real(y)
assert is_extended_real(z) is None
assert is_extended_real(z, Q.extended_real(z))
@@ -0,0 +1,164 @@
"""
Functions and wrapper object to call assumption property and predicate
query with same syntax.
In SymPy, there are two assumption systems. Old assumption system is
defined in sympy/core/assumptions, and it can be accessed by attribute
such as ``x.is_even``. New assumption system is defined in
sympy/assumptions, and it can be accessed by predicates such as
``Q.even(x)``.
Old assumption is fast, while new assumptions can freely take local facts.
In general, old assumption is used in evaluation method and new assumption
is used in refinement method.
In most cases, both evaluation and refinement follow the same process, and
the only difference is which assumption system is used. This module provides
``is_[...]()`` functions and ``AssumptionsWrapper()`` class which allows
using two systems with same syntax so that parallel code implementation can be
avoided.
Examples
========
For multiple use, use ``AssumptionsWrapper()``.
>>> from sympy import Q, Symbol
>>> from sympy.assumptions.wrapper import AssumptionsWrapper
>>> x = Symbol('x')
>>> _x = AssumptionsWrapper(x, Q.even(x))
>>> _x.is_integer
True
>>> _x.is_odd
False
For single use, use ``is_[...]()`` functions.
>>> from sympy.assumptions.wrapper import is_infinite
>>> a = Symbol('a')
>>> print(is_infinite(a))
None
>>> is_infinite(a, Q.finite(a))
False
"""
from sympy.assumptions import ask, Q
from sympy.core.basic import Basic
from sympy.core.sympify import _sympify
def make_eval_method(fact):
def getit(self):
pred = getattr(Q, fact)
ret = ask(pred(self.expr), self.assumptions)
return ret
return getit
# we subclass Basic to use the fact deduction and caching
class AssumptionsWrapper(Basic):
"""
Wrapper over ``Basic`` instances to call predicate query by
``.is_[...]`` property
Parameters
==========
expr : Basic
assumptions : Boolean, optional
Examples
========
>>> from sympy import Q, Symbol
>>> from sympy.assumptions.wrapper import AssumptionsWrapper
>>> x = Symbol('x', even=True)
>>> AssumptionsWrapper(x).is_integer
True
>>> y = Symbol('y')
>>> AssumptionsWrapper(y, Q.even(y)).is_integer
True
With ``AssumptionsWrapper``, both evaluation and refinement can be supported
by single implementation.
>>> from sympy import Function
>>> class MyAbs(Function):
... @classmethod
... def eval(cls, x, assumptions=True):
... _x = AssumptionsWrapper(x, assumptions)
... if _x.is_nonnegative:
... return x
... if _x.is_negative:
... return -x
... def _eval_refine(self, assumptions):
... return MyAbs.eval(self.args[0], assumptions)
>>> MyAbs(x)
MyAbs(x)
>>> MyAbs(x).refine(Q.positive(x))
x
>>> MyAbs(Symbol('y', negative=True))
-y
"""
def __new__(cls, expr, assumptions=None):
if assumptions is None:
return expr
obj = super().__new__(cls, expr, _sympify(assumptions))
obj.expr = expr
obj.assumptions = assumptions
return obj
_eval_is_algebraic = make_eval_method("algebraic")
_eval_is_antihermitian = make_eval_method("antihermitian")
_eval_is_commutative = make_eval_method("commutative")
_eval_is_complex = make_eval_method("complex")
_eval_is_composite = make_eval_method("composite")
_eval_is_even = make_eval_method("even")
_eval_is_extended_negative = make_eval_method("extended_negative")
_eval_is_extended_nonnegative = make_eval_method("extended_nonnegative")
_eval_is_extended_nonpositive = make_eval_method("extended_nonpositive")
_eval_is_extended_nonzero = make_eval_method("extended_nonzero")
_eval_is_extended_positive = make_eval_method("extended_positive")
_eval_is_extended_real = make_eval_method("extended_real")
_eval_is_finite = make_eval_method("finite")
_eval_is_hermitian = make_eval_method("hermitian")
_eval_is_imaginary = make_eval_method("imaginary")
_eval_is_infinite = make_eval_method("infinite")
_eval_is_integer = make_eval_method("integer")
_eval_is_irrational = make_eval_method("irrational")
_eval_is_negative = make_eval_method("negative")
_eval_is_noninteger = make_eval_method("noninteger")
_eval_is_nonnegative = make_eval_method("nonnegative")
_eval_is_nonpositive = make_eval_method("nonpositive")
_eval_is_nonzero = make_eval_method("nonzero")
_eval_is_odd = make_eval_method("odd")
_eval_is_polar = make_eval_method("polar")
_eval_is_positive = make_eval_method("positive")
_eval_is_prime = make_eval_method("prime")
_eval_is_rational = make_eval_method("rational")
_eval_is_real = make_eval_method("real")
_eval_is_transcendental = make_eval_method("transcendental")
_eval_is_zero = make_eval_method("zero")
# one shot functions which are faster than AssumptionsWrapper
def is_infinite(obj, assumptions=None):
if assumptions is None:
return obj.is_infinite
return ask(Q.infinite(obj), assumptions)
def is_extended_real(obj, assumptions=None):
if assumptions is None:
return obj.is_extended_real
return ask(Q.extended_real(obj), assumptions)
def is_extended_nonnegative(obj, assumptions=None):
if assumptions is None:
return obj.is_extended_nonnegative
return ask(Q.extended_nonnegative(obj), assumptions)
@@ -0,0 +1,83 @@
import sys
from time import time
from sympy.ntheory.residue_ntheory import (discrete_log,
_discrete_log_trial_mul, _discrete_log_shanks_steps,
_discrete_log_pollard_rho, _discrete_log_pohlig_hellman)
# Cyclic group (Z/pZ)* with p prime, order p - 1 and generator g
data_set_1 = [
# p, p - 1, g
[191, 190, 19],
[46639, 46638, 6],
[14789363, 14789362, 2],
[4254225211, 4254225210, 2],
[432751500361, 432751500360, 7],
[158505390797053, 158505390797052, 2],
[6575202655312007, 6575202655312006, 5],
[8430573471995353769, 8430573471995353768, 3],
[3938471339744997827267, 3938471339744997827266, 2],
[875260951364705563393093, 875260951364705563393092, 5],
]
# Cyclic sub-groups of (Z/nZ)* with prime order p and generator g
# (n, p are primes and n = 2 * p + 1)
data_set_2 = [
# n, p, g
[227, 113, 3],
[2447, 1223, 2],
[24527, 12263, 2],
[245639, 122819, 2],
[2456747, 1228373, 3],
[24567899, 12283949, 3],
[245679023, 122839511, 2],
[2456791307, 1228395653, 3],
[24567913439, 12283956719, 2],
[245679135407, 122839567703, 2],
[2456791354763, 1228395677381, 3],
[24567913550903, 12283956775451, 2],
[245679135509519, 122839567754759, 2],
]
# Cyclic sub-groups of (Z/nZ)* with smooth order o and generator g
data_set_3 = [
# n, o, g
[2**118, 2**116, 3],
]
def bench_discrete_log(data_set, algo=None):
if algo is None:
f = discrete_log
elif algo == 'trial':
f = _discrete_log_trial_mul
elif algo == 'shanks':
f = _discrete_log_shanks_steps
elif algo == 'rho':
f = _discrete_log_pollard_rho
elif algo == 'ph':
f = _discrete_log_pohlig_hellman
else:
raise ValueError("Argument 'algo' should be one"
" of ('trial', 'shanks', 'rho' or 'ph')")
for i, data in enumerate(data_set):
for j, (n, p, g) in enumerate(data):
t = time()
l = f(n, pow(g, p - 1, n), g, p)
t = time() - t
print('[%02d-%03d] %15.10f' % (i, j, t))
assert l == p - 1
if __name__ == '__main__':
algo = sys.argv[1] \
if len(sys.argv) > 1 else None
data_set = [
data_set_1,
data_set_2,
data_set_3,
]
bench_discrete_log(data_set, algo)
@@ -0,0 +1,261 @@
# conceal the implicit import from the code quality tester
from sympy.core.numbers import (oo, pi)
from sympy.core.symbol import (Symbol, symbols)
from sympy.functions.elementary.exponential import exp
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.special.bessel import besseli
from sympy.functions.special.gamma_functions import gamma
from sympy.integrals.integrals import integrate
from sympy.integrals.transforms import (mellin_transform,
inverse_fourier_transform, inverse_mellin_transform,
laplace_transform, inverse_laplace_transform, fourier_transform)
LT = laplace_transform
FT = fourier_transform
MT = mellin_transform
IFT = inverse_fourier_transform
ILT = inverse_laplace_transform
IMT = inverse_mellin_transform
from sympy.abc import x, y
nu, beta, rho = symbols('nu beta rho')
apos, bpos, cpos, dpos, posk, p = symbols('a b c d k p', positive=True)
k = Symbol('k', real=True)
negk = Symbol('k', negative=True)
mu1, mu2 = symbols('mu1 mu2', real=True, nonzero=True, finite=True)
sigma1, sigma2 = symbols('sigma1 sigma2', real=True, nonzero=True,
finite=True, positive=True)
rate = Symbol('lambda', positive=True)
def normal(x, mu, sigma):
return 1/sqrt(2*pi*sigma**2)*exp(-(x - mu)**2/2/sigma**2)
def exponential(x, rate):
return rate*exp(-rate*x)
alpha, beta = symbols('alpha beta', positive=True)
betadist = x**(alpha - 1)*(1 + x)**(-alpha - beta)*gamma(alpha + beta) \
/gamma(alpha)/gamma(beta)
kint = Symbol('k', integer=True, positive=True)
chi = 2**(1 - kint/2)*x**(kint - 1)*exp(-x**2/2)/gamma(kint/2)
chisquared = 2**(-k/2)/gamma(k/2)*x**(k/2 - 1)*exp(-x/2)
dagum = apos*p/x*(x/bpos)**(apos*p)/(1 + x**apos/bpos**apos)**(p + 1)
d1, d2 = symbols('d1 d2', positive=True)
f = sqrt(((d1*x)**d1 * d2**d2)/(d1*x + d2)**(d1 + d2))/x \
/gamma(d1/2)/gamma(d2/2)*gamma((d1 + d2)/2)
nupos, sigmapos = symbols('nu sigma', positive=True)
rice = x/sigmapos**2*exp(-(x**2 + nupos**2)/2/sigmapos**2)*besseli(0, x*
nupos/sigmapos**2)
mu = Symbol('mu', real=True)
laplace = exp(-abs(x - mu)/bpos)/2/bpos
u = Symbol('u', polar=True)
tpos = Symbol('t', positive=True)
def E(expr):
integrate(expr*exponential(x, rate)*normal(y, mu1, sigma1),
(x, 0, oo), (y, -oo, oo), meijerg=True)
integrate(expr*exponential(x, rate)*normal(y, mu1, sigma1),
(y, -oo, oo), (x, 0, oo), meijerg=True)
bench = [
'MT(x**nu*Heaviside(x - 1), x, s)',
'MT(x**nu*Heaviside(1 - x), x, s)',
'MT((1-x)**(beta - 1)*Heaviside(1-x), x, s)',
'MT((x-1)**(beta - 1)*Heaviside(x-1), x, s)',
'MT((1+x)**(-rho), x, s)',
'MT(abs(1-x)**(-rho), x, s)',
'MT((1-x)**(beta-1)*Heaviside(1-x) + a*(x-1)**(beta-1)*Heaviside(x-1), x, s)',
'MT((x**a-b**a)/(x-b), x, s)',
'MT((x**a-bpos**a)/(x-bpos), x, s)',
'MT(exp(-x), x, s)',
'MT(exp(-1/x), x, s)',
'MT(log(x)**4*Heaviside(1-x), x, s)',
'MT(log(x)**3*Heaviside(x-1), x, s)',
'MT(log(x + 1), x, s)',
'MT(log(1/x + 1), x, s)',
'MT(log(abs(1 - x)), x, s)',
'MT(log(abs(1 - 1/x)), x, s)',
'MT(log(x)/(x+1), x, s)',
'MT(log(x)**2/(x+1), x, s)',
'MT(log(x)/(x+1)**2, x, s)',
'MT(erf(sqrt(x)), x, s)',
'MT(besselj(a, 2*sqrt(x)), x, s)',
'MT(sin(sqrt(x))*besselj(a, sqrt(x)), x, s)',
'MT(cos(sqrt(x))*besselj(a, sqrt(x)), x, s)',
'MT(besselj(a, sqrt(x))**2, x, s)',
'MT(besselj(a, sqrt(x))*besselj(-a, sqrt(x)), x, s)',
'MT(besselj(a - 1, sqrt(x))*besselj(a, sqrt(x)), x, s)',
'MT(besselj(a, sqrt(x))*besselj(b, sqrt(x)), x, s)',
'MT(besselj(a, sqrt(x))**2 + besselj(-a, sqrt(x))**2, x, s)',
'MT(bessely(a, 2*sqrt(x)), x, s)',
'MT(sin(sqrt(x))*bessely(a, sqrt(x)), x, s)',
'MT(cos(sqrt(x))*bessely(a, sqrt(x)), x, s)',
'MT(besselj(a, sqrt(x))*bessely(a, sqrt(x)), x, s)',
'MT(besselj(a, sqrt(x))*bessely(b, sqrt(x)), x, s)',
'MT(bessely(a, sqrt(x))**2, x, s)',
'MT(besselk(a, 2*sqrt(x)), x, s)',
'MT(besselj(a, 2*sqrt(2*sqrt(x)))*besselk(a, 2*sqrt(2*sqrt(x))), x, s)',
'MT(besseli(a, sqrt(x))*besselk(a, sqrt(x)), x, s)',
'MT(besseli(b, sqrt(x))*besselk(a, sqrt(x)), x, s)',
'MT(exp(-x/2)*besselk(a, x/2), x, s)',
# later: ILT, IMT
'LT((t-apos)**bpos*exp(-cpos*(t-apos))*Heaviside(t-apos), t, s)',
'LT(t**apos, t, s)',
'LT(Heaviside(t), t, s)',
'LT(Heaviside(t - apos), t, s)',
'LT(1 - exp(-apos*t), t, s)',
'LT((exp(2*t)-1)*exp(-bpos - t)*Heaviside(t)/2, t, s, noconds=True)',
'LT(exp(t), t, s)',
'LT(exp(2*t), t, s)',
'LT(exp(apos*t), t, s)',
'LT(log(t/apos), t, s)',
'LT(erf(t), t, s)',
'LT(sin(apos*t), t, s)',
'LT(cos(apos*t), t, s)',
'LT(exp(-apos*t)*sin(bpos*t), t, s)',
'LT(exp(-apos*t)*cos(bpos*t), t, s)',
'LT(besselj(0, t), t, s, noconds=True)',
'LT(besselj(1, t), t, s, noconds=True)',
'FT(Heaviside(1 - abs(2*apos*x)), x, k)',
'FT(Heaviside(1-abs(apos*x))*(1-abs(apos*x)), x, k)',
'FT(exp(-apos*x)*Heaviside(x), x, k)',
'IFT(1/(apos + 2*pi*I*x), x, posk, noconds=False)',
'IFT(1/(apos + 2*pi*I*x), x, -posk, noconds=False)',
'IFT(1/(apos + 2*pi*I*x), x, negk)',
'FT(x*exp(-apos*x)*Heaviside(x), x, k)',
'FT(exp(-apos*x)*sin(bpos*x)*Heaviside(x), x, k)',
'FT(exp(-apos*x**2), x, k)',
'IFT(sqrt(pi/apos)*exp(-(pi*k)**2/apos), k, x)',
'FT(exp(-apos*abs(x)), x, k)',
'integrate(normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)',
'integrate(x*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)',
'integrate(x**2*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)',
'integrate(x**3*normal(x, mu1, sigma1), (x, -oo, oo), meijerg=True)',
'integrate(normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
'integrate(x*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
'integrate(y*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
'integrate(x*y*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
'integrate((x+y+1)*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
'integrate((x+y-1)*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
'integrate(x**2*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
'integrate(y**2*normal(x, mu1, sigma1)*normal(y, mu2, sigma2),'
' (x, -oo, oo), (y, -oo, oo), meijerg=True)',
'integrate(exponential(x, rate), (x, 0, oo), meijerg=True)',
'integrate(x*exponential(x, rate), (x, 0, oo), meijerg=True)',
'integrate(x**2*exponential(x, rate), (x, 0, oo), meijerg=True)',
'E(1)',
'E(x*y)',
'E(x*y**2)',
'E((x+y+1)**2)',
'E(x+y+1)',
'E((x+y-1)**2)',
'integrate(betadist, (x, 0, oo), meijerg=True)',
'integrate(x*betadist, (x, 0, oo), meijerg=True)',
'integrate(x**2*betadist, (x, 0, oo), meijerg=True)',
'integrate(chi, (x, 0, oo), meijerg=True)',
'integrate(x*chi, (x, 0, oo), meijerg=True)',
'integrate(x**2*chi, (x, 0, oo), meijerg=True)',
'integrate(chisquared, (x, 0, oo), meijerg=True)',
'integrate(x*chisquared, (x, 0, oo), meijerg=True)',
'integrate(x**2*chisquared, (x, 0, oo), meijerg=True)',
'integrate(((x-k)/sqrt(2*k))**3*chisquared, (x, 0, oo), meijerg=True)',
'integrate(dagum, (x, 0, oo), meijerg=True)',
'integrate(x*dagum, (x, 0, oo), meijerg=True)',
'integrate(x**2*dagum, (x, 0, oo), meijerg=True)',
'integrate(f, (x, 0, oo), meijerg=True)',
'integrate(x*f, (x, 0, oo), meijerg=True)',
'integrate(x**2*f, (x, 0, oo), meijerg=True)',
'integrate(rice, (x, 0, oo), meijerg=True)',
'integrate(laplace, (x, -oo, oo), meijerg=True)',
'integrate(x*laplace, (x, -oo, oo), meijerg=True)',
'integrate(x**2*laplace, (x, -oo, oo), meijerg=True)',
'integrate(log(x) * x**(k-1) * exp(-x) / gamma(k), (x, 0, oo))',
'integrate(sin(z*x)*(x**2-1)**(-(y+S(1)/2)), (x, 1, oo), meijerg=True)',
'integrate(besselj(0,x)*besselj(1,x)*exp(-x**2), (x, 0, oo), meijerg=True)',
'integrate(besselj(0,x)*besselj(1,x)*besselk(0,x), (x, 0, oo), meijerg=True)',
'integrate(besselj(0,x)*besselj(1,x)*exp(-x**2), (x, 0, oo), meijerg=True)',
'integrate(besselj(a,x)*besselj(b,x)/x, (x,0,oo), meijerg=True)',
'hyperexpand(meijerg((-s - a/2 + 1, -s + a/2 + 1), (-a/2 - S(1)/2, -s + a/2 + S(3)/2), (a/2, -a/2), (-a/2 - S(1)/2, -s + a/2 + S(3)/2), 1))',
"gammasimp(S('2**(2*s)*(-pi*gamma(-a + 1)*gamma(a + 1)*gamma(-a - s + 1)*gamma(-a + s - 1/2)*gamma(a - s + 3/2)*gamma(a + s + 1)/(a*(a + s)) - gamma(-a - 1/2)*gamma(-a + 1)*gamma(a + 1)*gamma(a + 3/2)*gamma(-s + 3/2)*gamma(s - 1/2)*gamma(-a + s + 1)*gamma(a - s + 1)/(a*(-a + s)))*gamma(-2*s + 1)*gamma(s + 1)/(pi*s*gamma(-a - 1/2)*gamma(a + 3/2)*gamma(-s + 1)*gamma(-s + 3/2)*gamma(s - 1/2)*gamma(-a - s + 1)*gamma(-a + s - 1/2)*gamma(a - s + 1)*gamma(a - s + 3/2))'))",
'mellin_transform(E1(x), x, s)',
'inverse_mellin_transform(gamma(s)/s, s, x, (0, oo))',
'mellin_transform(expint(a, x), x, s)',
'mellin_transform(Si(x), x, s)',
'inverse_mellin_transform(-2**s*sqrt(pi)*gamma((s + 1)/2)/(2*s*gamma(-s/2 + 1)), s, x, (-1, 0))',
'mellin_transform(Ci(sqrt(x)), x, s)',
'inverse_mellin_transform(-4**s*sqrt(pi)*gamma(s)/(2*s*gamma(-s + S(1)/2)),s, u, (0, 1))',
'laplace_transform(Ci(x), x, s)',
'laplace_transform(expint(a, x), x, s)',
'laplace_transform(expint(1, x), x, s)',
'laplace_transform(expint(2, x), x, s)',
'inverse_laplace_transform(-log(1 + s**2)/2/s, s, u)',
'inverse_laplace_transform(log(s + 1)/s, s, x)',
'inverse_laplace_transform((s - log(s + 1))/s**2, s, x)',
'laplace_transform(Chi(x), x, s)',
'laplace_transform(Shi(x), x, s)',
'integrate(exp(-z*x)/x, (x, 1, oo), meijerg=True, conds="none")',
'integrate(exp(-z*x)/x**2, (x, 1, oo), meijerg=True, conds="none")',
'integrate(exp(-z*x)/x**3, (x, 1, oo), meijerg=True,conds="none")',
'integrate(-cos(x)/x, (x, tpos, oo), meijerg=True)',
'integrate(-sin(x)/x, (x, tpos, oo), meijerg=True)',
'integrate(sin(x)/x, (x, 0, z), meijerg=True)',
'integrate(sinh(x)/x, (x, 0, z), meijerg=True)',
'integrate(exp(-x)/x, x, meijerg=True)',
'integrate(exp(-x)/x**2, x, meijerg=True)',
'integrate(cos(u)/u, u, meijerg=True)',
'integrate(cosh(u)/u, u, meijerg=True)',
'integrate(expint(1, x), x, meijerg=True)',
'integrate(expint(2, x), x, meijerg=True)',
'integrate(Si(x), x, meijerg=True)',
'integrate(Ci(u), u, meijerg=True)',
'integrate(Shi(x), x, meijerg=True)',
'integrate(Chi(u), u, meijerg=True)',
'integrate(Si(x)*exp(-x), (x, 0, oo), meijerg=True)',
'integrate(expint(1, x)*sin(x), (x, 0, oo), meijerg=True)'
]
from time import time
from sympy.core.cache import clear_cache
import sys
timings = []
if __name__ == '__main__':
for n, string in enumerate(bench):
clear_cache()
_t = time()
exec(string)
_t = time() - _t
timings += [(_t, string)]
sys.stdout.write('.')
sys.stdout.flush()
if n % (len(bench) // 10) == 0:
sys.stdout.write('%s' % (10*n // len(bench)))
print()
timings.sort(key=lambda x: -x[0])
for ti, string in timings:
print('%.2fs %s' % (ti, string))
@@ -0,0 +1,134 @@
#!/usr/bin/env python
from sympy.core.random import random
from sympy.core.numbers import (I, Integer, pi)
from sympy.core.symbol import Symbol
from sympy.core.sympify import sympify
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.elementary.trigonometric import sin
from sympy.polys.polytools import factor
from sympy.simplify.simplify import simplify
from sympy.abc import x, y, z
from timeit import default_timer as clock
def bench_R1():
"real(f(f(f(f(f(f(f(f(f(f(i/2)))))))))))"
def f(z):
return sqrt(Integer(1)/3)*z**2 + I/3
f(f(f(f(f(f(f(f(f(f(I/2)))))))))).as_real_imag()[0]
def bench_R2():
"Hermite polynomial hermite(15, y)"
def hermite(n, y):
if n == 1:
return 2*y
if n == 0:
return 1
return (2*y*hermite(n - 1, y) - 2*(n - 1)*hermite(n - 2, y)).expand()
hermite(15, y)
def bench_R3():
"a = [bool(f==f) for _ in range(10)]"
f = x + y + z
[bool(f == f) for _ in range(10)]
def bench_R4():
# we don't have Tuples
pass
def bench_R5():
"blowup(L, 8); L=uniq(L)"
def blowup(L, n):
for i in range(n):
L.append( (L[i] + L[i + 1]) * L[i + 2] )
def uniq(x):
v = set(x)
return v
L = [x, y, z]
blowup(L, 8)
L = uniq(L)
def bench_R6():
"sum(simplify((x+sin(i))/x+(x-sin(i))/x) for i in range(100))"
sum(simplify((x + sin(i))/x + (x - sin(i))/x) for i in range(100))
def bench_R7():
"[f.subs(x, random()) for _ in range(10**4)]"
f = x**24 + 34*x**12 + 45*x**3 + 9*x**18 + 34*x**10 + 32*x**21
[f.subs(x, random()) for _ in range(10**4)]
def bench_R8():
"right(x^2,0,5,10^4)"
def right(f, a, b, n):
a = sympify(a)
b = sympify(b)
n = sympify(n)
x = f.atoms(Symbol).pop()
Deltax = (b - a)/n
c = a
est = 0
for i in range(n):
c += Deltax
est += f.subs(x, c)
return est*Deltax
right(x**2, 0, 5, 10**4)
def _bench_R9():
"factor(x^20 - pi^5*y^20)"
factor(x**20 - pi**5*y**20)
def bench_R10():
"v = [-pi,-pi+1/10..,pi]"
def srange(min, max, step):
v = [min]
while (max - v[-1]).evalf() > 0:
v.append(v[-1] + step)
return v[:-1]
srange(-pi, pi, sympify(1)/10)
def bench_R11():
"a = [random() + random()*I for w in [0..1000]]"
[random() + random()*I for w in range(1000)]
def bench_S1():
"e=(x+y+z+1)**7;f=e*(e+1);f.expand()"
e = (x + y + z + 1)**7
f = e*(e + 1)
f.expand()
if __name__ == '__main__':
benchmarks = [
bench_R1,
bench_R2,
bench_R3,
bench_R5,
bench_R6,
bench_R7,
bench_R8,
#_bench_R9,
bench_R10,
bench_R11,
#bench_S1,
]
report = []
for b in benchmarks:
t = clock()
b()
t = clock() - t
print("%s%65s: %f" % (b.__name__, b.__doc__, t))
@@ -0,0 +1,25 @@
"""Calculus-related methods."""
from .euler import euler_equations
from .singularities import (singularities, is_increasing,
is_strictly_increasing, is_decreasing,
is_strictly_decreasing, is_monotonic)
from .finite_diff import finite_diff_weights, apply_finite_diff, differentiate_finite
from .util import (periodicity, not_empty_in, is_convex,
stationary_points, minimum, maximum)
from .accumulationbounds import AccumBounds
__all__ = [
'euler_equations',
'singularities', 'is_increasing',
'is_strictly_increasing', 'is_decreasing',
'is_strictly_decreasing', 'is_monotonic',
'finite_diff_weights', 'apply_finite_diff', 'differentiate_finite',
'periodicity', 'not_empty_in', 'is_convex', 'stationary_points',
'minimum', 'maximum',
'AccumBounds'
]
@@ -0,0 +1,804 @@
from sympy.core import Add, Mul, Pow, S
from sympy.core.basic import Basic
from sympy.core.expr import Expr
from sympy.core.numbers import _sympifyit, oo, zoo
from sympy.core.relational import is_le, is_lt, is_ge, is_gt
from sympy.core.sympify import _sympify
from sympy.functions.elementary.miscellaneous import Min, Max
from sympy.logic.boolalg import And
from sympy.multipledispatch import dispatch
from sympy.series.order import Order
from sympy.sets.sets import FiniteSet
class AccumulationBounds(Expr):
r"""An accumulation bounds.
# Note AccumulationBounds has an alias: AccumBounds
AccumulationBounds represent an interval `[a, b]`, which is always closed
at the ends. Here `a` and `b` can be any value from extended real numbers.
The intended meaning of AccummulationBounds is to give an approximate
location of the accumulation points of a real function at a limit point.
Let `a` and `b` be reals such that `a \le b`.
`\left\langle a, b\right\rangle = \{x \in \mathbb{R} \mid a \le x \le b\}`
`\left\langle -\infty, b\right\rangle = \{x \in \mathbb{R} \mid x \le b\} \cup \{-\infty, \infty\}`
`\left\langle a, \infty \right\rangle = \{x \in \mathbb{R} \mid a \le x\} \cup \{-\infty, \infty\}`
`\left\langle -\infty, \infty \right\rangle = \mathbb{R} \cup \{-\infty, \infty\}`
``oo`` and ``-oo`` are added to the second and third definition respectively,
since if either ``-oo`` or ``oo`` is an argument, then the other one should
be included (though not as an end point). This is forced, since we have,
for example, ``1/AccumBounds(0, 1) = AccumBounds(1, oo)``, and the limit at
`0` is not one-sided. As `x` tends to `0-`, then `1/x \rightarrow -\infty`, so `-\infty`
should be interpreted as belonging to ``AccumBounds(1, oo)`` though it need
not appear explicitly.
In many cases it suffices to know that the limit set is bounded.
However, in some other cases more exact information could be useful.
For example, all accumulation values of `\cos(x) + 1` are non-negative.
(``AccumBounds(-1, 1) + 1 = AccumBounds(0, 2)``)
A AccumulationBounds object is defined to be real AccumulationBounds,
if its end points are finite reals.
Let `X`, `Y` be real AccumulationBounds, then their sum, difference,
product are defined to be the following sets:
`X + Y = \{ x+y \mid x \in X \cap y \in Y\}`
`X - Y = \{ x-y \mid x \in X \cap y \in Y\}`
`X \times Y = \{ x \times y \mid x \in X \cap y \in Y\}`
When an AccumBounds is raised to a negative power, if 0 is contained
between the bounds then an infinite range is returned, otherwise if an
endpoint is 0 then a semi-infinite range with consistent sign will be returned.
AccumBounds in expressions behave a lot like Intervals but the
semantics are not necessarily the same. Division (or exponentiation
to a negative integer power) could be handled with *intervals* by
returning a union of the results obtained after splitting the
bounds between negatives and positives, but that is not done with
AccumBounds. In addition, bounds are assumed to be independent of
each other; if the same bound is used in more than one place in an
expression, the result may not be the supremum or infimum of the
expression (see below). Finally, when a boundary is ``1``,
exponentiation to the power of ``oo`` yields ``oo``, neither
``1`` nor ``nan``.
Examples
========
>>> from sympy import AccumBounds, sin, exp, log, pi, E, S, oo
>>> from sympy.abc import x
>>> AccumBounds(0, 1) + AccumBounds(1, 2)
AccumBounds(1, 3)
>>> AccumBounds(0, 1) - AccumBounds(0, 2)
AccumBounds(-2, 1)
>>> AccumBounds(-2, 3)*AccumBounds(-1, 1)
AccumBounds(-3, 3)
>>> AccumBounds(1, 2)*AccumBounds(3, 5)
AccumBounds(3, 10)
The exponentiation of AccumulationBounds is defined
as follows:
If 0 does not belong to `X` or `n > 0` then
`X^n = \{ x^n \mid x \in X\}`
>>> AccumBounds(1, 4)**(S(1)/2)
AccumBounds(1, 2)
otherwise, an infinite or semi-infinite result is obtained:
>>> 1/AccumBounds(-1, 1)
AccumBounds(-oo, oo)
>>> 1/AccumBounds(0, 2)
AccumBounds(1/2, oo)
>>> 1/AccumBounds(-oo, 0)
AccumBounds(-oo, 0)
A boundary of 1 will always generate all nonnegatives:
>>> AccumBounds(1, 2)**oo
AccumBounds(0, oo)
>>> AccumBounds(0, 1)**oo
AccumBounds(0, oo)
If the exponent is itself an AccumulationBounds or is not an
integer then unevaluated results will be returned unless the base
values are positive:
>>> AccumBounds(2, 3)**AccumBounds(-1, 2)
AccumBounds(1/3, 9)
>>> AccumBounds(-2, 3)**AccumBounds(-1, 2)
AccumBounds(-2, 3)**AccumBounds(-1, 2)
>>> AccumBounds(-2, -1)**(S(1)/2)
sqrt(AccumBounds(-2, -1))
Note: `\left\langle a, b\right\rangle^2` is not same as `\left\langle a, b\right\rangle \times \left\langle a, b\right\rangle`
>>> AccumBounds(-1, 1)**2
AccumBounds(0, 1)
>>> AccumBounds(1, 3) < 4
True
>>> AccumBounds(1, 3) < -1
False
Some elementary functions can also take AccumulationBounds as input.
A function `f` evaluated for some real AccumulationBounds `\left\langle a, b \right\rangle`
is defined as `f(\left\langle a, b\right\rangle) = \{ f(x) \mid a \le x \le b \}`
>>> sin(AccumBounds(pi/6, pi/3))
AccumBounds(1/2, sqrt(3)/2)
>>> exp(AccumBounds(0, 1))
AccumBounds(1, E)
>>> log(AccumBounds(1, E))
AccumBounds(0, 1)
Some symbol in an expression can be substituted for a AccumulationBounds
object. But it does not necessarily evaluate the AccumulationBounds for
that expression.
The same expression can be evaluated to different values depending upon
the form it is used for substitution since each instance of an
AccumulationBounds is considered independent. For example:
>>> (x**2 + 2*x + 1).subs(x, AccumBounds(-1, 1))
AccumBounds(-1, 4)
>>> ((x + 1)**2).subs(x, AccumBounds(-1, 1))
AccumBounds(0, 4)
References
==========
.. [1] https://en.wikipedia.org/wiki/Interval_arithmetic
.. [2] https://fab.cba.mit.edu/classes/S62.12/docs/Hickey_interval.pdf
Notes
=====
Do not use ``AccumulationBounds`` for floating point interval arithmetic
calculations, use ``mpmath.iv`` instead.
"""
is_extended_real = True
is_number = False
def __new__(cls, min, max) -> Expr: # type: ignore
min = _sympify(min)
max = _sympify(max)
# Only allow real intervals (use symbols with 'is_extended_real=True').
if not min.is_extended_real or not max.is_extended_real:
raise ValueError("Only real AccumulationBounds are supported")
if max == min:
return max
# Make sure that the created AccumBounds object will be valid.
if max.is_number and min.is_number:
bad = max.is_comparable and min.is_comparable and max < min
else:
bad = (max - min).is_extended_negative
if bad:
raise ValueError(
"Lower limit should be smaller than upper limit")
return Basic.__new__(cls, min, max)
# setting the operation priority
_op_priority = 11.0
def _eval_is_real(self):
if self.min.is_real and self.max.is_real:
return True
@property
def min(self):
"""
Returns the minimum possible value attained by AccumulationBounds
object.
Examples
========
>>> from sympy import AccumBounds
>>> AccumBounds(1, 3).min
1
"""
return self.args[0]
@property
def max(self):
"""
Returns the maximum possible value attained by AccumulationBounds
object.
Examples
========
>>> from sympy import AccumBounds
>>> AccumBounds(1, 3).max
3
"""
return self.args[1]
@property
def delta(self):
"""
Returns the difference of maximum possible value attained by
AccumulationBounds object and minimum possible value attained
by AccumulationBounds object.
Examples
========
>>> from sympy import AccumBounds
>>> AccumBounds(1, 3).delta
2
"""
return self.max - self.min
@property
def mid(self):
"""
Returns the mean of maximum possible value attained by
AccumulationBounds object and minimum possible value
attained by AccumulationBounds object.
Examples
========
>>> from sympy import AccumBounds
>>> AccumBounds(1, 3).mid
2
"""
return (self.min + self.max) / 2
@_sympifyit('other', NotImplemented)
def _eval_power(self, other):
return self.__pow__(other)
@_sympifyit('other', NotImplemented)
def __add__(self, other):
if isinstance(other, Expr):
if isinstance(other, AccumBounds):
return AccumBounds(
Add(self.min, other.min),
Add(self.max, other.max))
if other is S.Infinity and self.min is S.NegativeInfinity or \
other is S.NegativeInfinity and self.max is S.Infinity:
return AccumBounds(-oo, oo)
elif other.is_extended_real:
if self.min is S.NegativeInfinity and self.max is S.Infinity:
return AccumBounds(-oo, oo)
elif self.min is S.NegativeInfinity:
return AccumBounds(-oo, self.max + other)
elif self.max is S.Infinity:
return AccumBounds(self.min + other, oo)
else:
return AccumBounds(Add(self.min, other), Add(self.max, other))
return Add(self, other, evaluate=False)
return NotImplemented
__radd__ = __add__
def __neg__(self):
return AccumBounds(-self.max, -self.min)
@_sympifyit('other', NotImplemented)
def __sub__(self, other):
if isinstance(other, Expr):
if isinstance(other, AccumBounds):
return AccumBounds(
Add(self.min, -other.max),
Add(self.max, -other.min))
if other is S.NegativeInfinity and self.min is S.NegativeInfinity or \
other is S.Infinity and self.max is S.Infinity:
return AccumBounds(-oo, oo)
elif other.is_extended_real:
if self.min is S.NegativeInfinity and self.max is S.Infinity:
return AccumBounds(-oo, oo)
elif self.min is S.NegativeInfinity:
return AccumBounds(-oo, self.max - other)
elif self.max is S.Infinity:
return AccumBounds(self.min - other, oo)
else:
return AccumBounds(
Add(self.min, -other),
Add(self.max, -other))
return Add(self, -other, evaluate=False)
return NotImplemented
@_sympifyit('other', NotImplemented)
def __rsub__(self, other):
return self.__neg__() + other
@_sympifyit('other', NotImplemented)
def __mul__(self, other):
if self.args == (-oo, oo):
return self
if isinstance(other, Expr):
if isinstance(other, AccumBounds):
if other.args == (-oo, oo):
return other
v = set()
for a in self.args:
vi = other*a
v.update(vi.args or (vi,))
return AccumBounds(Min(*v), Max(*v))
if other is S.Infinity:
if self.min.is_zero:
return AccumBounds(0, oo)
if self.max.is_zero:
return AccumBounds(-oo, 0)
if other is S.NegativeInfinity:
if self.min.is_zero:
return AccumBounds(-oo, 0)
if self.max.is_zero:
return AccumBounds(0, oo)
if other.is_extended_real:
if other.is_zero:
if self.max is S.Infinity:
return AccumBounds(0, oo)
if self.min is S.NegativeInfinity:
return AccumBounds(-oo, 0)
return S.Zero
if other.is_extended_positive:
return AccumBounds(
Mul(self.min, other),
Mul(self.max, other))
elif other.is_extended_negative:
return AccumBounds(
Mul(self.max, other),
Mul(self.min, other))
if isinstance(other, Order):
return other
return Mul(self, other, evaluate=False)
return NotImplemented
__rmul__ = __mul__
@_sympifyit('other', NotImplemented)
def __truediv__(self, other):
if isinstance(other, Expr):
if isinstance(other, AccumBounds):
if other.min.is_positive or other.max.is_negative:
return self * AccumBounds(1/other.max, 1/other.min)
if (self.min.is_extended_nonpositive and self.max.is_extended_nonnegative and
other.min.is_extended_nonpositive and other.max.is_extended_nonnegative):
if self.min.is_zero and other.min.is_zero:
return AccumBounds(0, oo)
if self.max.is_zero and other.min.is_zero:
return AccumBounds(-oo, 0)
return AccumBounds(-oo, oo)
if self.max.is_extended_negative:
if other.min.is_extended_negative:
if other.max.is_zero:
return AccumBounds(self.max / other.min, oo)
if other.max.is_extended_positive:
# if we were dealing with intervals we would return
# Union(Interval(-oo, self.max/other.max),
# Interval(self.max/other.min, oo))
return AccumBounds(-oo, oo)
if other.min.is_zero and other.max.is_extended_positive:
return AccumBounds(-oo, self.max / other.max)
if self.min.is_extended_positive:
if other.min.is_extended_negative:
if other.max.is_zero:
return AccumBounds(-oo, self.min / other.min)
if other.max.is_extended_positive:
# if we were dealing with intervals we would return
# Union(Interval(-oo, self.min/other.min),
# Interval(self.min/other.max, oo))
return AccumBounds(-oo, oo)
if other.min.is_zero and other.max.is_extended_positive:
return AccumBounds(self.min / other.max, oo)
elif other.is_extended_real:
if other in (S.Infinity, S.NegativeInfinity):
if self == AccumBounds(-oo, oo):
return AccumBounds(-oo, oo)
if self.max is S.Infinity:
return AccumBounds(Min(0, other), Max(0, other))
if self.min is S.NegativeInfinity:
return AccumBounds(Min(0, -other), Max(0, -other))
if other.is_extended_positive:
return AccumBounds(self.min / other, self.max / other)
elif other.is_extended_negative:
return AccumBounds(self.max / other, self.min / other)
if (1 / other) is S.ComplexInfinity:
return Mul(self, 1 / other, evaluate=False)
else:
return Mul(self, 1 / other)
return NotImplemented
@_sympifyit('other', NotImplemented)
def __rtruediv__(self, other):
if isinstance(other, Expr):
if other.is_extended_real:
if other.is_zero:
return S.Zero
if (self.min.is_extended_nonpositive and self.max.is_extended_nonnegative):
if self.min.is_zero:
if other.is_extended_positive:
return AccumBounds(Mul(other, 1 / self.max), oo)
if other.is_extended_negative:
return AccumBounds(-oo, Mul(other, 1 / self.max))
if self.max.is_zero:
if other.is_extended_positive:
return AccumBounds(-oo, Mul(other, 1 / self.min))
if other.is_extended_negative:
return AccumBounds(Mul(other, 1 / self.min), oo)
return AccumBounds(-oo, oo)
else:
return AccumBounds(Min(other / self.min, other / self.max),
Max(other / self.min, other / self.max))
return Mul(other, 1 / self, evaluate=False)
else:
return NotImplemented
@_sympifyit('other', NotImplemented)
def __pow__(self, other):
if isinstance(other, Expr):
if other is S.Infinity:
if self.min.is_extended_nonnegative:
if self.max < 1:
return S.Zero
if self.min > 1:
return S.Infinity
return AccumBounds(0, oo)
elif self.max.is_extended_negative:
if self.min > -1:
return S.Zero
if self.max < -1:
return zoo
return S.NaN
else:
if self.min > -1:
if self.max < 1:
return S.Zero
return AccumBounds(0, oo)
return AccumBounds(-oo, oo)
if other is S.NegativeInfinity:
return (1/self)**oo
# generically true
if (self.max - self.min).is_nonnegative:
# well defined
if self.min.is_nonnegative:
# no 0 to worry about
if other.is_nonnegative:
# no infinity to worry about
return self.func(self.min**other, self.max**other)
if other.is_zero:
return S.One # x**0 = 1
if other.is_Integer or other.is_integer:
if self.min.is_extended_positive:
return AccumBounds(
Min(self.min**other, self.max**other),
Max(self.min**other, self.max**other))
elif self.max.is_extended_negative:
return AccumBounds(
Min(self.max**other, self.min**other),
Max(self.max**other, self.min**other))
if other % 2 == 0:
if other.is_extended_negative:
if self.min.is_zero:
return AccumBounds(self.max**other, oo)
if self.max.is_zero:
return AccumBounds(self.min**other, oo)
return (1/self)**(-other)
return AccumBounds(
S.Zero, Max(self.min**other, self.max**other))
elif other % 2 == 1:
if other.is_extended_negative:
if self.min.is_zero:
return AccumBounds(self.max**other, oo)
if self.max.is_zero:
return AccumBounds(-oo, self.min**other)
return (1/self)**(-other)
return AccumBounds(self.min**other, self.max**other)
# non-integer exponent
# 0**neg or neg**frac yields complex
if (other.is_number or other.is_rational) and (
self.min.is_extended_nonnegative or (
other.is_extended_nonnegative and
self.min.is_extended_nonnegative)):
num, den = other.as_numer_denom()
if num is S.One:
return AccumBounds(*[i**(1/den) for i in self.args])
elif den is not S.One: # e.g. if other is not Float
return (self**num)**(1/den) # ok for non-negative base
if isinstance(other, AccumBounds):
if (self.min.is_extended_positive or
self.min.is_extended_nonnegative and
other.min.is_extended_nonnegative):
p = [self**i for i in other.args]
if not any(i.is_Pow for i in p):
a = [j for i in p for j in i.args or (i,)]
try:
return self.func(min(a), max(a))
except TypeError: # can't sort
pass
return Pow(self, other, evaluate=False)
return NotImplemented
@_sympifyit('other', NotImplemented)
def __rpow__(self, other):
if other.is_real and other.is_extended_nonnegative and (
self.max - self.min).is_extended_positive:
if other is S.One:
return S.One
if other.is_extended_positive:
a, b = [other**i for i in self.args]
if min(a, b) != a:
a, b = b, a
return self.func(a, b)
if other.is_zero:
if self.min.is_zero:
return self.func(0, 1)
if self.min.is_extended_positive:
return S.Zero
return Pow(other, self, evaluate=False)
def __abs__(self):
if self.max.is_extended_negative:
return self.__neg__()
elif self.min.is_extended_negative:
return AccumBounds(S.Zero, Max(abs(self.min), self.max))
else:
return self
def __contains__(self, other):
"""
Returns ``True`` if other is contained in self, where other
belongs to extended real numbers, ``False`` if not contained,
otherwise TypeError is raised.
Examples
========
>>> from sympy import AccumBounds, oo
>>> 1 in AccumBounds(-1, 3)
True
-oo and oo go together as limits (in AccumulationBounds).
>>> -oo in AccumBounds(1, oo)
True
>>> oo in AccumBounds(-oo, 0)
True
"""
other = _sympify(other)
if other in (S.Infinity, S.NegativeInfinity):
if self.min is S.NegativeInfinity or self.max is S.Infinity:
return True
return False
rv = And(self.min <= other, self.max >= other)
if rv not in (True, False):
raise TypeError("input failed to evaluate")
return rv
def intersection(self, other):
"""
Returns the intersection of 'self' and 'other'.
Here other can be an instance of :py:class:`~.FiniteSet` or AccumulationBounds.
Parameters
==========
other : AccumulationBounds
Another AccumulationBounds object with which the intersection
has to be computed.
Returns
=======
AccumulationBounds
Intersection of ``self`` and ``other``.
Examples
========
>>> from sympy import AccumBounds, FiniteSet
>>> AccumBounds(1, 3).intersection(AccumBounds(2, 4))
AccumBounds(2, 3)
>>> AccumBounds(1, 3).intersection(AccumBounds(4, 6))
EmptySet
>>> AccumBounds(1, 4).intersection(FiniteSet(1, 2, 5))
{1, 2}
"""
if not isinstance(other, (AccumBounds, FiniteSet)):
raise TypeError(
"Input must be AccumulationBounds or FiniteSet object")
if isinstance(other, FiniteSet):
fin_set = S.EmptySet
for i in other:
if i in self:
fin_set = fin_set + FiniteSet(i)
return fin_set
if self.max < other.min or self.min > other.max:
return S.EmptySet
if self.min <= other.min:
if self.max <= other.max:
return AccumBounds(other.min, self.max)
if self.max > other.max:
return other
if other.min <= self.min:
if other.max < self.max:
return AccumBounds(self.min, other.max)
if other.max > self.max:
return self
def union(self, other):
# TODO : Devise a better method for Union of AccumBounds
# this method is not actually correct and
# can be made better
if not isinstance(other, AccumBounds):
raise TypeError(
"Input must be AccumulationBounds or FiniteSet object")
if self.min <= other.min and self.max >= other.min:
return AccumBounds(self.min, Max(self.max, other.max))
if other.min <= self.min and other.max >= self.min:
return AccumBounds(other.min, Max(self.max, other.max))
@dispatch(AccumulationBounds, AccumulationBounds) # type: ignore # noqa:F811
def _eval_is_le(lhs, rhs): # noqa:F811
if is_le(lhs.max, rhs.min):
return True
if is_gt(lhs.min, rhs.max):
return False
@dispatch(AccumulationBounds, Basic) # type: ignore # noqa:F811
def _eval_is_le(lhs, rhs): # noqa: F811
"""
Returns ``True `` if range of values attained by ``lhs`` AccumulationBounds
object is greater than the range of values attained by ``rhs``,
where ``rhs`` may be any value of type AccumulationBounds object or
extended real number value, ``False`` if ``rhs`` satisfies
the same property, else an unevaluated :py:class:`~.Relational`.
Examples
========
>>> from sympy import AccumBounds, oo
>>> AccumBounds(1, 3) > AccumBounds(4, oo)
False
>>> AccumBounds(1, 4) > AccumBounds(3, 4)
AccumBounds(1, 4) > AccumBounds(3, 4)
>>> AccumBounds(1, oo) > -1
True
"""
if not rhs.is_extended_real:
raise TypeError(
"Invalid comparison of %s %s" %
(type(rhs), rhs))
elif rhs.is_comparable:
if is_le(lhs.max, rhs):
return True
if is_gt(lhs.min, rhs):
return False
@dispatch(AccumulationBounds, AccumulationBounds)
def _eval_is_ge(lhs, rhs): # noqa:F811
if is_ge(lhs.min, rhs.max):
return True
if is_lt(lhs.max, rhs.min):
return False
@dispatch(AccumulationBounds, Expr) # type:ignore
def _eval_is_ge(lhs, rhs): # noqa: F811
"""
Returns ``True`` if range of values attained by ``lhs`` AccumulationBounds
object is less that the range of values attained by ``rhs``, where
other may be any value of type AccumulationBounds object or extended
real number value, ``False`` if ``rhs`` satisfies the same
property, else an unevaluated :py:class:`~.Relational`.
Examples
========
>>> from sympy import AccumBounds, oo
>>> AccumBounds(1, 3) >= AccumBounds(4, oo)
False
>>> AccumBounds(1, 4) >= AccumBounds(3, 4)
AccumBounds(1, 4) >= AccumBounds(3, 4)
>>> AccumBounds(1, oo) >= 1
True
"""
if not rhs.is_extended_real:
raise TypeError(
"Invalid comparison of %s %s" %
(type(rhs), rhs))
elif rhs.is_comparable:
if is_ge(lhs.min, rhs):
return True
if is_lt(lhs.max, rhs):
return False
@dispatch(Expr, AccumulationBounds) # type:ignore
def _eval_is_ge(lhs, rhs): # noqa:F811
if not lhs.is_extended_real:
raise TypeError(
"Invalid comparison of %s %s" %
(type(lhs), lhs))
elif lhs.is_comparable:
if is_le(rhs.max, lhs):
return True
if is_gt(rhs.min, lhs):
return False
@dispatch(AccumulationBounds, AccumulationBounds) # type:ignore
def _eval_is_ge(lhs, rhs): # noqa:F811
if is_ge(lhs.min, rhs.max):
return True
if is_lt(lhs.max, rhs.min):
return False
# setting an alias for AccumulationBounds
AccumBounds = AccumulationBounds
@@ -0,0 +1,108 @@
"""
This module implements a method to find
Euler-Lagrange Equations for given Lagrangian.
"""
from itertools import combinations_with_replacement
from sympy.core.function import (Derivative, Function, diff)
from sympy.core.relational import Eq
from sympy.core.singleton import S
from sympy.core.symbol import Symbol
from sympy.core.sympify import sympify
from sympy.utilities.iterables import iterable
def euler_equations(L, funcs=(), vars=()):
r"""
Find the Euler-Lagrange equations [1]_ for a given Lagrangian.
Parameters
==========
L : Expr
The Lagrangian that should be a function of the functions listed
in the second argument and their derivatives.
For example, in the case of two functions $f(x,y)$, $g(x,y)$ and
two independent variables $x$, $y$ the Lagrangian has the form:
.. math:: L\left(f(x,y),g(x,y),\frac{\partial f(x,y)}{\partial x},
\frac{\partial f(x,y)}{\partial y},
\frac{\partial g(x,y)}{\partial x},
\frac{\partial g(x,y)}{\partial y},x,y\right)
In many cases it is not necessary to provide anything, except the
Lagrangian, it will be auto-detected (and an error raised if this
cannot be done).
funcs : Function or an iterable of Functions
The functions that the Lagrangian depends on. The Euler equations
are differential equations for each of these functions.
vars : Symbol or an iterable of Symbols
The Symbols that are the independent variables of the functions.
Returns
=======
eqns : list of Eq
The list of differential equations, one for each function.
Examples
========
>>> from sympy import euler_equations, Symbol, Function
>>> x = Function('x')
>>> t = Symbol('t')
>>> L = (x(t).diff(t))**2/2 - x(t)**2/2
>>> euler_equations(L, x(t), t)
[Eq(-x(t) - Derivative(x(t), (t, 2)), 0)]
>>> u = Function('u')
>>> x = Symbol('x')
>>> L = (u(t, x).diff(t))**2/2 - (u(t, x).diff(x))**2/2
>>> euler_equations(L, u(t, x), [t, x])
[Eq(-Derivative(u(t, x), (t, 2)) + Derivative(u(t, x), (x, 2)), 0)]
References
==========
.. [1] https://en.wikipedia.org/wiki/Euler%E2%80%93Lagrange_equation
"""
funcs = tuple(funcs) if iterable(funcs) else (funcs,)
if not funcs:
funcs = tuple(L.atoms(Function))
else:
for f in funcs:
if not isinstance(f, Function):
raise TypeError('Function expected, got: %s' % f)
vars = tuple(vars) if iterable(vars) else (vars,)
if not vars:
vars = funcs[0].args
else:
vars = tuple(sympify(var) for var in vars)
if not all(isinstance(v, Symbol) for v in vars):
raise TypeError('Variables are not symbols, got %s' % vars)
for f in funcs:
if not vars == f.args:
raise ValueError("Variables %s do not match args: %s" % (vars, f))
order = max([len(d.variables) for d in L.atoms(Derivative)
if d.expr in funcs] + [0])
eqns = []
for f in funcs:
eq = diff(L, f)
for i in range(1, order + 1):
for p in combinations_with_replacement(vars, i):
eq = eq + S.NegativeOne**i*diff(L, diff(f, *p), *p)
new_eq = Eq(eq, 0)
if isinstance(new_eq, Eq):
eqns.append(new_eq)
return eqns
@@ -0,0 +1,476 @@
"""
Finite difference weights
=========================
This module implements an algorithm for efficient generation of finite
difference weights for ordinary differentials of functions for
derivatives from 0 (interpolation) up to arbitrary order.
The core algorithm is provided in the finite difference weight generating
function (``finite_diff_weights``), and two convenience functions are provided
for:
- estimating a derivative (or interpolate) directly from a series of points
is also provided (``apply_finite_diff``).
- differentiating by using finite difference approximations
(``differentiate_finite``).
"""
from sympy.core.function import Derivative
from sympy.core.singleton import S
from sympy.core.function import Subs
from sympy.core.traversal import preorder_traversal
from sympy.utilities.exceptions import sympy_deprecation_warning
from sympy.utilities.iterables import iterable
def finite_diff_weights(order, x_list, x0=S.One):
"""
Calculates the finite difference weights for an arbitrarily spaced
one-dimensional grid (``x_list``) for derivatives at ``x0`` of order
0, 1, ..., up to ``order`` using a recursive formula. Order of accuracy
is at least ``len(x_list) - order``, if ``x_list`` is defined correctly.
Parameters
==========
order: int
Up to what derivative order weights should be calculated.
0 corresponds to interpolation.
x_list: sequence
Sequence of (unique) values for the independent variable.
It is useful (but not necessary) to order ``x_list`` from
nearest to furthest from ``x0``; see examples below.
x0: Number or Symbol
Root or value of the independent variable for which the finite
difference weights should be generated. Default is ``S.One``.
Returns
=======
list
A list of sublists, each corresponding to coefficients for
increasing derivative order, and each containing lists of
coefficients for increasing subsets of x_list.
Examples
========
>>> from sympy import finite_diff_weights, S
>>> res = finite_diff_weights(1, [-S(1)/2, S(1)/2, S(3)/2, S(5)/2], 0)
>>> res
[[[1, 0, 0, 0],
[1/2, 1/2, 0, 0],
[3/8, 3/4, -1/8, 0],
[5/16, 15/16, -5/16, 1/16]],
[[0, 0, 0, 0],
[-1, 1, 0, 0],
[-1, 1, 0, 0],
[-23/24, 7/8, 1/8, -1/24]]]
>>> res[0][-1] # FD weights for 0th derivative, using full x_list
[5/16, 15/16, -5/16, 1/16]
>>> res[1][-1] # FD weights for 1st derivative
[-23/24, 7/8, 1/8, -1/24]
>>> res[1][-2] # FD weights for 1st derivative, using x_list[:-1]
[-1, 1, 0, 0]
>>> res[1][-1][0] # FD weight for 1st deriv. for x_list[0]
-23/24
>>> res[1][-1][1] # FD weight for 1st deriv. for x_list[1], etc.
7/8
Each sublist contains the most accurate formula at the end.
Note, that in the above example ``res[1][1]`` is the same as ``res[1][2]``.
Since res[1][2] has an order of accuracy of
``len(x_list[:3]) - order = 3 - 1 = 2``, the same is true for ``res[1][1]``!
>>> res = finite_diff_weights(1, [S(0), S(1), -S(1), S(2), -S(2)], 0)[1]
>>> res
[[0, 0, 0, 0, 0],
[-1, 1, 0, 0, 0],
[0, 1/2, -1/2, 0, 0],
[-1/2, 1, -1/3, -1/6, 0],
[0, 2/3, -2/3, -1/12, 1/12]]
>>> res[0] # no approximation possible, using x_list[0] only
[0, 0, 0, 0, 0]
>>> res[1] # classic forward step approximation
[-1, 1, 0, 0, 0]
>>> res[2] # classic centered approximation
[0, 1/2, -1/2, 0, 0]
>>> res[3:] # higher order approximations
[[-1/2, 1, -1/3, -1/6, 0], [0, 2/3, -2/3, -1/12, 1/12]]
Let us compare this to a differently defined ``x_list``. Pay attention to
``foo[i][k]`` corresponding to the gridpoint defined by ``x_list[k]``.
>>> foo = finite_diff_weights(1, [-S(2), -S(1), S(0), S(1), S(2)], 0)[1]
>>> foo
[[0, 0, 0, 0, 0],
[-1, 1, 0, 0, 0],
[1/2, -2, 3/2, 0, 0],
[1/6, -1, 1/2, 1/3, 0],
[1/12, -2/3, 0, 2/3, -1/12]]
>>> foo[1] # not the same and of lower accuracy as res[1]!
[-1, 1, 0, 0, 0]
>>> foo[2] # classic double backward step approximation
[1/2, -2, 3/2, 0, 0]
>>> foo[4] # the same as res[4]
[1/12, -2/3, 0, 2/3, -1/12]
Note that, unless you plan on using approximations based on subsets of
``x_list``, the order of gridpoints does not matter.
The capability to generate weights at arbitrary points can be
used e.g. to minimize Runge's phenomenon by using Chebyshev nodes:
>>> from sympy import cos, symbols, pi, simplify
>>> N, (h, x) = 4, symbols('h x')
>>> x_list = [x+h*cos(i*pi/(N)) for i in range(N,-1,-1)] # chebyshev nodes
>>> print(x_list)
[-h + x, -sqrt(2)*h/2 + x, x, sqrt(2)*h/2 + x, h + x]
>>> mycoeffs = finite_diff_weights(1, x_list, 0)[1][4]
>>> [simplify(c) for c in mycoeffs] #doctest: +NORMALIZE_WHITESPACE
[(h**3/2 + h**2*x - 3*h*x**2 - 4*x**3)/h**4,
(-sqrt(2)*h**3 - 4*h**2*x + 3*sqrt(2)*h*x**2 + 8*x**3)/h**4,
(6*h**2*x - 8*x**3)/h**4,
(sqrt(2)*h**3 - 4*h**2*x - 3*sqrt(2)*h*x**2 + 8*x**3)/h**4,
(-h**3/2 + h**2*x + 3*h*x**2 - 4*x**3)/h**4]
Notes
=====
If weights for a finite difference approximation of 3rd order
derivative is wanted, weights for 0th, 1st and 2nd order are
calculated "for free", so are formulae using subsets of ``x_list``.
This is something one can take advantage of to save computational cost.
Be aware that one should define ``x_list`` from nearest to furthest from
``x0``. If not, subsets of ``x_list`` will yield poorer approximations,
which might not grand an order of accuracy of ``len(x_list) - order``.
See also
========
sympy.calculus.finite_diff.apply_finite_diff
References
==========
.. [1] Generation of Finite Difference Formulas on Arbitrarily Spaced
Grids, Bengt Fornberg; Mathematics of computation; 51; 184;
(1988); 699-706; doi:10.1090/S0025-5718-1988-0935077-0
"""
# The notation below closely corresponds to the one used in the paper.
order = S(order)
if not order.is_number:
raise ValueError("Cannot handle symbolic order.")
if order < 0:
raise ValueError("Negative derivative order illegal.")
if int(order) != order:
raise ValueError("Non-integer order illegal")
M = order
N = len(x_list) - 1
delta = [[[0 for nu in range(N+1)] for n in range(N+1)] for
m in range(M+1)]
delta[0][0][0] = S.One
c1 = S.One
for n in range(1, N+1):
c2 = S.One
for nu in range(n):
c3 = x_list[n] - x_list[nu]
c2 = c2 * c3
if n <= M:
delta[n][n-1][nu] = 0
for m in range(min(n, M)+1):
delta[m][n][nu] = (x_list[n]-x0)*delta[m][n-1][nu] -\
m*delta[m-1][n-1][nu]
delta[m][n][nu] /= c3
for m in range(min(n, M)+1):
delta[m][n][n] = c1/c2*(m*delta[m-1][n-1][n-1] -
(x_list[n-1]-x0)*delta[m][n-1][n-1])
c1 = c2
return delta
def apply_finite_diff(order, x_list, y_list, x0=S.Zero):
"""
Calculates the finite difference approximation of
the derivative of requested order at ``x0`` from points
provided in ``x_list`` and ``y_list``.
Parameters
==========
order: int
order of derivative to approximate. 0 corresponds to interpolation.
x_list: sequence
Sequence of (unique) values for the independent variable.
y_list: sequence
The function value at corresponding values for the independent
variable in x_list.
x0: Number or Symbol
At what value of the independent variable the derivative should be
evaluated. Defaults to 0.
Returns
=======
sympy.core.add.Add or sympy.core.numbers.Number
The finite difference expression approximating the requested
derivative order at ``x0``.
Examples
========
>>> from sympy import apply_finite_diff
>>> cube = lambda arg: (1.0*arg)**3
>>> xlist = range(-3,3+1)
>>> apply_finite_diff(2, xlist, map(cube, xlist), 2) - 12 # doctest: +SKIP
-3.55271367880050e-15
we see that the example above only contain rounding errors.
apply_finite_diff can also be used on more abstract objects:
>>> from sympy import IndexedBase, Idx
>>> x, y = map(IndexedBase, 'xy')
>>> i = Idx('i')
>>> x_list, y_list = zip(*[(x[i+j], y[i+j]) for j in range(-1,2)])
>>> apply_finite_diff(1, x_list, y_list, x[i])
((x[i + 1] - x[i])/(-x[i - 1] + x[i]) - 1)*y[i]/(x[i + 1] - x[i]) -
(x[i + 1] - x[i])*y[i - 1]/((x[i + 1] - x[i - 1])*(-x[i - 1] + x[i])) +
(-x[i - 1] + x[i])*y[i + 1]/((x[i + 1] - x[i - 1])*(x[i + 1] - x[i]))
Notes
=====
Order = 0 corresponds to interpolation.
Only supply so many points you think makes sense
to around x0 when extracting the derivative (the function
need to be well behaved within that region). Also beware
of Runge's phenomenon.
See also
========
sympy.calculus.finite_diff.finite_diff_weights
References
==========
Fortran 90 implementation with Python interface for numerics: finitediff_
.. _finitediff: https://github.com/bjodah/finitediff
"""
# In the original paper the following holds for the notation:
# M = order
# N = len(x_list) - 1
N = len(x_list) - 1
if len(x_list) != len(y_list):
raise ValueError("x_list and y_list not equal in length.")
delta = finite_diff_weights(order, x_list, x0)
derivative = 0
for nu in range(len(x_list)):
derivative += delta[order][N][nu]*y_list[nu]
return derivative
def _as_finite_diff(derivative, points=1, x0=None, wrt=None):
"""
Returns an approximation of a derivative of a function in
the form of a finite difference formula. The expression is a
weighted sum of the function at a number of discrete values of
(one of) the independent variable(s).
Parameters
==========
derivative: a Derivative instance
points: sequence or coefficient, optional
If sequence: discrete values (length >= order+1) of the
independent variable used for generating the finite
difference weights.
If it is a coefficient, it will be used as the step-size
for generating an equidistant sequence of length order+1
centered around ``x0``. default: 1 (step-size 1)
x0: number or Symbol, optional
the value of the independent variable (``wrt``) at which the
derivative is to be approximated. Default: same as ``wrt``.
wrt: Symbol, optional
"with respect to" the variable for which the (partial)
derivative is to be approximated for. If not provided it
is required that the Derivative is ordinary. Default: ``None``.
Examples
========
>>> from sympy import symbols, Function, exp, sqrt, Symbol
>>> from sympy.calculus.finite_diff import _as_finite_diff
>>> x, h = symbols('x h')
>>> f = Function('f')
>>> _as_finite_diff(f(x).diff(x))
-f(x - 1/2) + f(x + 1/2)
The default step size and number of points are 1 and ``order + 1``
respectively. We can change the step size by passing a symbol
as a parameter:
>>> _as_finite_diff(f(x).diff(x), h)
-f(-h/2 + x)/h + f(h/2 + x)/h
We can also specify the discretized values to be used in a sequence:
>>> _as_finite_diff(f(x).diff(x), [x, x+h, x+2*h])
-3*f(x)/(2*h) + 2*f(h + x)/h - f(2*h + x)/(2*h)
The algorithm is not restricted to use equidistant spacing, nor
do we need to make the approximation around ``x0``, but we can get
an expression estimating the derivative at an offset:
>>> e, sq2 = exp(1), sqrt(2)
>>> xl = [x-h, x+h, x+e*h]
>>> _as_finite_diff(f(x).diff(x, 1), xl, x+h*sq2)
2*h*((h + sqrt(2)*h)/(2*h) - (-sqrt(2)*h + h)/(2*h))*f(E*h + x)/((-h + E*h)*(h + E*h)) +
(-(-sqrt(2)*h + h)/(2*h) - (-sqrt(2)*h + E*h)/(2*h))*f(-h + x)/(h + E*h) +
(-(h + sqrt(2)*h)/(2*h) + (-sqrt(2)*h + E*h)/(2*h))*f(h + x)/(-h + E*h)
Partial derivatives are also supported:
>>> y = Symbol('y')
>>> d2fdxdy=f(x,y).diff(x,y)
>>> _as_finite_diff(d2fdxdy, wrt=x)
-Derivative(f(x - 1/2, y), y) + Derivative(f(x + 1/2, y), y)
See also
========
sympy.calculus.finite_diff.apply_finite_diff
sympy.calculus.finite_diff.finite_diff_weights
"""
if derivative.is_Derivative:
pass
elif derivative.is_Atom:
return derivative
else:
return derivative.fromiter(
[_as_finite_diff(ar, points, x0, wrt) for ar
in derivative.args], **derivative.assumptions0)
if wrt is None:
old = None
for v in derivative.variables:
if old is v:
continue
derivative = _as_finite_diff(derivative, points, x0, v)
old = v
return derivative
order = derivative.variables.count(wrt)
if x0 is None:
x0 = wrt
if not iterable(points):
if getattr(points, 'is_Function', False) and wrt in points.args:
points = points.subs(wrt, x0)
# points is simply the step-size, let's make it a
# equidistant sequence centered around x0
if order % 2 == 0:
# even order => odd number of points, grid point included
points = [x0 + points*i for i
in range(-order//2, order//2 + 1)]
else:
# odd order => even number of points, half-way wrt grid point
points = [x0 + points*S(i)/2 for i
in range(-order, order + 1, 2)]
others = [wrt, 0]
for v in set(derivative.variables):
if v == wrt:
continue
others += [v, derivative.variables.count(v)]
if len(points) < order+1:
raise ValueError("Too few points for order %d" % order)
return apply_finite_diff(order, points, [
Derivative(derivative.expr.subs({wrt: x}), *others) for
x in points], x0)
def differentiate_finite(expr, *symbols,
points=1, x0=None, wrt=None, evaluate=False):
r""" Differentiate expr and replace Derivatives with finite differences.
Parameters
==========
expr : expression
\*symbols : differentiate with respect to symbols
points: sequence, coefficient or undefined function, optional
see ``Derivative.as_finite_difference``
x0: number or Symbol, optional
see ``Derivative.as_finite_difference``
wrt: Symbol, optional
see ``Derivative.as_finite_difference``
Examples
========
>>> from sympy import sin, Function, differentiate_finite
>>> from sympy.abc import x, y, h
>>> f, g = Function('f'), Function('g')
>>> differentiate_finite(f(x)*g(x), x, points=[x-h, x+h])
-f(-h + x)*g(-h + x)/(2*h) + f(h + x)*g(h + x)/(2*h)
``differentiate_finite`` works on any expression, including the expressions
with embedded derivatives:
>>> differentiate_finite(f(x) + sin(x), x, 2)
-2*f(x) + f(x - 1) + f(x + 1) - 2*sin(x) + sin(x - 1) + sin(x + 1)
>>> differentiate_finite(f(x, y), x, y)
f(x - 1/2, y - 1/2) - f(x - 1/2, y + 1/2) - f(x + 1/2, y - 1/2) + f(x + 1/2, y + 1/2)
>>> differentiate_finite(f(x)*g(x).diff(x), x)
(-g(x) + g(x + 1))*f(x + 1/2) - (g(x) - g(x - 1))*f(x - 1/2)
To make finite difference with non-constant discretization step use
undefined functions:
>>> dx = Function('dx')
>>> differentiate_finite(f(x)*g(x).diff(x), points=dx(x))
-(-g(x - dx(x)/2 - dx(x - dx(x)/2)/2)/dx(x - dx(x)/2) +
g(x - dx(x)/2 + dx(x - dx(x)/2)/2)/dx(x - dx(x)/2))*f(x - dx(x)/2)/dx(x) +
(-g(x + dx(x)/2 - dx(x + dx(x)/2)/2)/dx(x + dx(x)/2) +
g(x + dx(x)/2 + dx(x + dx(x)/2)/2)/dx(x + dx(x)/2))*f(x + dx(x)/2)/dx(x)
"""
if any(term.is_Derivative for term in list(preorder_traversal(expr))):
evaluate = False
Dexpr = expr.diff(*symbols, evaluate=evaluate)
if evaluate:
sympy_deprecation_warning("""
The evaluate flag to differentiate_finite() is deprecated.
evaluate=True expands the intermediate derivatives before computing
differences, but this usually not what you want, as it does not
satisfy the product rule.
""",
deprecated_since_version="1.5",
active_deprecations_target="deprecated-differentiate_finite-evaluate",
)
return Dexpr.replace(
lambda arg: arg.is_Derivative,
lambda arg: arg.as_finite_difference(points=points, x0=x0, wrt=wrt))
else:
DFexpr = Dexpr.as_finite_difference(points=points, x0=x0, wrt=wrt)
return DFexpr.replace(
lambda arg: isinstance(arg, Subs),
lambda arg: arg.expr.as_finite_difference(
points=points, x0=arg.point[0], wrt=arg.variables[0]))
@@ -0,0 +1,406 @@
"""
Singularities
=============
This module implements algorithms for finding singularities for a function
and identifying types of functions.
The differential calculus methods in this module include methods to identify
the following function types in the given ``Interval``:
- Increasing
- Strictly Increasing
- Decreasing
- Strictly Decreasing
- Monotonic
"""
from sympy.core.power import Pow
from sympy.core.singleton import S
from sympy.core.symbol import Symbol
from sympy.core.sympify import sympify
from sympy.functions.elementary.exponential import log
from sympy.functions.elementary.trigonometric import sec, csc, cot, tan, cos
from sympy.functions.elementary.hyperbolic import (
sech, csch, coth, tanh, cosh, asech, acsch, atanh, acoth)
from sympy.utilities.misc import filldedent
def singularities(expression, symbol, domain=None):
"""
Find singularities of a given function.
Parameters
==========
expression : Expr
The target function in which singularities need to be found.
symbol : Symbol
The symbol over the values of which the singularity in
expression in being searched for.
Returns
=======
Set
A set of values for ``symbol`` for which ``expression`` has a
singularity. An ``EmptySet`` is returned if ``expression`` has no
singularities for any given value of ``Symbol``.
Raises
======
NotImplementedError
Methods for determining the singularities of this function have
not been developed.
Notes
=====
This function does not find non-isolated singularities
nor does it find branch points of the expression.
Currently supported functions are:
- univariate continuous (real or complex) functions
References
==========
.. [1] https://en.wikipedia.org/wiki/Mathematical_singularity
Examples
========
>>> from sympy import singularities, Symbol, log
>>> x = Symbol('x', real=True)
>>> y = Symbol('y', real=False)
>>> singularities(x**2 + x + 1, x)
EmptySet
>>> singularities(1/(x + 1), x)
{-1}
>>> singularities(1/(y**2 + 1), y)
{-I, I}
>>> singularities(1/(y**3 + 1), y)
{-1, 1/2 - sqrt(3)*I/2, 1/2 + sqrt(3)*I/2}
>>> singularities(log(x), x)
{0}
"""
from sympy.solvers.solveset import solveset
if domain is None:
domain = S.Reals if symbol.is_real else S.Complexes
try:
sings = S.EmptySet
e = expression.rewrite([sec, csc, cot, tan], cos)
e = e.rewrite([sech, csch, coth, tanh], cosh)
for i in e.atoms(Pow):
if i.exp.is_infinite:
raise NotImplementedError
if i.exp.is_negative:
# XXX: exponent of varying sign not handled
sings += solveset(i.base, symbol, domain)
for i in expression.atoms(log, asech, acsch):
sings += solveset(i.args[0], symbol, domain)
for i in expression.atoms(atanh, acoth):
sings += solveset(i.args[0] - 1, symbol, domain)
sings += solveset(i.args[0] + 1, symbol, domain)
return sings
except NotImplementedError:
raise NotImplementedError(filldedent('''
Methods for determining the singularities
of this function have not been developed.'''))
###########################################################################
# DIFFERENTIAL CALCULUS METHODS #
###########################################################################
def monotonicity_helper(expression, predicate, interval=S.Reals, symbol=None):
"""
Helper function for functions checking function monotonicity.
Parameters
==========
expression : Expr
The target function which is being checked
predicate : function
The property being tested for. The function takes in an integer
and returns a boolean. The integer input is the derivative and
the boolean result should be true if the property is being held,
and false otherwise.
interval : Set, optional
The range of values in which we are testing, defaults to all reals.
symbol : Symbol, optional
The symbol present in expression which gets varied over the given range.
It returns a boolean indicating whether the interval in which
the function's derivative satisfies given predicate is a superset
of the given interval.
Returns
=======
Boolean
True if ``predicate`` is true for all the derivatives when ``symbol``
is varied in ``range``, False otherwise.
"""
from sympy.solvers.solveset import solveset
expression = sympify(expression)
free = expression.free_symbols
if symbol is None:
if len(free) > 1:
raise NotImplementedError(
'The function has not yet been implemented'
' for all multivariate expressions.'
)
variable = symbol or (free.pop() if free else Symbol('x'))
derivative = expression.diff(variable)
predicate_interval = solveset(predicate(derivative), variable, S.Reals)
return interval.is_subset(predicate_interval)
def is_increasing(expression, interval=S.Reals, symbol=None):
"""
Return whether the function is increasing in the given interval.
Parameters
==========
expression : Expr
The target function which is being checked.
interval : Set, optional
The range of values in which we are testing (defaults to set of
all real numbers).
symbol : Symbol, optional
The symbol present in expression which gets varied over the given range.
Returns
=======
Boolean
True if ``expression`` is increasing (either strictly increasing or
constant) in the given ``interval``, False otherwise.
Examples
========
>>> from sympy import is_increasing
>>> from sympy.abc import x, y
>>> from sympy import S, Interval, oo
>>> is_increasing(x**3 - 3*x**2 + 4*x, S.Reals)
True
>>> is_increasing(-x**2, Interval(-oo, 0))
True
>>> is_increasing(-x**2, Interval(0, oo))
False
>>> is_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval(-2, 3))
False
>>> is_increasing(x**2 + y, Interval(1, 2), x)
True
"""
return monotonicity_helper(expression, lambda x: x >= 0, interval, symbol)
def is_strictly_increasing(expression, interval=S.Reals, symbol=None):
"""
Return whether the function is strictly increasing in the given interval.
Parameters
==========
expression : Expr
The target function which is being checked.
interval : Set, optional
The range of values in which we are testing (defaults to set of
all real numbers).
symbol : Symbol, optional
The symbol present in expression which gets varied over the given range.
Returns
=======
Boolean
True if ``expression`` is strictly increasing in the given ``interval``,
False otherwise.
Examples
========
>>> from sympy import is_strictly_increasing
>>> from sympy.abc import x, y
>>> from sympy import Interval, oo
>>> is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.Ropen(-oo, -2))
True
>>> is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.Lopen(3, oo))
True
>>> is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.open(-2, 3))
False
>>> is_strictly_increasing(-x**2, Interval(0, oo))
False
>>> is_strictly_increasing(-x**2 + y, Interval(-oo, 0), x)
False
"""
return monotonicity_helper(expression, lambda x: x > 0, interval, symbol)
def is_decreasing(expression, interval=S.Reals, symbol=None):
"""
Return whether the function is decreasing in the given interval.
Parameters
==========
expression : Expr
The target function which is being checked.
interval : Set, optional
The range of values in which we are testing (defaults to set of
all real numbers).
symbol : Symbol, optional
The symbol present in expression which gets varied over the given range.
Returns
=======
Boolean
True if ``expression`` is decreasing (either strictly decreasing or
constant) in the given ``interval``, False otherwise.
Examples
========
>>> from sympy import is_decreasing
>>> from sympy.abc import x, y
>>> from sympy import S, Interval, oo
>>> is_decreasing(1/(x**2 - 3*x), Interval.open(S(3)/2, 3))
True
>>> is_decreasing(1/(x**2 - 3*x), Interval.open(1.5, 3))
True
>>> is_decreasing(1/(x**2 - 3*x), Interval.Lopen(3, oo))
True
>>> is_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, S(3)/2))
False
>>> is_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, 1.5))
False
>>> is_decreasing(-x**2, Interval(-oo, 0))
False
>>> is_decreasing(-x**2 + y, Interval(-oo, 0), x)
False
"""
return monotonicity_helper(expression, lambda x: x <= 0, interval, symbol)
def is_strictly_decreasing(expression, interval=S.Reals, symbol=None):
"""
Return whether the function is strictly decreasing in the given interval.
Parameters
==========
expression : Expr
The target function which is being checked.
interval : Set, optional
The range of values in which we are testing (defaults to set of
all real numbers).
symbol : Symbol, optional
The symbol present in expression which gets varied over the given range.
Returns
=======
Boolean
True if ``expression`` is strictly decreasing in the given ``interval``,
False otherwise.
Examples
========
>>> from sympy import is_strictly_decreasing
>>> from sympy.abc import x, y
>>> from sympy import S, Interval, oo
>>> is_strictly_decreasing(1/(x**2 - 3*x), Interval.Lopen(3, oo))
True
>>> is_strictly_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, S(3)/2))
False
>>> is_strictly_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, 1.5))
False
>>> is_strictly_decreasing(-x**2, Interval(-oo, 0))
False
>>> is_strictly_decreasing(-x**2 + y, Interval(-oo, 0), x)
False
"""
return monotonicity_helper(expression, lambda x: x < 0, interval, symbol)
def is_monotonic(expression, interval=S.Reals, symbol=None):
"""
Return whether the function is monotonic in the given interval.
Parameters
==========
expression : Expr
The target function which is being checked.
interval : Set, optional
The range of values in which we are testing (defaults to set of
all real numbers).
symbol : Symbol, optional
The symbol present in expression which gets varied over the given range.
Returns
=======
Boolean
True if ``expression`` is monotonic in the given ``interval``,
False otherwise.
Raises
======
NotImplementedError
Monotonicity check has not been implemented for the queried function.
Examples
========
>>> from sympy import is_monotonic
>>> from sympy.abc import x, y
>>> from sympy import S, Interval, oo
>>> is_monotonic(1/(x**2 - 3*x), Interval.open(S(3)/2, 3))
True
>>> is_monotonic(1/(x**2 - 3*x), Interval.open(1.5, 3))
True
>>> is_monotonic(1/(x**2 - 3*x), Interval.Lopen(3, oo))
True
>>> is_monotonic(x**3 - 3*x**2 + 4*x, S.Reals)
True
>>> is_monotonic(-x**2, S.Reals)
False
>>> is_monotonic(x**2 + y + 1, Interval(1, 2), x)
True
"""
from sympy.solvers.solveset import solveset
expression = sympify(expression)
free = expression.free_symbols
if symbol is None and len(free) > 1:
raise NotImplementedError(
'is_monotonic has not yet been implemented'
' for all multivariate expressions.'
)
variable = symbol or (free.pop() if free else Symbol('x'))
turning_points = solveset(expression.diff(variable), variable, interval)
return interval.intersection(turning_points) is S.EmptySet
@@ -0,0 +1,336 @@
from sympy.core.numbers import (E, Rational, oo, pi, zoo)
from sympy.core.singleton import S
from sympy.core.symbol import Symbol
from sympy.functions.elementary.exponential import (exp, log)
from sympy.functions.elementary.miscellaneous import (Max, Min, sqrt)
from sympy.functions.elementary.trigonometric import (cos, sin, tan)
from sympy.calculus.accumulationbounds import AccumBounds
from sympy.core import Add, Mul, Pow
from sympy.core.expr import unchanged
from sympy.testing.pytest import raises, XFAIL
from sympy.abc import x
a = Symbol('a', real=True)
B = AccumBounds
def test_AccumBounds():
assert B(1, 2).args == (1, 2)
assert B(1, 2).delta is S.One
assert B(1, 2).mid == Rational(3, 2)
assert B(1, 3).is_real == True
assert B(1, 1) is S.One
assert B(1, 2) + 1 == B(2, 3)
assert 1 + B(1, 2) == B(2, 3)
assert B(1, 2) + B(2, 3) == B(3, 5)
assert -B(1, 2) == B(-2, -1)
assert B(1, 2) - 1 == B(0, 1)
assert 1 - B(1, 2) == B(-1, 0)
assert B(2, 3) - B(1, 2) == B(0, 2)
assert x + B(1, 2) == Add(B(1, 2), x)
assert a + B(1, 2) == B(1 + a, 2 + a)
assert B(1, 2) - x == Add(B(1, 2), -x)
assert B(-oo, 1) + oo == B(-oo, oo)
assert B(1, oo) + oo is oo
assert B(1, oo) - oo == B(-oo, oo)
assert (-oo - B(-1, oo)) is -oo
assert B(-oo, 1) - oo is -oo
assert B(1, oo) - oo == B(-oo, oo)
assert B(-oo, 1) - (-oo) == B(-oo, oo)
assert (oo - B(1, oo)) == B(-oo, oo)
assert (-oo - B(1, oo)) is -oo
assert B(1, 2)/2 == B(S.Half, 1)
assert 2/B(2, 3) == B(Rational(2, 3), 1)
assert 1/B(-1, 1) == B(-oo, oo)
assert abs(B(1, 2)) == B(1, 2)
assert abs(B(-2, -1)) == B(1, 2)
assert abs(B(-2, 1)) == B(0, 2)
assert abs(B(-1, 2)) == B(0, 2)
c = Symbol('c')
raises(ValueError, lambda: B(0, c))
raises(ValueError, lambda: B(1, -1))
r = Symbol('r', real=True)
raises(ValueError, lambda: B(r, r - 1))
def test_AccumBounds_mul():
assert B(1, 2)*2 == B(2, 4)
assert 2*B(1, 2) == B(2, 4)
assert B(1, 2)*B(2, 3) == B(2, 6)
assert B(0, 2)*B(2, oo) == B(0, oo)
l, r = B(-oo, oo), B(-a, a)
assert l*r == B(-oo, oo)
assert r*l == B(-oo, oo)
l, r = B(1, oo), B(-3, -2)
assert l*r == B(-oo, -2)
assert r*l == B(-oo, -2)
assert B(1, 2)*0 == 0
assert B(1, oo)*0 == B(0, oo)
assert B(-oo, 1)*0 == B(-oo, 0)
assert B(-oo, oo)*0 == B(-oo, oo)
assert B(1, 2)*x == Mul(B(1, 2), x, evaluate=False)
assert B(0, 2)*oo == B(0, oo)
assert B(-2, 0)*oo == B(-oo, 0)
assert B(0, 2)*(-oo) == B(-oo, 0)
assert B(-2, 0)*(-oo) == B(0, oo)
assert B(-1, 1)*oo == B(-oo, oo)
assert B(-1, 1)*(-oo) == B(-oo, oo)
assert B(-oo, oo)*oo == B(-oo, oo)
def test_AccumBounds_div():
assert B(-1, 3)/B(3, 4) == B(Rational(-1, 3), 1)
assert B(-2, 4)/B(-3, 4) == B(-oo, oo)
assert B(-3, -2)/B(-4, 0) == B(S.Half, oo)
# these two tests can have a better answer
# after Union of B is improved
assert B(-3, -2)/B(-2, 1) == B(-oo, oo)
assert B(2, 3)/B(-2, 2) == B(-oo, oo)
assert B(-3, -2)/B(0, 4) == B(-oo, Rational(-1, 2))
assert B(2, 4)/B(-3, 0) == B(-oo, Rational(-2, 3))
assert B(2, 4)/B(0, 3) == B(Rational(2, 3), oo)
assert B(0, 1)/B(0, 1) == B(0, oo)
assert B(-1, 0)/B(0, 1) == B(-oo, 0)
assert B(-1, 2)/B(-2, 2) == B(-oo, oo)
assert 1/B(-1, 2) == B(-oo, oo)
assert 1/B(0, 2) == B(S.Half, oo)
assert (-1)/B(0, 2) == B(-oo, Rational(-1, 2))
assert 1/B(-oo, 0) == B(-oo, 0)
assert 1/B(-1, 0) == B(-oo, -1)
assert (-2)/B(-oo, 0) == B(0, oo)
assert 1/B(-oo, -1) == B(-1, 0)
assert B(1, 2)/a == Mul(B(1, 2), 1/a, evaluate=False)
assert B(1, 2)/0 == B(1, 2)*zoo
assert B(1, oo)/oo == B(0, oo)
assert B(1, oo)/(-oo) == B(-oo, 0)
assert B(-oo, -1)/oo == B(-oo, 0)
assert B(-oo, -1)/(-oo) == B(0, oo)
assert B(-oo, oo)/oo == B(-oo, oo)
assert B(-oo, oo)/(-oo) == B(-oo, oo)
assert B(-1, oo)/oo == B(0, oo)
assert B(-1, oo)/(-oo) == B(-oo, 0)
assert B(-oo, 1)/oo == B(-oo, 0)
assert B(-oo, 1)/(-oo) == B(0, oo)
def test_issue_18795():
r = Symbol('r', real=True)
a = B(-1,1)
c = B(7, oo)
b = B(-oo, oo)
assert c - tan(r) == B(7-tan(r), oo)
assert b + tan(r) == B(-oo, oo)
assert (a + r)/a == B(-oo, oo)*B(r - 1, r + 1)
assert (b + a)/a == B(-oo, oo)
def test_AccumBounds_func():
assert (x**2 + 2*x + 1).subs(x, B(-1, 1)) == B(-1, 4)
assert exp(B(0, 1)) == B(1, E)
assert exp(B(-oo, oo)) == B(0, oo)
assert log(B(3, 6)) == B(log(3), log(6))
@XFAIL
def test_AccumBounds_powf():
nn = Symbol('nn', nonnegative=True)
assert B(1 + nn, 2 + nn)**B(1, 2) == B(1 + nn, (2 + nn)**2)
i = Symbol('i', integer=True, negative=True)
assert B(1, 2)**i == B(2**i, 1)
def test_AccumBounds_pow():
assert B(0, 2)**2 == B(0, 4)
assert B(-1, 1)**2 == B(0, 1)
assert B(1, 2)**2 == B(1, 4)
assert B(-1, 2)**3 == B(-1, 8)
assert B(-1, 1)**0 == 1
assert B(1, 2)**Rational(5, 2) == B(1, 4*sqrt(2))
assert B(0, 2)**S.Half == B(0, sqrt(2))
neg = Symbol('neg', negative=True)
assert unchanged(Pow, B(neg, 1), S.Half)
nn = Symbol('nn', nonnegative=True)
assert B(nn, nn + 1)**S.Half == B(sqrt(nn), sqrt(nn + 1))
assert B(nn, nn + 1)**nn == B(nn**nn, (nn + 1)**nn)
assert unchanged(Pow, B(nn, nn + 1), x)
i = Symbol('i', integer=True)
assert B(1, 2)**i == B(Min(1, 2**i), Max(1, 2**i))
i = Symbol('i', integer=True, nonnegative=True)
assert B(1, 2)**i == B(1, 2**i)
assert B(0, 1)**i == B(0**i, 1)
assert B(1, 5)**(-2) == B(Rational(1, 25), 1)
assert B(-1, 3)**(-2) == B(0, oo)
assert B(0, 2)**(-3) == B(Rational(1, 8), oo)
assert B(-2, 0)**(-3) == B(-oo, -Rational(1, 8))
assert B(0, 2)**(-2) == B(Rational(1, 4), oo)
assert B(-1, 2)**(-3) == B(-oo, oo)
assert B(-3, -2)**(-3) == B(Rational(-1, 8), Rational(-1, 27))
assert B(-3, -2)**(-2) == B(Rational(1, 9), Rational(1, 4))
assert B(0, oo)**S.Half == B(0, oo)
assert B(-oo, 0)**(-2) == B(0, oo)
assert B(-2, 0)**(-2) == B(Rational(1, 4), oo)
assert B(Rational(1, 3), S.Half)**oo is S.Zero
assert B(0, S.Half)**oo is S.Zero
assert B(S.Half, 1)**oo == B(0, oo)
assert B(0, 1)**oo == B(0, oo)
assert B(2, 3)**oo is oo
assert B(1, 2)**oo == B(0, oo)
assert B(S.Half, 3)**oo == B(0, oo)
assert B(Rational(-1, 3), Rational(-1, 4))**oo is S.Zero
assert B(-1, Rational(-1, 2))**oo is S.NaN
assert B(-3, -2)**oo is zoo
assert B(-2, -1)**oo is S.NaN
assert B(-2, Rational(-1, 2))**oo is S.NaN
assert B(Rational(-1, 2), S.Half)**oo is S.Zero
assert B(Rational(-1, 2), 1)**oo == B(0, oo)
assert B(Rational(-2, 3), 2)**oo == B(0, oo)
assert B(-1, 1)**oo == B(-oo, oo)
assert B(-1, S.Half)**oo == B(-oo, oo)
assert B(-1, 2)**oo == B(-oo, oo)
assert B(-2, S.Half)**oo == B(-oo, oo)
assert B(1, 2)**x == Pow(B(1, 2), x, evaluate=False)
assert B(2, 3)**(-oo) is S.Zero
assert B(0, 2)**(-oo) == B(0, oo)
assert B(-1, 2)**(-oo) == B(-oo, oo)
assert (tan(x)**sin(2*x)).subs(x, B(0, pi/2)) == \
Pow(B(-oo, oo), B(0, 1))
def test_AccumBounds_exponent():
# base is 0
z = 0**B(a, a + S.Half)
assert z.subs(a, 0) == B(0, 1)
assert z.subs(a, 1) == 0
p = z.subs(a, -1)
assert p.is_Pow and p.args == (0, B(-1, -S.Half))
# base > 0
# when base is 1 the type of bounds does not matter
assert 1**B(a, a + 1) == 1
# otherwise we need to know if 0 is in the bounds
assert S.Half**B(-2, 2) == B(S(1)/4, 4)
assert 2**B(-2, 2) == B(S(1)/4, 4)
# +eps may introduce +oo
# if there is a negative integer exponent
assert B(0, 1)**B(S(1)/2, 1) == B(0, 1)
assert B(0, 1)**B(0, 1) == B(0, 1)
# positive bases have positive bounds
assert B(2, 3)**B(-3, -2) == B(S(1)/27, S(1)/4)
assert B(2, 3)**B(-3, 2) == B(S(1)/27, 9)
# bounds generating imaginary parts unevaluated
assert unchanged(Pow, B(-1, 1), B(1, 2))
assert B(0, S(1)/2)**B(1, oo) == B(0, S(1)/2)
assert B(0, 1)**B(1, oo) == B(0, oo)
assert B(0, 2)**B(1, oo) == B(0, oo)
assert B(0, oo)**B(1, oo) == B(0, oo)
assert B(S(1)/2, 1)**B(1, oo) == B(0, oo)
assert B(S(1)/2, 1)**B(-oo, -1) == B(0, oo)
assert B(S(1)/2, 1)**B(-oo, oo) == B(0, oo)
assert B(S(1)/2, 2)**B(1, oo) == B(0, oo)
assert B(S(1)/2, 2)**B(-oo, -1) == B(0, oo)
assert B(S(1)/2, 2)**B(-oo, oo) == B(0, oo)
assert B(S(1)/2, oo)**B(1, oo) == B(0, oo)
assert B(S(1)/2, oo)**B(-oo, -1) == B(0, oo)
assert B(S(1)/2, oo)**B(-oo, oo) == B(0, oo)
assert B(1, 2)**B(1, oo) == B(0, oo)
assert B(1, 2)**B(-oo, -1) == B(0, oo)
assert B(1, 2)**B(-oo, oo) == B(0, oo)
assert B(1, oo)**B(1, oo) == B(0, oo)
assert B(1, oo)**B(-oo, -1) == B(0, oo)
assert B(1, oo)**B(-oo, oo) == B(0, oo)
assert B(2, oo)**B(1, oo) == B(2, oo)
assert B(2, oo)**B(-oo, -1) == B(0, S(1)/2)
assert B(2, oo)**B(-oo, oo) == B(0, oo)
def test_comparison_AccumBounds():
assert (B(1, 3) < 4) == S.true
assert (B(1, 3) < -1) == S.false
assert (B(1, 3) < 2).rel_op == '<'
assert (B(1, 3) <= 2).rel_op == '<='
assert (B(1, 3) > 4) == S.false
assert (B(1, 3) > -1) == S.true
assert (B(1, 3) > 2).rel_op == '>'
assert (B(1, 3) >= 2).rel_op == '>='
assert (B(1, 3) < B(4, 6)) == S.true
assert (B(1, 3) < B(2, 4)).rel_op == '<'
assert (B(1, 3) < B(-2, 0)) == S.false
assert (B(1, 3) <= B(4, 6)) == S.true
assert (B(1, 3) <= B(-2, 0)) == S.false
assert (B(1, 3) > B(4, 6)) == S.false
assert (B(1, 3) > B(-2, 0)) == S.true
assert (B(1, 3) >= B(4, 6)) == S.false
assert (B(1, 3) >= B(-2, 0)) == S.true
# issue 13499
assert (cos(x) > 0).subs(x, oo) == (B(-1, 1) > 0)
c = Symbol('c')
raises(TypeError, lambda: (B(0, 1) < c))
raises(TypeError, lambda: (B(0, 1) <= c))
raises(TypeError, lambda: (B(0, 1) > c))
raises(TypeError, lambda: (B(0, 1) >= c))
def test_contains_AccumBounds():
assert (1 in B(1, 2)) == S.true
raises(TypeError, lambda: a in B(1, 2))
assert 0 in B(-1, 0)
raises(TypeError, lambda:
(cos(1)**2 + sin(1)**2 - 1) in B(-1, 0))
assert (-oo in B(1, oo)) == S.true
assert (oo in B(-oo, 0)) == S.true
# issue 13159
assert Mul(0, B(-1, 1)) == Mul(B(-1, 1), 0) == 0
import itertools
for perm in itertools.permutations([0, B(-1, 1), x]):
assert Mul(*perm) == 0
def test_intersection_AccumBounds():
assert B(0, 3).intersection(B(1, 2)) == B(1, 2)
assert B(0, 3).intersection(B(1, 4)) == B(1, 3)
assert B(0, 3).intersection(B(-1, 2)) == B(0, 2)
assert B(0, 3).intersection(B(-1, 4)) == B(0, 3)
assert B(0, 1).intersection(B(2, 3)) == S.EmptySet
raises(TypeError, lambda: B(0, 3).intersection(1))
def test_union_AccumBounds():
assert B(0, 3).union(B(1, 2)) == B(0, 3)
assert B(0, 3).union(B(1, 4)) == B(0, 4)
assert B(0, 3).union(B(-1, 2)) == B(-1, 3)
assert B(0, 3).union(B(-1, 4)) == B(-1, 4)
raises(TypeError, lambda: B(0, 3).union(1))
@@ -0,0 +1,74 @@
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.trigonometric import (cos, sin)
from sympy.testing.pytest import raises
from sympy.calculus.euler import euler_equations as euler
def test_euler_interface():
x = Function('x')
y = Symbol('y')
t = Symbol('t')
raises(TypeError, lambda: euler())
raises(TypeError, lambda: euler(D(x(t), t)*y(t), [x(t), y]))
raises(ValueError, lambda: euler(D(x(t), t)*x(y), [x(t), x(y)]))
raises(TypeError, lambda: euler(D(x(t), t)**2, x(0)))
raises(TypeError, lambda: euler(D(x(t), t)*y(t), [t]))
assert euler(D(x(t), t)**2/2, {x(t)}) == [Eq(-D(x(t), t, t), 0)]
assert euler(D(x(t), t)**2/2, x(t), {t}) == [Eq(-D(x(t), t, t), 0)]
def test_euler_pendulum():
x = Function('x')
t = Symbol('t')
L = D(x(t), t)**2/2 + cos(x(t))
assert euler(L, x(t), t) == [Eq(-sin(x(t)) - D(x(t), t, t), 0)]
def test_euler_henonheiles():
x = Function('x')
y = Function('y')
t = Symbol('t')
L = sum(D(z(t), t)**2/2 - z(t)**2/2 for z in [x, y])
L += -x(t)**2*y(t) + y(t)**3/3
assert euler(L, [x(t), y(t)], t) == [Eq(-2*x(t)*y(t) - x(t) -
D(x(t), t, t), 0),
Eq(-x(t)**2 + y(t)**2 -
y(t) - D(y(t), t, t), 0)]
def test_euler_sineg():
psi = Function('psi')
t = Symbol('t')
x = Symbol('x')
L = D(psi(t, x), t)**2/2 - D(psi(t, x), x)**2/2 + cos(psi(t, x))
assert euler(L, psi(t, x), [t, x]) == [Eq(-sin(psi(t, x)) -
D(psi(t, x), t, t) +
D(psi(t, x), x, x), 0)]
def test_euler_high_order():
# an example from hep-th/0309038
m = Symbol('m')
k = Symbol('k')
x = Function('x')
y = Function('y')
t = Symbol('t')
L = (m*D(x(t), t)**2/2 + m*D(y(t), t)**2/2 -
k*D(x(t), t)*D(y(t), t, t) + k*D(y(t), t)*D(x(t), t, t))
assert euler(L, [x(t), y(t)]) == [Eq(2*k*D(y(t), t, t, t) -
m*D(x(t), t, t), 0),
Eq(-2*k*D(x(t), t, t, t) -
m*D(y(t), t, t), 0)]
w = Symbol('w')
L = D(x(t, w), t, w)**2/2
assert euler(L) == [Eq(D(x(t, w), t, t, w, w), 0)]
def test_issue_18653():
x, y, z = symbols("x y z")
f, g, h = symbols("f g h", cls=Function, args=(x, y))
f, g, h = f(), g(), h()
expr2 = f.diff(x)*h.diff(z)
assert euler(expr2, (f,), (x, y)) == []
@@ -0,0 +1,164 @@
from itertools import product
from sympy.core.function import (Function, diff)
from sympy.core.numbers import Rational
from sympy.core.singleton import S
from sympy.core.symbol import symbols
from sympy.functions.elementary.exponential import exp
from sympy.calculus.finite_diff import (
apply_finite_diff, differentiate_finite, finite_diff_weights,
_as_finite_diff
)
from sympy.testing.pytest import raises, warns_deprecated_sympy
def test_apply_finite_diff():
x, h = symbols('x h')
f = Function('f')
assert (apply_finite_diff(1, [x-h, x+h], [f(x-h), f(x+h)], x) -
(f(x+h)-f(x-h))/(2*h)).simplify() == 0
assert (apply_finite_diff(1, [5, 6, 7], [f(5), f(6), f(7)], 5) -
(Rational(-3, 2)*f(5) + 2*f(6) - S.Half*f(7))).simplify() == 0
raises(ValueError, lambda: apply_finite_diff(1, [x, h], [f(x)]))
def test_finite_diff_weights():
d = finite_diff_weights(1, [5, 6, 7], 5)
assert d[1][2] == [Rational(-3, 2), 2, Rational(-1, 2)]
# Table 1, p. 702 in doi:10.1090/S0025-5718-1988-0935077-0
# --------------------------------------------------------
xl = [0, 1, -1, 2, -2, 3, -3, 4, -4]
# d holds all coefficients
d = finite_diff_weights(4, xl, S.Zero)
# Zeroeth derivative
for i in range(5):
assert d[0][i] == [S.One] + [S.Zero]*8
# First derivative
assert d[1][0] == [S.Zero]*9
assert d[1][2] == [S.Zero, S.Half, Rational(-1, 2)] + [S.Zero]*6
assert d[1][4] == [S.Zero, Rational(2, 3), Rational(-2, 3), Rational(-1, 12), Rational(1, 12)] + [S.Zero]*4
assert d[1][6] == [S.Zero, Rational(3, 4), Rational(-3, 4), Rational(-3, 20), Rational(3, 20),
Rational(1, 60), Rational(-1, 60)] + [S.Zero]*2
assert d[1][8] == [S.Zero, Rational(4, 5), Rational(-4, 5), Rational(-1, 5), Rational(1, 5),
Rational(4, 105), Rational(-4, 105), Rational(-1, 280), Rational(1, 280)]
# Second derivative
for i in range(2):
assert d[2][i] == [S.Zero]*9
assert d[2][2] == [-S(2), S.One, S.One] + [S.Zero]*6
assert d[2][4] == [Rational(-5, 2), Rational(4, 3), Rational(4, 3), Rational(-1, 12), Rational(-1, 12)] + [S.Zero]*4
assert d[2][6] == [Rational(-49, 18), Rational(3, 2), Rational(3, 2), Rational(-3, 20), Rational(-3, 20),
Rational(1, 90), Rational(1, 90)] + [S.Zero]*2
assert d[2][8] == [Rational(-205, 72), Rational(8, 5), Rational(8, 5), Rational(-1, 5), Rational(-1, 5),
Rational(8, 315), Rational(8, 315), Rational(-1, 560), Rational(-1, 560)]
# Third derivative
for i in range(3):
assert d[3][i] == [S.Zero]*9
assert d[3][4] == [S.Zero, -S.One, S.One, S.Half, Rational(-1, 2)] + [S.Zero]*4
assert d[3][6] == [S.Zero, Rational(-13, 8), Rational(13, 8), S.One, -S.One,
Rational(-1, 8), Rational(1, 8)] + [S.Zero]*2
assert d[3][8] == [S.Zero, Rational(-61, 30), Rational(61, 30), Rational(169, 120), Rational(-169, 120),
Rational(-3, 10), Rational(3, 10), Rational(7, 240), Rational(-7, 240)]
# Fourth derivative
for i in range(4):
assert d[4][i] == [S.Zero]*9
assert d[4][4] == [S(6), -S(4), -S(4), S.One, S.One] + [S.Zero]*4
assert d[4][6] == [Rational(28, 3), Rational(-13, 2), Rational(-13, 2), S(2), S(2),
Rational(-1, 6), Rational(-1, 6)] + [S.Zero]*2
assert d[4][8] == [Rational(91, 8), Rational(-122, 15), Rational(-122, 15), Rational(169, 60), Rational(169, 60),
Rational(-2, 5), Rational(-2, 5), Rational(7, 240), Rational(7, 240)]
# Table 2, p. 703 in doi:10.1090/S0025-5718-1988-0935077-0
# --------------------------------------------------------
xl = [[j/S(2) for j in list(range(-i*2+1, 0, 2))+list(range(1, i*2+1, 2))]
for i in range(1, 5)]
# d holds all coefficients
d = [finite_diff_weights({0: 1, 1: 2, 2: 4, 3: 4}[i], xl[i], 0) for
i in range(4)]
# Zeroth derivative
assert d[0][0][1] == [S.Half, S.Half]
assert d[1][0][3] == [Rational(-1, 16), Rational(9, 16), Rational(9, 16), Rational(-1, 16)]
assert d[2][0][5] == [Rational(3, 256), Rational(-25, 256), Rational(75, 128), Rational(75, 128),
Rational(-25, 256), Rational(3, 256)]
assert d[3][0][7] == [Rational(-5, 2048), Rational(49, 2048), Rational(-245, 2048), Rational(1225, 2048),
Rational(1225, 2048), Rational(-245, 2048), Rational(49, 2048), Rational(-5, 2048)]
# First derivative
assert d[0][1][1] == [-S.One, S.One]
assert d[1][1][3] == [Rational(1, 24), Rational(-9, 8), Rational(9, 8), Rational(-1, 24)]
assert d[2][1][5] == [Rational(-3, 640), Rational(25, 384), Rational(-75, 64),
Rational(75, 64), Rational(-25, 384), Rational(3, 640)]
assert d[3][1][7] == [Rational(5, 7168), Rational(-49, 5120),
Rational(245, 3072), Rational(-1225, 1024),
Rational(1225, 1024), Rational(-245, 3072),
Rational(49, 5120), Rational(-5, 7168)]
# Reasonably the rest of the table is also correct... (testing of that
# deemed excessive at the moment)
raises(ValueError, lambda: finite_diff_weights(-1, [1, 2]))
raises(ValueError, lambda: finite_diff_weights(1.2, [1, 2]))
x = symbols('x')
raises(ValueError, lambda: finite_diff_weights(x, [1, 2]))
def test_as_finite_diff():
x = symbols('x')
f = Function('f')
dx = Function('dx')
_as_finite_diff(f(x).diff(x), [x-2, x-1, x, x+1, x+2])
# Use of undefined functions in ``points``
df_true = -f(x+dx(x)/2-dx(x+dx(x)/2)/2) / dx(x+dx(x)/2) \
+ f(x+dx(x)/2+dx(x+dx(x)/2)/2) / dx(x+dx(x)/2)
df_test = diff(f(x), x).as_finite_difference(points=dx(x), x0=x+dx(x)/2)
assert (df_test - df_true).simplify() == 0
def test_differentiate_finite():
x, y, h = symbols('x y h')
f = Function('f')
with warns_deprecated_sympy():
res0 = differentiate_finite(f(x, y) + exp(42), x, y, evaluate=True)
xm, xp, ym, yp = [v + sign*S.Half for v, sign in product([x, y], [-1, 1])]
ref0 = f(xm, ym) + f(xp, yp) - f(xm, yp) - f(xp, ym)
assert (res0 - ref0).simplify() == 0
g = Function('g')
with warns_deprecated_sympy():
res1 = differentiate_finite(f(x)*g(x) + 42, x, evaluate=True)
ref1 = (-f(x - S.Half) + f(x + S.Half))*g(x) + \
(-g(x - S.Half) + g(x + S.Half))*f(x)
assert (res1 - ref1).simplify() == 0
res2 = differentiate_finite(f(x) + x**3 + 42, x, points=[x-1, x+1])
ref2 = (f(x + 1) + (x + 1)**3 - f(x - 1) - (x - 1)**3)/2
assert (res2 - ref2).simplify() == 0
raises(TypeError, lambda: differentiate_finite(f(x)*g(x), x,
pints=[x-1, x+1]))
res3 = differentiate_finite(f(x)*g(x).diff(x), x)
ref3 = (-g(x) + g(x + 1))*f(x + S.Half) - (g(x) - g(x - 1))*f(x - S.Half)
assert res3 == ref3
res4 = differentiate_finite(f(x)*g(x).diff(x).diff(x), x)
ref4 = -((g(x - Rational(3, 2)) - 2*g(x - S.Half) + g(x + S.Half))*f(x - S.Half)) \
+ (g(x - S.Half) - 2*g(x + S.Half) + g(x + Rational(3, 2)))*f(x + S.Half)
assert res4 == ref4
res5_expr = f(x).diff(x)*g(x).diff(x)
res5 = differentiate_finite(res5_expr, points=[x-h, x, x+h])
ref5 = (-2*f(x)/h + f(-h + x)/(2*h) + 3*f(h + x)/(2*h))*(-2*g(x)/h + g(-h + x)/(2*h) \
+ 3*g(h + x)/(2*h))/(2*h) - (2*f(x)/h - 3*f(-h + x)/(2*h) - \
f(h + x)/(2*h))*(2*g(x)/h - 3*g(-h + x)/(2*h) - g(h + x)/(2*h))/(2*h)
assert res5 == ref5
@@ -0,0 +1,122 @@
from sympy.core.numbers import (I, Rational, pi, oo)
from sympy.core.singleton import S
from sympy.core.symbol import Symbol, Dummy
from sympy.core.function import Lambda
from sympy.functions.elementary.exponential import (exp, log)
from sympy.functions.elementary.trigonometric import sec, csc
from sympy.functions.elementary.hyperbolic import (coth, sech,
atanh, asech, acoth, acsch)
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.calculus.singularities import (
singularities,
is_increasing,
is_strictly_increasing,
is_decreasing,
is_strictly_decreasing,
is_monotonic
)
from sympy.sets import Interval, FiniteSet, Union, ImageSet
from sympy.testing.pytest import raises
from sympy.abc import x, y
def test_singularities():
x = Symbol('x')
assert singularities(x**2, x) == S.EmptySet
assert singularities(x/(x**2 + 3*x + 2), x) == FiniteSet(-2, -1)
assert singularities(1/(x**2 + 1), x) == FiniteSet(I, -I)
assert singularities(x/(x**3 + 1), x) == \
FiniteSet(-1, (1 - sqrt(3) * I) / 2, (1 + sqrt(3) * I) / 2)
assert singularities(1/(y**2 + 2*I*y + 1), y) == \
FiniteSet(-I + sqrt(2)*I, -I - sqrt(2)*I)
_n = Dummy('n')
assert singularities(sech(x), x).dummy_eq(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 singularities(coth(x), x).dummy_eq(Union(
ImageSet(Lambda(_n, 2*_n*I*pi + I*pi), S.Integers),
ImageSet(Lambda(_n, 2*_n*I*pi), S.Integers)))
assert singularities(atanh(x), x) == FiniteSet(-1, 1)
assert singularities(acoth(x), x) == FiniteSet(-1, 1)
assert singularities(asech(x), x) == FiniteSet(0)
assert singularities(acsch(x), x) == FiniteSet(0)
x = Symbol('x', real=True)
assert singularities(1/(x**2 + 1), x) == S.EmptySet
assert singularities(exp(1/x), x, S.Reals) == FiniteSet(0)
assert singularities(exp(1/x), x, Interval(1, 2)) == S.EmptySet
assert singularities(log((x - 2)**2), x, Interval(1, 3)) == FiniteSet(2)
raises(NotImplementedError, lambda: singularities(x**-oo, x))
assert singularities(sec(x), x, Interval(0, 3*pi)) == FiniteSet(
pi/2, 3*pi/2, 5*pi/2)
assert singularities(csc(x), x, Interval(0, 3*pi)) == FiniteSet(
0, pi, 2*pi, 3*pi)
def test_is_increasing():
"""Test whether is_increasing returns correct value."""
a = Symbol('a', negative=True)
assert is_increasing(x**3 - 3*x**2 + 4*x, S.Reals)
assert is_increasing(-x**2, Interval(-oo, 0))
assert not is_increasing(-x**2, Interval(0, oo))
assert not is_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval(-2, 3))
assert is_increasing(x**2 + y, Interval(1, oo), x)
assert is_increasing(-x**2*a, Interval(1, oo), x)
assert is_increasing(1)
assert is_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval(-2, 3)) is False
def test_is_strictly_increasing():
"""Test whether is_strictly_increasing returns correct value."""
assert is_strictly_increasing(
4*x**3 - 6*x**2 - 72*x + 30, Interval.Ropen(-oo, -2))
assert is_strictly_increasing(
4*x**3 - 6*x**2 - 72*x + 30, Interval.Lopen(3, oo))
assert not is_strictly_increasing(
4*x**3 - 6*x**2 - 72*x + 30, Interval.open(-2, 3))
assert not is_strictly_increasing(-x**2, Interval(0, oo))
assert not is_strictly_decreasing(1)
assert is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.open(-2, 3)) is False
def test_is_decreasing():
"""Test whether is_decreasing returns correct value."""
b = Symbol('b', positive=True)
assert is_decreasing(1/(x**2 - 3*x), Interval.open(Rational(3,2), 3))
assert is_decreasing(1/(x**2 - 3*x), Interval.open(1.5, 3))
assert is_decreasing(1/(x**2 - 3*x), Interval.Lopen(3, oo))
assert not is_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, Rational(3, 2)))
assert not is_decreasing(-x**2, Interval(-oo, 0))
assert not is_decreasing(-x**2*b, Interval(-oo, 0), x)
def test_is_strictly_decreasing():
"""Test whether is_strictly_decreasing returns correct value."""
assert is_strictly_decreasing(1/(x**2 - 3*x), Interval.Lopen(3, oo))
assert not is_strictly_decreasing(
1/(x**2 - 3*x), Interval.Ropen(-oo, Rational(3, 2)))
assert not is_strictly_decreasing(-x**2, Interval(-oo, 0))
assert not is_strictly_decreasing(1)
assert is_strictly_decreasing(1/(x**2 - 3*x), Interval.open(Rational(3,2), 3))
assert is_strictly_decreasing(1/(x**2 - 3*x), Interval.open(1.5, 3))
def test_is_monotonic():
"""Test whether is_monotonic returns correct value."""
assert is_monotonic(1/(x**2 - 3*x), Interval.open(Rational(3,2), 3))
assert is_monotonic(1/(x**2 - 3*x), Interval.open(1.5, 3))
assert is_monotonic(1/(x**2 - 3*x), Interval.Lopen(3, oo))
assert is_monotonic(x**3 - 3*x**2 + 4*x, S.Reals)
assert not is_monotonic(-x**2, S.Reals)
assert is_monotonic(x**2 + y + 1, Interval(1, 2), x)
raises(NotImplementedError, lambda: is_monotonic(x**2 + y + 1))
def test_issue_23401():
x = Symbol('x')
expr = (x + 1)/(-1.0e-3*x**2 + 0.1*x + 0.1)
assert is_increasing(expr, Interval(1,2), x)
@@ -0,0 +1,392 @@
from sympy.core.function import Lambda
from sympy.core.numbers import (E, I, Rational, oo, pi)
from sympy.core.relational import Eq
from sympy.core.singleton import S
from sympy.core.symbol import (Dummy, Symbol)
from sympy.functions.elementary.complexes import (Abs, re)
from sympy.functions.elementary.exponential import (exp, log)
from sympy.functions.elementary.integers import frac
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.elementary.piecewise import Piecewise
from sympy.functions.elementary.trigonometric import (
cos, cot, csc, sec, sin, tan, asin, acos, atan, acot, asec, acsc)
from sympy.functions.elementary.hyperbolic import (sinh, cosh, tanh, coth,
sech, csch, asinh, acosh, atanh, acoth, asech, acsch)
from sympy.functions.special.gamma_functions import gamma
from sympy.functions.special.error_functions import expint
from sympy.matrices.expressions.matexpr import MatrixSymbol
from sympy.simplify.simplify import simplify
from sympy.calculus.util import (function_range, continuous_domain, not_empty_in,
periodicity, lcim, is_convex,
stationary_points, minimum, maximum)
from sympy.sets.sets import (Interval, FiniteSet, Complement, Union)
from sympy.sets.fancysets import ImageSet
from sympy.sets.conditionset import ConditionSet
from sympy.testing.pytest import XFAIL, raises, _both_exp_pow, slow
from sympy.abc import x, y
a = Symbol('a', real=True)
def test_function_range():
assert function_range(sin(x), x, Interval(-pi/2, pi/2)
) == Interval(-1, 1)
assert function_range(sin(x), x, Interval(0, pi)
) == Interval(0, 1)
assert function_range(tan(x), x, Interval(0, pi)
) == Interval(-oo, oo)
assert function_range(tan(x), x, Interval(pi/2, pi)
) == Interval(-oo, 0)
assert function_range((x + 3)/(x - 2), x, Interval(-5, 5)
) == Union(Interval(-oo, Rational(2, 7)), Interval(Rational(8, 3), oo))
assert function_range(1/(x**2), x, Interval(-1, 1)
) == Interval(1, oo)
assert function_range(exp(x), x, Interval(-1, 1)
) == Interval(exp(-1), exp(1))
assert function_range(log(x) - x, x, S.Reals
) == Interval(-oo, -1)
assert function_range(sqrt(3*x - 1), x, Interval(0, 2)
) == Interval(0, sqrt(5))
assert function_range(x*(x - 1) - (x**2 - x), x, S.Reals
) == FiniteSet(0)
assert function_range(x*(x - 1) - (x**2 - x) + y, x, S.Reals
) == FiniteSet(y)
assert function_range(sin(x), x, Union(Interval(-5, -3), FiniteSet(4))
) == Union(Interval(-sin(3), 1), FiniteSet(sin(4)))
assert function_range(cos(x), x, Interval(-oo, -4)
) == Interval(-1, 1)
assert function_range(cos(x), x, S.EmptySet) == S.EmptySet
assert function_range(x/sqrt(x**2+1), x, S.Reals) == Interval.open(-1,1)
raises(NotImplementedError, lambda : function_range(
exp(x)*(sin(x) - cos(x))/2 - x, x, S.Reals))
raises(NotImplementedError, lambda : function_range(
sin(x) + x, x, S.Reals)) # issue 13273
raises(NotImplementedError, lambda : function_range(
log(x), x, S.Integers))
raises(NotImplementedError, lambda : function_range(
sin(x)/2, x, S.Naturals))
@slow
def test_function_range1():
assert function_range(tan(x)**2 + tan(3*x)**2 + 1, x, S.Reals) == Interval(1,oo)
def test_continuous_domain():
assert continuous_domain(sin(x), x, Interval(0, 2*pi)) == Interval(0, 2*pi)
assert continuous_domain(tan(x), x, Interval(0, 2*pi)) == \
Union(Interval(0, pi/2, False, True), Interval(pi/2, pi*Rational(3, 2), True, True),
Interval(pi*Rational(3, 2), 2*pi, True, False))
assert continuous_domain(cot(x), x, Interval(0, 2*pi)) == Union(
Interval.open(0, pi), Interval.open(pi, 2*pi))
assert continuous_domain((x - 1)/((x - 1)**2), x, S.Reals) == \
Union(Interval(-oo, 1, True, True), Interval(1, oo, True, True))
assert continuous_domain(log(x) + log(4*x - 1), x, S.Reals) == \
Interval(Rational(1, 4), oo, True, True)
assert continuous_domain(1/sqrt(x - 3), x, S.Reals) == Interval(3, oo, True, True)
assert continuous_domain(1/x - 2, x, S.Reals) == \
Union(Interval.open(-oo, 0), Interval.open(0, oo))
assert continuous_domain(1/(x**2 - 4) + 2, x, S.Reals) == \
Union(Interval.open(-oo, -2), Interval.open(-2, 2), Interval.open(2, oo))
assert continuous_domain((x+1)**pi, x, S.Reals) == Interval(-1, oo)
assert continuous_domain((x+1)**(pi/2), x, S.Reals) == Interval(-1, oo)
assert continuous_domain(x**x, x, S.Reals) == Interval(0, oo)
assert continuous_domain((x+1)**log(x**2), x, S.Reals) == Union(
Interval.Ropen(-1, 0), Interval.open(0, oo))
domain = continuous_domain(log(tan(x)**2 + 1), x, S.Reals)
assert not domain.contains(3*pi/2)
assert domain.contains(5)
d = Symbol('d', even=True, zero=False)
assert continuous_domain(x**(1/d), x, S.Reals) == Interval(0, oo)
n = Dummy('n')
assert continuous_domain(1/sin(x), x, S.Reals).dummy_eq(Complement(
S.Reals, Union(ImageSet(Lambda(n, 2*n*pi + pi), S.Integers),
ImageSet(Lambda(n, 2*n*pi), S.Integers))))
assert continuous_domain(sin(x) + cos(x), x, S.Reals) == S.Reals
assert continuous_domain(asin(x), x, S.Reals) == Interval(-1, 1) # issue #21786
assert continuous_domain(1/acos(log(x)), x, S.Reals) == Interval.Ropen(exp(-1), E)
assert continuous_domain(sinh(x)+cosh(x), x, S.Reals) == S.Reals
assert continuous_domain(tanh(x)+sech(x), x, S.Reals) == S.Reals
assert continuous_domain(atan(x)+asinh(x), x, S.Reals) == S.Reals
assert continuous_domain(acosh(x), x, S.Reals) == Interval(1, oo)
assert continuous_domain(atanh(x), x, S.Reals) == Interval.open(-1, 1)
assert continuous_domain(atanh(x)+acosh(x), x, S.Reals) == S.EmptySet
assert continuous_domain(asech(x), x, S.Reals) == Interval.Lopen(0, 1)
assert continuous_domain(acoth(x), x, S.Reals) == Union(
Interval.open(-oo, -1), Interval.open(1, oo))
assert continuous_domain(asec(x), x, S.Reals) == Union(
Interval(-oo, -1), Interval(1, oo))
assert continuous_domain(acsc(x), x, S.Reals) == Union(
Interval(-oo, -1), Interval(1, oo))
for f in (coth, acsch, csch):
assert continuous_domain(f(x), x, S.Reals) == Union(
Interval.open(-oo, 0), Interval.open(0, oo))
assert continuous_domain(acot(x), x, S.Reals).contains(0) == False
assert continuous_domain(1/(exp(x) - x), x, S.Reals) == Complement(
S.Reals, ConditionSet(x, Eq(-x + exp(x), 0), S.Reals))
assert continuous_domain(frac(x**2), x, Interval(-2,-1)) == Union(
Interval.open(-2, -sqrt(3)), Interval.open(-sqrt(2), -1),
Interval.open(-sqrt(3), -sqrt(2)))
assert continuous_domain(frac(x), x, S.Reals) == Complement(
S.Reals, S.Integers)
raises(NotImplementedError, lambda : continuous_domain(
1/(x**2+1), x, S.Complexes))
raises(NotImplementedError, lambda : continuous_domain(
gamma(x), x, Interval(-5,0)))
assert continuous_domain(x + gamma(pi), x, S.Reals) == S.Reals
@XFAIL
def test_continuous_domain_acot():
acot_cont = Piecewise((pi+acot(x), x<0), (acot(x), True))
assert continuous_domain(acot_cont, x, S.Reals) == S.Reals
@XFAIL
def test_continuous_domain_gamma():
assert continuous_domain(gamma(x), x, S.Reals).contains(-1) == False
@XFAIL
def test_continuous_domain_neg_power():
assert continuous_domain((x-2)**(1-x), x, S.Reals) == Interval.open(2, oo)
def test_not_empty_in():
assert not_empty_in(FiniteSet(x, 2*x).intersect(Interval(1, 2, True, False)), x) == \
Interval(S.Half, 2, True, False)
assert not_empty_in(FiniteSet(x, x**2).intersect(Interval(1, 2)), x) == \
Union(Interval(-sqrt(2), -1), Interval(1, 2))
assert not_empty_in(FiniteSet(x**2 + x, x).intersect(Interval(2, 4)), x) == \
Union(Interval(-sqrt(17)/2 - S.Half, -2),
Interval(1, Rational(-1, 2) + sqrt(17)/2), Interval(2, 4))
assert not_empty_in(FiniteSet(x/(x - 1)).intersect(S.Reals), x) == \
Complement(S.Reals, FiniteSet(1))
assert not_empty_in(FiniteSet(a/(a - 1)).intersect(S.Reals), a) == \
Complement(S.Reals, FiniteSet(1))
assert not_empty_in(FiniteSet((x**2 - 3*x + 2)/(x - 1)).intersect(S.Reals), x) == \
Complement(S.Reals, FiniteSet(1))
assert not_empty_in(FiniteSet(3, 4, x/(x - 1)).intersect(Interval(2, 3)), x) == \
Interval(-oo, oo)
assert not_empty_in(FiniteSet(4, x/(x - 1)).intersect(Interval(2, 3)), x) == \
Interval(S(3)/2, 2)
assert not_empty_in(FiniteSet(x/(x**2 - 1)).intersect(S.Reals), x) == \
Complement(S.Reals, FiniteSet(-1, 1))
assert not_empty_in(FiniteSet(x, x**2).intersect(Union(Interval(1, 3, True, True),
Interval(4, 5))), x) == \
Union(Interval(-sqrt(5), -2), Interval(-sqrt(3), -1, True, True),
Interval(1, 3, True, True), Interval(4, 5))
assert not_empty_in(FiniteSet(1).intersect(Interval(3, 4)), x) == S.EmptySet
assert not_empty_in(FiniteSet(x**2/(x + 2)).intersect(Interval(1, oo)), x) == \
Union(Interval(-2, -1, True, False), Interval(2, oo))
raises(ValueError, lambda: not_empty_in(x))
raises(ValueError, lambda: not_empty_in(Interval(0, 1), x))
raises(NotImplementedError,
lambda: not_empty_in(FiniteSet(x).intersect(S.Reals), x, a))
@_both_exp_pow
def test_periodicity():
assert periodicity(sin(2*x), x) == pi
assert periodicity((-2)*tan(4*x), x) == pi/4
assert periodicity(sin(x)**2, x) == 2*pi
assert periodicity(3**tan(3*x), x) == pi/3
assert periodicity(tan(x)*cos(x), x) == 2*pi
assert periodicity(sin(x)**(tan(x)), x) == 2*pi
assert periodicity(tan(x)*sec(x), x) == 2*pi
assert periodicity(sin(2*x)*cos(2*x) - y, x) == pi/2
assert periodicity(tan(x) + cot(x), x) == pi
assert periodicity(sin(x) - cos(2*x), x) == 2*pi
assert periodicity(sin(x) - 1, x) == 2*pi
assert periodicity(sin(4*x) + sin(x)*cos(x), x) == pi
assert periodicity(exp(sin(x)), x) == 2*pi
assert periodicity(log(cot(2*x)) - sin(cos(2*x)), x) == pi
assert periodicity(sin(2*x)*exp(tan(x) - csc(2*x)), x) == pi
assert periodicity(cos(sec(x) - csc(2*x)), x) == 2*pi
assert periodicity(tan(sin(2*x)), x) == pi
assert periodicity(2*tan(x)**2, x) == pi
assert periodicity(sin(x%4), x) == 4
assert periodicity(sin(x)%4, x) == 2*pi
assert periodicity(tan((3*x-2)%4), x) == Rational(4, 3)
assert periodicity((sqrt(2)*(x+1)+x) % 3, x) == 3 / (sqrt(2)+1)
assert periodicity((x**2+1) % x, x) is None
assert periodicity(sin(re(x)), x) == 2*pi
assert periodicity(sin(x)**2 + cos(x)**2, x) is S.Zero
assert periodicity(tan(x), y) is S.Zero
assert periodicity(sin(x) + I*cos(x), x) == 2*pi
assert periodicity(x - sin(2*y), y) == pi
assert periodicity(exp(x), x) is None
assert periodicity(exp(I*x), x) == 2*pi
assert periodicity(exp(I*a), a) == 2*pi
assert periodicity(exp(a), a) is None
assert periodicity(exp(log(sin(a) + I*cos(2*a)), evaluate=False), a) == 2*pi
assert periodicity(exp(log(sin(2*a) + I*cos(a)), evaluate=False), a) == 2*pi
assert periodicity(exp(sin(a)), a) == 2*pi
assert periodicity(exp(2*I*a), a) == pi
assert periodicity(exp(a + I*sin(a)), a) is None
assert periodicity(exp(cos(a/2) + sin(a)), a) == 4*pi
assert periodicity(log(x), x) is None
assert periodicity(exp(x)**sin(x), x) is None
assert periodicity(sin(x)**y, y) is None
assert periodicity(Abs(sin(Abs(sin(x)))), x) == pi
assert all(periodicity(Abs(f(x)), x) == pi for f in (
cos, sin, sec, csc, tan, cot))
assert periodicity(Abs(sin(tan(x))), x) == pi
assert periodicity(Abs(sin(sin(x) + tan(x))), x) == 2*pi
assert periodicity(sin(x) > S.Half, x) == 2*pi
assert periodicity(x > 2, x) is None
assert periodicity(x**3 - x**2 + 1, x) is None
assert periodicity(Abs(x), x) is None
assert periodicity(Abs(x**2 - 1), x) is None
assert periodicity((x**2 + 4)%2, x) is None
assert periodicity((E**x)%3, x) is None
assert periodicity(sin(expint(1, x))/expint(1, x), x) is None
# returning `None` for any Piecewise
p = Piecewise((0, x < -1), (x**2, x <= 1), (log(x), True))
assert periodicity(p, x) is None
m = MatrixSymbol('m', 3, 3)
raises(NotImplementedError, lambda: periodicity(sin(m), m))
raises(NotImplementedError, lambda: periodicity(sin(m[0, 0]), m))
raises(NotImplementedError, lambda: periodicity(sin(m), m[0, 0]))
raises(NotImplementedError, lambda: periodicity(sin(m[0, 0]), m[0, 0]))
def test_periodicity_check():
assert periodicity(tan(x), x, check=True) == pi
assert periodicity(sin(x) + cos(x), x, check=True) == 2*pi
assert periodicity(sec(x), x) == 2*pi
assert periodicity(sin(x*y), x) == 2*pi/abs(y)
assert periodicity(Abs(sec(sec(x))), x) == pi
def test_lcim():
assert lcim([S.Half, S(2), S(3)]) == 6
assert lcim([pi/2, pi/4, pi]) == pi
assert lcim([2*pi, pi/2]) == 2*pi
assert lcim([S.One, 2*pi]) is None
assert lcim([S(2) + 2*E, E/3 + Rational(1, 3), S.One + E]) == S(2) + 2*E
def test_is_convex():
assert is_convex(1/x, x, domain=Interval.open(0, oo)) == True
assert is_convex(1/x, x, domain=Interval(-oo, 0)) == False
assert is_convex(x**2, x, domain=Interval(0, oo)) == True
assert is_convex(1/x**3, x, domain=Interval.Lopen(0, oo)) == True
assert is_convex(-1/x**3, x, domain=Interval.Ropen(-oo, 0)) == True
assert is_convex(log(x) ,x) == False
assert is_convex(x**2+y**2, x, y) == True
assert is_convex(cos(x) + cos(y), x) == False
assert is_convex(8*x**2 - 2*y**2, x, y) == False
def test_stationary_points():
assert stationary_points(sin(x), x, Interval(-pi/2, pi/2)
) == {-pi/2, pi/2}
assert stationary_points(sin(x), x, Interval.Ropen(0, pi/4)
) is S.EmptySet
assert stationary_points(tan(x), x,
) is S.EmptySet
assert stationary_points(sin(x)*cos(x), x, Interval(0, pi)
) == {pi/4, pi*Rational(3, 4)}
assert stationary_points(sec(x), x, Interval(0, pi)
) == {0, pi}
assert stationary_points((x+3)*(x-2), x
) == FiniteSet(Rational(-1, 2))
assert stationary_points((x + 3)/(x - 2), x, Interval(-5, 5)
) is S.EmptySet
assert stationary_points((x**2+3)/(x-2), x
) == {2 - sqrt(7), 2 + sqrt(7)}
assert stationary_points((x**2+3)/(x-2), x, Interval(0, 5)
) == {2 + sqrt(7)}
assert stationary_points(x**4 + x**3 - 5*x**2, x, S.Reals
) == FiniteSet(-2, 0, Rational(5, 4))
assert stationary_points(exp(x), x
) is S.EmptySet
assert stationary_points(log(x) - x, x, S.Reals
) == {1}
assert stationary_points(cos(x), x, Union(Interval(0, 5), Interval(-6, -3))
) == {0, -pi, pi}
assert stationary_points(y, x, S.Reals
) == S.Reals
assert stationary_points(y, x, S.EmptySet) == S.EmptySet
def test_maximum():
assert maximum(sin(x), x) is S.One
assert maximum(sin(x), x, Interval(0, 1)) == sin(1)
assert maximum(tan(x), x) is oo
assert maximum(tan(x), x, Interval(-pi/4, pi/4)) is S.One
assert maximum(sin(x)*cos(x), x, S.Reals) == S.Half
assert simplify(maximum(sin(x)*cos(x), x, Interval(pi*Rational(3, 8), pi*Rational(5, 8)))
) == sqrt(2)/4
assert maximum((x+3)*(x-2), x) is oo
assert maximum((x+3)*(x-2), x, Interval(-5, 0)) == S(14)
assert maximum((x+3)/(x-2), x, Interval(-5, 0)) == Rational(2, 7)
assert simplify(maximum(-x**4-x**3+x**2+10, x)
) == 41*sqrt(41)/512 + Rational(5419, 512)
assert maximum(exp(x), x, Interval(-oo, 2)) == exp(2)
assert maximum(log(x) - x, x, S.Reals) is S.NegativeOne
assert maximum(cos(x), x, Union(Interval(0, 5), Interval(-6, -3))
) is S.One
assert maximum(cos(x)-sin(x), x, S.Reals) == sqrt(2)
assert maximum(y, x, S.Reals) == y
assert maximum(abs(a**3 + a), a, Interval(0, 2)) == 10
assert maximum(abs(60*a**3 + 24*a), a, Interval(0, 2)) == 528
assert maximum(abs(12*a*(5*a**2 + 2)), a, Interval(0, 2)) == 528
assert maximum(x/sqrt(x**2+1), x, S.Reals) == 1
raises(ValueError, lambda : maximum(sin(x), x, S.EmptySet))
raises(ValueError, lambda : maximum(log(cos(x)), x, S.EmptySet))
raises(ValueError, lambda : maximum(1/(x**2 + y**2 + 1), x, S.EmptySet))
raises(ValueError, lambda : maximum(sin(x), sin(x)))
raises(ValueError, lambda : maximum(sin(x), x*y, S.EmptySet))
raises(ValueError, lambda : maximum(sin(x), S.One))
def test_minimum():
assert minimum(sin(x), x) is S.NegativeOne
assert minimum(sin(x), x, Interval(1, 4)) == sin(4)
assert minimum(tan(x), x) is -oo
assert minimum(tan(x), x, Interval(-pi/4, pi/4)) is S.NegativeOne
assert minimum(sin(x)*cos(x), x, S.Reals) == Rational(-1, 2)
assert simplify(minimum(sin(x)*cos(x), x, Interval(pi*Rational(3, 8), pi*Rational(5, 8)))
) == -sqrt(2)/4
assert minimum((x+3)*(x-2), x) == Rational(-25, 4)
assert minimum((x+3)/(x-2), x, Interval(-5, 0)) == Rational(-3, 2)
assert minimum(x**4-x**3+x**2+10, x) == S(10)
assert minimum(exp(x), x, Interval(-2, oo)) == exp(-2)
assert minimum(log(x) - x, x, S.Reals) is -oo
assert minimum(cos(x), x, Union(Interval(0, 5), Interval(-6, -3))
) is S.NegativeOne
assert minimum(cos(x)-sin(x), x, S.Reals) == -sqrt(2)
assert minimum(y, x, S.Reals) == y
assert minimum(x/sqrt(x**2+1), x, S.Reals) == -1
raises(ValueError, lambda : minimum(sin(x), x, S.EmptySet))
raises(ValueError, lambda : minimum(log(cos(x)), x, S.EmptySet))
raises(ValueError, lambda : minimum(1/(x**2 + y**2 + 1), x, S.EmptySet))
raises(ValueError, lambda : minimum(sin(x), sin(x)))
raises(ValueError, lambda : minimum(sin(x), x*y, S.EmptySet))
raises(ValueError, lambda : minimum(sin(x), S.One))
def test_issue_19869():
assert (maximum(sqrt(3)*(x - 1)/(3*sqrt(x**2 + 1)), x)
) == sqrt(3)/3
def test_issue_16469():
f = abs(a)
assert function_range(f, a, S.Reals) == Interval(0, oo, False, True)
@_both_exp_pow
def test_issue_18747():
assert periodicity(exp(pi*I*(x/4 + S.Half/2)), x) == 8
def test_issue_25942():
assert (acos(x) > pi/3).as_set() == Interval.Ropen(-1, S(1)/2)
@@ -0,0 +1,895 @@
from .accumulationbounds import AccumBounds, AccumulationBounds # noqa: F401
from .singularities import singularities
from sympy.core import Pow, S
from sympy.core.function import diff, expand_mul, Function
from sympy.core.kind import NumberKind
from sympy.core.mod import Mod
from sympy.core.numbers import equal_valued
from sympy.core.relational import Relational
from sympy.core.symbol import Symbol, Dummy
from sympy.core.sympify import _sympify
from sympy.functions.elementary.complexes import Abs, im, re
from sympy.functions.elementary.exponential import exp, log
from sympy.functions.elementary.integers import frac
from sympy.functions.elementary.piecewise import Piecewise
from sympy.functions.elementary.trigonometric import (
TrigonometricFunction, sin, cos, tan, cot, csc, sec,
asin, acos, acot, atan, asec, acsc)
from sympy.functions.elementary.hyperbolic import (sinh, cosh, tanh, coth,
sech, csch, asinh, acosh, atanh, acoth, asech, acsch)
from sympy.polys.polytools import degree, lcm_list
from sympy.sets.sets import (Interval, Intersection, FiniteSet, Union,
Complement)
from sympy.sets.fancysets import ImageSet
from sympy.sets.conditionset import ConditionSet
from sympy.utilities import filldedent
from sympy.utilities.iterables import iterable
from sympy.matrices.dense import hessian
def continuous_domain(f, symbol, domain):
"""
Returns the domain on which the function expression f is continuous.
This function is limited by the ability to determine the various
singularities and discontinuities of the given function.
The result is either given as a union of intervals or constructed using
other set operations.
Parameters
==========
f : :py:class:`~.Expr`
The concerned function.
symbol : :py:class:`~.Symbol`
The variable for which the intervals are to be determined.
domain : :py:class:`~.Interval`
The domain over which the continuity of the symbol has to be checked.
Examples
========
>>> from sympy import Interval, Symbol, S, tan, log, pi, sqrt
>>> from sympy.calculus.util import continuous_domain
>>> x = Symbol('x')
>>> continuous_domain(1/x, x, S.Reals)
Union(Interval.open(-oo, 0), Interval.open(0, oo))
>>> continuous_domain(tan(x), x, Interval(0, pi))
Union(Interval.Ropen(0, pi/2), Interval.Lopen(pi/2, pi))
>>> continuous_domain(sqrt(x - 2), x, Interval(-5, 5))
Interval(2, 5)
>>> continuous_domain(log(2*x - 1), x, S.Reals)
Interval.open(1/2, oo)
Returns
=======
:py:class:`~.Interval`
Union of all intervals where the function is continuous.
Raises
======
NotImplementedError
If the method to determine continuity of such a function
has not yet been developed.
"""
from sympy.solvers.inequalities import solve_univariate_inequality
if not domain.is_subset(S.Reals):
raise NotImplementedError(filldedent('''
Domain must be a subset of S.Reals.
'''))
implemented = [Pow, exp, log, Abs, frac,
sin, cos, tan, cot, sec, csc,
asin, acos, atan, acot, asec, acsc,
sinh, cosh, tanh, coth, sech, csch,
asinh, acosh, atanh, acoth, asech, acsch]
used = [fct.func for fct in f.atoms(Function) if fct.has(symbol)]
if any(func not in implemented for func in used):
raise NotImplementedError(filldedent('''
Unable to determine the domain of the given function.
'''))
x = Symbol('x')
constraints = {
log: (x > 0,),
asin: (x >= -1, x <= 1),
acos: (x >= -1, x <= 1),
acosh: (x >= 1,),
atanh: (x > -1, x < 1),
asech: (x > 0, x <= 1)
}
constraints_union = {
asec: (x <= -1, x >= 1),
acsc: (x <= -1, x >= 1),
acoth: (x < -1, x > 1)
}
cont_domain = domain
for atom in f.atoms(Pow):
den = atom.exp.as_numer_denom()[1]
if atom.exp.is_rational and den.is_odd:
pass # 0**negative handled by singularities()
else:
constraint = solve_univariate_inequality(atom.base >= 0,
symbol).as_set()
cont_domain = Intersection(constraint, cont_domain)
for atom in f.atoms(Function):
if atom.func in constraints:
for c in constraints[atom.func]:
constraint_relational = c.subs(x, atom.args[0])
constraint_set = solve_univariate_inequality(
constraint_relational, symbol).as_set()
cont_domain = Intersection(constraint_set, cont_domain)
elif atom.func in constraints_union:
constraint_set = S.EmptySet
for c in constraints_union[atom.func]:
constraint_relational = c.subs(x, atom.args[0])
constraint_set += solve_univariate_inequality(
constraint_relational, symbol).as_set()
cont_domain = Intersection(constraint_set, cont_domain)
# XXX: the discontinuities below could be factored out in
# a new "discontinuities()".
elif atom.func == acot:
from sympy.solvers.solveset import solveset_real
# Sympy's acot() has a step discontinuity at 0. Since it's
# neither an essential singularity nor a pole, singularities()
# will not report it. But it's still relevant for determining
# the continuity of the function f.
cont_domain -= solveset_real(atom.args[0], symbol)
# Note that the above may introduce spurious discontinuities, e.g.
# for abs(acot(x)) at 0.
elif atom.func == frac:
from sympy.solvers.solveset import solveset_real
r = function_range(atom.args[0], symbol, domain)
r = Intersection(r, S.Integers)
if r.is_finite_set:
discont = S.EmptySet
for n in r:
discont += solveset_real(atom.args[0]-n, symbol)
else:
discont = ConditionSet(
symbol, S.Integers.contains(atom.args[0]), cont_domain)
cont_domain -= discont
return cont_domain - singularities(f, symbol, domain)
def function_range(f, symbol, domain):
"""
Finds the range of a function in a given domain.
This method is limited by the ability to determine the singularities and
determine limits.
Parameters
==========
f : :py:class:`~.Expr`
The concerned function.
symbol : :py:class:`~.Symbol`
The variable for which the range of function is to be determined.
domain : :py:class:`~.Interval`
The domain under which the range of the function has to be found.
Examples
========
>>> from sympy import Interval, Symbol, S, exp, log, pi, sqrt, sin, tan
>>> from sympy.calculus.util import function_range
>>> x = Symbol('x')
>>> function_range(sin(x), x, Interval(0, 2*pi))
Interval(-1, 1)
>>> function_range(tan(x), x, Interval(-pi/2, pi/2))
Interval(-oo, oo)
>>> function_range(1/x, x, S.Reals)
Union(Interval.open(-oo, 0), Interval.open(0, oo))
>>> function_range(exp(x), x, S.Reals)
Interval.open(0, oo)
>>> function_range(log(x), x, S.Reals)
Interval(-oo, oo)
>>> function_range(sqrt(x), x, Interval(-5, 9))
Interval(0, 3)
Returns
=======
:py:class:`~.Interval`
Union of all ranges for all intervals under domain where function is
continuous.
Raises
======
NotImplementedError
If any of the intervals, in the given domain, for which function
is continuous are not finite or real,
OR if the critical points of the function on the domain cannot be found.
"""
if domain is S.EmptySet:
return S.EmptySet
period = periodicity(f, symbol)
if period == S.Zero:
# the expression is constant wrt symbol
return FiniteSet(f.expand())
from sympy.series.limits import limit
from sympy.solvers.solveset import solveset
if period is not None:
if isinstance(domain, Interval):
if (domain.inf - domain.sup).is_infinite:
domain = Interval(0, period)
elif isinstance(domain, Union):
for sub_dom in domain.args:
if isinstance(sub_dom, Interval) and \
((sub_dom.inf - sub_dom.sup).is_infinite):
domain = Interval(0, period)
intervals = continuous_domain(f, symbol, domain)
range_int = S.EmptySet
if isinstance(intervals,(Interval, FiniteSet)):
interval_iter = (intervals,)
elif isinstance(intervals, Union):
interval_iter = intervals.args
else:
raise NotImplementedError("Unable to find range for the given domain.")
for interval in interval_iter:
if isinstance(interval, FiniteSet):
for singleton in interval:
if singleton in domain:
range_int += FiniteSet(f.subs(symbol, singleton))
elif isinstance(interval, Interval):
vals = S.EmptySet
critical_values = S.EmptySet
bounds = ((interval.left_open, interval.inf, '+'),
(interval.right_open, interval.sup, '-'))
for is_open, limit_point, direction in bounds:
if is_open:
critical_values += FiniteSet(limit(f, symbol, limit_point, direction))
vals += critical_values
else:
vals += FiniteSet(f.subs(symbol, limit_point))
critical_points = solveset(f.diff(symbol), symbol, interval)
if not iterable(critical_points):
raise NotImplementedError(
'Unable to find critical points for {}'.format(f))
if isinstance(critical_points, ImageSet):
raise NotImplementedError(
'Infinite number of critical points for {}'.format(f))
for critical_point in critical_points:
vals += FiniteSet(f.subs(symbol, critical_point))
left_open, right_open = False, False
if critical_values is not S.EmptySet:
if critical_values.inf == vals.inf:
left_open = True
if critical_values.sup == vals.sup:
right_open = True
range_int += Interval(vals.inf, vals.sup, left_open, right_open)
else:
raise NotImplementedError("Unable to find range for the given domain.")
return range_int
def not_empty_in(finset_intersection, *syms):
"""
Finds the domain of the functions in ``finset_intersection`` in which the
``finite_set`` is not-empty.
Parameters
==========
finset_intersection : Intersection of FiniteSet
The unevaluated intersection of FiniteSet containing
real-valued functions with Union of Sets
syms : Tuple of symbols
Symbol for which domain is to be found
Raises
======
NotImplementedError
The algorithms to find the non-emptiness of the given FiniteSet are
not yet implemented.
ValueError
The input is not valid.
RuntimeError
It is a bug, please report it to the github issue tracker
(https://github.com/sympy/sympy/issues).
Examples
========
>>> from sympy import FiniteSet, Interval, not_empty_in, oo
>>> from sympy.abc import x
>>> not_empty_in(FiniteSet(x/2).intersect(Interval(0, 1)), x)
Interval(0, 2)
>>> not_empty_in(FiniteSet(x, x**2).intersect(Interval(1, 2)), x)
Union(Interval(1, 2), Interval(-sqrt(2), -1))
>>> not_empty_in(FiniteSet(x**2/(x + 2)).intersect(Interval(1, oo)), x)
Union(Interval.Lopen(-2, -1), Interval(2, oo))
"""
# TODO: handle piecewise defined functions
# TODO: handle transcendental functions
# TODO: handle multivariate functions
if len(syms) == 0:
raise ValueError("One or more symbols must be given in syms.")
if finset_intersection is S.EmptySet:
return S.EmptySet
if isinstance(finset_intersection, Union):
elm_in_sets = finset_intersection.args[0]
return Union(not_empty_in(finset_intersection.args[1], *syms),
elm_in_sets)
if isinstance(finset_intersection, FiniteSet):
finite_set = finset_intersection
_sets = S.Reals
else:
finite_set = finset_intersection.args[1]
_sets = finset_intersection.args[0]
if not isinstance(finite_set, FiniteSet):
raise ValueError('A FiniteSet must be given, not %s: %s' %
(type(finite_set), finite_set))
if len(syms) == 1:
symb = syms[0]
else:
raise NotImplementedError('more than one variables %s not handled' %
(syms,))
def elm_domain(expr, intrvl):
""" Finds the domain of an expression in any given interval """
from sympy.solvers.solveset import solveset
_start = intrvl.start
_end = intrvl.end
_singularities = solveset(expr.as_numer_denom()[1], symb,
domain=S.Reals)
if intrvl.right_open:
if _end is S.Infinity:
_domain1 = S.Reals
else:
_domain1 = solveset(expr < _end, symb, domain=S.Reals)
else:
_domain1 = solveset(expr <= _end, symb, domain=S.Reals)
if intrvl.left_open:
if _start is S.NegativeInfinity:
_domain2 = S.Reals
else:
_domain2 = solveset(expr > _start, symb, domain=S.Reals)
else:
_domain2 = solveset(expr >= _start, symb, domain=S.Reals)
# domain in the interval
expr_with_sing = Intersection(_domain1, _domain2)
expr_domain = Complement(expr_with_sing, _singularities)
return expr_domain
if isinstance(_sets, Interval):
return Union(*[elm_domain(element, _sets) for element in finite_set])
if isinstance(_sets, Union):
_domain = S.EmptySet
for intrvl in _sets.args:
_domain_element = Union(*[elm_domain(element, intrvl)
for element in finite_set])
_domain = Union(_domain, _domain_element)
return _domain
def periodicity(f, symbol, check=False):
"""
Tests the given function for periodicity in the given symbol.
Parameters
==========
f : :py:class:`~.Expr`
The concerned function.
symbol : :py:class:`~.Symbol`
The variable for which the period is to be determined.
check : bool, optional
The flag to verify whether the value being returned is a period or not.
Returns
=======
period
The period of the function is returned.
``None`` is returned when the function is aperiodic or has a complex period.
The value of $0$ is returned as the period of a constant function.
Raises
======
NotImplementedError
The value of the period computed cannot be verified.
Notes
=====
Currently, we do not support functions with a complex period.
The period of functions having complex periodic values such
as ``exp``, ``sinh`` is evaluated to ``None``.
The value returned might not be the "fundamental" period of the given
function i.e. it may not be the smallest periodic value of the function.
The verification of the period through the ``check`` flag is not reliable
due to internal simplification of the given expression. Hence, it is set
to ``False`` by default.
Examples
========
>>> from sympy import periodicity, Symbol, sin, cos, tan, exp
>>> x = Symbol('x')
>>> f = sin(x) + sin(2*x) + sin(3*x)
>>> periodicity(f, x)
2*pi
>>> periodicity(sin(x)*cos(x), x)
pi
>>> periodicity(exp(tan(2*x) - 1), x)
pi/2
>>> periodicity(sin(4*x)**cos(2*x), x)
pi
>>> periodicity(exp(x), x)
"""
if symbol.kind is not NumberKind:
raise NotImplementedError("Cannot use symbol of kind %s" % symbol.kind)
temp = Dummy('x', real=True)
f = f.subs(symbol, temp)
symbol = temp
def _check(orig_f, period):
'''Return the checked period or raise an error.'''
new_f = orig_f.subs(symbol, symbol + period)
if new_f.equals(orig_f):
return period
else:
raise NotImplementedError(filldedent('''
The period of the given function cannot be verified.
When `%s` was replaced with `%s + %s` in `%s`, the result
was `%s` which was not recognized as being the same as
the original function.
So either the period was wrong or the two forms were
not recognized as being equal.
Set check=False to obtain the value.''' %
(symbol, symbol, period, orig_f, new_f)))
orig_f = f
period = None
if isinstance(f, Relational):
f = f.lhs - f.rhs
f = f.simplify()
if symbol not in f.free_symbols:
return S.Zero
if isinstance(f, TrigonometricFunction):
try:
period = f.period(symbol)
except NotImplementedError:
pass
if isinstance(f, Abs):
arg = f.args[0]
if isinstance(arg, (sec, csc, cos)):
# all but tan and cot might have a
# a period that is half as large
# so recast as sin
arg = sin(arg.args[0])
period = periodicity(arg, symbol)
if period is not None and isinstance(arg, sin):
# the argument of Abs was a trigonometric other than
# cot or tan; test to see if the half-period
# is valid. Abs(arg) has behaviour equivalent to
# orig_f, so use that for test:
orig_f = Abs(arg)
try:
return _check(orig_f, period/2)
except NotImplementedError as err:
if check:
raise NotImplementedError(err)
# else let new orig_f and period be
# checked below
if isinstance(f, exp) or (f.is_Pow and f.base == S.Exp1):
f = Pow(S.Exp1, expand_mul(f.exp))
if im(f) != 0:
period_real = periodicity(re(f), symbol)
period_imag = periodicity(im(f), symbol)
if period_real is not None and period_imag is not None:
period = lcim([period_real, period_imag])
if f.is_Pow and f.base != S.Exp1:
base, expo = f.args
base_has_sym = base.has(symbol)
expo_has_sym = expo.has(symbol)
if base_has_sym and not expo_has_sym:
period = periodicity(base, symbol)
elif expo_has_sym and not base_has_sym:
period = periodicity(expo, symbol)
else:
period = _periodicity(f.args, symbol)
elif f.is_Mul:
coeff, g = f.as_independent(symbol, as_Add=False)
if isinstance(g, TrigonometricFunction) or not equal_valued(coeff, 1):
period = periodicity(g, symbol)
else:
period = _periodicity(g.args, symbol)
elif f.is_Add:
k, g = f.as_independent(symbol)
if k is not S.Zero:
return periodicity(g, symbol)
period = _periodicity(g.args, symbol)
elif isinstance(f, Mod):
a, n = f.args
if a == symbol:
period = n
elif isinstance(a, TrigonometricFunction):
period = periodicity(a, symbol)
#check if 'f' is linear in 'symbol'
elif (a.is_polynomial(symbol) and degree(a, symbol) == 1 and
symbol not in n.free_symbols):
period = Abs(n / a.diff(symbol))
elif isinstance(f, Piecewise):
pass # not handling Piecewise yet as the return type is not favorable
elif period is None:
from sympy.solvers.decompogen import compogen, decompogen
g_s = decompogen(f, symbol)
num_of_gs = len(g_s)
if num_of_gs > 1:
for index, g in enumerate(reversed(g_s)):
start_index = num_of_gs - 1 - index
g = compogen(g_s[start_index:], symbol)
if g not in (orig_f, f): # Fix for issue 12620
period = periodicity(g, symbol)
if period is not None:
break
if period is not None:
if check:
return _check(orig_f, period)
return period
return None
def _periodicity(args, symbol):
"""
Helper for `periodicity` to find the period of a list of simpler
functions.
It uses the `lcim` method to find the least common period of
all the functions.
Parameters
==========
args : Tuple of :py:class:`~.Symbol`
All the symbols present in a function.
symbol : :py:class:`~.Symbol`
The symbol over which the function is to be evaluated.
Returns
=======
period
The least common period of the function for all the symbols
of the function.
``None`` if for at least one of the symbols the function is aperiodic.
"""
periods = []
for f in args:
period = periodicity(f, symbol)
if period is None:
return None
if period is not S.Zero:
periods.append(period)
if len(periods) > 1:
return lcim(periods)
if periods:
return periods[0]
def lcim(numbers):
"""Returns the least common integral multiple of a list of numbers.
The numbers can be rational or irrational or a mixture of both.
`None` is returned for incommensurable numbers.
Parameters
==========
numbers : list
Numbers (rational and/or irrational) for which lcim is to be found.
Returns
=======
number
lcim if it exists, otherwise ``None`` for incommensurable numbers.
Examples
========
>>> from sympy.calculus.util import lcim
>>> from sympy import S, pi
>>> lcim([S(1)/2, S(3)/4, S(5)/6])
15/2
>>> lcim([2*pi, 3*pi, pi, pi/2])
6*pi
>>> lcim([S(1), 2*pi])
"""
result = None
if all(num.is_irrational for num in numbers):
factorized_nums = [num.factor() for num in numbers]
factors_num = [num.as_coeff_Mul() for num in factorized_nums]
term = factors_num[0][1]
if all(factor == term for coeff, factor in factors_num):
common_term = term
coeffs = [coeff for coeff, factor in factors_num]
result = lcm_list(coeffs) * common_term
elif all(num.is_rational for num in numbers):
result = lcm_list(numbers)
else:
pass
return result
def is_convex(f, *syms, domain=S.Reals):
r"""Determines the convexity of the function passed in the argument.
Parameters
==========
f : :py:class:`~.Expr`
The concerned function.
syms : Tuple of :py:class:`~.Symbol`
The variables with respect to which the convexity is to be determined.
domain : :py:class:`~.Interval`, optional
The domain over which the convexity of the function has to be checked.
If unspecified, S.Reals will be the default domain.
Returns
=======
bool
The method returns ``True`` if the function is convex otherwise it
returns ``False``.
Raises
======
NotImplementedError
The check for the convexity of multivariate functions is not implemented yet.
Notes
=====
To determine concavity of a function pass `-f` as the concerned function.
To determine logarithmic convexity of a function pass `\log(f)` as
concerned function.
To determine logarithmic concavity of a function pass `-\log(f)` as
concerned function.
Currently, convexity check of multivariate functions is not handled.
Examples
========
>>> from sympy import is_convex, symbols, exp, oo, Interval
>>> x = symbols('x')
>>> is_convex(exp(x), x)
True
>>> is_convex(x**3, x, domain = Interval(-1, oo))
False
>>> is_convex(1/x**2, x, domain=Interval.open(0, oo))
True
References
==========
.. [1] https://en.wikipedia.org/wiki/Convex_function
.. [2] http://www.ifp.illinois.edu/~angelia/L3_convfunc.pdf
.. [3] https://en.wikipedia.org/wiki/Logarithmically_convex_function
.. [4] https://en.wikipedia.org/wiki/Logarithmically_concave_function
.. [5] https://en.wikipedia.org/wiki/Concave_function
"""
if len(syms) > 1 :
return hessian(f, syms).is_positive_semidefinite
from sympy.solvers.inequalities import solve_univariate_inequality
f = _sympify(f)
var = syms[0]
if any(s in domain for s in singularities(f, var)):
return False
condition = f.diff(var, 2) < 0
if solve_univariate_inequality(condition, var, False, domain):
return False
return True
def stationary_points(f, symbol, domain=S.Reals):
"""
Returns the stationary points of a function (where derivative of the
function is 0) in the given domain.
Parameters
==========
f : :py:class:`~.Expr`
The concerned function.
symbol : :py:class:`~.Symbol`
The variable for which the stationary points are to be determined.
domain : :py:class:`~.Interval`
The domain over which the stationary points have to be checked.
If unspecified, ``S.Reals`` will be the default domain.
Returns
=======
Set
A set of stationary points for the function. If there are no
stationary point, an :py:class:`~.EmptySet` is returned.
Examples
========
>>> from sympy import Interval, Symbol, S, sin, pi, pprint, stationary_points
>>> x = Symbol('x')
>>> stationary_points(1/x, x, S.Reals)
EmptySet
>>> pprint(stationary_points(sin(x), x), use_unicode=False)
pi 3*pi
{2*n*pi + -- | n in Integers} U {2*n*pi + ---- | n in Integers}
2 2
>>> stationary_points(sin(x),x, Interval(0, 4*pi))
{pi/2, 3*pi/2, 5*pi/2, 7*pi/2}
"""
from sympy.solvers.solveset import solveset
if domain is S.EmptySet:
return S.EmptySet
domain = continuous_domain(f, symbol, domain)
set = solveset(diff(f, symbol), symbol, domain)
return set
def maximum(f, symbol, domain=S.Reals):
"""
Returns the maximum value of a function in the given domain.
Parameters
==========
f : :py:class:`~.Expr`
The concerned function.
symbol : :py:class:`~.Symbol`
The variable for maximum value needs to be determined.
domain : :py:class:`~.Interval`
The domain over which the maximum have to be checked.
If unspecified, then the global maximum is returned.
Returns
=======
number
Maximum value of the function in given domain.
Examples
========
>>> from sympy import Interval, Symbol, S, sin, cos, pi, maximum
>>> x = Symbol('x')
>>> f = -x**2 + 2*x + 5
>>> maximum(f, x, S.Reals)
6
>>> maximum(sin(x), x, Interval(-pi, pi/4))
sqrt(2)/2
>>> maximum(sin(x)*cos(x), x)
1/2
"""
if isinstance(symbol, Symbol):
if domain is S.EmptySet:
raise ValueError("Maximum value not defined for empty domain.")
return function_range(f, symbol, domain).sup
else:
raise ValueError("%s is not a valid symbol." % symbol)
def minimum(f, symbol, domain=S.Reals):
"""
Returns the minimum value of a function in the given domain.
Parameters
==========
f : :py:class:`~.Expr`
The concerned function.
symbol : :py:class:`~.Symbol`
The variable for minimum value needs to be determined.
domain : :py:class:`~.Interval`
The domain over which the minimum have to be checked.
If unspecified, then the global minimum is returned.
Returns
=======
number
Minimum value of the function in the given domain.
Examples
========
>>> from sympy import Interval, Symbol, S, sin, cos, minimum
>>> x = Symbol('x')
>>> f = x**2 + 2*x + 5
>>> minimum(f, x, S.Reals)
4
>>> minimum(sin(x), x, Interval(2, 3))
sin(3)
>>> minimum(sin(x)*cos(x), x)
-1/2
"""
if isinstance(symbol, Symbol):
if domain is S.EmptySet:
raise ValueError("Minimum value not defined for empty domain.")
return function_range(f, symbol, domain).inf
else:
raise ValueError("%s is not a valid symbol." % symbol)
@@ -0,0 +1,33 @@
"""
Category Theory module.
Provides some of the fundamental category-theory-related classes,
including categories, morphisms, diagrams. Functors are not
implemented yet.
The general reference work this module tries to follow is
[JoyOfCats] J. Adamek, H. Herrlich. G. E. Strecker: Abstract and
Concrete Categories. The Joy of Cats.
The latest version of this book should be available for free download
from
katmat.math.uni-bremen.de/acc/acc.pdf
"""
from .baseclasses import (Object, Morphism, IdentityMorphism,
NamedMorphism, CompositeMorphism, Category,
Diagram)
from .diagram_drawing import (DiagramGrid, XypicDiagramDrawer,
xypic_draw_diagram, preview_diagram)
__all__ = [
'Object', 'Morphism', 'IdentityMorphism', 'NamedMorphism',
'CompositeMorphism', 'Category', 'Diagram',
'DiagramGrid', 'XypicDiagramDrawer', 'xypic_draw_diagram',
'preview_diagram',
]
@@ -0,0 +1,978 @@
from sympy.core import S, Basic, Dict, Symbol, Tuple, sympify
from sympy.core.symbol import Str
from sympy.sets import Set, FiniteSet, EmptySet
from sympy.utilities.iterables import iterable
class Class(Set):
r"""
The base class for any kind of class in the set-theoretic sense.
Explanation
===========
In axiomatic set theories, everything is a class. A class which
can be a member of another class is a set. A class which is not a
member of another class is a proper class. The class `\{1, 2\}`
is a set; the class of all sets is a proper class.
This class is essentially a synonym for :class:`sympy.core.Set`.
The goal of this class is to assure easier migration to the
eventual proper implementation of set theory.
"""
is_proper = False
class Object(Symbol):
"""
The base class for any kind of object in an abstract category.
Explanation
===========
While technically any instance of :class:`~.Basic` will do, this
class is the recommended way to create abstract objects in
abstract categories.
"""
class Morphism(Basic):
"""
The base class for any morphism in an abstract category.
Explanation
===========
In abstract categories, a morphism is an arrow between two
category objects. The object where the arrow starts is called the
domain, while the object where the arrow ends is called the
codomain.
Two morphisms between the same pair of objects are considered to
be the same morphisms. To distinguish between morphisms between
the same objects use :class:`NamedMorphism`.
It is prohibited to instantiate this class. Use one of the
derived classes instead.
See Also
========
IdentityMorphism, NamedMorphism, CompositeMorphism
"""
def __new__(cls, domain, codomain):
raise(NotImplementedError(
"Cannot instantiate Morphism. Use derived classes instead."))
@property
def domain(self):
"""
Returns the domain of the morphism.
Examples
========
>>> from sympy.categories import Object, NamedMorphism
>>> A = Object("A")
>>> B = Object("B")
>>> f = NamedMorphism(A, B, "f")
>>> f.domain
Object("A")
"""
return self.args[0]
@property
def codomain(self):
"""
Returns the codomain of the morphism.
Examples
========
>>> from sympy.categories import Object, NamedMorphism
>>> A = Object("A")
>>> B = Object("B")
>>> f = NamedMorphism(A, B, "f")
>>> f.codomain
Object("B")
"""
return self.args[1]
def compose(self, other):
r"""
Composes self with the supplied morphism.
The order of elements in the composition is the usual order,
i.e., to construct `g\circ f` use ``g.compose(f)``.
Examples
========
>>> from sympy.categories import Object, NamedMorphism
>>> A = Object("A")
>>> B = Object("B")
>>> C = Object("C")
>>> f = NamedMorphism(A, B, "f")
>>> g = NamedMorphism(B, C, "g")
>>> g * f
CompositeMorphism((NamedMorphism(Object("A"), Object("B"), "f"),
NamedMorphism(Object("B"), Object("C"), "g")))
>>> (g * f).domain
Object("A")
>>> (g * f).codomain
Object("C")
"""
return CompositeMorphism(other, self)
def __mul__(self, other):
r"""
Composes self with the supplied morphism.
The semantics of this operation is given by the following
equation: ``g * f == g.compose(f)`` for composable morphisms
``g`` and ``f``.
See Also
========
compose
"""
return self.compose(other)
class IdentityMorphism(Morphism):
"""
Represents an identity morphism.
Explanation
===========
An identity morphism is a morphism with equal domain and codomain,
which acts as an identity with respect to composition.
Examples
========
>>> from sympy.categories import Object, NamedMorphism, IdentityMorphism
>>> A = Object("A")
>>> B = Object("B")
>>> f = NamedMorphism(A, B, "f")
>>> id_A = IdentityMorphism(A)
>>> id_B = IdentityMorphism(B)
>>> f * id_A == f
True
>>> id_B * f == f
True
See Also
========
Morphism
"""
def __new__(cls, domain):
return Basic.__new__(cls, domain)
@property
def codomain(self):
return self.domain
class NamedMorphism(Morphism):
"""
Represents a morphism which has a name.
Explanation
===========
Names are used to distinguish between morphisms which have the
same domain and codomain: two named morphisms are equal if they
have the same domains, codomains, and names.
Examples
========
>>> from sympy.categories import Object, NamedMorphism
>>> A = Object("A")
>>> B = Object("B")
>>> f = NamedMorphism(A, B, "f")
>>> f
NamedMorphism(Object("A"), Object("B"), "f")
>>> f.name
'f'
See Also
========
Morphism
"""
def __new__(cls, domain, codomain, name):
if not name:
raise ValueError("Empty morphism names not allowed.")
if not isinstance(name, Str):
name = Str(name)
return Basic.__new__(cls, domain, codomain, name)
@property
def name(self):
"""
Returns the name of the morphism.
Examples
========
>>> from sympy.categories import Object, NamedMorphism
>>> A = Object("A")
>>> B = Object("B")
>>> f = NamedMorphism(A, B, "f")
>>> f.name
'f'
"""
return self.args[2].name
class CompositeMorphism(Morphism):
r"""
Represents a morphism which is a composition of other morphisms.
Explanation
===========
Two composite morphisms are equal if the morphisms they were
obtained from (components) are the same and were listed in the
same order.
The arguments to the constructor for this class should be listed
in diagram order: to obtain the composition `g\circ f` from the
instances of :class:`Morphism` ``g`` and ``f`` use
``CompositeMorphism(f, g)``.
Examples
========
>>> from sympy.categories import Object, NamedMorphism, CompositeMorphism
>>> A = Object("A")
>>> B = Object("B")
>>> C = Object("C")
>>> f = NamedMorphism(A, B, "f")
>>> g = NamedMorphism(B, C, "g")
>>> g * f
CompositeMorphism((NamedMorphism(Object("A"), Object("B"), "f"),
NamedMorphism(Object("B"), Object("C"), "g")))
>>> CompositeMorphism(f, g) == g * f
True
"""
@staticmethod
def _add_morphism(t, morphism):
"""
Intelligently adds ``morphism`` to tuple ``t``.
Explanation
===========
If ``morphism`` is a composite morphism, its components are
added to the tuple. If ``morphism`` is an identity, nothing
is added to the tuple.
No composability checks are performed.
"""
if isinstance(morphism, CompositeMorphism):
# ``morphism`` is a composite morphism; we have to
# denest its components.
return t + morphism.components
elif isinstance(morphism, IdentityMorphism):
# ``morphism`` is an identity. Nothing happens.
return t
else:
return t + Tuple(morphism)
def __new__(cls, *components):
if components and not isinstance(components[0], Morphism):
# Maybe the user has explicitly supplied a list of
# morphisms.
return CompositeMorphism.__new__(cls, *components[0])
normalised_components = Tuple()
for current, following in zip(components, components[1:]):
if not isinstance(current, Morphism) or \
not isinstance(following, Morphism):
raise TypeError("All components must be morphisms.")
if current.codomain != following.domain:
raise ValueError("Uncomposable morphisms.")
normalised_components = CompositeMorphism._add_morphism(
normalised_components, current)
# We haven't added the last morphism to the list of normalised
# components. Add it now.
normalised_components = CompositeMorphism._add_morphism(
normalised_components, components[-1])
if not normalised_components:
# If ``normalised_components`` is empty, only identities
# were supplied. Since they all were composable, they are
# all the same identities.
return components[0]
elif len(normalised_components) == 1:
# No sense to construct a whole CompositeMorphism.
return normalised_components[0]
return Basic.__new__(cls, normalised_components)
@property
def components(self):
"""
Returns the components of this composite morphism.
Examples
========
>>> from sympy.categories import Object, NamedMorphism
>>> A = Object("A")
>>> B = Object("B")
>>> C = Object("C")
>>> f = NamedMorphism(A, B, "f")
>>> g = NamedMorphism(B, C, "g")
>>> (g * f).components
(NamedMorphism(Object("A"), Object("B"), "f"),
NamedMorphism(Object("B"), Object("C"), "g"))
"""
return self.args[0]
@property
def domain(self):
"""
Returns the domain of this composite morphism.
The domain of the composite morphism is the domain of its
first component.
Examples
========
>>> from sympy.categories import Object, NamedMorphism
>>> A = Object("A")
>>> B = Object("B")
>>> C = Object("C")
>>> f = NamedMorphism(A, B, "f")
>>> g = NamedMorphism(B, C, "g")
>>> (g * f).domain
Object("A")
"""
return self.components[0].domain
@property
def codomain(self):
"""
Returns the codomain of this composite morphism.
The codomain of the composite morphism is the codomain of its
last component.
Examples
========
>>> from sympy.categories import Object, NamedMorphism
>>> A = Object("A")
>>> B = Object("B")
>>> C = Object("C")
>>> f = NamedMorphism(A, B, "f")
>>> g = NamedMorphism(B, C, "g")
>>> (g * f).codomain
Object("C")
"""
return self.components[-1].codomain
def flatten(self, new_name):
"""
Forgets the composite structure of this morphism.
Explanation
===========
If ``new_name`` is not empty, returns a :class:`NamedMorphism`
with the supplied name, otherwise returns a :class:`Morphism`.
In both cases the domain of the new morphism is the domain of
this composite morphism and the codomain of the new morphism
is the codomain of this composite morphism.
Examples
========
>>> from sympy.categories import Object, NamedMorphism
>>> A = Object("A")
>>> B = Object("B")
>>> C = Object("C")
>>> f = NamedMorphism(A, B, "f")
>>> g = NamedMorphism(B, C, "g")
>>> (g * f).flatten("h")
NamedMorphism(Object("A"), Object("C"), "h")
"""
return NamedMorphism(self.domain, self.codomain, new_name)
class Category(Basic):
r"""
An (abstract) category.
Explanation
===========
A category [JoyOfCats] is a quadruple `\mbox{K} = (O, \hom, id,
\circ)` consisting of
* a (set-theoretical) class `O`, whose members are called
`K`-objects,
* for each pair `(A, B)` of `K`-objects, a set `\hom(A, B)` whose
members are called `K`-morphisms from `A` to `B`,
* for a each `K`-object `A`, a morphism `id:A\rightarrow A`,
called the `K`-identity of `A`,
* a composition law `\circ` associating with every `K`-morphisms
`f:A\rightarrow B` and `g:B\rightarrow C` a `K`-morphism `g\circ
f:A\rightarrow C`, called the composite of `f` and `g`.
Composition is associative, `K`-identities are identities with
respect to composition, and the sets `\hom(A, B)` are pairwise
disjoint.
This class knows nothing about its objects and morphisms.
Concrete cases of (abstract) categories should be implemented as
classes derived from this one.
Certain instances of :class:`Diagram` can be asserted to be
commutative in a :class:`Category` by supplying the argument
``commutative_diagrams`` in the constructor.
Examples
========
>>> from sympy.categories import Object, NamedMorphism, Diagram, Category
>>> from sympy import FiniteSet
>>> A = Object("A")
>>> B = Object("B")
>>> C = Object("C")
>>> f = NamedMorphism(A, B, "f")
>>> g = NamedMorphism(B, C, "g")
>>> d = Diagram([f, g])
>>> K = Category("K", commutative_diagrams=[d])
>>> K.commutative_diagrams == FiniteSet(d)
True
See Also
========
Diagram
"""
def __new__(cls, name, objects=EmptySet, commutative_diagrams=EmptySet):
if not name:
raise ValueError("A Category cannot have an empty name.")
if not isinstance(name, Str):
name = Str(name)
if not isinstance(objects, Class):
objects = Class(objects)
new_category = Basic.__new__(cls, name, objects,
FiniteSet(*commutative_diagrams))
return new_category
@property
def name(self):
"""
Returns the name of this category.
Examples
========
>>> from sympy.categories import Category
>>> K = Category("K")
>>> K.name
'K'
"""
return self.args[0].name
@property
def objects(self):
"""
Returns the class of objects of this category.
Examples
========
>>> from sympy.categories import Object, Category
>>> from sympy import FiniteSet
>>> A = Object("A")
>>> B = Object("B")
>>> K = Category("K", FiniteSet(A, B))
>>> K.objects
Class({Object("A"), Object("B")})
"""
return self.args[1]
@property
def commutative_diagrams(self):
"""
Returns the :class:`~.FiniteSet` of diagrams which are known to
be commutative in this category.
Examples
========
>>> from sympy.categories import Object, NamedMorphism, Diagram, Category
>>> from sympy import FiniteSet
>>> A = Object("A")
>>> B = Object("B")
>>> C = Object("C")
>>> f = NamedMorphism(A, B, "f")
>>> g = NamedMorphism(B, C, "g")
>>> d = Diagram([f, g])
>>> K = Category("K", commutative_diagrams=[d])
>>> K.commutative_diagrams == FiniteSet(d)
True
"""
return self.args[2]
def hom(self, A, B):
raise NotImplementedError(
"hom-sets are not implemented in Category.")
def all_morphisms(self):
raise NotImplementedError(
"Obtaining the class of morphisms is not implemented in Category.")
class Diagram(Basic):
r"""
Represents a diagram in a certain category.
Explanation
===========
Informally, a diagram is a collection of objects of a category and
certain morphisms between them. A diagram is still a monoid with
respect to morphism composition; i.e., identity morphisms, as well
as all composites of morphisms included in the diagram belong to
the diagram. For a more formal approach to this notion see
[Pare1970].
The components of composite morphisms are also added to the
diagram. No properties are assigned to such morphisms by default.
A commutative diagram is often accompanied by a statement of the
following kind: "if such morphisms with such properties exist,
then such morphisms which such properties exist and the diagram is
commutative". To represent this, an instance of :class:`Diagram`
includes a collection of morphisms which are the premises and
another collection of conclusions. ``premises`` and
``conclusions`` associate morphisms belonging to the corresponding
categories with the :class:`~.FiniteSet`'s of their properties.
The set of properties of a composite morphism is the intersection
of the sets of properties of its components. The domain and
codomain of a conclusion morphism should be among the domains and
codomains of the morphisms listed as the premises of a diagram.
No checks are carried out of whether the supplied object and
morphisms do belong to one and the same category.
Examples
========
>>> from sympy.categories import Object, NamedMorphism, Diagram
>>> from sympy import pprint, default_sort_key
>>> A = Object("A")
>>> B = Object("B")
>>> C = Object("C")
>>> f = NamedMorphism(A, B, "f")
>>> g = NamedMorphism(B, C, "g")
>>> d = Diagram([f, g])
>>> premises_keys = sorted(d.premises.keys(), key=default_sort_key)
>>> pprint(premises_keys, use_unicode=False)
[g*f:A-->C, id:A-->A, id:B-->B, id:C-->C, f:A-->B, g:B-->C]
>>> pprint(d.premises, use_unicode=False)
{g*f:A-->C: EmptySet, id:A-->A: EmptySet, id:B-->B: EmptySet,
id:C-->C: EmptySet, f:A-->B: EmptySet, g:B-->C: EmptySet}
>>> d = Diagram([f, g], {g * f: "unique"})
>>> pprint(d.conclusions,use_unicode=False)
{g*f:A-->C: {unique}}
References
==========
[Pare1970] B. Pareigis: Categories and functors. Academic Press, 1970.
"""
@staticmethod
def _set_dict_union(dictionary, key, value):
"""
If ``key`` is in ``dictionary``, set the new value of ``key``
to be the union between the old value and ``value``.
Otherwise, set the value of ``key`` to ``value.
Returns ``True`` if the key already was in the dictionary and
``False`` otherwise.
"""
if key in dictionary:
dictionary[key] = dictionary[key] | value
return True
else:
dictionary[key] = value
return False
@staticmethod
def _add_morphism_closure(morphisms, morphism, props, add_identities=True,
recurse_composites=True):
"""
Adds a morphism and its attributes to the supplied dictionary
``morphisms``. If ``add_identities`` is True, also adds the
identity morphisms for the domain and the codomain of
``morphism``.
"""
if not Diagram._set_dict_union(morphisms, morphism, props):
# We have just added a new morphism.
if isinstance(morphism, IdentityMorphism):
if props:
# Properties for identity morphisms don't really
# make sense, because very much is known about
# identity morphisms already, so much that they
# are trivial. Having properties for identity
# morphisms would only be confusing.
raise ValueError(
"Instances of IdentityMorphism cannot have properties.")
return
if add_identities:
empty = EmptySet
id_dom = IdentityMorphism(morphism.domain)
id_cod = IdentityMorphism(morphism.codomain)
Diagram._set_dict_union(morphisms, id_dom, empty)
Diagram._set_dict_union(morphisms, id_cod, empty)
for existing_morphism, existing_props in list(morphisms.items()):
new_props = existing_props & props
if morphism.domain == existing_morphism.codomain:
left = morphism * existing_morphism
Diagram._set_dict_union(morphisms, left, new_props)
if morphism.codomain == existing_morphism.domain:
right = existing_morphism * morphism
Diagram._set_dict_union(morphisms, right, new_props)
if isinstance(morphism, CompositeMorphism) and recurse_composites:
# This is a composite morphism, add its components as
# well.
empty = EmptySet
for component in morphism.components:
Diagram._add_morphism_closure(morphisms, component, empty,
add_identities)
def __new__(cls, *args):
"""
Construct a new instance of Diagram.
Explanation
===========
If no arguments are supplied, an empty diagram is created.
If at least an argument is supplied, ``args[0]`` is
interpreted as the premises of the diagram. If ``args[0]`` is
a list, it is interpreted as a list of :class:`Morphism`'s, in
which each :class:`Morphism` has an empty set of properties.
If ``args[0]`` is a Python dictionary or a :class:`Dict`, it
is interpreted as a dictionary associating to some
:class:`Morphism`'s some properties.
If at least two arguments are supplied ``args[1]`` is
interpreted as the conclusions of the diagram. The type of
``args[1]`` is interpreted in exactly the same way as the type
of ``args[0]``. If only one argument is supplied, the diagram
has no conclusions.
Examples
========
>>> from sympy.categories import Object, NamedMorphism
>>> from sympy.categories import IdentityMorphism, Diagram
>>> A = Object("A")
>>> B = Object("B")
>>> C = Object("C")
>>> f = NamedMorphism(A, B, "f")
>>> g = NamedMorphism(B, C, "g")
>>> d = Diagram([f, g])
>>> IdentityMorphism(A) in d.premises.keys()
True
>>> g * f in d.premises.keys()
True
>>> d = Diagram([f, g], {g * f: "unique"})
>>> d.conclusions[g * f]
{unique}
"""
premises = {}
conclusions = {}
# Here we will keep track of the objects which appear in the
# premises.
objects = EmptySet
if len(args) >= 1:
# We've got some premises in the arguments.
premises_arg = args[0]
if isinstance(premises_arg, list):
# The user has supplied a list of morphisms, none of
# which have any attributes.
empty = EmptySet
for morphism in premises_arg:
objects |= FiniteSet(morphism.domain, morphism.codomain)
Diagram._add_morphism_closure(premises, morphism, empty)
elif isinstance(premises_arg, (dict, Dict)):
# The user has supplied a dictionary of morphisms and
# their properties.
for morphism, props in premises_arg.items():
objects |= FiniteSet(morphism.domain, morphism.codomain)
Diagram._add_morphism_closure(
premises, morphism, FiniteSet(*props) if iterable(props) else FiniteSet(props))
if len(args) >= 2:
# We also have some conclusions.
conclusions_arg = args[1]
if isinstance(conclusions_arg, list):
# The user has supplied a list of morphisms, none of
# which have any attributes.
empty = EmptySet
for morphism in conclusions_arg:
# Check that no new objects appear in conclusions.
if ((sympify(objects.contains(morphism.domain)) is S.true) and
(sympify(objects.contains(morphism.codomain)) is S.true)):
# No need to add identities and recurse
# composites this time.
Diagram._add_morphism_closure(
conclusions, morphism, empty, add_identities=False,
recurse_composites=False)
elif isinstance(conclusions_arg, (dict, Dict)):
# The user has supplied a dictionary of morphisms and
# their properties.
for morphism, props in conclusions_arg.items():
# Check that no new objects appear in conclusions.
if (morphism.domain in objects) and \
(morphism.codomain in objects):
# No need to add identities and recurse
# composites this time.
Diagram._add_morphism_closure(
conclusions, morphism, FiniteSet(*props) if iterable(props) else FiniteSet(props),
add_identities=False, recurse_composites=False)
return Basic.__new__(cls, Dict(premises), Dict(conclusions), objects)
@property
def premises(self):
"""
Returns the premises of this diagram.
Examples
========
>>> from sympy.categories import Object, NamedMorphism
>>> from sympy.categories import IdentityMorphism, Diagram
>>> from sympy import pretty
>>> A = Object("A")
>>> B = Object("B")
>>> f = NamedMorphism(A, B, "f")
>>> id_A = IdentityMorphism(A)
>>> id_B = IdentityMorphism(B)
>>> d = Diagram([f])
>>> print(pretty(d.premises, use_unicode=False))
{id:A-->A: EmptySet, id:B-->B: EmptySet, f:A-->B: EmptySet}
"""
return self.args[0]
@property
def conclusions(self):
"""
Returns the conclusions of this diagram.
Examples
========
>>> from sympy.categories import Object, NamedMorphism
>>> from sympy.categories import IdentityMorphism, Diagram
>>> from sympy import FiniteSet
>>> A = Object("A")
>>> B = Object("B")
>>> C = Object("C")
>>> f = NamedMorphism(A, B, "f")
>>> g = NamedMorphism(B, C, "g")
>>> d = Diagram([f, g])
>>> IdentityMorphism(A) in d.premises.keys()
True
>>> g * f in d.premises.keys()
True
>>> d = Diagram([f, g], {g * f: "unique"})
>>> d.conclusions[g * f] == FiniteSet("unique")
True
"""
return self.args[1]
@property
def objects(self):
"""
Returns the :class:`~.FiniteSet` of objects that appear in this
diagram.
Examples
========
>>> from sympy.categories import Object, NamedMorphism, Diagram
>>> A = Object("A")
>>> B = Object("B")
>>> C = Object("C")
>>> f = NamedMorphism(A, B, "f")
>>> g = NamedMorphism(B, C, "g")
>>> d = Diagram([f, g])
>>> d.objects
{Object("A"), Object("B"), Object("C")}
"""
return self.args[2]
def hom(self, A, B):
"""
Returns a 2-tuple of sets of morphisms between objects ``A`` and
``B``: one set of morphisms listed as premises, and the other set
of morphisms listed as conclusions.
Examples
========
>>> from sympy.categories import Object, NamedMorphism, Diagram
>>> from sympy import pretty
>>> A = Object("A")
>>> B = Object("B")
>>> C = Object("C")
>>> f = NamedMorphism(A, B, "f")
>>> g = NamedMorphism(B, C, "g")
>>> d = Diagram([f, g], {g * f: "unique"})
>>> print(pretty(d.hom(A, C), use_unicode=False))
({g*f:A-->C}, {g*f:A-->C})
See Also
========
Object, Morphism
"""
premises = EmptySet
conclusions = EmptySet
for morphism in self.premises.keys():
if (morphism.domain == A) and (morphism.codomain == B):
premises |= FiniteSet(morphism)
for morphism in self.conclusions.keys():
if (morphism.domain == A) and (morphism.codomain == B):
conclusions |= FiniteSet(morphism)
return (premises, conclusions)
def is_subdiagram(self, diagram):
"""
Checks whether ``diagram`` is a subdiagram of ``self``.
Diagram `D'` is a subdiagram of `D` if all premises
(conclusions) of `D'` are contained in the premises
(conclusions) of `D`. The morphisms contained
both in `D'` and `D` should have the same properties for `D'`
to be a subdiagram of `D`.
Examples
========
>>> from sympy.categories import Object, NamedMorphism, Diagram
>>> A = Object("A")
>>> B = Object("B")
>>> C = Object("C")
>>> f = NamedMorphism(A, B, "f")
>>> g = NamedMorphism(B, C, "g")
>>> d = Diagram([f, g], {g * f: "unique"})
>>> d1 = Diagram([f])
>>> d.is_subdiagram(d1)
True
>>> d1.is_subdiagram(d)
False
"""
premises = all((m in self.premises) and
(diagram.premises[m] == self.premises[m])
for m in diagram.premises)
if not premises:
return False
conclusions = all((m in self.conclusions) and
(diagram.conclusions[m] == self.conclusions[m])
for m in diagram.conclusions)
# Premises is surely ``True`` here.
return conclusions
def subdiagram_from_objects(self, objects):
"""
If ``objects`` is a subset of the objects of ``self``, returns
a diagram which has as premises all those premises of ``self``
which have a domains and codomains in ``objects``, likewise
for conclusions. Properties are preserved.
Examples
========
>>> from sympy.categories import Object, NamedMorphism, Diagram
>>> from sympy import FiniteSet
>>> A = Object("A")
>>> B = Object("B")
>>> C = Object("C")
>>> f = NamedMorphism(A, B, "f")
>>> g = NamedMorphism(B, C, "g")
>>> d = Diagram([f, g], {f: "unique", g*f: "veryunique"})
>>> d1 = d.subdiagram_from_objects(FiniteSet(A, B))
>>> d1 == Diagram([f], {f: "unique"})
True
"""
if not objects.is_subset(self.objects):
raise ValueError(
"Supplied objects should all belong to the diagram.")
new_premises = {}
for morphism, props in self.premises.items():
if ((sympify(objects.contains(morphism.domain)) is S.true) and
(sympify(objects.contains(morphism.codomain)) is S.true)):
new_premises[morphism] = props
new_conclusions = {}
for morphism, props in self.conclusions.items():
if ((sympify(objects.contains(morphism.domain)) is S.true) and
(sympify(objects.contains(morphism.codomain)) is S.true)):
new_conclusions[morphism] = props
return Diagram(new_premises, new_conclusions)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,209 @@
from sympy.categories import (Object, Morphism, IdentityMorphism,
NamedMorphism, CompositeMorphism,
Diagram, Category)
from sympy.categories.baseclasses import Class
from sympy.testing.pytest import raises
from sympy.core.containers import (Dict, Tuple)
from sympy.sets import EmptySet
from sympy.sets.sets import FiniteSet
def test_morphisms():
A = Object("A")
B = Object("B")
C = Object("C")
D = Object("D")
# Test the base morphism.
f = NamedMorphism(A, B, "f")
assert f.domain == A
assert f.codomain == B
assert f == NamedMorphism(A, B, "f")
# Test identities.
id_A = IdentityMorphism(A)
id_B = IdentityMorphism(B)
assert id_A.domain == A
assert id_A.codomain == A
assert id_A == IdentityMorphism(A)
assert id_A != id_B
# Test named morphisms.
g = NamedMorphism(B, C, "g")
assert g.name == "g"
assert g != f
assert g == NamedMorphism(B, C, "g")
assert g != NamedMorphism(B, C, "f")
# Test composite morphisms.
assert f == CompositeMorphism(f)
k = g.compose(f)
assert k.domain == A
assert k.codomain == C
assert k.components == Tuple(f, g)
assert g * f == k
assert CompositeMorphism(f, g) == k
assert CompositeMorphism(g * f) == g * f
# Test the associativity of composition.
h = NamedMorphism(C, D, "h")
p = h * g
u = h * g * f
assert h * k == u
assert p * f == u
assert CompositeMorphism(f, g, h) == u
# Test flattening.
u2 = u.flatten("u")
assert isinstance(u2, NamedMorphism)
assert u2.name == "u"
assert u2.domain == A
assert u2.codomain == D
# Test identities.
assert f * id_A == f
assert id_B * f == f
assert id_A * id_A == id_A
assert CompositeMorphism(id_A) == id_A
# Test bad compositions.
raises(ValueError, lambda: f * g)
raises(TypeError, lambda: f.compose(None))
raises(TypeError, lambda: id_A.compose(None))
raises(TypeError, lambda: f * None)
raises(TypeError, lambda: id_A * None)
raises(TypeError, lambda: CompositeMorphism(f, None, 1))
raises(ValueError, lambda: NamedMorphism(A, B, ""))
raises(NotImplementedError, lambda: Morphism(A, B))
def test_diagram():
A = Object("A")
B = Object("B")
C = Object("C")
f = NamedMorphism(A, B, "f")
g = NamedMorphism(B, C, "g")
id_A = IdentityMorphism(A)
id_B = IdentityMorphism(B)
empty = EmptySet
# Test the addition of identities.
d1 = Diagram([f])
assert d1.objects == FiniteSet(A, B)
assert d1.hom(A, B) == (FiniteSet(f), empty)
assert d1.hom(A, A) == (FiniteSet(id_A), empty)
assert d1.hom(B, B) == (FiniteSet(id_B), empty)
assert d1 == Diagram([id_A, f])
assert d1 == Diagram([f, f])
# Test the addition of composites.
d2 = Diagram([f, g])
homAC = d2.hom(A, C)[0]
assert d2.objects == FiniteSet(A, B, C)
assert g * f in d2.premises.keys()
assert homAC == FiniteSet(g * f)
# Test equality, inequality and hash.
d11 = Diagram([f])
assert d1 == d11
assert d1 != d2
assert hash(d1) == hash(d11)
d11 = Diagram({f: "unique"})
assert d1 != d11
# Make sure that (re-)adding composites (with new properties)
# works as expected.
d = Diagram([f, g], {g * f: "unique"})
assert d.conclusions == Dict({g * f: FiniteSet("unique")})
# Check the hom-sets when there are premises and conclusions.
assert d.hom(A, C) == (FiniteSet(g * f), FiniteSet(g * f))
d = Diagram([f, g], [g * f])
assert d.hom(A, C) == (FiniteSet(g * f), FiniteSet(g * f))
# Check how the properties of composite morphisms are computed.
d = Diagram({f: ["unique", "isomorphism"], g: "unique"})
assert d.premises[g * f] == FiniteSet("unique")
# Check that conclusion morphisms with new objects are not allowed.
d = Diagram([f], [g])
assert d.conclusions == Dict({})
# Test an empty diagram.
d = Diagram()
assert d.premises == Dict({})
assert d.conclusions == Dict({})
assert d.objects == empty
# Check a SymPy Dict object.
d = Diagram(Dict({f: FiniteSet("unique", "isomorphism"), g: "unique"}))
assert d.premises[g * f] == FiniteSet("unique")
# Check the addition of components of composite morphisms.
d = Diagram([g * f])
assert f in d.premises
assert g in d.premises
# Check subdiagrams.
d = Diagram([f, g], {g * f: "unique"})
d1 = Diagram([f])
assert d.is_subdiagram(d1)
assert not d1.is_subdiagram(d)
d = Diagram([NamedMorphism(B, A, "f'")])
assert not d.is_subdiagram(d1)
assert not d1.is_subdiagram(d)
d1 = Diagram([f, g], {g * f: ["unique", "something"]})
assert not d.is_subdiagram(d1)
assert not d1.is_subdiagram(d)
d = Diagram({f: "blooh"})
d1 = Diagram({f: "bleeh"})
assert not d.is_subdiagram(d1)
assert not d1.is_subdiagram(d)
d = Diagram([f, g], {f: "unique", g * f: "veryunique"})
d1 = d.subdiagram_from_objects(FiniteSet(A, B))
assert d1 == Diagram([f], {f: "unique"})
raises(ValueError, lambda: d.subdiagram_from_objects(FiniteSet(A,
Object("D"))))
raises(ValueError, lambda: Diagram({IdentityMorphism(A): "unique"}))
def test_category():
A = Object("A")
B = Object("B")
C = Object("C")
f = NamedMorphism(A, B, "f")
g = NamedMorphism(B, C, "g")
d1 = Diagram([f, g])
d2 = Diagram([f])
objects = d1.objects | d2.objects
K = Category("K", objects, commutative_diagrams=[d1, d2])
assert K.name == "K"
assert K.objects == Class(objects)
assert K.commutative_diagrams == FiniteSet(d1, d2)
raises(ValueError, lambda: Category(""))
@@ -0,0 +1,919 @@
from sympy.categories.diagram_drawing import _GrowableGrid, ArrowStringDescription
from sympy.categories import (DiagramGrid, Object, NamedMorphism,
Diagram, XypicDiagramDrawer, xypic_draw_diagram)
from sympy.sets.sets import FiniteSet
def test_GrowableGrid():
grid = _GrowableGrid(1, 2)
# Check dimensions.
assert grid.width == 1
assert grid.height == 2
# Check initialization of elements.
assert grid[0, 0] is None
assert grid[1, 0] is None
# Check assignment to elements.
grid[0, 0] = 1
grid[1, 0] = "two"
assert grid[0, 0] == 1
assert grid[1, 0] == "two"
# Check appending a row.
grid.append_row()
assert grid.width == 1
assert grid.height == 3
assert grid[0, 0] == 1
assert grid[1, 0] == "two"
assert grid[2, 0] is None
# Check appending a column.
grid.append_column()
assert grid.width == 2
assert grid.height == 3
assert grid[0, 0] == 1
assert grid[1, 0] == "two"
assert grid[2, 0] is None
assert grid[0, 1] is None
assert grid[1, 1] is None
assert grid[2, 1] is None
grid = _GrowableGrid(1, 2)
grid[0, 0] = 1
grid[1, 0] = "two"
# Check prepending a row.
grid.prepend_row()
assert grid.width == 1
assert grid.height == 3
assert grid[0, 0] is None
assert grid[1, 0] == 1
assert grid[2, 0] == "two"
# Check prepending a column.
grid.prepend_column()
assert grid.width == 2
assert grid.height == 3
assert grid[0, 0] is None
assert grid[1, 0] is None
assert grid[2, 0] is None
assert grid[0, 1] is None
assert grid[1, 1] == 1
assert grid[2, 1] == "two"
def test_DiagramGrid():
# Set up some objects and morphisms.
A = Object("A")
B = Object("B")
C = Object("C")
D = Object("D")
E = Object("E")
f = NamedMorphism(A, B, "f")
g = NamedMorphism(B, C, "g")
h = NamedMorphism(D, A, "h")
k = NamedMorphism(D, B, "k")
# A one-morphism diagram.
d = Diagram([f])
grid = DiagramGrid(d)
assert grid.width == 2
assert grid.height == 1
assert grid[0, 0] == A
assert grid[0, 1] == B
assert grid.morphisms == {f: FiniteSet()}
# A triangle.
d = Diagram([f, g], {g * f: "unique"})
grid = DiagramGrid(d)
assert grid.width == 2
assert grid.height == 2
assert grid[0, 0] == A
assert grid[0, 1] == B
assert grid[1, 0] == C
assert grid[1, 1] is None
assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(),
g * f: FiniteSet("unique")}
# A triangle with a "loop" morphism.
l_A = NamedMorphism(A, A, "l_A")
d = Diagram([f, g, l_A])
grid = DiagramGrid(d)
assert grid.width == 2
assert grid.height == 2
assert grid[0, 0] == A
assert grid[0, 1] == B
assert grid[1, 0] is None
assert grid[1, 1] == C
assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), l_A: FiniteSet()}
# A simple diagram.
d = Diagram([f, g, h, k])
grid = DiagramGrid(d)
assert grid.width == 3
assert grid.height == 2
assert grid[0, 0] == A
assert grid[0, 1] == B
assert grid[0, 2] == D
assert grid[1, 0] is None
assert grid[1, 1] == C
assert grid[1, 2] is None
assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), h: FiniteSet(),
k: FiniteSet()}
assert str(grid) == '[[Object("A"), Object("B"), Object("D")], ' \
'[None, Object("C"), None]]'
# A chain of morphisms.
f = NamedMorphism(A, B, "f")
g = NamedMorphism(B, C, "g")
h = NamedMorphism(C, D, "h")
k = NamedMorphism(D, E, "k")
d = Diagram([f, g, h, k])
grid = DiagramGrid(d)
assert grid.width == 3
assert grid.height == 3
assert grid[0, 0] == A
assert grid[0, 1] == B
assert grid[0, 2] is None
assert grid[1, 0] is None
assert grid[1, 1] == C
assert grid[1, 2] == D
assert grid[2, 0] is None
assert grid[2, 1] is None
assert grid[2, 2] == E
assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), h: FiniteSet(),
k: FiniteSet()}
# A square.
f = NamedMorphism(A, B, "f")
g = NamedMorphism(B, D, "g")
h = NamedMorphism(A, C, "h")
k = NamedMorphism(C, D, "k")
d = Diagram([f, g, h, k])
grid = DiagramGrid(d)
assert grid.width == 2
assert grid.height == 2
assert grid[0, 0] == A
assert grid[0, 1] == B
assert grid[1, 0] == C
assert grid[1, 1] == D
assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), h: FiniteSet(),
k: FiniteSet()}
# A strange diagram which resulted from a typo when creating a
# test for five lemma, but which allowed to stop one extra problem
# in the algorithm.
A = Object("A")
B = Object("B")
C = Object("C")
D = Object("D")
E = Object("E")
A_ = Object("A'")
B_ = Object("B'")
C_ = Object("C'")
D_ = Object("D'")
E_ = Object("E'")
f = NamedMorphism(A, B, "f")
g = NamedMorphism(B, C, "g")
h = NamedMorphism(C, D, "h")
i = NamedMorphism(D, E, "i")
# These 4 morphisms should be between primed objects.
j = NamedMorphism(A, B, "j")
k = NamedMorphism(B, C, "k")
l = NamedMorphism(C, D, "l")
m = NamedMorphism(D, E, "m")
o = NamedMorphism(A, A_, "o")
p = NamedMorphism(B, B_, "p")
q = NamedMorphism(C, C_, "q")
r = NamedMorphism(D, D_, "r")
s = NamedMorphism(E, E_, "s")
d = Diagram([f, g, h, i, j, k, l, m, o, p, q, r, s])
grid = DiagramGrid(d)
assert grid.width == 3
assert grid.height == 4
assert grid[0, 0] is None
assert grid[0, 1] == A
assert grid[0, 2] == A_
assert grid[1, 0] == C
assert grid[1, 1] == B
assert grid[1, 2] == B_
assert grid[2, 0] == C_
assert grid[2, 1] == D
assert grid[2, 2] == D_
assert grid[3, 0] is None
assert grid[3, 1] == E
assert grid[3, 2] == E_
morphisms = {}
for m in [f, g, h, i, j, k, l, m, o, p, q, r, s]:
morphisms[m] = FiniteSet()
assert grid.morphisms == morphisms
# A cube.
A1 = Object("A1")
A2 = Object("A2")
A3 = Object("A3")
A4 = Object("A4")
A5 = Object("A5")
A6 = Object("A6")
A7 = Object("A7")
A8 = Object("A8")
# The top face of the cube.
f1 = NamedMorphism(A1, A2, "f1")
f2 = NamedMorphism(A1, A3, "f2")
f3 = NamedMorphism(A2, A4, "f3")
f4 = NamedMorphism(A3, A4, "f3")
# The bottom face of the cube.
f5 = NamedMorphism(A5, A6, "f5")
f6 = NamedMorphism(A5, A7, "f6")
f7 = NamedMorphism(A6, A8, "f7")
f8 = NamedMorphism(A7, A8, "f8")
# The remaining morphisms.
f9 = NamedMorphism(A1, A5, "f9")
f10 = NamedMorphism(A2, A6, "f10")
f11 = NamedMorphism(A3, A7, "f11")
f12 = NamedMorphism(A4, A8, "f11")
d = Diagram([f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12])
grid = DiagramGrid(d)
assert grid.width == 4
assert grid.height == 3
assert grid[0, 0] is None
assert grid[0, 1] == A5
assert grid[0, 2] == A6
assert grid[0, 3] is None
assert grid[1, 0] is None
assert grid[1, 1] == A1
assert grid[1, 2] == A2
assert grid[1, 3] is None
assert grid[2, 0] == A7
assert grid[2, 1] == A3
assert grid[2, 2] == A4
assert grid[2, 3] == A8
morphisms = {}
for m in [f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12]:
morphisms[m] = FiniteSet()
assert grid.morphisms == morphisms
# A line diagram.
A = Object("A")
B = Object("B")
C = Object("C")
D = Object("D")
E = Object("E")
f = NamedMorphism(A, B, "f")
g = NamedMorphism(B, C, "g")
h = NamedMorphism(C, D, "h")
i = NamedMorphism(D, E, "i")
d = Diagram([f, g, h, i])
grid = DiagramGrid(d, layout="sequential")
assert grid.width == 5
assert grid.height == 1
assert grid[0, 0] == A
assert grid[0, 1] == B
assert grid[0, 2] == C
assert grid[0, 3] == D
assert grid[0, 4] == E
assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), h: FiniteSet(),
i: FiniteSet()}
# Test the transposed version.
grid = DiagramGrid(d, layout="sequential", transpose=True)
assert grid.width == 1
assert grid.height == 5
assert grid[0, 0] == A
assert grid[1, 0] == B
assert grid[2, 0] == C
assert grid[3, 0] == D
assert grid[4, 0] == E
assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), h: FiniteSet(),
i: FiniteSet()}
# A pullback.
m1 = NamedMorphism(A, B, "m1")
m2 = NamedMorphism(A, C, "m2")
s1 = NamedMorphism(B, D, "s1")
s2 = NamedMorphism(C, D, "s2")
f1 = NamedMorphism(E, B, "f1")
f2 = NamedMorphism(E, C, "f2")
g = NamedMorphism(E, A, "g")
d = Diagram([m1, m2, s1, s2, f1, f2], {g: "unique"})
grid = DiagramGrid(d)
assert grid.width == 3
assert grid.height == 2
assert grid[0, 0] == A
assert grid[0, 1] == B
assert grid[0, 2] == E
assert grid[1, 0] == C
assert grid[1, 1] == D
assert grid[1, 2] is None
morphisms = {g: FiniteSet("unique")}
for m in [m1, m2, s1, s2, f1, f2]:
morphisms[m] = FiniteSet()
assert grid.morphisms == morphisms
# Test the pullback with sequential layout, just for stress
# testing.
grid = DiagramGrid(d, layout="sequential")
assert grid.width == 5
assert grid.height == 1
assert grid[0, 0] == D
assert grid[0, 1] == B
assert grid[0, 2] == A
assert grid[0, 3] == C
assert grid[0, 4] == E
assert grid.morphisms == morphisms
# Test a pullback with object grouping.
grid = DiagramGrid(d, groups=FiniteSet(E, FiniteSet(A, B, C, D)))
assert grid.width == 3
assert grid.height == 2
assert grid[0, 0] == E
assert grid[0, 1] == A
assert grid[0, 2] == B
assert grid[1, 0] is None
assert grid[1, 1] == C
assert grid[1, 2] == D
assert grid.morphisms == morphisms
# Five lemma, actually.
A = Object("A")
B = Object("B")
C = Object("C")
D = Object("D")
E = Object("E")
A_ = Object("A'")
B_ = Object("B'")
C_ = Object("C'")
D_ = Object("D'")
E_ = Object("E'")
f = NamedMorphism(A, B, "f")
g = NamedMorphism(B, C, "g")
h = NamedMorphism(C, D, "h")
i = NamedMorphism(D, E, "i")
j = NamedMorphism(A_, B_, "j")
k = NamedMorphism(B_, C_, "k")
l = NamedMorphism(C_, D_, "l")
m = NamedMorphism(D_, E_, "m")
o = NamedMorphism(A, A_, "o")
p = NamedMorphism(B, B_, "p")
q = NamedMorphism(C, C_, "q")
r = NamedMorphism(D, D_, "r")
s = NamedMorphism(E, E_, "s")
d = Diagram([f, g, h, i, j, k, l, m, o, p, q, r, s])
grid = DiagramGrid(d)
assert grid.width == 5
assert grid.height == 3
assert grid[0, 0] is None
assert grid[0, 1] == A
assert grid[0, 2] == A_
assert grid[0, 3] is None
assert grid[0, 4] is None
assert grid[1, 0] == C
assert grid[1, 1] == B
assert grid[1, 2] == B_
assert grid[1, 3] == C_
assert grid[1, 4] is None
assert grid[2, 0] == D
assert grid[2, 1] == E
assert grid[2, 2] is None
assert grid[2, 3] == D_
assert grid[2, 4] == E_
morphisms = {}
for m in [f, g, h, i, j, k, l, m, o, p, q, r, s]:
morphisms[m] = FiniteSet()
assert grid.morphisms == morphisms
# Test the five lemma with object grouping.
grid = DiagramGrid(d, FiniteSet(
FiniteSet(A, B, C, D, E), FiniteSet(A_, B_, C_, D_, E_)))
assert grid.width == 6
assert grid.height == 3
assert grid[0, 0] == A
assert grid[0, 1] == B
assert grid[0, 2] is None
assert grid[0, 3] == A_
assert grid[0, 4] == B_
assert grid[0, 5] is None
assert grid[1, 0] is None
assert grid[1, 1] == C
assert grid[1, 2] == D
assert grid[1, 3] is None
assert grid[1, 4] == C_
assert grid[1, 5] == D_
assert grid[2, 0] is None
assert grid[2, 1] is None
assert grid[2, 2] == E
assert grid[2, 3] is None
assert grid[2, 4] is None
assert grid[2, 5] == E_
assert grid.morphisms == morphisms
# Test the five lemma with object grouping, but mixing containers
# to represent groups.
grid = DiagramGrid(d, [(A, B, C, D, E), {A_, B_, C_, D_, E_}])
assert grid.width == 6
assert grid.height == 3
assert grid[0, 0] == A
assert grid[0, 1] == B
assert grid[0, 2] is None
assert grid[0, 3] == A_
assert grid[0, 4] == B_
assert grid[0, 5] is None
assert grid[1, 0] is None
assert grid[1, 1] == C
assert grid[1, 2] == D
assert grid[1, 3] is None
assert grid[1, 4] == C_
assert grid[1, 5] == D_
assert grid[2, 0] is None
assert grid[2, 1] is None
assert grid[2, 2] == E
assert grid[2, 3] is None
assert grid[2, 4] is None
assert grid[2, 5] == E_
assert grid.morphisms == morphisms
# Test the five lemma with object grouping and hints.
grid = DiagramGrid(d, {
FiniteSet(A, B, C, D, E): {"layout": "sequential",
"transpose": True},
FiniteSet(A_, B_, C_, D_, E_): {"layout": "sequential",
"transpose": True}},
transpose=True)
assert grid.width == 5
assert grid.height == 2
assert grid[0, 0] == A
assert grid[0, 1] == B
assert grid[0, 2] == C
assert grid[0, 3] == D
assert grid[0, 4] == E
assert grid[1, 0] == A_
assert grid[1, 1] == B_
assert grid[1, 2] == C_
assert grid[1, 3] == D_
assert grid[1, 4] == E_
assert grid.morphisms == morphisms
# A two-triangle disconnected diagram.
f = NamedMorphism(A, B, "f")
g = NamedMorphism(B, C, "g")
f_ = NamedMorphism(A_, B_, "f")
g_ = NamedMorphism(B_, C_, "g")
d = Diagram([f, g, f_, g_], {g * f: "unique", g_ * f_: "unique"})
grid = DiagramGrid(d)
assert grid.width == 4
assert grid.height == 2
assert grid[0, 0] == A
assert grid[0, 1] == B
assert grid[0, 2] == A_
assert grid[0, 3] == B_
assert grid[1, 0] == C
assert grid[1, 1] is None
assert grid[1, 2] == C_
assert grid[1, 3] is None
assert grid.morphisms == {f: FiniteSet(), g: FiniteSet(), f_: FiniteSet(),
g_: FiniteSet(), g * f: FiniteSet("unique"),
g_ * f_: FiniteSet("unique")}
# A two-morphism disconnected diagram.
f = NamedMorphism(A, B, "f")
g = NamedMorphism(C, D, "g")
d = Diagram([f, g])
grid = DiagramGrid(d)
assert grid.width == 4
assert grid.height == 1
assert grid[0, 0] == A
assert grid[0, 1] == B
assert grid[0, 2] == C
assert grid[0, 3] == D
assert grid.morphisms == {f: FiniteSet(), g: FiniteSet()}
# Test a one-object diagram.
f = NamedMorphism(A, A, "f")
d = Diagram([f])
grid = DiagramGrid(d)
assert grid.width == 1
assert grid.height == 1
assert grid[0, 0] == A
# Test a two-object disconnected diagram.
g = NamedMorphism(B, B, "g")
d = Diagram([f, g])
grid = DiagramGrid(d)
assert grid.width == 2
assert grid.height == 1
assert grid[0, 0] == A
assert grid[0, 1] == B
def test_DiagramGrid_pseudopod():
# Test a diagram in which even growing a pseudopod does not
# eventually help.
A = Object("A")
B = Object("B")
C = Object("C")
D = Object("D")
E = Object("E")
F = Object("F")
A_ = Object("A'")
B_ = Object("B'")
C_ = Object("C'")
D_ = Object("D'")
E_ = Object("E'")
f1 = NamedMorphism(A, B, "f1")
f2 = NamedMorphism(A, C, "f2")
f3 = NamedMorphism(A, D, "f3")
f4 = NamedMorphism(A, E, "f4")
f5 = NamedMorphism(A, A_, "f5")
f6 = NamedMorphism(A, B_, "f6")
f7 = NamedMorphism(A, C_, "f7")
f8 = NamedMorphism(A, D_, "f8")
f9 = NamedMorphism(A, E_, "f9")
f10 = NamedMorphism(A, F, "f10")
d = Diagram([f1, f2, f3, f4, f5, f6, f7, f8, f9, f10])
grid = DiagramGrid(d)
assert grid.width == 5
assert grid.height == 3
assert grid[0, 0] == E
assert grid[0, 1] == C
assert grid[0, 2] == C_
assert grid[0, 3] == E_
assert grid[0, 4] == F
assert grid[1, 0] == D
assert grid[1, 1] == A
assert grid[1, 2] == A_
assert grid[1, 3] is None
assert grid[1, 4] is None
assert grid[2, 0] == D_
assert grid[2, 1] == B
assert grid[2, 2] == B_
assert grid[2, 3] is None
assert grid[2, 4] is None
morphisms = {}
for f in [f1, f2, f3, f4, f5, f6, f7, f8, f9, f10]:
morphisms[f] = FiniteSet()
assert grid.morphisms == morphisms
def test_ArrowStringDescription():
astr = ArrowStringDescription("cm", "", None, "", "", "d", "r", "_", "f")
assert str(astr) == "\\ar[dr]_{f}"
astr = ArrowStringDescription("cm", "", 12, "", "", "d", "r", "_", "f")
assert str(astr) == "\\ar[dr]_{f}"
astr = ArrowStringDescription("cm", "^", 12, "", "", "d", "r", "_", "f")
assert str(astr) == "\\ar@/^12cm/[dr]_{f}"
astr = ArrowStringDescription("cm", "", 12, "r", "", "d", "r", "_", "f")
assert str(astr) == "\\ar[dr]_{f}"
astr = ArrowStringDescription("cm", "", 12, "r", "u", "d", "r", "_", "f")
assert str(astr) == "\\ar@(r,u)[dr]_{f}"
astr = ArrowStringDescription("cm", "", 12, "r", "u", "d", "r", "_", "f")
assert str(astr) == "\\ar@(r,u)[dr]_{f}"
astr = ArrowStringDescription("cm", "", 12, "r", "u", "d", "r", "_", "f")
astr.arrow_style = "{-->}"
assert str(astr) == "\\ar@(r,u)@{-->}[dr]_{f}"
astr = ArrowStringDescription("cm", "_", 12, "", "", "d", "r", "_", "f")
astr.arrow_style = "{-->}"
assert str(astr) == "\\ar@/_12cm/@{-->}[dr]_{f}"
def test_XypicDiagramDrawer_line():
# A linear diagram.
A = Object("A")
B = Object("B")
C = Object("C")
D = Object("D")
E = Object("E")
f = NamedMorphism(A, B, "f")
g = NamedMorphism(B, C, "g")
h = NamedMorphism(C, D, "h")
i = NamedMorphism(D, E, "i")
d = Diagram([f, g, h, i])
grid = DiagramGrid(d, layout="sequential")
drawer = XypicDiagramDrawer()
assert drawer.draw(d, grid) == "\\xymatrix{\n" \
"A \\ar[r]^{f} & B \\ar[r]^{g} & C \\ar[r]^{h} & D \\ar[r]^{i} & E \n" \
"}\n"
# The same diagram, transposed.
grid = DiagramGrid(d, layout="sequential", transpose=True)
drawer = XypicDiagramDrawer()
assert drawer.draw(d, grid) == "\\xymatrix{\n" \
"A \\ar[d]^{f} \\\\\n" \
"B \\ar[d]^{g} \\\\\n" \
"C \\ar[d]^{h} \\\\\n" \
"D \\ar[d]^{i} \\\\\n" \
"E \n" \
"}\n"
def test_XypicDiagramDrawer_triangle():
# A triangle diagram.
A = Object("A")
B = Object("B")
C = Object("C")
f = NamedMorphism(A, B, "f")
g = NamedMorphism(B, C, "g")
d = Diagram([f, g], {g * f: "unique"})
grid = DiagramGrid(d)
drawer = XypicDiagramDrawer()
assert drawer.draw(d, grid) == "\\xymatrix{\n" \
"A \\ar[d]_{g\\circ f} \\ar[r]^{f} & B \\ar[ld]^{g} \\\\\n" \
"C & \n" \
"}\n"
# The same diagram, transposed.
grid = DiagramGrid(d, transpose=True)
drawer = XypicDiagramDrawer()
assert drawer.draw(d, grid) == "\\xymatrix{\n" \
"A \\ar[r]^{g\\circ f} \\ar[d]_{f} & C \\\\\n" \
"B \\ar[ru]_{g} & \n" \
"}\n"
# The same diagram, with a masked morphism.
assert drawer.draw(d, grid, masked=[g]) == "\\xymatrix{\n" \
"A \\ar[r]^{g\\circ f} \\ar[d]_{f} & C \\\\\n" \
"B & \n" \
"}\n"
# The same diagram with a formatter for "unique".
def formatter(astr):
astr.label = "\\exists !" + astr.label
astr.arrow_style = "{-->}"
drawer.arrow_formatters["unique"] = formatter
assert drawer.draw(d, grid) == "\\xymatrix{\n" \
"A \\ar@{-->}[r]^{\\exists !g\\circ f} \\ar[d]_{f} & C \\\\\n" \
"B \\ar[ru]_{g} & \n" \
"}\n"
# The same diagram with a default formatter.
def default_formatter(astr):
astr.label_displacement = "(0.45)"
drawer.default_arrow_formatter = default_formatter
assert drawer.draw(d, grid) == "\\xymatrix{\n" \
"A \\ar@{-->}[r]^(0.45){\\exists !g\\circ f} \\ar[d]_(0.45){f} & C \\\\\n" \
"B \\ar[ru]_(0.45){g} & \n" \
"}\n"
# A triangle diagram with a lot of morphisms between the same
# objects.
f1 = NamedMorphism(B, A, "f1")
f2 = NamedMorphism(A, B, "f2")
g1 = NamedMorphism(C, B, "g1")
g2 = NamedMorphism(B, C, "g2")
d = Diagram([f, f1, f2, g, g1, g2], {f1 * g1: "unique", g2 * f2: "unique"})
grid = DiagramGrid(d, transpose=True)
drawer = XypicDiagramDrawer()
assert drawer.draw(d, grid, masked=[f1*g1*g2*f2, g2*f2*f1*g1]) == \
"\\xymatrix{\n" \
"A \\ar[r]^{g_{2}\\circ f_{2}} \\ar[d]_{f} \\ar@/^3mm/[d]^{f_{2}} " \
"& C \\ar@/^3mm/[l]^{f_{1}\\circ g_{1}} \\ar@/^3mm/[ld]^{g_{1}} \\\\\n" \
"B \\ar@/^3mm/[u]^{f_{1}} \\ar[ru]_{g} \\ar@/^3mm/[ru]^{g_{2}} & \n" \
"}\n"
def test_XypicDiagramDrawer_cube():
# A cube diagram.
A1 = Object("A1")
A2 = Object("A2")
A3 = Object("A3")
A4 = Object("A4")
A5 = Object("A5")
A6 = Object("A6")
A7 = Object("A7")
A8 = Object("A8")
# The top face of the cube.
f1 = NamedMorphism(A1, A2, "f1")
f2 = NamedMorphism(A1, A3, "f2")
f3 = NamedMorphism(A2, A4, "f3")
f4 = NamedMorphism(A3, A4, "f3")
# The bottom face of the cube.
f5 = NamedMorphism(A5, A6, "f5")
f6 = NamedMorphism(A5, A7, "f6")
f7 = NamedMorphism(A6, A8, "f7")
f8 = NamedMorphism(A7, A8, "f8")
# The remaining morphisms.
f9 = NamedMorphism(A1, A5, "f9")
f10 = NamedMorphism(A2, A6, "f10")
f11 = NamedMorphism(A3, A7, "f11")
f12 = NamedMorphism(A4, A8, "f11")
d = Diagram([f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12])
grid = DiagramGrid(d)
drawer = XypicDiagramDrawer()
assert drawer.draw(d, grid) == "\\xymatrix{\n" \
"& A_{5} \\ar[r]^{f_{5}} \\ar[ldd]_{f_{6}} & A_{6} \\ar[rdd]^{f_{7}} " \
"& \\\\\n" \
"& A_{1} \\ar[r]^{f_{1}} \\ar[d]^{f_{2}} \\ar[u]^{f_{9}} & A_{2} " \
"\\ar[d]^{f_{3}} \\ar[u]_{f_{10}} & \\\\\n" \
"A_{7} \\ar@/_3mm/[rrr]_{f_{8}} & A_{3} \\ar[r]^{f_{3}} \\ar[l]_{f_{11}} " \
"& A_{4} \\ar[r]^{f_{11}} & A_{8} \n" \
"}\n"
# The same diagram, transposed.
grid = DiagramGrid(d, transpose=True)
drawer = XypicDiagramDrawer()
assert drawer.draw(d, grid) == "\\xymatrix{\n" \
"& & A_{7} \\ar@/^3mm/[ddd]^{f_{8}} \\\\\n" \
"A_{5} \\ar[d]_{f_{5}} \\ar[rru]^{f_{6}} & A_{1} \\ar[d]^{f_{1}} " \
"\\ar[r]^{f_{2}} \\ar[l]^{f_{9}} & A_{3} \\ar[d]_{f_{3}} " \
"\\ar[u]^{f_{11}} \\\\\n" \
"A_{6} \\ar[rrd]_{f_{7}} & A_{2} \\ar[r]^{f_{3}} \\ar[l]^{f_{10}} " \
"& A_{4} \\ar[d]_{f_{11}} \\\\\n" \
"& & A_{8} \n" \
"}\n"
def test_XypicDiagramDrawer_curved_and_loops():
# A simple diagram, with a curved arrow.
A = Object("A")
B = Object("B")
C = Object("C")
D = Object("D")
f = NamedMorphism(A, B, "f")
g = NamedMorphism(B, C, "g")
h = NamedMorphism(D, A, "h")
k = NamedMorphism(D, B, "k")
d = Diagram([f, g, h, k])
grid = DiagramGrid(d)
drawer = XypicDiagramDrawer()
assert drawer.draw(d, grid) == "\\xymatrix{\n" \
"A \\ar[r]_{f} & B \\ar[d]^{g} & D \\ar[l]^{k} \\ar@/_3mm/[ll]_{h} \\\\\n" \
"& C & \n" \
"}\n"
# The same diagram, transposed.
grid = DiagramGrid(d, transpose=True)
drawer = XypicDiagramDrawer()
assert drawer.draw(d, grid) == "\\xymatrix{\n" \
"A \\ar[d]^{f} & \\\\\n" \
"B \\ar[r]^{g} & C \\\\\n" \
"D \\ar[u]_{k} \\ar@/^3mm/[uu]^{h} & \n" \
"}\n"
# The same diagram, larger and rotated.
assert drawer.draw(d, grid, diagram_format="@+1cm@dr") == \
"\\xymatrix@+1cm@dr{\n" \
"A \\ar[d]^{f} & \\\\\n" \
"B \\ar[r]^{g} & C \\\\\n" \
"D \\ar[u]_{k} \\ar@/^3mm/[uu]^{h} & \n" \
"}\n"
# A simple diagram with three curved arrows.
h1 = NamedMorphism(D, A, "h1")
h2 = NamedMorphism(A, D, "h2")
k = NamedMorphism(D, B, "k")
d = Diagram([f, g, h, k, h1, h2])
grid = DiagramGrid(d)
drawer = XypicDiagramDrawer()
assert drawer.draw(d, grid) == "\\xymatrix{\n" \
"A \\ar[r]_{f} \\ar@/^3mm/[rr]^{h_{2}} & B \\ar[d]^{g} & D \\ar[l]^{k} " \
"\\ar@/_7mm/[ll]_{h} \\ar@/_11mm/[ll]_{h_{1}} \\\\\n" \
"& C & \n" \
"}\n"
# The same diagram, transposed.
grid = DiagramGrid(d, transpose=True)
drawer = XypicDiagramDrawer()
assert drawer.draw(d, grid) == "\\xymatrix{\n" \
"A \\ar[d]^{f} \\ar@/_3mm/[dd]_{h_{2}} & \\\\\n" \
"B \\ar[r]^{g} & C \\\\\n" \
"D \\ar[u]_{k} \\ar@/^7mm/[uu]^{h} \\ar@/^11mm/[uu]^{h_{1}} & \n" \
"}\n"
# The same diagram, with "loop" morphisms.
l_A = NamedMorphism(A, A, "l_A")
l_D = NamedMorphism(D, D, "l_D")
l_C = NamedMorphism(C, C, "l_C")
d = Diagram([f, g, h, k, h1, h2, l_A, l_D, l_C])
grid = DiagramGrid(d)
drawer = XypicDiagramDrawer()
assert drawer.draw(d, grid) == "\\xymatrix{\n" \
"A \\ar[r]_{f} \\ar@/^3mm/[rr]^{h_{2}} \\ar@(u,l)[]^{l_{A}} " \
"& B \\ar[d]^{g} & D \\ar[l]^{k} \\ar@/_7mm/[ll]_{h} " \
"\\ar@/_11mm/[ll]_{h_{1}} \\ar@(r,u)[]^{l_{D}} \\\\\n" \
"& C \\ar@(l,d)[]^{l_{C}} & \n" \
"}\n"
# The same diagram with "loop" morphisms, transposed.
grid = DiagramGrid(d, transpose=True)
drawer = XypicDiagramDrawer()
assert drawer.draw(d, grid) == "\\xymatrix{\n" \
"A \\ar[d]^{f} \\ar@/_3mm/[dd]_{h_{2}} \\ar@(r,u)[]^{l_{A}} & \\\\\n" \
"B \\ar[r]^{g} & C \\ar@(r,u)[]^{l_{C}} \\\\\n" \
"D \\ar[u]_{k} \\ar@/^7mm/[uu]^{h} \\ar@/^11mm/[uu]^{h_{1}} " \
"\\ar@(l,d)[]^{l_{D}} & \n" \
"}\n"
# The same diagram with two "loop" morphisms per object.
l_A_ = NamedMorphism(A, A, "n_A")
l_D_ = NamedMorphism(D, D, "n_D")
l_C_ = NamedMorphism(C, C, "n_C")
d = Diagram([f, g, h, k, h1, h2, l_A, l_D, l_C, l_A_, l_D_, l_C_])
grid = DiagramGrid(d)
drawer = XypicDiagramDrawer()
assert drawer.draw(d, grid) == "\\xymatrix{\n" \
"A \\ar[r]_{f} \\ar@/^3mm/[rr]^{h_{2}} \\ar@(u,l)[]^{l_{A}} " \
"\\ar@/^3mm/@(l,d)[]^{n_{A}} & B \\ar[d]^{g} & D \\ar[l]^{k} " \
"\\ar@/_7mm/[ll]_{h} \\ar@/_11mm/[ll]_{h_{1}} \\ar@(r,u)[]^{l_{D}} " \
"\\ar@/^3mm/@(d,r)[]^{n_{D}} \\\\\n" \
"& C \\ar@(l,d)[]^{l_{C}} \\ar@/^3mm/@(d,r)[]^{n_{C}} & \n" \
"}\n"
# The same diagram with two "loop" morphisms per object, transposed.
grid = DiagramGrid(d, transpose=True)
drawer = XypicDiagramDrawer()
assert drawer.draw(d, grid) == "\\xymatrix{\n" \
"A \\ar[d]^{f} \\ar@/_3mm/[dd]_{h_{2}} \\ar@(r,u)[]^{l_{A}} " \
"\\ar@/^3mm/@(u,l)[]^{n_{A}} & \\\\\n" \
"B \\ar[r]^{g} & C \\ar@(r,u)[]^{l_{C}} \\ar@/^3mm/@(d,r)[]^{n_{C}} \\\\\n" \
"D \\ar[u]_{k} \\ar@/^7mm/[uu]^{h} \\ar@/^11mm/[uu]^{h_{1}} " \
"\\ar@(l,d)[]^{l_{D}} \\ar@/^3mm/@(d,r)[]^{n_{D}} & \n" \
"}\n"
def test_xypic_draw_diagram():
# A linear diagram.
A = Object("A")
B = Object("B")
C = Object("C")
D = Object("D")
E = Object("E")
f = NamedMorphism(A, B, "f")
g = NamedMorphism(B, C, "g")
h = NamedMorphism(C, D, "h")
i = NamedMorphism(D, E, "i")
d = Diagram([f, g, h, i])
grid = DiagramGrid(d, layout="sequential")
drawer = XypicDiagramDrawer()
assert drawer.draw(d, grid) == xypic_draw_diagram(d, layout="sequential")
@@ -0,0 +1,24 @@
""" The ``sympy.codegen`` module contains classes and functions for building
abstract syntax trees of algorithms. These trees may then be printed by the
code-printers in ``sympy.printing``.
There are several submodules available:
- ``sympy.codegen.ast``: AST nodes useful across multiple languages.
- ``sympy.codegen.cnodes``: AST nodes useful for the C family of languages.
- ``sympy.codegen.fnodes``: AST nodes useful for Fortran.
- ``sympy.codegen.cfunctions``: functions specific to C (C99 math functions)
- ``sympy.codegen.ffunctions``: functions specific to Fortran (e.g. ``kind``).
"""
from .ast import (
Assignment, aug_assign, CodeBlock, For, Attribute, Variable, Declaration,
While, Scope, Print, FunctionPrototype, FunctionDefinition, FunctionCall
)
__all__ = [
'Assignment', 'aug_assign', 'CodeBlock', 'For', 'Attribute', 'Variable',
'Declaration', 'While', 'Scope', 'Print', 'FunctionPrototype',
'FunctionDefinition', 'FunctionCall',
]
@@ -0,0 +1,18 @@
"""This module provides containers for python objects that are valid
printing targets but are not a subclass of SymPy's Printable.
"""
from sympy.core.containers import Tuple
class List(Tuple):
"""Represents a (frozen) (Python) list (for code printing purposes)."""
def __eq__(self, other):
if isinstance(other, list):
return self == List(*other)
else:
return self.args == other
def __hash__(self):
return super().__hash__()
@@ -0,0 +1,180 @@
from sympy.core.containers import Tuple
from sympy.core.numbers import oo
from sympy.core.relational import (Gt, Lt)
from sympy.core.symbol import (Dummy, Symbol)
from sympy.functions.elementary.complexes import Abs
from sympy.functions.elementary.miscellaneous import Min, Max
from sympy.logic.boolalg import And
from sympy.codegen.ast import (
Assignment, AddAugmentedAssignment, break_, CodeBlock, Declaration, FunctionDefinition,
Print, Return, Scope, While, Variable, Pointer, real
)
from sympy.codegen.cfunctions import isnan
""" This module collects functions for constructing ASTs representing algorithms. """
def newtons_method(expr, wrt, atol=1e-12, delta=None, *, rtol=4e-16, debug=False,
itermax=None, counter=None, delta_fn=lambda e, x: -e/e.diff(x),
cse=False, handle_nan=None,
bounds=None):
""" Generates an AST for Newton-Raphson method (a root-finding algorithm).
Explanation
===========
Returns an abstract syntax tree (AST) based on ``sympy.codegen.ast`` for Netwon's
method of root-finding.
Parameters
==========
expr : expression
wrt : Symbol
With respect to, i.e. what is the variable.
atol : number or expression
Absolute tolerance (stopping criterion)
rtol : number or expression
Relative tolerance (stopping criterion)
delta : Symbol
Will be a ``Dummy`` if ``None``.
debug : bool
Whether to print convergence information during iterations
itermax : number or expr
Maximum number of iterations.
counter : Symbol
Will be a ``Dummy`` if ``None``.
delta_fn: Callable[[Expr, Symbol], Expr]
computes the step, default is newtons method. For e.g. Halley's method
use delta_fn=lambda e, x: -2*e*e.diff(x)/(2*e.diff(x)**2 - e*e.diff(x, 2))
cse: bool
Perform common sub-expression elimination on delta expression
handle_nan: Token
How to handle occurrence of not-a-number (NaN).
bounds: Optional[tuple[Expr, Expr]]
Perform optimization within bounds
Examples
========
>>> from sympy import symbols, cos
>>> from sympy.codegen.ast import Assignment
>>> from sympy.codegen.algorithms import newtons_method
>>> x, dx, atol = symbols('x dx atol')
>>> expr = cos(x) - x**3
>>> algo = newtons_method(expr, x, atol=atol, delta=dx)
>>> algo.has(Assignment(dx, -expr/expr.diff(x)))
True
References
==========
.. [1] https://en.wikipedia.org/wiki/Newton%27s_method
"""
if delta is None:
delta = Dummy()
Wrapper = Scope
name_d = 'delta'
else:
Wrapper = lambda x: x
name_d = delta.name
delta_expr = delta_fn(expr, wrt)
if cse:
from sympy.simplify.cse_main import cse
cses, (red,) = cse([delta_expr.factor()])
whl_bdy = [Assignment(dum, sub_e) for dum, sub_e in cses]
whl_bdy += [Assignment(delta, red)]
else:
whl_bdy = [Assignment(delta, delta_expr)]
if handle_nan is not None:
whl_bdy += [While(isnan(delta), CodeBlock(handle_nan, break_))]
whl_bdy += [AddAugmentedAssignment(wrt, delta)]
if bounds is not None:
whl_bdy += [Assignment(wrt, Min(Max(wrt, bounds[0]), bounds[1]))]
if debug:
prnt = Print([wrt, delta], r"{}=%12.5g {}=%12.5g\n".format(wrt.name, name_d))
whl_bdy += [prnt]
req = Gt(Abs(delta), atol + rtol*Abs(wrt))
declars = [Declaration(Variable(delta, type=real, value=oo))]
if itermax is not None:
counter = counter or Dummy(integer=True)
v_counter = Variable.deduced(counter, 0)
declars.append(Declaration(v_counter))
whl_bdy.append(AddAugmentedAssignment(counter, 1))
req = And(req, Lt(counter, itermax))
whl = While(req, CodeBlock(*whl_bdy))
blck = declars
if debug:
blck.append(Print([wrt], r"{}=%12.5g\n".format(wrt.name)))
blck += [whl]
return Wrapper(CodeBlock(*blck))
def _symbol_of(arg):
if isinstance(arg, Declaration):
arg = arg.variable.symbol
elif isinstance(arg, Variable):
arg = arg.symbol
return arg
def newtons_method_function(expr, wrt, params=None, func_name="newton", attrs=Tuple(), *, delta=None, **kwargs):
""" Generates an AST for a function implementing the Newton-Raphson method.
Parameters
==========
expr : expression
wrt : Symbol
With respect to, i.e. what is the variable
params : iterable of symbols
Symbols appearing in expr that are taken as constants during the iterations
(these will be accepted as parameters to the generated function).
func_name : str
Name of the generated function.
attrs : Tuple
Attribute instances passed as ``attrs`` to ``FunctionDefinition``.
\\*\\*kwargs :
Keyword arguments passed to :func:`sympy.codegen.algorithms.newtons_method`.
Examples
========
>>> from sympy import symbols, cos
>>> from sympy.codegen.algorithms import newtons_method_function
>>> from sympy.codegen.pyutils import render_as_module
>>> x = symbols('x')
>>> expr = cos(x) - x**3
>>> func = newtons_method_function(expr, x)
>>> py_mod = render_as_module(func) # source code as string
>>> namespace = {}
>>> exec(py_mod, namespace, namespace)
>>> res = eval('newton(0.5)', namespace)
>>> abs(res - 0.865474033102) < 1e-12
True
See Also
========
sympy.codegen.algorithms.newtons_method
"""
if params is None:
params = (wrt,)
pointer_subs = {p.symbol: Symbol('(*%s)' % p.symbol.name)
for p in params if isinstance(p, Pointer)}
if delta is None:
delta = Symbol('d_' + wrt.name)
if expr.has(delta):
delta = None # will use Dummy
algo = newtons_method(expr, wrt, delta=delta, **kwargs).xreplace(pointer_subs)
if isinstance(algo, Scope):
algo = algo.body
not_in_params = expr.free_symbols.difference({_symbol_of(p) for p in params})
if not_in_params:
raise ValueError("Missing symbols in params: %s" % ', '.join(map(str, not_in_params)))
declars = tuple(Variable(p, real) for p in params)
body = CodeBlock(algo, Return(wrt))
return FunctionDefinition(real, func_name, declars, body, attrs=attrs)
@@ -0,0 +1,187 @@
import math
from sympy.sets.sets import Interval
from sympy.calculus.singularities import is_increasing, is_decreasing
from sympy.codegen.rewriting import Optimization
from sympy.core.function import UndefinedFunction
"""
This module collects classes useful for approximate rewriting of expressions.
This can be beneficial when generating numeric code for which performance is
of greater importance than precision (e.g. for preconditioners used in iterative
methods).
"""
class SumApprox(Optimization):
"""
Approximates sum by neglecting small terms.
Explanation
===========
If terms are expressions which can be determined to be monotonic, then
bounds for those expressions are added.
Parameters
==========
bounds : dict
Mapping expressions to length 2 tuple of bounds (low, high).
reltol : number
Threshold for when to ignore a term. Taken relative to the largest
lower bound among bounds.
Examples
========
>>> from sympy import exp
>>> from sympy.abc import x, y, z
>>> from sympy.codegen.rewriting import optimize
>>> from sympy.codegen.approximations import SumApprox
>>> bounds = {x: (-1, 1), y: (1000, 2000), z: (-10, 3)}
>>> sum_approx3 = SumApprox(bounds, reltol=1e-3)
>>> sum_approx2 = SumApprox(bounds, reltol=1e-2)
>>> sum_approx1 = SumApprox(bounds, reltol=1e-1)
>>> expr = 3*(x + y + exp(z))
>>> optimize(expr, [sum_approx3])
3*(x + y + exp(z))
>>> optimize(expr, [sum_approx2])
3*y + 3*exp(z)
>>> optimize(expr, [sum_approx1])
3*y
"""
def __init__(self, bounds, reltol, **kwargs):
super().__init__(**kwargs)
self.bounds = bounds
self.reltol = reltol
def __call__(self, expr):
return expr.factor().replace(self.query, lambda arg: self.value(arg))
def query(self, expr):
return expr.is_Add
def value(self, add):
for term in add.args:
if term.is_number or term in self.bounds or len(term.free_symbols) != 1:
continue
fs, = term.free_symbols
if fs not in self.bounds:
continue
intrvl = Interval(*self.bounds[fs])
if is_increasing(term, intrvl, fs):
self.bounds[term] = (
term.subs({fs: self.bounds[fs][0]}),
term.subs({fs: self.bounds[fs][1]})
)
elif is_decreasing(term, intrvl, fs):
self.bounds[term] = (
term.subs({fs: self.bounds[fs][1]}),
term.subs({fs: self.bounds[fs][0]})
)
else:
return add
if all(term.is_number or term in self.bounds for term in add.args):
bounds = [(term, term) if term.is_number else self.bounds[term] for term in add.args]
largest_abs_guarantee = 0
for lo, hi in bounds:
if lo <= 0 <= hi:
continue
largest_abs_guarantee = max(largest_abs_guarantee,
min(abs(lo), abs(hi)))
new_terms = []
for term, (lo, hi) in zip(add.args, bounds):
if max(abs(lo), abs(hi)) >= largest_abs_guarantee*self.reltol:
new_terms.append(term)
return add.func(*new_terms)
else:
return add
class SeriesApprox(Optimization):
""" Approximates functions by expanding them as a series.
Parameters
==========
bounds : dict
Mapping expressions to length 2 tuple of bounds (low, high).
reltol : number
Threshold for when to ignore a term. Taken relative to the largest
lower bound among bounds.
max_order : int
Largest order to include in series expansion
n_point_checks : int (even)
The validity of an expansion (with respect to reltol) is checked at
discrete points (linearly spaced over the bounds of the variable). The
number of points used in this numerical check is given by this number.
Examples
========
>>> from sympy import sin, pi
>>> from sympy.abc import x, y
>>> from sympy.codegen.rewriting import optimize
>>> from sympy.codegen.approximations import SeriesApprox
>>> bounds = {x: (-.1, .1), y: (pi-1, pi+1)}
>>> series_approx2 = SeriesApprox(bounds, reltol=1e-2)
>>> series_approx3 = SeriesApprox(bounds, reltol=1e-3)
>>> series_approx8 = SeriesApprox(bounds, reltol=1e-8)
>>> expr = sin(x)*sin(y)
>>> optimize(expr, [series_approx2])
x*(-y + (y - pi)**3/6 + pi)
>>> optimize(expr, [series_approx3])
(-x**3/6 + x)*sin(y)
>>> optimize(expr, [series_approx8])
sin(x)*sin(y)
"""
def __init__(self, bounds, reltol, max_order=4, n_point_checks=4, **kwargs):
super().__init__(**kwargs)
self.bounds = bounds
self.reltol = reltol
self.max_order = max_order
if n_point_checks % 2 == 1:
raise ValueError("Checking the solution at expansion point is not helpful")
self.n_point_checks = n_point_checks
self._prec = math.ceil(-math.log10(self.reltol))
def __call__(self, expr):
return expr.factor().replace(self.query, lambda arg: self.value(arg))
def query(self, expr):
return (expr.is_Function and not isinstance(expr, UndefinedFunction)
and len(expr.args) == 1)
def value(self, fexpr):
free_symbols = fexpr.free_symbols
if len(free_symbols) != 1:
return fexpr
symb, = free_symbols
if symb not in self.bounds:
return fexpr
lo, hi = self.bounds[symb]
x0 = (lo + hi)/2
cheapest = None
for n in range(self.max_order+1, 0, -1):
fseri = fexpr.series(symb, x0=x0, n=n).removeO()
n_ok = True
for idx in range(self.n_point_checks):
x = lo + idx*(hi - lo)/(self.n_point_checks - 1)
val = fseri.xreplace({symb: x})
ref = fexpr.xreplace({symb: x})
if abs((1 - val/ref).evalf(self._prec)) > self.reltol:
n_ok = False
break
if n_ok:
cheapest = fseri
else:
break
if cheapest is None:
return fexpr
else:
return cheapest
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,558 @@
"""
This module contains SymPy functions mathcin corresponding to special math functions in the
C standard library (since C99, also available in C++11).
The functions defined in this module allows the user to express functions such as ``expm1``
as a SymPy function for symbolic manipulation.
"""
from sympy.core.function import ArgumentIndexError, Function
from sympy.core.numbers import Rational
from sympy.core.power import Pow
from sympy.core.singleton import S
from sympy.functions.elementary.exponential import exp, log
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.logic.boolalg import BooleanFunction, true, false
def _expm1(x):
return exp(x) - S.One
class expm1(Function):
"""
Represents the exponential function minus one.
Explanation
===========
The benefit of using ``expm1(x)`` over ``exp(x) - 1``
is that the latter is prone to cancellation under finite precision
arithmetic when x is close to zero.
Examples
========
>>> from sympy.abc import x
>>> from sympy.codegen.cfunctions import expm1
>>> '%.0e' % expm1(1e-99).evalf()
'1e-99'
>>> from math import exp
>>> exp(1e-99) - 1
0.0
>>> expm1(x).diff(x)
exp(x)
See Also
========
log1p
"""
nargs = 1
def fdiff(self, argindex=1):
"""
Returns the first derivative of this function.
"""
if argindex == 1:
return exp(*self.args)
else:
raise ArgumentIndexError(self, argindex)
def _eval_expand_func(self, **hints):
return _expm1(*self.args)
def _eval_rewrite_as_exp(self, arg, **kwargs):
return exp(arg) - S.One
_eval_rewrite_as_tractable = _eval_rewrite_as_exp
@classmethod
def eval(cls, arg):
exp_arg = exp.eval(arg)
if exp_arg is not None:
return exp_arg - S.One
def _eval_is_real(self):
return self.args[0].is_real
def _eval_is_finite(self):
return self.args[0].is_finite
def _log1p(x):
return log(x + S.One)
class log1p(Function):
"""
Represents the natural logarithm of a number plus one.
Explanation
===========
The benefit of using ``log1p(x)`` over ``log(x + 1)``
is that the latter is prone to cancellation under finite precision
arithmetic when x is close to zero.
Examples
========
>>> from sympy.abc import x
>>> from sympy.codegen.cfunctions import log1p
>>> from sympy import expand_log
>>> '%.0e' % expand_log(log1p(1e-99)).evalf()
'1e-99'
>>> from math import log
>>> log(1 + 1e-99)
0.0
>>> log1p(x).diff(x)
1/(x + 1)
See Also
========
expm1
"""
nargs = 1
def fdiff(self, argindex=1):
"""
Returns the first derivative of this function.
"""
if argindex == 1:
return S.One/(self.args[0] + S.One)
else:
raise ArgumentIndexError(self, argindex)
def _eval_expand_func(self, **hints):
return _log1p(*self.args)
def _eval_rewrite_as_log(self, arg, **kwargs):
return _log1p(arg)
_eval_rewrite_as_tractable = _eval_rewrite_as_log
@classmethod
def eval(cls, arg):
if arg.is_Rational:
return log(arg + S.One)
elif not arg.is_Float: # not safe to add 1 to Float
return log.eval(arg + S.One)
elif arg.is_number:
return log(Rational(arg) + S.One)
def _eval_is_real(self):
return (self.args[0] + S.One).is_nonnegative
def _eval_is_finite(self):
if (self.args[0] + S.One).is_zero:
return False
return self.args[0].is_finite
def _eval_is_positive(self):
return self.args[0].is_positive
def _eval_is_zero(self):
return self.args[0].is_zero
def _eval_is_nonnegative(self):
return self.args[0].is_nonnegative
_Two = S(2)
def _exp2(x):
return Pow(_Two, x)
class exp2(Function):
"""
Represents the exponential function with base two.
Explanation
===========
The benefit of using ``exp2(x)`` over ``2**x``
is that the latter is not as efficient under finite precision
arithmetic.
Examples
========
>>> from sympy.abc import x
>>> from sympy.codegen.cfunctions import exp2
>>> exp2(2).evalf() == 4.0
True
>>> exp2(x).diff(x)
log(2)*exp2(x)
See Also
========
log2
"""
nargs = 1
def fdiff(self, argindex=1):
"""
Returns the first derivative of this function.
"""
if argindex == 1:
return self*log(_Two)
else:
raise ArgumentIndexError(self, argindex)
def _eval_rewrite_as_Pow(self, arg, **kwargs):
return _exp2(arg)
_eval_rewrite_as_tractable = _eval_rewrite_as_Pow
def _eval_expand_func(self, **hints):
return _exp2(*self.args)
@classmethod
def eval(cls, arg):
if arg.is_number:
return _exp2(arg)
def _log2(x):
return log(x)/log(_Two)
class log2(Function):
"""
Represents the logarithm function with base two.
Explanation
===========
The benefit of using ``log2(x)`` over ``log(x)/log(2)``
is that the latter is not as efficient under finite precision
arithmetic.
Examples
========
>>> from sympy.abc import x
>>> from sympy.codegen.cfunctions import log2
>>> log2(4).evalf() == 2.0
True
>>> log2(x).diff(x)
1/(x*log(2))
See Also
========
exp2
log10
"""
nargs = 1
def fdiff(self, argindex=1):
"""
Returns the first derivative of this function.
"""
if argindex == 1:
return S.One/(log(_Two)*self.args[0])
else:
raise ArgumentIndexError(self, argindex)
@classmethod
def eval(cls, arg):
if arg.is_number:
result = log.eval(arg, base=_Two)
if result.is_Atom:
return result
elif arg.is_Pow and arg.base == _Two:
return arg.exp
def _eval_evalf(self, *args, **kwargs):
return self.rewrite(log).evalf(*args, **kwargs)
def _eval_expand_func(self, **hints):
return _log2(*self.args)
def _eval_rewrite_as_log(self, arg, **kwargs):
return _log2(arg)
_eval_rewrite_as_tractable = _eval_rewrite_as_log
def _fma(x, y, z):
return x*y + z
class fma(Function):
"""
Represents "fused multiply add".
Explanation
===========
The benefit of using ``fma(x, y, z)`` over ``x*y + z``
is that, under finite precision arithmetic, the former is
supported by special instructions on some CPUs.
Examples
========
>>> from sympy.abc import x, y, z
>>> from sympy.codegen.cfunctions import fma
>>> fma(x, y, z).diff(x)
y
"""
nargs = 3
def fdiff(self, argindex=1):
"""
Returns the first derivative of this function.
"""
if argindex in (1, 2):
return self.args[2 - argindex]
elif argindex == 3:
return S.One
else:
raise ArgumentIndexError(self, argindex)
def _eval_expand_func(self, **hints):
return _fma(*self.args)
def _eval_rewrite_as_tractable(self, arg, limitvar=None, **kwargs):
return _fma(arg)
_Ten = S(10)
def _log10(x):
return log(x)/log(_Ten)
class log10(Function):
"""
Represents the logarithm function with base ten.
Examples
========
>>> from sympy.abc import x
>>> from sympy.codegen.cfunctions import log10
>>> log10(100).evalf() == 2.0
True
>>> log10(x).diff(x)
1/(x*log(10))
See Also
========
log2
"""
nargs = 1
def fdiff(self, argindex=1):
"""
Returns the first derivative of this function.
"""
if argindex == 1:
return S.One/(log(_Ten)*self.args[0])
else:
raise ArgumentIndexError(self, argindex)
@classmethod
def eval(cls, arg):
if arg.is_number:
result = log.eval(arg, base=_Ten)
if result.is_Atom:
return result
elif arg.is_Pow and arg.base == _Ten:
return arg.exp
def _eval_expand_func(self, **hints):
return _log10(*self.args)
def _eval_rewrite_as_log(self, arg, **kwargs):
return _log10(arg)
_eval_rewrite_as_tractable = _eval_rewrite_as_log
def _Sqrt(x):
return Pow(x, S.Half)
class Sqrt(Function): # 'sqrt' already defined in sympy.functions.elementary.miscellaneous
"""
Represents the square root function.
Explanation
===========
The reason why one would use ``Sqrt(x)`` over ``sqrt(x)``
is that the latter is internally represented as ``Pow(x, S.Half)`` which
may not be what one wants when doing code-generation.
Examples
========
>>> from sympy.abc import x
>>> from sympy.codegen.cfunctions import Sqrt
>>> Sqrt(x)
Sqrt(x)
>>> Sqrt(x).diff(x)
1/(2*sqrt(x))
See Also
========
Cbrt
"""
nargs = 1
def fdiff(self, argindex=1):
"""
Returns the first derivative of this function.
"""
if argindex == 1:
return Pow(self.args[0], Rational(-1, 2))/_Two
else:
raise ArgumentIndexError(self, argindex)
def _eval_expand_func(self, **hints):
return _Sqrt(*self.args)
def _eval_rewrite_as_Pow(self, arg, **kwargs):
return _Sqrt(arg)
_eval_rewrite_as_tractable = _eval_rewrite_as_Pow
def _Cbrt(x):
return Pow(x, Rational(1, 3))
class Cbrt(Function): # 'cbrt' already defined in sympy.functions.elementary.miscellaneous
"""
Represents the cube root function.
Explanation
===========
The reason why one would use ``Cbrt(x)`` over ``cbrt(x)``
is that the latter is internally represented as ``Pow(x, Rational(1, 3))`` which
may not be what one wants when doing code-generation.
Examples
========
>>> from sympy.abc import x
>>> from sympy.codegen.cfunctions import Cbrt
>>> Cbrt(x)
Cbrt(x)
>>> Cbrt(x).diff(x)
1/(3*x**(2/3))
See Also
========
Sqrt
"""
nargs = 1
def fdiff(self, argindex=1):
"""
Returns the first derivative of this function.
"""
if argindex == 1:
return Pow(self.args[0], Rational(-_Two/3))/3
else:
raise ArgumentIndexError(self, argindex)
def _eval_expand_func(self, **hints):
return _Cbrt(*self.args)
def _eval_rewrite_as_Pow(self, arg, **kwargs):
return _Cbrt(arg)
_eval_rewrite_as_tractable = _eval_rewrite_as_Pow
def _hypot(x, y):
return sqrt(Pow(x, 2) + Pow(y, 2))
class hypot(Function):
"""
Represents the hypotenuse function.
Explanation
===========
The hypotenuse function is provided by e.g. the math library
in the C99 standard, hence one may want to represent the function
symbolically when doing code-generation.
Examples
========
>>> from sympy.abc import x, y
>>> from sympy.codegen.cfunctions import hypot
>>> hypot(3, 4).evalf() == 5.0
True
>>> hypot(x, y)
hypot(x, y)
>>> hypot(x, y).diff(x)
x/hypot(x, y)
"""
nargs = 2
def fdiff(self, argindex=1):
"""
Returns the first derivative of this function.
"""
if argindex in (1, 2):
return 2*self.args[argindex-1]/(_Two*self.func(*self.args))
else:
raise ArgumentIndexError(self, argindex)
def _eval_expand_func(self, **hints):
return _hypot(*self.args)
def _eval_rewrite_as_Pow(self, arg, **kwargs):
return _hypot(arg)
_eval_rewrite_as_tractable = _eval_rewrite_as_Pow
class isnan(BooleanFunction):
nargs = 1
@classmethod
def eval(cls, arg):
if arg is S.NaN:
return true
elif arg.is_number:
return false
else:
return None
class isinf(BooleanFunction):
nargs = 1
@classmethod
def eval(cls, arg):
if arg.is_infinite:
return true
elif arg.is_finite:
return false
else:
return None
@@ -0,0 +1,156 @@
"""
AST nodes specific to the C family of languages
"""
from sympy.codegen.ast import (
Attribute, Declaration, Node, String, Token, Type, none,
FunctionCall, CodeBlock
)
from sympy.core.basic import Basic
from sympy.core.containers import Tuple
from sympy.core.sympify import sympify
void = Type('void')
restrict = Attribute('restrict') # guarantees no pointer aliasing
volatile = Attribute('volatile')
static = Attribute('static')
def alignof(arg):
""" Generate of FunctionCall instance for calling 'alignof' """
return FunctionCall('alignof', [String(arg) if isinstance(arg, str) else arg])
def sizeof(arg):
""" Generate of FunctionCall instance for calling 'sizeof'
Examples
========
>>> from sympy.codegen.ast import real
>>> from sympy.codegen.cnodes import sizeof
>>> from sympy import ccode
>>> ccode(sizeof(real))
'sizeof(double)'
"""
return FunctionCall('sizeof', [String(arg) if isinstance(arg, str) else arg])
class CommaOperator(Basic):
""" Represents the comma operator in C """
def __new__(cls, *args):
return Basic.__new__(cls, *[sympify(arg) for arg in args])
class Label(Node):
""" Label for use with e.g. goto statement.
Examples
========
>>> from sympy import ccode, Symbol
>>> from sympy.codegen.cnodes import Label, PreIncrement
>>> print(ccode(Label('foo')))
foo:
>>> print(ccode(Label('bar', [PreIncrement(Symbol('a'))])))
bar:
++(a);
"""
__slots__ = _fields = ('name', 'body')
defaults = {'body': none}
_construct_name = String
@classmethod
def _construct_body(cls, itr):
if isinstance(itr, CodeBlock):
return itr
else:
return CodeBlock(*itr)
class goto(Token):
""" Represents goto in C """
__slots__ = _fields = ('label',)
_construct_label = Label
class PreDecrement(Basic):
""" Represents the pre-decrement operator
Examples
========
>>> from sympy.abc import x
>>> from sympy.codegen.cnodes import PreDecrement
>>> from sympy import ccode
>>> ccode(PreDecrement(x))
'--(x)'
"""
nargs = 1
class PostDecrement(Basic):
""" Represents the post-decrement operator
Examples
========
>>> from sympy.abc import x
>>> from sympy.codegen.cnodes import PostDecrement
>>> from sympy import ccode
>>> ccode(PostDecrement(x))
'(x)--'
"""
nargs = 1
class PreIncrement(Basic):
""" Represents the pre-increment operator
Examples
========
>>> from sympy.abc import x
>>> from sympy.codegen.cnodes import PreIncrement
>>> from sympy import ccode
>>> ccode(PreIncrement(x))
'++(x)'
"""
nargs = 1
class PostIncrement(Basic):
""" Represents the post-increment operator
Examples
========
>>> from sympy.abc import x
>>> from sympy.codegen.cnodes import PostIncrement
>>> from sympy import ccode
>>> ccode(PostIncrement(x))
'(x)++'
"""
nargs = 1
class struct(Node):
""" Represents a struct in C """
__slots__ = _fields = ('name', 'declarations')
defaults = {'name': none}
_construct_name = String
@classmethod
def _construct_declarations(cls, args):
return Tuple(*[Declaration(arg) for arg in args])
class union(struct):
""" Represents a union in C """
__slots__ = ()
@@ -0,0 +1,8 @@
from sympy.printing.c import C99CodePrinter
def render_as_source_file(content, Printer=C99CodePrinter, settings=None):
""" Renders a C source file (with required #include statements) """
printer = Printer(settings or {})
code_str = printer.doprint(content)
includes = '\n'.join(['#include <%s>' % h for h in printer.headers])
return includes + '\n\n' + code_str
@@ -0,0 +1,14 @@
"""
AST nodes specific to C++.
"""
from sympy.codegen.ast import Attribute, String, Token, Type, none
class using(Token):
""" Represents a 'using' statement in C++ """
__slots__ = _fields = ('type', 'alias')
defaults = {'alias': none}
_construct_type = Type
_construct_alias = String
constexpr = Attribute('constexpr')
@@ -0,0 +1,658 @@
"""
AST nodes specific to Fortran.
The functions defined in this module allows the user to express functions such as ``dsign``
as a SymPy function for symbolic manipulation.
"""
from __future__ import annotations
from sympy.codegen.ast import (
Attribute, CodeBlock, FunctionCall, Node, none, String,
Token, _mk_Tuple, Variable
)
from sympy.core.basic import Basic
from sympy.core.containers import Tuple
from sympy.core.expr import Expr
from sympy.core.function import Function
from sympy.core.numbers import Float, Integer
from sympy.core.symbol import Str
from sympy.core.sympify import sympify
from sympy.logic import true, false
from sympy.utilities.iterables import iterable
pure = Attribute('pure')
elemental = Attribute('elemental') # (all elemental procedures are also pure)
intent_in = Attribute('intent_in')
intent_out = Attribute('intent_out')
intent_inout = Attribute('intent_inout')
allocatable = Attribute('allocatable')
class Program(Token):
""" Represents a 'program' block in Fortran.
Examples
========
>>> from sympy.codegen.ast import Print
>>> from sympy.codegen.fnodes import Program
>>> prog = Program('myprogram', [Print([42])])
>>> from sympy import fcode
>>> print(fcode(prog, source_format='free'))
program myprogram
print *, 42
end program
"""
__slots__ = _fields = ('name', 'body')
_construct_name = String
_construct_body = staticmethod(lambda body: CodeBlock(*body))
class use_rename(Token):
""" Represents a renaming in a use statement in Fortran.
Examples
========
>>> from sympy.codegen.fnodes import use_rename, use
>>> from sympy import fcode
>>> ren = use_rename("thingy", "convolution2d")
>>> print(fcode(ren, source_format='free'))
thingy => convolution2d
>>> full = use('signallib', only=['snr', ren])
>>> print(fcode(full, source_format='free'))
use signallib, only: snr, thingy => convolution2d
"""
__slots__ = _fields = ('local', 'original')
_construct_local = String
_construct_original = String
def _name(arg):
if hasattr(arg, 'name'):
return arg.name
else:
return String(arg)
class use(Token):
""" Represents a use statement in Fortran.
Examples
========
>>> from sympy.codegen.fnodes import use
>>> from sympy import fcode
>>> fcode(use('signallib'), source_format='free')
'use signallib'
>>> fcode(use('signallib', [('metric', 'snr')]), source_format='free')
'use signallib, metric => snr'
>>> fcode(use('signallib', only=['snr', 'convolution2d']), source_format='free')
'use signallib, only: snr, convolution2d'
"""
__slots__ = _fields = ('namespace', 'rename', 'only')
defaults = {'rename': none, 'only': none}
_construct_namespace = staticmethod(_name)
_construct_rename = staticmethod(lambda args: Tuple(*[arg if isinstance(arg, use_rename) else use_rename(*arg) for arg in args]))
_construct_only = staticmethod(lambda args: Tuple(*[arg if isinstance(arg, use_rename) else _name(arg) for arg in args]))
class Module(Token):
""" Represents a module in Fortran.
Examples
========
>>> from sympy.codegen.fnodes import Module
>>> from sympy import fcode
>>> print(fcode(Module('signallib', ['implicit none'], []), source_format='free'))
module signallib
implicit none
<BLANKLINE>
contains
<BLANKLINE>
<BLANKLINE>
end module
"""
__slots__ = _fields = ('name', 'declarations', 'definitions')
defaults = {'declarations': Tuple()}
_construct_name = String
@classmethod
def _construct_declarations(cls, args):
args = [Str(arg) if isinstance(arg, str) else arg for arg in args]
return CodeBlock(*args)
_construct_definitions = staticmethod(lambda arg: CodeBlock(*arg))
class Subroutine(Node):
""" Represents a subroutine in Fortran.
Examples
========
>>> from sympy import fcode, symbols
>>> from sympy.codegen.ast import Print
>>> from sympy.codegen.fnodes import Subroutine
>>> x, y = symbols('x y', real=True)
>>> sub = Subroutine('mysub', [x, y], [Print([x**2 + y**2, x*y])])
>>> print(fcode(sub, source_format='free', standard=2003))
subroutine mysub(x, y)
real*8 :: x
real*8 :: y
print *, x**2 + y**2, x*y
end subroutine
"""
__slots__ = ('name', 'parameters', 'body')
_fields = __slots__ + Node._fields
_construct_name = String
_construct_parameters = staticmethod(lambda params: Tuple(*map(Variable.deduced, params)))
@classmethod
def _construct_body(cls, itr):
if isinstance(itr, CodeBlock):
return itr
else:
return CodeBlock(*itr)
class SubroutineCall(Token):
""" Represents a call to a subroutine in Fortran.
Examples
========
>>> from sympy.codegen.fnodes import SubroutineCall
>>> from sympy import fcode
>>> fcode(SubroutineCall('mysub', 'x y'.split()))
' call mysub(x, y)'
"""
__slots__ = _fields = ('name', 'subroutine_args')
_construct_name = staticmethod(_name)
_construct_subroutine_args = staticmethod(_mk_Tuple)
class Do(Token):
""" Represents a Do loop in in Fortran.
Examples
========
>>> from sympy import fcode, symbols
>>> from sympy.codegen.ast import aug_assign, Print
>>> from sympy.codegen.fnodes import Do
>>> i, n = symbols('i n', integer=True)
>>> r = symbols('r', real=True)
>>> body = [aug_assign(r, '+', 1/i), Print([i, r])]
>>> do1 = Do(body, i, 1, n)
>>> print(fcode(do1, source_format='free'))
do i = 1, n
r = r + 1d0/i
print *, i, r
end do
>>> do2 = Do(body, i, 1, n, 2)
>>> print(fcode(do2, source_format='free'))
do i = 1, n, 2
r = r + 1d0/i
print *, i, r
end do
"""
__slots__ = _fields = ('body', 'counter', 'first', 'last', 'step', 'concurrent')
defaults = {'step': Integer(1), 'concurrent': false}
_construct_body = staticmethod(lambda body: CodeBlock(*body))
_construct_counter = staticmethod(sympify)
_construct_first = staticmethod(sympify)
_construct_last = staticmethod(sympify)
_construct_step = staticmethod(sympify)
_construct_concurrent = staticmethod(lambda arg: true if arg else false)
class ArrayConstructor(Token):
""" Represents an array constructor.
Examples
========
>>> from sympy import fcode
>>> from sympy.codegen.fnodes import ArrayConstructor
>>> ac = ArrayConstructor([1, 2, 3])
>>> fcode(ac, standard=95, source_format='free')
'(/1, 2, 3/)'
>>> fcode(ac, standard=2003, source_format='free')
'[1, 2, 3]'
"""
__slots__ = _fields = ('elements',)
_construct_elements = staticmethod(_mk_Tuple)
class ImpliedDoLoop(Token):
""" Represents an implied do loop in Fortran.
Examples
========
>>> from sympy import Symbol, fcode
>>> from sympy.codegen.fnodes import ImpliedDoLoop, ArrayConstructor
>>> i = Symbol('i', integer=True)
>>> idl = ImpliedDoLoop(i**3, i, -3, 3, 2) # -27, -1, 1, 27
>>> ac = ArrayConstructor([-28, idl, 28]) # -28, -27, -1, 1, 27, 28
>>> fcode(ac, standard=2003, source_format='free')
'[-28, (i**3, i = -3, 3, 2), 28]'
"""
__slots__ = _fields = ('expr', 'counter', 'first', 'last', 'step')
defaults = {'step': Integer(1)}
_construct_expr = staticmethod(sympify)
_construct_counter = staticmethod(sympify)
_construct_first = staticmethod(sympify)
_construct_last = staticmethod(sympify)
_construct_step = staticmethod(sympify)
class Extent(Basic):
""" Represents a dimension extent.
Examples
========
>>> from sympy.codegen.fnodes import Extent
>>> e = Extent(-3, 3) # -3, -2, -1, 0, 1, 2, 3
>>> from sympy import fcode
>>> fcode(e, source_format='free')
'-3:3'
>>> from sympy.codegen.ast import Variable, real
>>> from sympy.codegen.fnodes import dimension, intent_out
>>> dim = dimension(e, e)
>>> arr = Variable('x', real, attrs=[dim, intent_out])
>>> fcode(arr.as_Declaration(), source_format='free', standard=2003)
'real*8, dimension(-3:3, -3:3), intent(out) :: x'
"""
def __new__(cls, *args):
if len(args) == 2:
low, high = args
return Basic.__new__(cls, sympify(low), sympify(high))
elif len(args) == 0 or (len(args) == 1 and args[0] in (':', None)):
return Basic.__new__(cls) # assumed shape
else:
raise ValueError("Expected 0 or 2 args (or one argument == None or ':')")
def _sympystr(self, printer):
if len(self.args) == 0:
return ':'
return ":".join(str(arg) for arg in self.args)
assumed_extent = Extent() # or Extent(':'), Extent(None)
def dimension(*args):
""" Creates a 'dimension' Attribute with (up to 7) extents.
Examples
========
>>> from sympy import fcode
>>> from sympy.codegen.fnodes import dimension, intent_in
>>> dim = dimension('2', ':') # 2 rows, runtime determined number of columns
>>> from sympy.codegen.ast import Variable, integer
>>> arr = Variable('a', integer, attrs=[dim, intent_in])
>>> fcode(arr.as_Declaration(), source_format='free', standard=2003)
'integer*4, dimension(2, :), intent(in) :: a'
"""
if len(args) > 7:
raise ValueError("Fortran only supports up to 7 dimensional arrays")
parameters = []
for arg in args:
if isinstance(arg, Extent):
parameters.append(arg)
elif isinstance(arg, str):
if arg == ':':
parameters.append(Extent())
else:
parameters.append(String(arg))
elif iterable(arg):
parameters.append(Extent(*arg))
else:
parameters.append(sympify(arg))
if len(args) == 0:
raise ValueError("Need at least one dimension")
return Attribute('dimension', parameters)
assumed_size = dimension('*')
def array(symbol, dim, intent=None, *, attrs=(), value=None, type=None):
""" Convenience function for creating a Variable instance for a Fortran array.
Parameters
==========
symbol : symbol
dim : Attribute or iterable
If dim is an ``Attribute`` it need to have the name 'dimension'. If it is
not an ``Attribute``, then it is passed to :func:`dimension` as ``*dim``
intent : str
One of: 'in', 'out', 'inout' or None
\\*\\*kwargs:
Keyword arguments for ``Variable`` ('type' & 'value')
Examples
========
>>> from sympy import fcode
>>> from sympy.codegen.ast import integer, real
>>> from sympy.codegen.fnodes import array
>>> arr = array('a', '*', 'in', type=integer)
>>> print(fcode(arr.as_Declaration(), source_format='free', standard=2003))
integer*4, dimension(*), intent(in) :: a
>>> x = array('x', [3, ':', ':'], intent='out', type=real)
>>> print(fcode(x.as_Declaration(value=1), source_format='free', standard=2003))
real*8, dimension(3, :, :), intent(out) :: x = 1
"""
if isinstance(dim, Attribute):
if str(dim.name) != 'dimension':
raise ValueError("Got an unexpected Attribute argument as dim: %s" % str(dim))
else:
dim = dimension(*dim)
attrs = list(attrs) + [dim]
if intent is not None:
if intent not in (intent_in, intent_out, intent_inout):
intent = {'in': intent_in, 'out': intent_out, 'inout': intent_inout}[intent]
attrs.append(intent)
if type is None:
return Variable.deduced(symbol, value=value, attrs=attrs)
else:
return Variable(symbol, type, value=value, attrs=attrs)
def _printable(arg):
return String(arg) if isinstance(arg, str) else sympify(arg)
def allocated(array):
""" Creates an AST node for a function call to Fortran's "allocated(...)"
Examples
========
>>> from sympy import fcode
>>> from sympy.codegen.fnodes import allocated
>>> alloc = allocated('x')
>>> fcode(alloc, source_format='free')
'allocated(x)'
"""
return FunctionCall('allocated', [_printable(array)])
def lbound(array, dim=None, kind=None):
""" Creates an AST node for a function call to Fortran's "lbound(...)"
Parameters
==========
array : Symbol or String
dim : expr
kind : expr
Examples
========
>>> from sympy import fcode
>>> from sympy.codegen.fnodes import lbound
>>> lb = lbound('arr', dim=2)
>>> fcode(lb, source_format='free')
'lbound(arr, 2)'
"""
return FunctionCall(
'lbound',
[_printable(array)] +
([_printable(dim)] if dim else []) +
([_printable(kind)] if kind else [])
)
def ubound(array, dim=None, kind=None):
return FunctionCall(
'ubound',
[_printable(array)] +
([_printable(dim)] if dim else []) +
([_printable(kind)] if kind else [])
)
def shape(source, kind=None):
""" Creates an AST node for a function call to Fortran's "shape(...)"
Parameters
==========
source : Symbol or String
kind : expr
Examples
========
>>> from sympy import fcode
>>> from sympy.codegen.fnodes import shape
>>> shp = shape('x')
>>> fcode(shp, source_format='free')
'shape(x)'
"""
return FunctionCall(
'shape',
[_printable(source)] +
([_printable(kind)] if kind else [])
)
def size(array, dim=None, kind=None):
""" Creates an AST node for a function call to Fortran's "size(...)"
Examples
========
>>> from sympy import fcode, Symbol
>>> from sympy.codegen.ast import FunctionDefinition, real, Return
>>> from sympy.codegen.fnodes import array, sum_, size
>>> a = Symbol('a', real=True)
>>> body = [Return((sum_(a**2)/size(a))**.5)]
>>> arr = array(a, dim=[':'], intent='in')
>>> fd = FunctionDefinition(real, 'rms', [arr], body)
>>> print(fcode(fd, source_format='free', standard=2003))
real*8 function rms(a)
real*8, dimension(:), intent(in) :: a
rms = sqrt(sum(a**2)*1d0/size(a))
end function
"""
return FunctionCall(
'size',
[_printable(array)] +
([_printable(dim)] if dim else []) +
([_printable(kind)] if kind else [])
)
def reshape(source, shape, pad=None, order=None):
""" Creates an AST node for a function call to Fortran's "reshape(...)"
Parameters
==========
source : Symbol or String
shape : ArrayExpr
"""
return FunctionCall(
'reshape',
[_printable(source), _printable(shape)] +
([_printable(pad)] if pad else []) +
([_printable(order)] if pad else [])
)
def bind_C(name=None):
""" Creates an Attribute ``bind_C`` with a name.
Parameters
==========
name : str
Examples
========
>>> from sympy import fcode, Symbol
>>> from sympy.codegen.ast import FunctionDefinition, real, Return
>>> from sympy.codegen.fnodes import array, sum_, bind_C
>>> a = Symbol('a', real=True)
>>> s = Symbol('s', integer=True)
>>> arr = array(a, dim=[s], intent='in')
>>> body = [Return((sum_(a**2)/s)**.5)]
>>> fd = FunctionDefinition(real, 'rms', [arr, s], body, attrs=[bind_C('rms')])
>>> print(fcode(fd, source_format='free', standard=2003))
real*8 function rms(a, s) bind(C, name="rms")
real*8, dimension(s), intent(in) :: a
integer*4 :: s
rms = sqrt(sum(a**2)/s)
end function
"""
return Attribute('bind_C', [String(name)] if name else [])
class GoTo(Token):
""" Represents a goto statement in Fortran
Examples
========
>>> from sympy.codegen.fnodes import GoTo
>>> go = GoTo([10, 20, 30], 'i')
>>> from sympy import fcode
>>> fcode(go, source_format='free')
'go to (10, 20, 30), i'
"""
__slots__ = _fields = ('labels', 'expr')
defaults = {'expr': none}
_construct_labels = staticmethod(_mk_Tuple)
_construct_expr = staticmethod(sympify)
class FortranReturn(Token):
""" AST node explicitly mapped to a fortran "return".
Explanation
===========
Because a return statement in fortran is different from C, and
in order to aid reuse of our codegen ASTs the ordinary
``.codegen.ast.Return`` is interpreted as assignment to
the result variable of the function. If one for some reason needs
to generate a fortran RETURN statement, this node should be used.
Examples
========
>>> from sympy.codegen.fnodes import FortranReturn
>>> from sympy import fcode
>>> fcode(FortranReturn('x'))
' return x'
"""
__slots__ = _fields = ('return_value',)
defaults = {'return_value': none}
_construct_return_value = staticmethod(sympify)
class FFunction(Function):
_required_standard = 77
def _fcode(self, printer):
name = self.__class__.__name__
if printer._settings['standard'] < self._required_standard:
raise NotImplementedError("%s requires Fortran %d or newer" %
(name, self._required_standard))
return '{}({})'.format(name, ', '.join(map(printer._print, self.args)))
class F95Function(FFunction):
_required_standard = 95
class isign(FFunction):
""" Fortran sign intrinsic for integer arguments. """
nargs = 2
class dsign(FFunction):
""" Fortran sign intrinsic for double precision arguments. """
nargs = 2
class cmplx(FFunction):
""" Fortran complex conversion function. """
nargs = 2 # may be extended to (2, 3) at a later point
class kind(FFunction):
""" Fortran kind function. """
nargs = 1
class merge(F95Function):
""" Fortran merge function """
nargs = 3
class _literal(Float):
_token: str
_decimals: int
def _fcode(self, printer, *args, **kwargs):
mantissa, sgnd_ex = ('%.{}e'.format(self._decimals) % self).split('e')
mantissa = mantissa.strip('0').rstrip('.')
ex_sgn, ex_num = sgnd_ex[0], sgnd_ex[1:].lstrip('0')
ex_sgn = '' if ex_sgn == '+' else ex_sgn
return (mantissa or '0') + self._token + ex_sgn + (ex_num or '0')
class literal_sp(_literal):
""" Fortran single precision real literal """
_token = 'e'
_decimals = 9
class literal_dp(_literal):
""" Fortran double precision real literal """
_token = 'd'
_decimals = 17
class sum_(Token, Expr):
__slots__ = _fields = ('array', 'dim', 'mask')
defaults = {'dim': none, 'mask': none}
_construct_array = staticmethod(sympify)
_construct_dim = staticmethod(sympify)
class product_(Token, Expr):
__slots__ = _fields = ('array', 'dim', 'mask')
defaults = {'dim': none, 'mask': none}
_construct_array = staticmethod(sympify)
_construct_dim = staticmethod(sympify)
@@ -0,0 +1,40 @@
from itertools import chain
from sympy.codegen.fnodes import Module
from sympy.core.symbol import Dummy
from sympy.printing.fortran import FCodePrinter
""" This module collects utilities for rendering Fortran code. """
def render_as_module(definitions, name, declarations=(), printer_settings=None):
""" Creates a ``Module`` instance and renders it as a string.
This generates Fortran source code for a module with the correct ``use`` statements.
Parameters
==========
definitions : iterable
Passed to :class:`sympy.codegen.fnodes.Module`.
name : str
Passed to :class:`sympy.codegen.fnodes.Module`.
declarations : iterable
Passed to :class:`sympy.codegen.fnodes.Module`. It will be extended with
use statements, 'implicit none' and public list generated from ``definitions``.
printer_settings : dict
Passed to ``FCodePrinter`` (default: ``{'standard': 2003, 'source_format': 'free'}``).
"""
printer_settings = printer_settings or {'standard': 2003, 'source_format': 'free'}
printer = FCodePrinter(printer_settings)
dummy = Dummy()
if isinstance(definitions, Module):
raise ValueError("This function expects to construct a module on its own.")
mod = Module(name, chain(declarations, [dummy]), definitions)
fstr = printer.doprint(mod)
module_use_str = ' %s\n' % ' \n'.join(['use %s, only: %s' % (k, ', '.join(v)) for
k, v in printer.module_uses.items()])
module_use_str += ' implicit none\n'
module_use_str += ' private\n'
module_use_str += ' public %s\n' % ', '.join([str(node.name) for node in definitions if getattr(node, 'name', None)])
return fstr.replace(printer.doprint(dummy), module_use_str)
@@ -0,0 +1,71 @@
"""
Additional AST nodes for operations on matrices. The nodes in this module
are meant to represent optimization of matrix expressions within codegen's
target languages that cannot be represented by SymPy expressions.
As an example, we can use :meth:`sympy.codegen.rewriting.optimize` and the
``matin_opt`` optimization provided in :mod:`sympy.codegen.rewriting` to
transform matrix multiplication under certain assumptions:
>>> from sympy import symbols, MatrixSymbol
>>> n = symbols('n', integer=True)
>>> A = MatrixSymbol('A', n, n)
>>> x = MatrixSymbol('x', n, 1)
>>> expr = A**(-1) * x
>>> from sympy import assuming, Q
>>> from sympy.codegen.rewriting import matinv_opt, optimize
>>> with assuming(Q.fullrank(A)):
... optimize(expr, [matinv_opt])
MatrixSolve(A, vector=x)
"""
from .ast import Token
from sympy.matrices import MatrixExpr
from sympy.core.sympify import sympify
class MatrixSolve(Token, MatrixExpr):
"""Represents an operation to solve a linear matrix equation.
Parameters
==========
matrix : MatrixSymbol
Matrix representing the coefficients of variables in the linear
equation. This matrix must be square and full-rank (i.e. all columns must
be linearly independent) for the solving operation to be valid.
vector : MatrixSymbol
One-column matrix representing the solutions to the equations
represented in ``matrix``.
Examples
========
>>> from sympy import symbols, MatrixSymbol
>>> from sympy.codegen.matrix_nodes import MatrixSolve
>>> n = symbols('n', integer=True)
>>> A = MatrixSymbol('A', n, n)
>>> x = MatrixSymbol('x', n, 1)
>>> from sympy.printing.numpy import NumPyPrinter
>>> NumPyPrinter().doprint(MatrixSolve(A, x))
'numpy.linalg.solve(A, x)'
>>> from sympy import octave_code
>>> octave_code(MatrixSolve(A, x))
'A \\\\ x'
"""
__slots__ = _fields = ('matrix', 'vector')
_construct_matrix = staticmethod(sympify)
_construct_vector = staticmethod(sympify)
@property
def shape(self):
return self.vector.shape
def _eval_derivative(self, x):
A, b = self.matrix, self.vector
return MatrixSolve(A, b.diff(x) - A.diff(x) * MatrixSolve(A, b))
@@ -0,0 +1,177 @@
from sympy.core.function import Add, ArgumentIndexError, Function
from sympy.core.power import Pow
from sympy.core.singleton import S
from sympy.core.sorting import default_sort_key
from sympy.core.sympify import sympify
from sympy.functions.elementary.exponential import exp, log
from sympy.functions.elementary.miscellaneous import Max, Min
from .ast import Token, none
def _logaddexp(x1, x2, *, evaluate=True):
return log(Add(exp(x1, evaluate=evaluate), exp(x2, evaluate=evaluate), evaluate=evaluate))
_two = S.One*2
_ln2 = log(_two)
def _lb(x, *, evaluate=True):
return log(x, evaluate=evaluate)/_ln2
def _exp2(x, *, evaluate=True):
return Pow(_two, x, evaluate=evaluate)
def _logaddexp2(x1, x2, *, evaluate=True):
return _lb(Add(_exp2(x1, evaluate=evaluate),
_exp2(x2, evaluate=evaluate), evaluate=evaluate))
class logaddexp(Function):
""" Logarithm of the sum of exponentiations of the inputs.
Helper class for use with e.g. numpy.logaddexp
See Also
========
https://numpy.org/doc/stable/reference/generated/numpy.logaddexp.html
"""
nargs = 2
def __new__(cls, *args):
return Function.__new__(cls, *sorted(args, key=default_sort_key))
def fdiff(self, argindex=1):
"""
Returns the first derivative of this function.
"""
if argindex == 1:
wrt, other = self.args
elif argindex == 2:
other, wrt = self.args
else:
raise ArgumentIndexError(self, argindex)
return S.One/(S.One + exp(other-wrt))
def _eval_rewrite_as_log(self, x1, x2, **kwargs):
return _logaddexp(x1, x2)
def _eval_evalf(self, *args, **kwargs):
return self.rewrite(log).evalf(*args, **kwargs)
def _eval_simplify(self, *args, **kwargs):
a, b = (x.simplify(**kwargs) for x in self.args)
candidate = _logaddexp(a, b)
if candidate != _logaddexp(a, b, evaluate=False):
return candidate
else:
return logaddexp(a, b)
class logaddexp2(Function):
""" Logarithm of the sum of exponentiations of the inputs in base-2.
Helper class for use with e.g. numpy.logaddexp2
See Also
========
https://numpy.org/doc/stable/reference/generated/numpy.logaddexp2.html
"""
nargs = 2
def __new__(cls, *args):
return Function.__new__(cls, *sorted(args, key=default_sort_key))
def fdiff(self, argindex=1):
"""
Returns the first derivative of this function.
"""
if argindex == 1:
wrt, other = self.args
elif argindex == 2:
other, wrt = self.args
else:
raise ArgumentIndexError(self, argindex)
return S.One/(S.One + _exp2(other-wrt))
def _eval_rewrite_as_log(self, x1, x2, **kwargs):
return _logaddexp2(x1, x2)
def _eval_evalf(self, *args, **kwargs):
return self.rewrite(log).evalf(*args, **kwargs)
def _eval_simplify(self, *args, **kwargs):
a, b = (x.simplify(**kwargs).factor() for x in self.args)
candidate = _logaddexp2(a, b)
if candidate != _logaddexp2(a, b, evaluate=False):
return candidate
else:
return logaddexp2(a, b)
class amin(Token):
""" Minimum value along an axis.
Helper class for use with e.g. numpy.amin
See Also
========
https://numpy.org/doc/stable/reference/generated/numpy.amin.html
"""
__slots__ = _fields = ('array', 'axis')
defaults = {'axis': none}
_construct_axis = staticmethod(sympify)
class amax(Token):
""" Maximum value along an axis.
Helper class for use with e.g. numpy.amax
See Also
========
https://numpy.org/doc/stable/reference/generated/numpy.amax.html
"""
__slots__ = _fields = ('array', 'axis')
defaults = {'axis': none}
_construct_axis = staticmethod(sympify)
class maximum(Function):
""" Element-wise maximum of array elements.
Helper class for use with e.g. numpy.maximum
See Also
========
https://numpy.org/doc/stable/reference/generated/numpy.maximum.html
"""
def _eval_rewrite_as_Max(self, *args):
return Max(*self.args)
class minimum(Function):
""" Element-wise minimum of array elements.
Helper class for use with e.g. numpy.minimum
See Also
========
https://numpy.org/doc/stable/reference/generated/numpy.minimum.html
"""
def _eval_rewrite_as_Min(self, *args):
return Min(*self.args)
@@ -0,0 +1,11 @@
from .abstract_nodes import List as AbstractList
from .ast import Token
class List(AbstractList):
pass
class NumExprEvaluate(Token):
"""represents a call to :class:`numexpr`s :func:`evaluate`"""
__slots__ = _fields = ('expr',)
@@ -0,0 +1,24 @@
from sympy.printing.pycode import PythonCodePrinter
""" This module collects utilities for rendering Python code. """
def render_as_module(content, standard='python3'):
"""Renders Python code as a module (with the required imports).
Parameters
==========
standard :
See the parameter ``standard`` in
:meth:`sympy.printing.pycode.pycode`
"""
printer = PythonCodePrinter({'standard':standard})
pystr = printer.doprint(content)
if printer._settings['fully_qualified_modules']:
module_imports_str = '\n'.join('import %s' % k for k in printer.module_imports)
else:
module_imports_str = '\n'.join(['from %s import %s' % (k, ', '.join(v)) for
k, v in printer.module_imports.items()])
return module_imports_str + '\n\n' + pystr
@@ -0,0 +1,357 @@
"""
Classes and functions useful for rewriting expressions for optimized code
generation. Some languages (or standards thereof), e.g. C99, offer specialized
math functions for better performance and/or precision.
Using the ``optimize`` function in this module, together with a collection of
rules (represented as instances of ``Optimization``), one can rewrite the
expressions for this purpose::
>>> from sympy import Symbol, exp, log
>>> from sympy.codegen.rewriting import optimize, optims_c99
>>> x = Symbol('x')
>>> optimize(3*exp(2*x) - 3, optims_c99)
3*expm1(2*x)
>>> optimize(exp(2*x) - 1 - exp(-33), optims_c99)
expm1(2*x) - exp(-33)
>>> optimize(log(3*x + 3), optims_c99)
log1p(x) + log(3)
>>> optimize(log(2*x + 3), optims_c99)
log(2*x + 3)
The ``optims_c99`` imported above is tuple containing the following instances
(which may be imported from ``sympy.codegen.rewriting``):
- ``expm1_opt``
- ``log1p_opt``
- ``exp2_opt``
- ``log2_opt``
- ``log2const_opt``
"""
from sympy.core.function import expand_log
from sympy.core.singleton import S
from sympy.core.symbol import Wild
from sympy.functions.elementary.complexes import sign
from sympy.functions.elementary.exponential import (exp, log)
from sympy.functions.elementary.miscellaneous import (Max, Min)
from sympy.functions.elementary.trigonometric import (cos, sin, sinc)
from sympy.assumptions import Q, ask
from sympy.codegen.cfunctions import log1p, log2, exp2, expm1
from sympy.codegen.matrix_nodes import MatrixSolve
from sympy.core.expr import UnevaluatedExpr
from sympy.core.power import Pow
from sympy.codegen.numpy_nodes import logaddexp, logaddexp2
from sympy.codegen.scipy_nodes import cosm1, powm1
from sympy.core.mul import Mul
from sympy.matrices.expressions.matexpr import MatrixSymbol
from sympy.utilities.iterables import sift
class Optimization:
""" Abstract base class for rewriting optimization.
Subclasses should implement ``__call__`` taking an expression
as argument.
Parameters
==========
cost_function : callable returning number
priority : number
"""
def __init__(self, cost_function=None, priority=1):
self.cost_function = cost_function
self.priority=priority
def cheapest(self, *args):
return min(args, key=self.cost_function)
class ReplaceOptim(Optimization):
""" Rewriting optimization calling replace on expressions.
Explanation
===========
The instance can be used as a function on expressions for which
it will apply the ``replace`` method (see
:meth:`sympy.core.basic.Basic.replace`).
Parameters
==========
query :
First argument passed to replace.
value :
Second argument passed to replace.
Examples
========
>>> from sympy import Symbol
>>> from sympy.codegen.rewriting import ReplaceOptim
>>> from sympy.codegen.cfunctions import exp2
>>> x = Symbol('x')
>>> exp2_opt = ReplaceOptim(lambda p: p.is_Pow and p.base == 2,
... lambda p: exp2(p.exp))
>>> exp2_opt(2**x)
exp2(x)
"""
def __init__(self, query, value, **kwargs):
super().__init__(**kwargs)
self.query = query
self.value = value
def __call__(self, expr):
return expr.replace(self.query, self.value)
def optimize(expr, optimizations):
""" Apply optimizations to an expression.
Parameters
==========
expr : expression
optimizations : iterable of ``Optimization`` instances
The optimizations will be sorted with respect to ``priority`` (highest first).
Examples
========
>>> from sympy import log, Symbol
>>> from sympy.codegen.rewriting import optims_c99, optimize
>>> x = Symbol('x')
>>> optimize(log(x+3)/log(2) + log(x**2 + 1), optims_c99)
log1p(x**2) + log2(x + 3)
"""
for optim in sorted(optimizations, key=lambda opt: opt.priority, reverse=True):
new_expr = optim(expr)
if optim.cost_function is None:
expr = new_expr
else:
expr = optim.cheapest(expr, new_expr)
return expr
exp2_opt = ReplaceOptim(
lambda p: p.is_Pow and p.base == 2,
lambda p: exp2(p.exp)
)
_d = Wild('d', properties=[lambda x: x.is_Dummy])
_u = Wild('u', properties=[lambda x: not x.is_number and not x.is_Add])
_v = Wild('v')
_w = Wild('w')
_n = Wild('n', properties=[lambda x: x.is_number])
sinc_opt1 = ReplaceOptim(
sin(_w)/_w, sinc(_w)
)
sinc_opt2 = ReplaceOptim(
sin(_n*_w)/_w, _n*sinc(_n*_w)
)
sinc_opts = (sinc_opt1, sinc_opt2)
log2_opt = ReplaceOptim(_v*log(_w)/log(2), _v*log2(_w), cost_function=lambda expr: expr.count(
lambda e: ( # division & eval of transcendentals are expensive floating point operations...
e.is_Pow and e.exp.is_negative # division
or (isinstance(e, (log, log2)) and not e.args[0].is_number)) # transcendental
)
)
log2const_opt = ReplaceOptim(log(2)*log2(_w), log(_w))
logsumexp_2terms_opt = ReplaceOptim(
lambda l: (isinstance(l, log)
and l.args[0].is_Add
and len(l.args[0].args) == 2
and all(isinstance(t, exp) for t in l.args[0].args)),
lambda l: (
Max(*[e.args[0] for e in l.args[0].args]) +
log1p(exp(Min(*[e.args[0] for e in l.args[0].args])))
)
)
class FuncMinusOneOptim(ReplaceOptim):
"""Specialization of ReplaceOptim for functions evaluating "f(x) - 1".
Explanation
===========
Numerical functions which go toward one as x go toward zero is often best
implemented by a dedicated function in order to avoid catastrophic
cancellation. One such example is ``expm1(x)`` in the C standard library
which evaluates ``exp(x) - 1``. Such functions preserves many more
significant digits when its argument is much smaller than one, compared
to subtracting one afterwards.
Parameters
==========
func :
The function which is subtracted by one.
func_m_1 :
The specialized function evaluating ``func(x) - 1``.
opportunistic : bool
When ``True``, apply the transformation as long as the magnitude of the
remaining number terms decreases. When ``False``, only apply the
transformation if it completely eliminates the number term.
Examples
========
>>> from sympy import symbols, exp
>>> from sympy.codegen.rewriting import FuncMinusOneOptim
>>> from sympy.codegen.cfunctions import expm1
>>> x, y = symbols('x y')
>>> expm1_opt = FuncMinusOneOptim(exp, expm1)
>>> expm1_opt(exp(x) + 2*exp(5*y) - 3)
expm1(x) + 2*expm1(5*y)
"""
def __init__(self, func, func_m_1, opportunistic=True):
weight = 10 # <-- this is an arbitrary number (heuristic)
super().__init__(lambda e: e.is_Add, self.replace_in_Add,
cost_function=lambda expr: expr.count_ops() - weight*expr.count(func_m_1))
self.func = func
self.func_m_1 = func_m_1
self.opportunistic = opportunistic
def _group_Add_terms(self, add):
numbers, non_num = sift(add.args, lambda arg: arg.is_number, binary=True)
numsum = sum(numbers)
terms_with_func, other = sift(non_num, lambda arg: arg.has(self.func), binary=True)
return numsum, terms_with_func, other
def replace_in_Add(self, e):
""" passed as second argument to Basic.replace(...) """
numsum, terms_with_func, other_non_num_terms = self._group_Add_terms(e)
if numsum == 0:
return e
substituted, untouched = [], []
for with_func in terms_with_func:
if with_func.is_Mul:
func, coeff = sift(with_func.args, lambda arg: arg.func == self.func, binary=True)
if len(func) == 1 and len(coeff) == 1:
func, coeff = func[0], coeff[0]
else:
coeff = None
elif with_func.func == self.func:
func, coeff = with_func, S.One
else:
coeff = None
if coeff is not None and coeff.is_number and sign(coeff) == -sign(numsum):
if self.opportunistic:
do_substitute = abs(coeff+numsum) < abs(numsum)
else:
do_substitute = coeff+numsum == 0
if do_substitute: # advantageous substitution
numsum += coeff
substituted.append(coeff*self.func_m_1(*func.args))
continue
untouched.append(with_func)
return e.func(numsum, *substituted, *untouched, *other_non_num_terms)
def __call__(self, expr):
alt1 = super().__call__(expr)
alt2 = super().__call__(expr.factor())
return self.cheapest(alt1, alt2)
expm1_opt = FuncMinusOneOptim(exp, expm1)
cosm1_opt = FuncMinusOneOptim(cos, cosm1)
powm1_opt = FuncMinusOneOptim(Pow, powm1)
log1p_opt = ReplaceOptim(
lambda e: isinstance(e, log),
lambda l: expand_log(l.replace(
log, lambda arg: log(arg.factor())
)).replace(log(_u+1), log1p(_u))
)
def create_expand_pow_optimization(limit, *, base_req=lambda b: b.is_symbol):
""" Creates an instance of :class:`ReplaceOptim` for expanding ``Pow``.
Explanation
===========
The requirements for expansions are that the base needs to be a symbol
and the exponent needs to be an Integer (and be less than or equal to
``limit``).
Parameters
==========
limit : int
The highest power which is expanded into multiplication.
base_req : function returning bool
Requirement on base for expansion to happen, default is to return
the ``is_symbol`` attribute of the base.
Examples
========
>>> from sympy import Symbol, sin
>>> from sympy.codegen.rewriting import create_expand_pow_optimization
>>> x = Symbol('x')
>>> expand_opt = create_expand_pow_optimization(3)
>>> expand_opt(x**5 + x**3)
x**5 + x*x*x
>>> expand_opt(x**5 + x**3 + sin(x)**3)
x**5 + sin(x)**3 + x*x*x
>>> opt2 = create_expand_pow_optimization(3, base_req=lambda b: not b.is_Function)
>>> opt2((x+1)**2 + sin(x)**2)
sin(x)**2 + (x + 1)*(x + 1)
"""
return ReplaceOptim(
lambda e: e.is_Pow and base_req(e.base) and e.exp.is_Integer and abs(e.exp) <= limit,
lambda p: (
UnevaluatedExpr(Mul(*([p.base]*+p.exp), evaluate=False)) if p.exp > 0 else
1/UnevaluatedExpr(Mul(*([p.base]*-p.exp), evaluate=False))
))
# Optimization procedures for turning A**(-1) * x into MatrixSolve(A, x)
def _matinv_predicate(expr):
# TODO: We should be able to support more than 2 elements
if expr.is_MatMul and len(expr.args) == 2:
left, right = expr.args
if left.is_Inverse and right.shape[1] == 1:
inv_arg = left.arg
if isinstance(inv_arg, MatrixSymbol):
return bool(ask(Q.fullrank(left.arg)))
return False
def _matinv_transform(expr):
left, right = expr.args
inv_arg = left.arg
return MatrixSolve(inv_arg, right)
matinv_opt = ReplaceOptim(_matinv_predicate, _matinv_transform)
logaddexp_opt = ReplaceOptim(log(exp(_v)+exp(_w)), logaddexp(_v, _w))
logaddexp2_opt = ReplaceOptim(log(Pow(2, _v)+Pow(2, _w)), logaddexp2(_v, _w)*log(2))
# Collections of optimizations:
optims_c99 = (expm1_opt, log1p_opt, exp2_opt, log2_opt, log2const_opt)
optims_numpy = optims_c99 + (logaddexp_opt, logaddexp2_opt,) + sinc_opts
optims_scipy = (cosm1_opt, powm1_opt)
@@ -0,0 +1,79 @@
from sympy.core.function import Add, ArgumentIndexError, Function
from sympy.core.power import Pow
from sympy.core.singleton import S
from sympy.functions.elementary.exponential import log
from sympy.functions.elementary.trigonometric import cos, sin
def _cosm1(x, *, evaluate=True):
return Add(cos(x, evaluate=evaluate), -S.One, evaluate=evaluate)
class cosm1(Function):
""" Minus one plus cosine of x, i.e. cos(x) - 1. For use when x is close to zero.
Helper class for use with e.g. scipy.special.cosm1
See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.cosm1.html
"""
nargs = 1
def fdiff(self, argindex=1):
"""
Returns the first derivative of this function.
"""
if argindex == 1:
return -sin(*self.args)
else:
raise ArgumentIndexError(self, argindex)
def _eval_rewrite_as_cos(self, x, **kwargs):
return _cosm1(x)
def _eval_evalf(self, *args, **kwargs):
return self.rewrite(cos).evalf(*args, **kwargs)
def _eval_simplify(self, **kwargs):
x, = self.args
candidate = _cosm1(x.simplify(**kwargs))
if candidate != _cosm1(x, evaluate=False):
return candidate
else:
return cosm1(x)
def _powm1(x, y, *, evaluate=True):
return Add(Pow(x, y, evaluate=evaluate), -S.One, evaluate=evaluate)
class powm1(Function):
""" Minus one plus x to the power of y, i.e. x**y - 1. For use when x is close to one or y is close to zero.
Helper class for use with e.g. scipy.special.powm1
See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.powm1.html
"""
nargs = 2
def fdiff(self, argindex=1):
"""
Returns the first derivative of this function.
"""
if argindex == 1:
return Pow(self.args[0], self.args[1])*self.args[1]/self.args[0]
elif argindex == 2:
return log(self.args[0])*Pow(*self.args)
else:
raise ArgumentIndexError(self, argindex)
def _eval_rewrite_as_Pow(self, x, y, **kwargs):
return _powm1(x, y)
def _eval_evalf(self, *args, **kwargs):
return self.rewrite(Pow).evalf(*args, **kwargs)
def _eval_simplify(self, **kwargs):
x, y = self.args
candidate = _powm1(x.simplify(**kwargs), y.simplify(**kwargs))
if candidate != _powm1(x, y, evaluate=False):
return candidate
else:
return powm1(x, y)
@@ -0,0 +1,14 @@
from sympy.core.symbol import symbols
from sympy.codegen.abstract_nodes import List
def test_List():
l = List(2, 3, 4)
assert l == List(2, 3, 4)
assert str(l) == "[2, 3, 4]"
x, y, z = symbols('x y z')
l = List(x**2,y**3,z**4)
# contrary to python's built-in list, we can call e.g. "replace" on List.
m = l.replace(lambda arg: arg.is_Pow and arg.exp>2, lambda p: p.base-p.exp)
assert m == [x**2, y-3, z-4]
hash(m)
@@ -0,0 +1,180 @@
import tempfile
from sympy import log, Min, Max, sqrt
from sympy.core.numbers import Float
from sympy.core.symbol import Symbol, symbols
from sympy.functions.elementary.trigonometric import cos
from sympy.codegen.ast import Assignment, Raise, RuntimeError_, QuotedString
from sympy.codegen.algorithms import newtons_method, newtons_method_function
from sympy.codegen.cfunctions import expm1
from sympy.codegen.fnodes import bind_C
from sympy.codegen.futils import render_as_module as f_module
from sympy.codegen.pyutils import render_as_module as py_module
from sympy.external import import_module
from sympy.printing.codeprinter import ccode
from sympy.utilities._compilation import compile_link_import_strings, has_c, has_fortran
from sympy.utilities._compilation.util import may_xfail
from sympy.testing.pytest import skip, raises, skip_under_pyodide
cython = import_module('cython')
wurlitzer = import_module('wurlitzer')
def test_newtons_method():
x, dx, atol = symbols('x dx atol')
expr = cos(x) - x**3
algo = newtons_method(expr, x, atol, dx)
assert algo.has(Assignment(dx, -expr/expr.diff(x)))
@may_xfail
def test_newtons_method_function__ccode():
x = Symbol('x', real=True)
expr = cos(x) - x**3
func = newtons_method_function(expr, x)
if not cython:
skip("cython not installed.")
if not has_c():
skip("No C compiler found.")
compile_kw = {"std": 'c99'}
with tempfile.TemporaryDirectory() as folder:
mod, info = compile_link_import_strings([
('newton.c', ('#include <math.h>\n'
'#include <stdio.h>\n') + ccode(func)),
('_newton.pyx', ("#cython: language_level={}\n".format("3") +
"cdef extern double newton(double)\n"
"def py_newton(x):\n"
" return newton(x)\n"))
], build_dir=folder, compile_kwargs=compile_kw)
assert abs(mod.py_newton(0.5) - 0.865474033102) < 1e-12
@may_xfail
def test_newtons_method_function__fcode():
x = Symbol('x', real=True)
expr = cos(x) - x**3
func = newtons_method_function(expr, x, attrs=[bind_C(name='newton')])
if not cython:
skip("cython not installed.")
if not has_fortran():
skip("No Fortran compiler found.")
f_mod = f_module([func], 'mod_newton')
with tempfile.TemporaryDirectory() as folder:
mod, info = compile_link_import_strings([
('newton.f90', f_mod),
('_newton.pyx', ("#cython: language_level={}\n".format("3") +
"cdef extern double newton(double*)\n"
"def py_newton(double x):\n"
" return newton(&x)\n"))
], build_dir=folder)
assert abs(mod.py_newton(0.5) - 0.865474033102) < 1e-12
def test_newtons_method_function__pycode():
x = Symbol('x', real=True)
expr = cos(x) - x**3
func = newtons_method_function(expr, x)
py_mod = py_module(func)
namespace = {}
exec(py_mod, namespace, namespace)
res = eval('newton(0.5)', namespace)
assert abs(res - 0.865474033102) < 1e-12
@may_xfail
@skip_under_pyodide("Emscripten does not support process spawning")
def test_newtons_method_function__ccode_parameters():
args = x, A, k, p = symbols('x A k p')
expr = A*cos(k*x) - p*x**3
raises(ValueError, lambda: newtons_method_function(expr, x))
use_wurlitzer = wurlitzer
func = newtons_method_function(expr, x, args, debug=use_wurlitzer)
if not has_c():
skip("No C compiler found.")
if not cython:
skip("cython not installed.")
compile_kw = {"std": 'c99'}
with tempfile.TemporaryDirectory() as folder:
mod, info = compile_link_import_strings([
('newton_par.c', ('#include <math.h>\n'
'#include <stdio.h>\n') + ccode(func)),
('_newton_par.pyx', ("#cython: language_level={}\n".format("3") +
"cdef extern double newton(double, double, double, double)\n"
"def py_newton(x, A=1, k=1, p=1):\n"
" return newton(x, A, k, p)\n"))
], compile_kwargs=compile_kw, build_dir=folder)
if use_wurlitzer:
with wurlitzer.pipes() as (out, err):
result = mod.py_newton(0.5)
else:
result = mod.py_newton(0.5)
assert abs(result - 0.865474033102) < 1e-12
if not use_wurlitzer:
skip("C-level output only tested when package 'wurlitzer' is available.")
out, err = out.read(), err.read()
assert err == ''
assert out == """\
x= 0.5
x= 1.1121 d_x= 0.61214
x= 0.90967 d_x= -0.20247
x= 0.86726 d_x= -0.042409
x= 0.86548 d_x= -0.0017867
x= 0.86547 d_x= -3.1022e-06
x= 0.86547 d_x= -9.3421e-12
x= 0.86547 d_x= 3.6902e-17
""" # try to run tests with LC_ALL=C if this assertion fails
def test_newtons_method_function__rtol_cse_nan():
a, b, c, N_geo, N_tot = symbols('a b c N_geo N_tot', real=True, nonnegative=True)
i = Symbol('i', integer=True, nonnegative=True)
N_ari = N_tot - N_geo - 1
delta_ari = (c-b)/N_ari
ln_delta_geo = log(b) + log(-expm1((log(a)-log(b))/N_geo))
eqb_log = ln_delta_geo - log(delta_ari)
def _clamp(low, expr, high):
return Min(Max(low, expr), high)
meth_kw = {
'clamped_newton': {'delta_fn': lambda e, x: _clamp(
(sqrt(a*x)-x)*0.99,
-e/e.diff(x),
(sqrt(c*x)-x)*0.99
)},
'halley': {'delta_fn': lambda e, x: (-2*(e*e.diff(x))/(2*e.diff(x)**2 - e*e.diff(x, 2)))},
'halley_alt': {'delta_fn': lambda e, x: (-e/e.diff(x)/(1-e/e.diff(x)*e.diff(x,2)/2/e.diff(x)))},
}
args = eqb_log, b
for use_cse in [False, True]:
kwargs = {
'params': (b, a, c, N_geo, N_tot), 'itermax': 60, 'debug': True, 'cse': use_cse,
'counter': i, 'atol': 1e-100, 'rtol': 2e-16, 'bounds': (a,c),
'handle_nan': Raise(RuntimeError_(QuotedString("encountered NaN.")))
}
func = {k: newtons_method_function(*args, func_name=f"{k}_b", **dict(kwargs, **kw)) for k, kw in meth_kw.items()}
py_mod = {k: py_module(v) for k, v in func.items()}
namespace = {}
root_find_b = {}
for k, v in py_mod.items():
ns = namespace[k] = {}
exec(v, ns, ns)
root_find_b[k] = ns[f'{k}_b']
ref = Float('13.2261515064168768938151923226496')
reftol = {'clamped_newton': 2e-16, 'halley': 2e-16, 'halley_alt': 3e-16}
guess = 4.0
for meth, func in root_find_b.items():
result = func(guess, 1e-2, 1e2, 50, 100)
req = ref*reftol[meth]
if use_cse:
req *= 2
assert abs(result - ref) < req
@@ -0,0 +1,58 @@
# This file contains tests that exercise multiple AST nodes
import tempfile
from sympy.external import import_module
from sympy.printing.codeprinter import ccode
from sympy.utilities._compilation import compile_link_import_strings, has_c
from sympy.utilities._compilation.util import may_xfail
from sympy.testing.pytest import skip, skip_under_pyodide
from sympy.codegen.ast import (
FunctionDefinition, FunctionPrototype, Variable, Pointer, real, Assignment,
integer, CodeBlock, While
)
from sympy.codegen.cnodes import void, PreIncrement
from sympy.codegen.cutils import render_as_source_file
cython = import_module('cython')
np = import_module('numpy')
def _mk_func1():
declars = n, inp, out = Variable('n', integer), Pointer('inp', real), Pointer('out', real)
i = Variable('i', integer)
whl = While(i<n, [Assignment(out[i], inp[i]), PreIncrement(i)])
body = CodeBlock(i.as_Declaration(value=0), whl)
return FunctionDefinition(void, 'our_test_function', declars, body)
def _render_compile_import(funcdef, build_dir):
code_str = render_as_source_file(funcdef, settings={"contract": False})
declar = ccode(FunctionPrototype.from_FunctionDefinition(funcdef))
return compile_link_import_strings([
('our_test_func.c', code_str),
('_our_test_func.pyx', ("#cython: language_level={}\n".format("3") +
"cdef extern {declar}\n"
"def _{fname}({typ}[:] inp, {typ}[:] out):\n"
" {fname}(inp.size, &inp[0], &out[0])").format(
declar=declar, fname=funcdef.name, typ='double'
))
], build_dir=build_dir)
@may_xfail
@skip_under_pyodide("Emscripten does not support process spawning")
def test_copying_function():
if not np:
skip("numpy not installed.")
if not has_c():
skip("No C compiler found.")
if not cython:
skip("Cython not found.")
info = None
with tempfile.TemporaryDirectory() as folder:
mod, info = _render_compile_import(_mk_func1(), build_dir=folder)
inp = np.arange(10.0)
out = np.empty_like(inp)
mod._our_test_function(inp, out)
assert np.allclose(inp, out)
@@ -0,0 +1,53 @@
import math
from sympy.core.symbol import symbols
from sympy.functions.elementary.exponential import exp
from sympy.codegen.rewriting import optimize
from sympy.codegen.approximations import SumApprox, SeriesApprox
def test_SumApprox_trivial():
x = symbols('x')
expr1 = 1 + x
sum_approx = SumApprox(bounds={x: (-1e-20, 1e-20)}, reltol=1e-16)
apx1 = optimize(expr1, [sum_approx])
assert apx1 - 1 == 0
def test_SumApprox_monotone_terms():
x, y, z = symbols('x y z')
expr1 = exp(z)*(x**2 + y**2 + 1)
bnds1 = {x: (0, 1e-3), y: (100, 1000)}
sum_approx_m2 = SumApprox(bounds=bnds1, reltol=1e-2)
sum_approx_m5 = SumApprox(bounds=bnds1, reltol=1e-5)
sum_approx_m11 = SumApprox(bounds=bnds1, reltol=1e-11)
assert (optimize(expr1, [sum_approx_m2])/exp(z) - (y**2)).simplify() == 0
assert (optimize(expr1, [sum_approx_m5])/exp(z) - (y**2 + 1)).simplify() == 0
assert (optimize(expr1, [sum_approx_m11])/exp(z) - (y**2 + 1 + x**2)).simplify() == 0
def test_SeriesApprox_trivial():
x, z = symbols('x z')
for factor in [1, exp(z)]:
x = symbols('x')
expr1 = exp(x)*factor
bnds1 = {x: (-1, 1)}
series_approx_50 = SeriesApprox(bounds=bnds1, reltol=0.50)
series_approx_10 = SeriesApprox(bounds=bnds1, reltol=0.10)
series_approx_05 = SeriesApprox(bounds=bnds1, reltol=0.05)
c = (bnds1[x][1] + bnds1[x][0])/2 # 0.0
f0 = math.exp(c) # 1.0
ref_50 = f0 + x + x**2/2
ref_10 = f0 + x + x**2/2 + x**3/6
ref_05 = f0 + x + x**2/2 + x**3/6 + x**4/24
res_50 = optimize(expr1, [series_approx_50])
res_10 = optimize(expr1, [series_approx_10])
res_05 = optimize(expr1, [series_approx_05])
assert (res_50/factor - ref_50).simplify() == 0
assert (res_10/factor - ref_10).simplify() == 0
assert (res_05/factor - ref_05).simplify() == 0
max_ord3 = SeriesApprox(bounds=bnds1, reltol=0.05, max_order=3)
assert optimize(expr1, [max_ord3]) == expr1
@@ -0,0 +1,661 @@
import math
from sympy.core.containers import Tuple
from sympy.core.numbers import nan, oo, Float, Integer
from sympy.core.relational import Lt
from sympy.core.symbol import symbols, Symbol
from sympy.functions.elementary.trigonometric import sin
from sympy.matrices.dense import Matrix
from sympy.matrices.expressions.matexpr import MatrixSymbol
from sympy.sets.fancysets import Range
from sympy.tensor.indexed import Idx, IndexedBase
from sympy.testing.pytest import raises
from sympy.codegen.ast import (
Assignment, Attribute, aug_assign, CodeBlock, For, Type, Variable, Pointer, Declaration,
AddAugmentedAssignment, SubAugmentedAssignment, MulAugmentedAssignment,
DivAugmentedAssignment, ModAugmentedAssignment, value_const, pointer_const,
integer, real, complex_, int8, uint8, float16 as f16, float32 as f32,
float64 as f64, float80 as f80, float128 as f128, complex64 as c64, complex128 as c128,
While, Scope, String, Print, QuotedString, FunctionPrototype, FunctionDefinition, Return,
FunctionCall, untyped, IntBaseType, intc, Node, none, NoneToken, Token, Comment
)
x, y, z, t, x0, x1, x2, a, b = symbols("x, y, z, t, x0, x1, x2, a, b")
n = symbols("n", integer=True)
A = MatrixSymbol('A', 3, 1)
mat = Matrix([1, 2, 3])
B = IndexedBase('B')
i = Idx("i", n)
A22 = MatrixSymbol('A22',2,2)
B22 = MatrixSymbol('B22',2,2)
def test_Assignment():
# Here we just do things to show they don't error
Assignment(x, y)
Assignment(x, 0)
Assignment(A, mat)
Assignment(A[1,0], 0)
Assignment(A[1,0], x)
Assignment(B[i], x)
Assignment(B[i], 0)
a = Assignment(x, y)
assert a.func(*a.args) == a
assert a.op == ':='
# Here we test things to show that they error
# Matrix to scalar
raises(ValueError, lambda: Assignment(B[i], A))
raises(ValueError, lambda: Assignment(B[i], mat))
raises(ValueError, lambda: Assignment(x, mat))
raises(ValueError, lambda: Assignment(x, A))
raises(ValueError, lambda: Assignment(A[1,0], mat))
# Scalar to matrix
raises(ValueError, lambda: Assignment(A, x))
raises(ValueError, lambda: Assignment(A, 0))
# Non-atomic lhs
raises(TypeError, lambda: Assignment(mat, A))
raises(TypeError, lambda: Assignment(0, x))
raises(TypeError, lambda: Assignment(x*x, 1))
raises(TypeError, lambda: Assignment(A + A, mat))
raises(TypeError, lambda: Assignment(B, 0))
def test_AugAssign():
# Here we just do things to show they don't error
aug_assign(x, '+', y)
aug_assign(x, '+', 0)
aug_assign(A, '+', mat)
aug_assign(A[1, 0], '+', 0)
aug_assign(A[1, 0], '+', x)
aug_assign(B[i], '+', x)
aug_assign(B[i], '+', 0)
# Check creation via aug_assign vs constructor
for binop, cls in [
('+', AddAugmentedAssignment),
('-', SubAugmentedAssignment),
('*', MulAugmentedAssignment),
('/', DivAugmentedAssignment),
('%', ModAugmentedAssignment),
]:
a = aug_assign(x, binop, y)
b = cls(x, y)
assert a.func(*a.args) == a == b
assert a.binop == binop
assert a.op == binop + '='
# Here we test things to show that they error
# Matrix to scalar
raises(ValueError, lambda: aug_assign(B[i], '+', A))
raises(ValueError, lambda: aug_assign(B[i], '+', mat))
raises(ValueError, lambda: aug_assign(x, '+', mat))
raises(ValueError, lambda: aug_assign(x, '+', A))
raises(ValueError, lambda: aug_assign(A[1, 0], '+', mat))
# Scalar to matrix
raises(ValueError, lambda: aug_assign(A, '+', x))
raises(ValueError, lambda: aug_assign(A, '+', 0))
# Non-atomic lhs
raises(TypeError, lambda: aug_assign(mat, '+', A))
raises(TypeError, lambda: aug_assign(0, '+', x))
raises(TypeError, lambda: aug_assign(x * x, '+', 1))
raises(TypeError, lambda: aug_assign(A + A, '+', mat))
raises(TypeError, lambda: aug_assign(B, '+', 0))
def test_Assignment_printing():
assignment_classes = [
Assignment,
AddAugmentedAssignment,
SubAugmentedAssignment,
MulAugmentedAssignment,
DivAugmentedAssignment,
ModAugmentedAssignment,
]
pairs = [
(x, 2 * y + 2),
(B[i], x),
(A22, B22),
(A[0, 0], x),
]
for cls in assignment_classes:
for lhs, rhs in pairs:
a = cls(lhs, rhs)
assert repr(a) == '%s(%s, %s)' % (cls.__name__, repr(lhs), repr(rhs))
def test_CodeBlock():
c = CodeBlock(Assignment(x, 1), Assignment(y, x + 1))
assert c.func(*c.args) == c
assert c.left_hand_sides == Tuple(x, y)
assert c.right_hand_sides == Tuple(1, x + 1)
def test_CodeBlock_topological_sort():
assignments = [
Assignment(x, y + z),
Assignment(z, 1),
Assignment(t, x),
Assignment(y, 2),
]
ordered_assignments = [
# Note that the unrelated z=1 and y=2 are kept in that order
Assignment(z, 1),
Assignment(y, 2),
Assignment(x, y + z),
Assignment(t, x),
]
c1 = CodeBlock.topological_sort(assignments)
assert c1 == CodeBlock(*ordered_assignments)
# Cycle
invalid_assignments = [
Assignment(x, y + z),
Assignment(z, 1),
Assignment(y, x),
Assignment(y, 2),
]
raises(ValueError, lambda: CodeBlock.topological_sort(invalid_assignments))
# Free symbols
free_assignments = [
Assignment(x, y + z),
Assignment(z, a * b),
Assignment(t, x),
Assignment(y, b + 3),
]
free_assignments_ordered = [
Assignment(z, a * b),
Assignment(y, b + 3),
Assignment(x, y + z),
Assignment(t, x),
]
c2 = CodeBlock.topological_sort(free_assignments)
assert c2 == CodeBlock(*free_assignments_ordered)
def test_CodeBlock_free_symbols():
c1 = CodeBlock(
Assignment(x, y + z),
Assignment(z, 1),
Assignment(t, x),
Assignment(y, 2),
)
assert c1.free_symbols == set()
c2 = CodeBlock(
Assignment(x, y + z),
Assignment(z, a * b),
Assignment(t, x),
Assignment(y, b + 3),
)
assert c2.free_symbols == {a, b}
def test_CodeBlock_cse():
c1 = CodeBlock(
Assignment(y, 1),
Assignment(x, sin(y)),
Assignment(z, sin(y)),
Assignment(t, x*z),
)
assert c1.cse() == CodeBlock(
Assignment(y, 1),
Assignment(x0, sin(y)),
Assignment(x, x0),
Assignment(z, x0),
Assignment(t, x*z),
)
# Multiple assignments to same symbol not supported
raises(NotImplementedError, lambda: CodeBlock(
Assignment(x, 1),
Assignment(y, 1), Assignment(y, 2)
).cse())
# Check auto-generated symbols do not collide with existing ones
c2 = CodeBlock(
Assignment(x0, sin(y) + 1),
Assignment(x1, 2 * sin(y)),
Assignment(z, x * y),
)
assert c2.cse() == CodeBlock(
Assignment(x2, sin(y)),
Assignment(x0, x2 + 1),
Assignment(x1, 2 * x2),
Assignment(z, x * y),
)
def test_CodeBlock_cse__issue_14118():
# see https://github.com/sympy/sympy/issues/14118
c = CodeBlock(
Assignment(A22, Matrix([[x, sin(y)],[3, 4]])),
Assignment(B22, Matrix([[sin(y), 2*sin(y)], [sin(y)**2, 7]]))
)
assert c.cse() == CodeBlock(
Assignment(x0, sin(y)),
Assignment(A22, Matrix([[x, x0],[3, 4]])),
Assignment(B22, Matrix([[x0, 2*x0], [x0**2, 7]]))
)
def test_For():
f = For(n, Range(0, 3), (Assignment(A[n, 0], x + n), aug_assign(x, '+', y)))
f = For(n, (1, 2, 3, 4, 5), (Assignment(A[n, 0], x + n),))
assert f.func(*f.args) == f
raises(TypeError, lambda: For(n, x, (x + y,)))
def test_none():
assert none.is_Atom
assert none == none
class Foo(Token):
pass
foo = Foo()
assert foo != none
assert none == None
assert none == NoneToken()
assert none.func(*none.args) == none
def test_String():
st = String('foobar')
assert st.is_Atom
assert st == String('foobar')
assert st.text == 'foobar'
assert st.func(**st.kwargs()) == st
assert st.func(*st.args) == st
class Signifier(String):
pass
si = Signifier('foobar')
assert si != st
assert si.text == st.text
s = String('foo')
assert str(s) == 'foo'
assert repr(s) == "String('foo')"
def test_Comment():
c = Comment('foobar')
assert c.text == 'foobar'
assert str(c) == 'foobar'
def test_Node():
n = Node()
assert n == Node()
assert n.func(*n.args) == n
def test_Type():
t = Type('MyType')
assert len(t.args) == 1
assert t.name == String('MyType')
assert str(t) == 'MyType'
assert repr(t) == "Type(String('MyType'))"
assert Type(t) == t
assert t.func(*t.args) == t
t1 = Type('t1')
t2 = Type('t2')
assert t1 != t2
assert t1 == t1 and t2 == t2
t1b = Type('t1')
assert t1 == t1b
assert t2 != t1b
def test_Type__from_expr():
assert Type.from_expr(i) == integer
u = symbols('u', real=True)
assert Type.from_expr(u) == real
assert Type.from_expr(n) == integer
assert Type.from_expr(3) == integer
assert Type.from_expr(3.0) == real
assert Type.from_expr(3+1j) == complex_
raises(ValueError, lambda: Type.from_expr(sum))
def test_Type__cast_check__integers():
# Rounding
raises(ValueError, lambda: integer.cast_check(3.5))
assert integer.cast_check('3') == 3
assert integer.cast_check(Float('3.0000000000000000000')) == 3
assert integer.cast_check(Float('3.0000000000000000001')) == 3 # unintuitive maybe?
# Range
assert int8.cast_check(127.0) == 127
raises(ValueError, lambda: int8.cast_check(128))
assert int8.cast_check(-128) == -128
raises(ValueError, lambda: int8.cast_check(-129))
assert uint8.cast_check(0) == 0
assert uint8.cast_check(128) == 128
raises(ValueError, lambda: uint8.cast_check(256.0))
raises(ValueError, lambda: uint8.cast_check(-1))
def test_Attribute():
noexcept = Attribute('noexcept')
assert noexcept == Attribute('noexcept')
alignas16 = Attribute('alignas', [16])
alignas32 = Attribute('alignas', [32])
assert alignas16 != alignas32
assert alignas16.func(*alignas16.args) == alignas16
def test_Variable():
v = Variable(x, type=real)
assert v == Variable(v)
assert v == Variable('x', type=real)
assert v.symbol == x
assert v.type == real
assert value_const not in v.attrs
assert v.func(*v.args) == v
assert str(v) == 'Variable(x, type=real)'
w = Variable(y, f32, attrs={value_const})
assert w.symbol == y
assert w.type == f32
assert value_const in w.attrs
assert w.func(*w.args) == w
v_n = Variable(n, type=Type.from_expr(n))
assert v_n.type == integer
assert v_n.func(*v_n.args) == v_n
v_i = Variable(i, type=Type.from_expr(n))
assert v_i.type == integer
assert v_i != v_n
a_i = Variable.deduced(i)
assert a_i.type == integer
assert Variable.deduced(Symbol('x', real=True)).type == real
assert a_i.func(*a_i.args) == a_i
v_n2 = Variable.deduced(n, value=3.5, cast_check=False)
assert v_n2.func(*v_n2.args) == v_n2
assert abs(v_n2.value - 3.5) < 1e-15
raises(ValueError, lambda: Variable.deduced(n, value=3.5, cast_check=True))
v_n3 = Variable.deduced(n)
assert v_n3.type == integer
assert str(v_n3) == 'Variable(n, type=integer)'
assert Variable.deduced(z, value=3).type == integer
assert Variable.deduced(z, value=3.0).type == real
assert Variable.deduced(z, value=3.0+1j).type == complex_
def test_Pointer():
p = Pointer(x)
assert p.symbol == x
assert p.type == untyped
assert value_const not in p.attrs
assert pointer_const not in p.attrs
assert p.func(*p.args) == p
u = symbols('u', real=True)
pu = Pointer(u, type=Type.from_expr(u), attrs={value_const, pointer_const})
assert pu.symbol is u
assert pu.type == real
assert value_const in pu.attrs
assert pointer_const in pu.attrs
assert pu.func(*pu.args) == pu
i = symbols('i', integer=True)
deref = pu[i]
assert deref.indices == (i,)
def test_Declaration():
u = symbols('u', real=True)
vu = Variable(u, type=Type.from_expr(u))
assert Declaration(vu).variable.type == real
vn = Variable(n, type=Type.from_expr(n))
assert Declaration(vn).variable.type == integer
# PR 19107, does not allow comparison between expressions and Basic
# lt = StrictLessThan(vu, vn)
# assert isinstance(lt, StrictLessThan)
vuc = Variable(u, Type.from_expr(u), value=3.0, attrs={value_const})
assert value_const in vuc.attrs
assert pointer_const not in vuc.attrs
decl = Declaration(vuc)
assert decl.variable == vuc
assert isinstance(decl.variable.value, Float)
assert decl.variable.value == 3.0
assert decl.func(*decl.args) == decl
assert vuc.as_Declaration() == decl
assert vuc.as_Declaration(value=None, attrs=None) == Declaration(vu)
vy = Variable(y, type=integer, value=3)
decl2 = Declaration(vy)
assert decl2.variable == vy
assert decl2.variable.value == Integer(3)
vi = Variable(i, type=Type.from_expr(i), value=3.0)
decl3 = Declaration(vi)
assert decl3.variable.type == integer
assert decl3.variable.value == 3.0
raises(ValueError, lambda: Declaration(vi, 42))
def test_IntBaseType():
assert intc.name == String('intc')
assert intc.args == (intc.name,)
assert str(IntBaseType('a').name) == 'a'
def test_FloatType():
assert f16.dig == 3
assert f32.dig == 6
assert f64.dig == 15
assert f80.dig == 18
assert f128.dig == 33
assert f16.decimal_dig == 5
assert f32.decimal_dig == 9
assert f64.decimal_dig == 17
assert f80.decimal_dig == 21
assert f128.decimal_dig == 36
assert f16.max_exponent == 16
assert f32.max_exponent == 128
assert f64.max_exponent == 1024
assert f80.max_exponent == 16384
assert f128.max_exponent == 16384
assert f16.min_exponent == -13
assert f32.min_exponent == -125
assert f64.min_exponent == -1021
assert f80.min_exponent == -16381
assert f128.min_exponent == -16381
assert abs(f16.eps / Float('0.00097656', precision=16) - 1) < 0.1*10**-f16.dig
assert abs(f32.eps / Float('1.1920929e-07', precision=32) - 1) < 0.1*10**-f32.dig
assert abs(f64.eps / Float('2.2204460492503131e-16', precision=64) - 1) < 0.1*10**-f64.dig
assert abs(f80.eps / Float('1.08420217248550443401e-19', precision=80) - 1) < 0.1*10**-f80.dig
assert abs(f128.eps / Float(' 1.92592994438723585305597794258492732e-34', precision=128) - 1) < 0.1*10**-f128.dig
assert abs(f16.max / Float('65504', precision=16) - 1) < .1*10**-f16.dig
assert abs(f32.max / Float('3.40282347e+38', precision=32) - 1) < 0.1*10**-f32.dig
assert abs(f64.max / Float('1.79769313486231571e+308', precision=64) - 1) < 0.1*10**-f64.dig # cf. np.finfo(np.float64).max
assert abs(f80.max / Float('1.18973149535723176502e+4932', precision=80) - 1) < 0.1*10**-f80.dig
assert abs(f128.max / Float('1.18973149535723176508575932662800702e+4932', precision=128) - 1) < 0.1*10**-f128.dig
# cf. np.finfo(np.float32).tiny
assert abs(f16.tiny / Float('6.1035e-05', precision=16) - 1) < 0.1*10**-f16.dig
assert abs(f32.tiny / Float('1.17549435e-38', precision=32) - 1) < 0.1*10**-f32.dig
assert abs(f64.tiny / Float('2.22507385850720138e-308', precision=64) - 1) < 0.1*10**-f64.dig
assert abs(f80.tiny / Float('3.36210314311209350626e-4932', precision=80) - 1) < 0.1*10**-f80.dig
assert abs(f128.tiny / Float('3.3621031431120935062626778173217526e-4932', precision=128) - 1) < 0.1*10**-f128.dig
assert f64.cast_check(0.5) == Float(0.5, 17)
assert abs(f64.cast_check(3.7) - 3.7) < 3e-17
assert isinstance(f64.cast_check(3), (Float, float))
assert f64.cast_nocheck(oo) == float('inf')
assert f64.cast_nocheck(-oo) == float('-inf')
assert f64.cast_nocheck(float(oo)) == float('inf')
assert f64.cast_nocheck(float(-oo)) == float('-inf')
assert math.isnan(f64.cast_nocheck(nan))
assert f32 != f64
assert f64 == f64.func(*f64.args)
def test_Type__cast_check__floating_point():
raises(ValueError, lambda: f32.cast_check(123.45678949))
raises(ValueError, lambda: f32.cast_check(12.345678949))
raises(ValueError, lambda: f32.cast_check(1.2345678949))
raises(ValueError, lambda: f32.cast_check(.12345678949))
assert abs(123.456789049 - f32.cast_check(123.456789049) - 4.9e-8) < 1e-8
assert abs(0.12345678904 - f32.cast_check(0.12345678904) - 4e-11) < 1e-11
dcm21 = Float('0.123456789012345670499') # 21 decimals
assert abs(dcm21 - f64.cast_check(dcm21) - 4.99e-19) < 1e-19
f80.cast_check(Float('0.12345678901234567890103', precision=88))
raises(ValueError, lambda: f80.cast_check(Float('0.12345678901234567890149', precision=88)))
v10 = 12345.67894
raises(ValueError, lambda: f32.cast_check(v10))
assert abs(Float(str(v10), precision=64+8) - f64.cast_check(v10)) < v10*1e-16
assert abs(f32.cast_check(2147483647) - 2147483650) < 1
def test_Type__cast_check__complex_floating_point():
val9_11 = 123.456789049 + 0.123456789049j
raises(ValueError, lambda: c64.cast_check(.12345678949 + .12345678949j))
assert abs(val9_11 - c64.cast_check(val9_11) - 4.9e-8) < 1e-8
dcm21 = Float('0.123456789012345670499') + 1e-20j # 21 decimals
assert abs(dcm21 - c128.cast_check(dcm21) - 4.99e-19) < 1e-19
v19 = Float('0.1234567890123456749') + 1j*Float('0.1234567890123456749')
raises(ValueError, lambda: c128.cast_check(v19))
def test_While():
xpp = AddAugmentedAssignment(x, 1)
whl1 = While(x < 2, [xpp])
assert whl1.condition.args[0] == x
assert whl1.condition.args[1] == 2
assert whl1.condition == Lt(x, 2, evaluate=False)
assert whl1.body.args == (xpp,)
assert whl1.func(*whl1.args) == whl1
cblk = CodeBlock(AddAugmentedAssignment(x, 1))
whl2 = While(x < 2, cblk)
assert whl1 == whl2
assert whl1 != While(x < 3, [xpp])
def test_Scope():
assign = Assignment(x, y)
incr = AddAugmentedAssignment(x, 1)
scp = Scope([assign, incr])
cblk = CodeBlock(assign, incr)
assert scp.body == cblk
assert scp == Scope(cblk)
assert scp != Scope([incr, assign])
assert scp.func(*scp.args) == scp
def test_Print():
fmt = "%d %.3f"
ps = Print([n, x], fmt)
assert str(ps.format_string) == fmt
assert ps.print_args == Tuple(n, x)
assert ps.args == (Tuple(n, x), QuotedString(fmt), none)
assert ps == Print((n, x), fmt)
assert ps != Print([x, n], fmt)
assert ps.func(*ps.args) == ps
ps2 = Print([n, x])
assert ps2 == Print([n, x])
assert ps2 != ps
assert ps2.format_string == None
def test_FunctionPrototype_and_FunctionDefinition():
vx = Variable(x, type=real)
vn = Variable(n, type=integer)
fp1 = FunctionPrototype(real, 'power', [vx, vn])
assert fp1.return_type == real
assert fp1.name == String('power')
assert fp1.parameters == Tuple(vx, vn)
assert fp1 == FunctionPrototype(real, 'power', [vx, vn])
assert fp1 != FunctionPrototype(real, 'power', [vn, vx])
assert fp1.func(*fp1.args) == fp1
body = [Assignment(x, x**n), Return(x)]
fd1 = FunctionDefinition(real, 'power', [vx, vn], body)
assert fd1.return_type == real
assert str(fd1.name) == 'power'
assert fd1.parameters == Tuple(vx, vn)
assert fd1.body == CodeBlock(*body)
assert fd1 == FunctionDefinition(real, 'power', [vx, vn], body)
assert fd1 != FunctionDefinition(real, 'power', [vx, vn], body[::-1])
assert fd1.func(*fd1.args) == fd1
fp2 = FunctionPrototype.from_FunctionDefinition(fd1)
assert fp2 == fp1
fd2 = FunctionDefinition.from_FunctionPrototype(fp1, body)
assert fd2 == fd1
def test_Return():
rs = Return(x)
assert rs.args == (x,)
assert rs == Return(x)
assert rs != Return(y)
assert rs.func(*rs.args) == rs
def test_FunctionCall():
fc = FunctionCall('power', (x, 3))
assert fc.function_args[0] == x
assert fc.function_args[1] == 3
assert len(fc.function_args) == 2
assert isinstance(fc.function_args[1], Integer)
assert fc == FunctionCall('power', (x, 3))
assert fc != FunctionCall('power', (3, x))
assert fc != FunctionCall('Power', (x, 3))
assert fc.func(*fc.args) == fc
fc2 = FunctionCall('fma', [2, 3, 4])
assert len(fc2.function_args) == 3
assert fc2.function_args[0] == 2
assert fc2.function_args[1] == 3
assert fc2.function_args[2] == 4
assert str(fc2) in ( # not sure if QuotedString is a better default...
'FunctionCall(fma, function_args=(2, 3, 4))',
'FunctionCall("fma", function_args=(2, 3, 4))',
)
def test_ast_replace():
x = Variable('x', real)
y = Variable('y', real)
n = Variable('n', integer)
pwer = FunctionDefinition(real, 'pwer', [x, n], [pow(x.symbol, n.symbol)])
pname = pwer.name
pcall = FunctionCall('pwer', [y, 3])
tree1 = CodeBlock(pwer, pcall)
assert str(tree1.args[0].name) == 'pwer'
assert str(tree1.args[1].name) == 'pwer'
for a, b in zip(tree1, [pwer, pcall]):
assert a == b
tree2 = tree1.replace(pname, String('power'))
assert str(tree1.args[0].name) == 'pwer'
assert str(tree1.args[1].name) == 'pwer'
assert str(tree2.args[0].name) == 'power'
assert str(tree2.args[1].name) == 'power'
@@ -0,0 +1,186 @@
from sympy.core.numbers import (Rational, pi)
from sympy.core.singleton import S
from sympy.core.symbol import (Symbol, symbols)
from sympy.functions.elementary.exponential import (exp, log)
from sympy.codegen.cfunctions import (
expm1, log1p, exp2, log2, fma, log10, Sqrt, Cbrt, hypot, isnan, isinf
)
from sympy.core.function import expand_log
def test_expm1():
# Eval
assert expm1(0) == 0
x = Symbol('x', real=True)
# Expand and rewrite
assert expm1(x).expand(func=True) - exp(x) == -1
assert expm1(x).rewrite('tractable') - exp(x) == -1
assert expm1(x).rewrite('exp') - exp(x) == -1
# Precision
assert not ((exp(1e-10).evalf() - 1) - 1e-10 - 5e-21) < 1e-22 # for comparison
assert abs(expm1(1e-10).evalf() - 1e-10 - 5e-21) < 1e-22
# Properties
assert expm1(x).is_real
assert expm1(x).is_finite
# Diff
assert expm1(42*x).diff(x) - 42*exp(42*x) == 0
assert expm1(42*x).diff(x) - expm1(42*x).expand(func=True).diff(x) == 0
def test_log1p():
# Eval
assert log1p(0) == 0
d = S(10)
assert expand_log(log1p(d**-1000) - log(d**1000 + 1) + log(d**1000)) == 0
x = Symbol('x', real=True)
# Expand and rewrite
assert log1p(x).expand(func=True) - log(x + 1) == 0
assert log1p(x).rewrite('tractable') - log(x + 1) == 0
assert log1p(x).rewrite('log') - log(x + 1) == 0
# Precision
assert not abs(log(1e-99 + 1).evalf() - 1e-99) < 1e-100 # for comparison
assert abs(expand_log(log1p(1e-99)).evalf() - 1e-99) < 1e-100
# Properties
assert log1p(-2**Rational(-1, 2)).is_real
assert not log1p(-1).is_finite
assert log1p(pi).is_finite
assert not log1p(x).is_positive
assert log1p(Symbol('y', positive=True)).is_positive
assert not log1p(x).is_zero
assert log1p(Symbol('z', zero=True)).is_zero
assert not log1p(x).is_nonnegative
assert log1p(Symbol('o', nonnegative=True)).is_nonnegative
# Diff
assert log1p(42*x).diff(x) - 42/(42*x + 1) == 0
assert log1p(42*x).diff(x) - log1p(42*x).expand(func=True).diff(x) == 0
def test_exp2():
# Eval
assert exp2(2) == 4
x = Symbol('x', real=True)
# Expand
assert exp2(x).expand(func=True) - 2**x == 0
# Diff
assert exp2(42*x).diff(x) - 42*exp2(42*x)*log(2) == 0
assert exp2(42*x).diff(x) - exp2(42*x).diff(x) == 0
def test_log2():
# Eval
assert log2(8) == 3
assert log2(pi) != log(pi)/log(2) # log2 should *save* (CPU) instructions
x = Symbol('x', real=True)
assert log2(x) != log(x)/log(2)
assert log2(2**x) == x
# Expand
assert log2(x).expand(func=True) - log(x)/log(2) == 0
# Diff
assert log2(42*x).diff() - 1/(log(2)*x) == 0
assert log2(42*x).diff() - log2(42*x).expand(func=True).diff(x) == 0
def test_fma():
x, y, z = symbols('x y z')
# Expand
assert fma(x, y, z).expand(func=True) - x*y - z == 0
expr = fma(17*x, 42*y, 101*z)
# Diff
assert expr.diff(x) - expr.expand(func=True).diff(x) == 0
assert expr.diff(y) - expr.expand(func=True).diff(y) == 0
assert expr.diff(z) - expr.expand(func=True).diff(z) == 0
assert expr.diff(x) - 17*42*y == 0
assert expr.diff(y) - 17*42*x == 0
assert expr.diff(z) - 101 == 0
def test_log10():
x = Symbol('x')
# Expand
assert log10(x).expand(func=True) - log(x)/log(10) == 0
# Diff
assert log10(42*x).diff(x) - 1/(log(10)*x) == 0
assert log10(42*x).diff(x) - log10(42*x).expand(func=True).diff(x) == 0
def test_Cbrt():
x = Symbol('x')
# Expand
assert Cbrt(x).expand(func=True) - x**Rational(1, 3) == 0
# Diff
assert Cbrt(42*x).diff(x) - 42*(42*x)**(Rational(1, 3) - 1)/3 == 0
assert Cbrt(42*x).diff(x) - Cbrt(42*x).expand(func=True).diff(x) == 0
def test_Sqrt():
x = Symbol('x')
# Expand
assert Sqrt(x).expand(func=True) - x**S.Half == 0
# Diff
assert Sqrt(42*x).diff(x) - 42*(42*x)**(S.Half - 1)/2 == 0
assert Sqrt(42*x).diff(x) - Sqrt(42*x).expand(func=True).diff(x) == 0
def test_hypot():
x, y = symbols('x y')
# Expand
assert hypot(x, y).expand(func=True) - (x**2 + y**2)**S.Half == 0
# Diff
assert hypot(17*x, 42*y).diff(x).expand(func=True) - hypot(17*x, 42*y).expand(func=True).diff(x) == 0
assert hypot(17*x, 42*y).diff(y).expand(func=True) - hypot(17*x, 42*y).expand(func=True).diff(y) == 0
assert hypot(17*x, 42*y).diff(x).expand(func=True) - 2*17*17*x*((17*x)**2 + (42*y)**2)**Rational(-1, 2)/2 == 0
assert hypot(17*x, 42*y).diff(y).expand(func=True) - 2*42*42*y*((17*x)**2 + (42*y)**2)**Rational(-1, 2)/2 == 0
def test_isnan_isinf():
x = Symbol('x')
# isinf
assert isinf(+S.Infinity) == True
assert isinf(-S.Infinity) == True
assert isinf(S.Pi) == False
isinfx = isinf(x)
assert isinfx not in (False, True)
assert isinfx.func is isinf
assert isinfx.args == (x,)
# isnan
assert isnan(S.NaN) == True
assert isnan(S.Pi) == False
isnanx = isnan(x)
assert isnanx not in (False, True)
assert isnanx.func is isnan
assert isnanx.args == (x,)
@@ -0,0 +1,112 @@
from sympy.core.symbol import symbols
from sympy.printing.codeprinter import ccode
from sympy.codegen.ast import Declaration, Variable, float64, int64, String, CodeBlock
from sympy.codegen.cnodes import (
alignof, CommaOperator, goto, Label, PreDecrement, PostDecrement, PreIncrement, PostIncrement,
sizeof, union, struct
)
x, y = symbols('x y')
def test_alignof():
ax = alignof(x)
assert ccode(ax) == 'alignof(x)'
assert ax.func(*ax.args) == ax
def test_CommaOperator():
expr = CommaOperator(PreIncrement(x), 2*x)
assert ccode(expr) == '(++(x), 2*x)'
assert expr.func(*expr.args) == expr
def test_goto_Label():
s = 'early_exit'
g = goto(s)
assert g.func(*g.args) == g
assert g != goto('foobar')
assert ccode(g) == 'goto early_exit'
l1 = Label(s)
assert ccode(l1) == 'early_exit:'
assert l1 == Label('early_exit')
assert l1 != Label('foobar')
body = [PreIncrement(x)]
l2 = Label(s, body)
assert l2.name == String("early_exit")
assert l2.body == CodeBlock(PreIncrement(x))
assert ccode(l2) == ("early_exit:\n"
"++(x);")
body = [PreIncrement(x), PreDecrement(y)]
l2 = Label(s, body)
assert l2.name == String("early_exit")
assert l2.body == CodeBlock(PreIncrement(x), PreDecrement(y))
assert ccode(l2) == ("early_exit:\n"
"{\n ++(x);\n --(y);\n}")
def test_PreDecrement():
p = PreDecrement(x)
assert p.func(*p.args) == p
assert ccode(p) == '--(x)'
def test_PostDecrement():
p = PostDecrement(x)
assert p.func(*p.args) == p
assert ccode(p) == '(x)--'
def test_PreIncrement():
p = PreIncrement(x)
assert p.func(*p.args) == p
assert ccode(p) == '++(x)'
def test_PostIncrement():
p = PostIncrement(x)
assert p.func(*p.args) == p
assert ccode(p) == '(x)++'
def test_sizeof():
typename = 'unsigned int'
sz = sizeof(typename)
assert ccode(sz) == 'sizeof(%s)' % typename
assert sz.func(*sz.args) == sz
assert not sz.is_Atom
assert sz.atoms() == {String('unsigned int'), String('sizeof')}
def test_struct():
vx, vy = Variable(x, type=float64), Variable(y, type=float64)
s = struct('vec2', [vx, vy])
assert s.func(*s.args) == s
assert s == struct('vec2', (vx, vy))
assert s != struct('vec2', (vy, vx))
assert str(s.name) == 'vec2'
assert len(s.declarations) == 2
assert all(isinstance(arg, Declaration) for arg in s.declarations)
assert ccode(s) == (
"struct vec2 {\n"
" double x;\n"
" double y;\n"
"}")
def test_union():
vx, vy = Variable(x, type=float64), Variable(y, type=int64)
u = union('dualuse', [vx, vy])
assert u.func(*u.args) == u
assert u == union('dualuse', (vx, vy))
assert str(u.name) == 'dualuse'
assert len(u.declarations) == 2
assert all(isinstance(arg, Declaration) for arg in u.declarations)
assert ccode(u) == (
"union dualuse {\n"
" double x;\n"
" int64_t y;\n"
"}")
@@ -0,0 +1,14 @@
from sympy.core.symbol import Symbol
from sympy.codegen.ast import Type
from sympy.codegen.cxxnodes import using
from sympy.printing.codeprinter import cxxcode
x = Symbol('x')
def test_using():
v = Type('std::vector')
u1 = using(v)
assert cxxcode(u1) == 'using std::vector'
u2 = using(v, 'vec')
assert cxxcode(u2) == 'using vec = std::vector'
@@ -0,0 +1,213 @@
import os
import tempfile
from sympy.core.symbol import (Symbol, symbols)
from sympy.codegen.ast import (
Assignment, Print, Declaration, FunctionDefinition, Return, real,
FunctionCall, Variable, Element, integer
)
from sympy.codegen.fnodes import (
allocatable, ArrayConstructor, isign, dsign, cmplx, kind, literal_dp,
Program, Module, use, Subroutine, dimension, assumed_extent, ImpliedDoLoop,
intent_out, size, Do, SubroutineCall, sum_, array, bind_C
)
from sympy.codegen.futils import render_as_module
from sympy.core.expr import unchanged
from sympy.external import import_module
from sympy.printing.codeprinter import fcode
from sympy.utilities._compilation import has_fortran, compile_run_strings, compile_link_import_strings
from sympy.utilities._compilation.util import may_xfail
from sympy.testing.pytest import skip, XFAIL
cython = import_module('cython')
np = import_module('numpy')
def test_size():
x = Symbol('x', real=True)
sx = size(x)
assert fcode(sx, source_format='free') == 'size(x)'
@may_xfail
def test_size_assumed_shape():
if not has_fortran():
skip("No fortran compiler found.")
a = Symbol('a', real=True)
body = [Return((sum_(a**2)/size(a))**.5)]
arr = array(a, dim=[':'], intent='in')
fd = FunctionDefinition(real, 'rms', [arr], body)
render_as_module([fd], 'mod_rms')
(stdout, stderr), info = compile_run_strings([
('rms.f90', render_as_module([fd], 'mod_rms')),
('main.f90', (
'program myprog\n'
'use mod_rms, only: rms\n'
'real*8, dimension(4), parameter :: x = [4, 2, 2, 2]\n'
'print "(f7.5)", dsqrt(7d0) - rms(x)\n'
'end program\n'
))
], clean=True)
assert '0.00000' in stdout
assert stderr == ''
assert info['exit_status'] == os.EX_OK
@XFAIL # https://github.com/sympy/sympy/issues/20265
@may_xfail
def test_ImpliedDoLoop():
if not has_fortran():
skip("No fortran compiler found.")
a, i = symbols('a i', integer=True)
idl = ImpliedDoLoop(i**3, i, -3, 3, 2)
ac = ArrayConstructor([-28, idl, 28])
a = array(a, dim=[':'], attrs=[allocatable])
prog = Program('idlprog', [
a.as_Declaration(),
Assignment(a, ac),
Print([a])
])
fsrc = fcode(prog, standard=2003, source_format='free')
(stdout, stderr), info = compile_run_strings([('main.f90', fsrc)], clean=True)
for numstr in '-28 -27 -1 1 27 28'.split():
assert numstr in stdout
assert stderr == ''
assert info['exit_status'] == os.EX_OK
@may_xfail
def test_Program():
x = Symbol('x', real=True)
vx = Variable.deduced(x, 42)
decl = Declaration(vx)
prnt = Print([x, x+1])
prog = Program('foo', [decl, prnt])
if not has_fortran():
skip("No fortran compiler found.")
(stdout, stderr), info = compile_run_strings([('main.f90', fcode(prog, standard=90))], clean=True)
assert '42' in stdout
assert '43' in stdout
assert stderr == ''
assert info['exit_status'] == os.EX_OK
@may_xfail
def test_Module():
x = Symbol('x', real=True)
v_x = Variable.deduced(x)
sq = FunctionDefinition(real, 'sqr', [v_x], [Return(x**2)])
mod_sq = Module('mod_sq', [], [sq])
sq_call = FunctionCall('sqr', [42.])
prg_sq = Program('foobar', [
use('mod_sq', only=['sqr']),
Print(['"Square of 42 = "', sq_call])
])
if not has_fortran():
skip("No fortran compiler found.")
(stdout, stderr), info = compile_run_strings([
('mod_sq.f90', fcode(mod_sq, standard=90)),
('main.f90', fcode(prg_sq, standard=90))
], clean=True)
assert '42' in stdout
assert str(42**2) in stdout
assert stderr == ''
@XFAIL # https://github.com/sympy/sympy/issues/20265
@may_xfail
def test_Subroutine():
# Code to generate the subroutine in the example from
# http://www.fortran90.org/src/best-practices.html#arrays
r = Symbol('r', real=True)
i = Symbol('i', integer=True)
v_r = Variable.deduced(r, attrs=(dimension(assumed_extent), intent_out))
v_i = Variable.deduced(i)
v_n = Variable('n', integer)
do_loop = Do([
Assignment(Element(r, [i]), literal_dp(1)/i**2)
], i, 1, v_n)
sub = Subroutine("f", [v_r], [
Declaration(v_n),
Declaration(v_i),
Assignment(v_n, size(r)),
do_loop
])
x = Symbol('x', real=True)
v_x3 = Variable.deduced(x, attrs=[dimension(3)])
mod = Module('mymod', definitions=[sub])
prog = Program('foo', [
use(mod, only=[sub]),
Declaration(v_x3),
SubroutineCall(sub, [v_x3]),
Print([sum_(v_x3), v_x3])
])
if not has_fortran():
skip("No fortran compiler found.")
(stdout, stderr), info = compile_run_strings([
('a.f90', fcode(mod, standard=90)),
('b.f90', fcode(prog, standard=90))
], clean=True)
ref = [1.0/i**2 for i in range(1, 4)]
assert str(sum(ref))[:-3] in stdout
for _ in ref:
assert str(_)[:-3] in stdout
assert stderr == ''
def test_isign():
x = Symbol('x', integer=True)
assert unchanged(isign, 1, x)
assert fcode(isign(1, x), standard=95, source_format='free') == 'isign(1, x)'
def test_dsign():
x = Symbol('x')
assert unchanged(dsign, 1, x)
assert fcode(dsign(literal_dp(1), x), standard=95, source_format='free') == 'dsign(1d0, x)'
def test_cmplx():
x = Symbol('x')
assert unchanged(cmplx, 1, x)
def test_kind():
x = Symbol('x')
assert unchanged(kind, x)
def test_literal_dp():
assert fcode(literal_dp(0), source_format='free') == '0d0'
@may_xfail
def test_bind_C():
if not has_fortran():
skip("No fortran compiler found.")
if not cython:
skip("Cython not found.")
if not np:
skip("NumPy not found.")
a = Symbol('a', real=True)
s = Symbol('s', integer=True)
body = [Return((sum_(a**2)/s)**.5)]
arr = array(a, dim=[s], intent='in')
fd = FunctionDefinition(real, 'rms', [arr, s], body, attrs=[bind_C('rms')])
f_mod = render_as_module([fd], 'mod_rms')
with tempfile.TemporaryDirectory() as folder:
mod, info = compile_link_import_strings([
('rms.f90', f_mod),
('_rms.pyx', (
"#cython: language_level={}\n".format("3") +
"cdef extern double rms(double*, int*)\n"
"def py_rms(double[::1] x):\n"
" cdef int s = x.size\n"
" return rms(&x[0], &s)\n"))
], build_dir=folder)
assert abs(mod.py_rms(np.array([2., 4., 2., 2.])) - 7**0.5) < 1e-14
@@ -0,0 +1,50 @@
from sympy.core.symbol import symbols
from sympy.core.function import Function
from sympy.matrices.dense import Matrix
from sympy.matrices.dense import zeros
from sympy.simplify.simplify import simplify
from sympy.codegen.matrix_nodes import MatrixSolve
from sympy.utilities.lambdify import lambdify
from sympy.printing.numpy import NumPyPrinter
from sympy.testing.pytest import skip
from sympy.external import import_module
def test_matrix_solve_issue_24862():
A = Matrix(3, 3, symbols('a:9'))
b = Matrix(3, 1, symbols('b:3'))
hash(MatrixSolve(A, b))
def test_matrix_solve_derivative_exact():
q = symbols('q')
a11, a12, a21, a22, b1, b2 = (
f(q) for f in symbols('a11 a12 a21 a22 b1 b2', cls=Function))
A = Matrix([[a11, a12], [a21, a22]])
b = Matrix([b1, b2])
x_lu = A.LUsolve(b)
dxdq_lu = A.LUsolve(b.diff(q) - A.diff(q) * A.LUsolve(b))
assert simplify(x_lu.diff(q) - dxdq_lu) == zeros(2, 1)
# dxdq_ms is the MatrixSolve equivalent of dxdq_lu
dxdq_ms = MatrixSolve(A, b.diff(q) - A.diff(q) * MatrixSolve(A, b))
assert MatrixSolve(A, b).diff(q) == dxdq_ms
def test_matrix_solve_derivative_numpy():
np = import_module('numpy')
if not np:
skip("numpy not installed.")
q = symbols('q')
a11, a12, a21, a22, b1, b2 = (
f(q) for f in symbols('a11 a12 a21 a22 b1 b2', cls=Function))
A = Matrix([[a11, a12], [a21, a22]])
b = Matrix([b1, b2])
dx_lu = A.LUsolve(b).diff(q)
subs = {a11.diff(q): 0.2, a12.diff(q): 0.3, a21.diff(q): 0.1,
a22.diff(q): 0.5, b1.diff(q): 0.4, b2.diff(q): 0.9,
a11: 1.3, a12: 0.5, a21: 1.2, a22: 4, b1: 6.2, b2: 3.5}
p, p_vals = zip(*subs.items())
dx_sm = MatrixSolve(A, b).diff(q)
np.testing.assert_allclose(
lambdify(p, dx_sm, printer=NumPyPrinter)(*p_vals),
lambdify(p, dx_lu, printer=NumPyPrinter)(*p_vals))
@@ -0,0 +1,69 @@
from itertools import product
from sympy.core.singleton import S
from sympy.core.symbol import symbols
from sympy.functions.elementary.exponential import (exp, log)
from sympy.functions.elementary.miscellaneous import Max, Min
from sympy.printing.repr import srepr
from sympy.codegen.numpy_nodes import logaddexp, logaddexp2, minimum, maximum, amax, amin
from sympy.testing.pytest import raises
x, y, z = symbols('x y z')
def test_logaddexp():
lae_xy = logaddexp(x, y)
ref_xy = log(exp(x) + exp(y))
for wrt, deriv_order in product([x, y, z], range(3)):
assert (
lae_xy.diff(wrt, deriv_order) -
ref_xy.diff(wrt, deriv_order)
).rewrite(log).simplify() == 0
one_third_e = 1*exp(1)/3
two_thirds_e = 2*exp(1)/3
logThirdE = log(one_third_e)
logTwoThirdsE = log(two_thirds_e)
lae_sum_to_e = logaddexp(logThirdE, logTwoThirdsE)
assert lae_sum_to_e.rewrite(log) == 1
assert lae_sum_to_e.simplify() == 1
was = logaddexp(2, 3)
assert srepr(was) == srepr(was.simplify()) # cannot simplify with 2, 3
def test_logaddexp2():
lae2_xy = logaddexp2(x, y)
ref2_xy = log(2**x + 2**y)/log(2)
for wrt, deriv_order in product([x, y, z], range(3)):
assert (
lae2_xy.diff(wrt, deriv_order) -
ref2_xy.diff(wrt, deriv_order)
).rewrite(log).cancel() == 0
def lb(x):
return log(x)/log(2)
two_thirds = S.One*2/3
four_thirds = 2*two_thirds
lbTwoThirds = lb(two_thirds)
lbFourThirds = lb(four_thirds)
lae2_sum_to_2 = logaddexp2(lbTwoThirds, lbFourThirds)
assert lae2_sum_to_2.rewrite(log) == 1
assert lae2_sum_to_2.simplify() == 1
was = logaddexp2(x, y)
assert srepr(was) == srepr(was.simplify()) # cannot simplify with x, y
def test_minimum_maximum():
for MM, mm in zip([Min, Max], [minimum, maximum]):
ref = MM(x, y, z)
m = mm(x, y, z)
assert m != ref
assert m.rewrite(MM) == ref
def test_amin_amax():
for am in [amin, amax]:
assert am(x).array == x
assert am(x).axis == None
assert am(x, axis=3).axis == 3
with raises(ValueError):
am(x, y, z)
@@ -0,0 +1,13 @@
from sympy.core.symbol import symbols
from sympy.codegen.pynodes import List
def test_List():
l = List(2, 3, 4)
assert l == List(2, 3, 4)
assert str(l) == "[2, 3, 4]"
x, y, z = symbols('x y z')
l = List(x**2,y**3,z**4)
# contrary to python's built-in list, we can call e.g. "replace" on List.
m = l.replace(lambda arg: arg.is_Pow and arg.exp>2, lambda p: p.base-p.exp)
assert m == [x**2, y-3, z-4]
@@ -0,0 +1,7 @@
from sympy.codegen.ast import Print
from sympy.codegen.pyutils import render_as_module
def test_standard():
ast = Print('x y'.split(), r"coordinate: %12.5g %12.5g\n")
assert render_as_module(ast, standard='python3') == \
'\n\nprint("coordinate: %12.5g %12.5g\\n" % (x, y), end="")'
@@ -0,0 +1,479 @@
import tempfile
from sympy.core.numbers import pi, Rational
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, log)
from sympy.functions.elementary.trigonometric import (cos, sin, sinc)
from sympy.matrices.expressions.matexpr import MatrixSymbol
from sympy.assumptions import assuming, Q
from sympy.external import import_module
from sympy.printing.codeprinter import ccode
from sympy.codegen.matrix_nodes import MatrixSolve
from sympy.codegen.cfunctions import log2, exp2, expm1, log1p
from sympy.codegen.numpy_nodes import logaddexp, logaddexp2
from sympy.codegen.scipy_nodes import cosm1, powm1
from sympy.codegen.rewriting import (
optimize, cosm1_opt, log2_opt, exp2_opt, expm1_opt, log1p_opt, powm1_opt, optims_c99,
create_expand_pow_optimization, matinv_opt, logaddexp_opt, logaddexp2_opt,
optims_numpy, optims_scipy, sinc_opts, FuncMinusOneOptim
)
from sympy.testing.pytest import XFAIL, skip
from sympy.utilities import lambdify
from sympy.utilities._compilation import compile_link_import_strings, has_c
from sympy.utilities._compilation.util import may_xfail
cython = import_module('cython')
numpy = import_module('numpy')
scipy = import_module('scipy')
def test_log2_opt():
x = Symbol('x')
expr1 = 7*log(3*x + 5)/(log(2))
opt1 = optimize(expr1, [log2_opt])
assert opt1 == 7*log2(3*x + 5)
assert opt1.rewrite(log) == expr1
expr2 = 3*log(5*x + 7)/(13*log(2))
opt2 = optimize(expr2, [log2_opt])
assert opt2 == 3*log2(5*x + 7)/13
assert opt2.rewrite(log) == expr2
expr3 = log(x)/log(2)
opt3 = optimize(expr3, [log2_opt])
assert opt3 == log2(x)
assert opt3.rewrite(log) == expr3
expr4 = log(x)/log(2) + log(x+1)
opt4 = optimize(expr4, [log2_opt])
assert opt4 == log2(x) + log(2)*log2(x+1)
assert opt4.rewrite(log) == expr4
expr5 = log(17)
opt5 = optimize(expr5, [log2_opt])
assert opt5 == expr5
expr6 = log(x + 3)/log(2)
opt6 = optimize(expr6, [log2_opt])
assert str(opt6) == 'log2(x + 3)'
assert opt6.rewrite(log) == expr6
def test_exp2_opt():
x = Symbol('x')
expr1 = 1 + 2**x
opt1 = optimize(expr1, [exp2_opt])
assert opt1 == 1 + exp2(x)
assert opt1.rewrite(Pow) == expr1
expr2 = 1 + 3**x
assert expr2 == optimize(expr2, [exp2_opt])
def test_expm1_opt():
x = Symbol('x')
expr1 = exp(x) - 1
opt1 = optimize(expr1, [expm1_opt])
assert expm1(x) - opt1 == 0
assert opt1.rewrite(exp) == expr1
expr2 = 3*exp(x) - 3
opt2 = optimize(expr2, [expm1_opt])
assert 3*expm1(x) == opt2
assert opt2.rewrite(exp) == expr2
expr3 = 3*exp(x) - 5
opt3 = optimize(expr3, [expm1_opt])
assert 3*expm1(x) - 2 == opt3
assert opt3.rewrite(exp) == expr3
expm1_opt_non_opportunistic = FuncMinusOneOptim(exp, expm1, opportunistic=False)
assert expr3 == optimize(expr3, [expm1_opt_non_opportunistic])
assert opt1 == optimize(expr1, [expm1_opt_non_opportunistic])
assert opt2 == optimize(expr2, [expm1_opt_non_opportunistic])
expr4 = 3*exp(x) + log(x) - 3
opt4 = optimize(expr4, [expm1_opt])
assert 3*expm1(x) + log(x) == opt4
assert opt4.rewrite(exp) == expr4
expr5 = 3*exp(2*x) - 3
opt5 = optimize(expr5, [expm1_opt])
assert 3*expm1(2*x) == opt5
assert opt5.rewrite(exp) == expr5
expr6 = (2*exp(x) + 1)/(exp(x) + 1) + 1
opt6 = optimize(expr6, [expm1_opt])
assert opt6.count_ops() <= expr6.count_ops()
def ev(e):
return e.subs(x, 3).evalf()
assert abs(ev(expr6) - ev(opt6)) < 1e-15
y = Symbol('y')
expr7 = (2*exp(x) - 1)/(1 - exp(y)) - 1/(1-exp(y))
opt7 = optimize(expr7, [expm1_opt])
assert -2*expm1(x)/expm1(y) == opt7
assert (opt7.rewrite(exp) - expr7).factor() == 0
expr8 = (1+exp(x))**2 - 4
opt8 = optimize(expr8, [expm1_opt])
tgt8a = (exp(x) + 3)*expm1(x)
tgt8b = 2*expm1(x) + expm1(2*x)
# Both tgt8a & tgt8b seem to give full precision (~16 digits for double)
# for x=1e-7 (compare with expr8 which only achieves ~8 significant digits).
# If we can show that either tgt8a or tgt8b is preferable, we can
# change this test to ensure the preferable version is returned.
assert (tgt8a - tgt8b).rewrite(exp).factor() == 0
assert opt8 in (tgt8a, tgt8b)
assert (opt8.rewrite(exp) - expr8).factor() == 0
expr9 = sin(expr8)
opt9 = optimize(expr9, [expm1_opt])
tgt9a = sin(tgt8a)
tgt9b = sin(tgt8b)
assert opt9 in (tgt9a, tgt9b)
assert (opt9.rewrite(exp) - expr9.rewrite(exp)).factor().is_zero
def test_expm1_two_exp_terms():
x, y = map(Symbol, 'x y'.split())
expr1 = exp(x) + exp(y) - 2
opt1 = optimize(expr1, [expm1_opt])
assert opt1 == expm1(x) + expm1(y)
def test_cosm1_opt():
x = Symbol('x')
expr1 = cos(x) - 1
opt1 = optimize(expr1, [cosm1_opt])
assert cosm1(x) - opt1 == 0
assert opt1.rewrite(cos) == expr1
expr2 = 3*cos(x) - 3
opt2 = optimize(expr2, [cosm1_opt])
assert 3*cosm1(x) == opt2
assert opt2.rewrite(cos) == expr2
expr3 = 3*cos(x) - 5
opt3 = optimize(expr3, [cosm1_opt])
assert 3*cosm1(x) - 2 == opt3
assert opt3.rewrite(cos) == expr3
cosm1_opt_non_opportunistic = FuncMinusOneOptim(cos, cosm1, opportunistic=False)
assert expr3 == optimize(expr3, [cosm1_opt_non_opportunistic])
assert opt1 == optimize(expr1, [cosm1_opt_non_opportunistic])
assert opt2 == optimize(expr2, [cosm1_opt_non_opportunistic])
expr4 = 3*cos(x) + log(x) - 3
opt4 = optimize(expr4, [cosm1_opt])
assert 3*cosm1(x) + log(x) == opt4
assert opt4.rewrite(cos) == expr4
expr5 = 3*cos(2*x) - 3
opt5 = optimize(expr5, [cosm1_opt])
assert 3*cosm1(2*x) == opt5
assert opt5.rewrite(cos) == expr5
expr6 = 2 - 2*cos(x)
opt6 = optimize(expr6, [cosm1_opt])
assert -2*cosm1(x) == opt6
assert opt6.rewrite(cos) == expr6
def test_cosm1_two_cos_terms():
x, y = map(Symbol, 'x y'.split())
expr1 = cos(x) + cos(y) - 2
opt1 = optimize(expr1, [cosm1_opt])
assert opt1 == cosm1(x) + cosm1(y)
def test_expm1_cosm1_mixed():
x = Symbol('x')
expr1 = exp(x) + cos(x) - 2
opt1 = optimize(expr1, [expm1_opt, cosm1_opt])
assert opt1 == cosm1(x) + expm1(x)
def _check_num_lambdify(expr, opt, val_subs, approx_ref, lambdify_kw=None, poorness=1e10):
""" poorness=1e10 signifies that `expr` loses precision of at least ten decimal digits. """
num_ref = expr.subs(val_subs).evalf()
eps = numpy.finfo(numpy.float64).eps
assert abs(num_ref - approx_ref) < approx_ref*eps
f1 = lambdify(list(val_subs.keys()), opt, **(lambdify_kw or {}))
args_float = tuple(map(float, val_subs.values()))
num_err1 = abs(f1(*args_float) - approx_ref)
assert num_err1 < abs(num_ref*eps)
f2 = lambdify(list(val_subs.keys()), expr, **(lambdify_kw or {}))
num_err2 = abs(f2(*args_float) - approx_ref)
assert num_err2 > abs(num_ref*eps*poorness) # this only ensures that the *test* works as intended
def test_cosm1_apart():
x = Symbol('x')
expr1 = 1/cos(x) - 1
opt1 = optimize(expr1, [cosm1_opt])
assert opt1 == -cosm1(x)/cos(x)
if scipy:
_check_num_lambdify(expr1, opt1, {x: S(10)**-30}, 5e-61, lambdify_kw={"modules": 'scipy'})
expr2 = 2/cos(x) - 2
opt2 = optimize(expr2, optims_scipy)
assert opt2 == -2*cosm1(x)/cos(x)
if scipy:
_check_num_lambdify(expr2, opt2, {x: S(10)**-30}, 1e-60, lambdify_kw={"modules": 'scipy'})
expr3 = pi/cos(3*x) - pi
opt3 = optimize(expr3, [cosm1_opt])
assert opt3 == -pi*cosm1(3*x)/cos(3*x)
if scipy:
_check_num_lambdify(expr3, opt3, {x: S(10)**-30/3}, float(5e-61*pi), lambdify_kw={"modules": 'scipy'})
def test_powm1():
args = x, y = map(Symbol, "xy")
expr1 = x**y - 1
opt1 = optimize(expr1, [powm1_opt])
assert opt1 == powm1(x, y)
for arg in args:
assert expr1.diff(arg) == opt1.diff(arg)
if scipy and tuple(map(int, scipy.version.version.split('.')[:3])) >= (1, 10, 0):
subs1_a = {x: Rational(*(1.0+1e-13).as_integer_ratio()), y: pi}
ref1_f64_a = 3.139081648208105e-13
_check_num_lambdify(expr1, opt1, subs1_a, ref1_f64_a, lambdify_kw={"modules": 'scipy'}, poorness=10**11)
subs1_b = {x: pi, y: Rational(*(1e-10).as_integer_ratio())}
ref1_f64_b = 1.1447298859149205e-10
_check_num_lambdify(expr1, opt1, subs1_b, ref1_f64_b, lambdify_kw={"modules": 'scipy'}, poorness=10**9)
def test_log1p_opt():
x = Symbol('x')
expr1 = log(x + 1)
opt1 = optimize(expr1, [log1p_opt])
assert log1p(x) - opt1 == 0
assert opt1.rewrite(log) == expr1
expr2 = log(3*x + 3)
opt2 = optimize(expr2, [log1p_opt])
assert log1p(x) + log(3) == opt2
assert (opt2.rewrite(log) - expr2).simplify() == 0
expr3 = log(2*x + 1)
opt3 = optimize(expr3, [log1p_opt])
assert log1p(2*x) - opt3 == 0
assert opt3.rewrite(log) == expr3
expr4 = log(x+3)
opt4 = optimize(expr4, [log1p_opt])
assert str(opt4) == 'log(x + 3)'
def test_optims_c99():
x = Symbol('x')
expr1 = 2**x + log(x)/log(2) + log(x + 1) + exp(x) - 1
opt1 = optimize(expr1, optims_c99).simplify()
assert opt1 == exp2(x) + log2(x) + log1p(x) + expm1(x)
assert opt1.rewrite(exp).rewrite(log).rewrite(Pow) == expr1
expr2 = log(x)/log(2) + log(x + 1)
opt2 = optimize(expr2, optims_c99)
assert opt2 == log2(x) + log1p(x)
assert opt2.rewrite(log) == expr2
expr3 = log(x)/log(2) + log(17*x + 17)
opt3 = optimize(expr3, optims_c99)
delta3 = opt3 - (log2(x) + log(17) + log1p(x))
assert delta3 == 0
assert (opt3.rewrite(log) - expr3).simplify() == 0
expr4 = 2**x + 3*log(5*x + 7)/(13*log(2)) + 11*exp(x) - 11 + log(17*x + 17)
opt4 = optimize(expr4, optims_c99).simplify()
delta4 = opt4 - (exp2(x) + 3*log2(5*x + 7)/13 + 11*expm1(x) + log(17) + log1p(x))
assert delta4 == 0
assert (opt4.rewrite(exp).rewrite(log).rewrite(Pow) - expr4).simplify() == 0
expr5 = 3*exp(2*x) - 3
opt5 = optimize(expr5, optims_c99)
delta5 = opt5 - 3*expm1(2*x)
assert delta5 == 0
assert opt5.rewrite(exp) == expr5
expr6 = exp(2*x) - 3
opt6 = optimize(expr6, optims_c99)
assert opt6 in (expm1(2*x) - 2, expr6) # expm1(2*x) - 2 is not better or worse
expr7 = log(3*x + 3)
opt7 = optimize(expr7, optims_c99)
delta7 = opt7 - (log(3) + log1p(x))
assert delta7 == 0
assert (opt7.rewrite(log) - expr7).simplify() == 0
expr8 = log(2*x + 3)
opt8 = optimize(expr8, optims_c99)
assert opt8 == expr8
def test_create_expand_pow_optimization():
cc = lambda x: ccode(
optimize(x, [create_expand_pow_optimization(4)]))
x = Symbol('x')
assert cc(x**4) == 'x*x*x*x'
assert cc(x**4 + x**2) == 'x*x + x*x*x*x'
assert cc(x**5 + x**4) == 'pow(x, 5) + x*x*x*x'
assert cc(sin(x)**4) == 'pow(sin(x), 4)'
# gh issue 15335
assert cc(x**(-4)) == '1.0/(x*x*x*x)'
assert cc(x**(-5)) == 'pow(x, -5)'
assert cc(-x**4) == '-(x*x*x*x)'
assert cc(x**4 - x**2) == '-(x*x) + x*x*x*x'
i = Symbol('i', integer=True)
assert cc(x**i - x**2) == 'pow(x, i) - (x*x)'
y = Symbol('y', real=True)
assert cc(Abs(exp(y**4))) == "exp(y*y*y*y)"
# gh issue 20753
cc2 = lambda x: ccode(optimize(x, [create_expand_pow_optimization(
4, base_req=lambda b: b.is_Function)]))
assert cc2(x**3 + sin(x)**3) == "pow(x, 3) + sin(x)*sin(x)*sin(x)"
def test_matsolve():
n = Symbol('n', integer=True)
A = MatrixSymbol('A', n, n)
x = MatrixSymbol('x', n, 1)
with assuming(Q.fullrank(A)):
assert optimize(A**(-1) * x, [matinv_opt]) == MatrixSolve(A, x)
assert optimize(A**(-1) * x + x, [matinv_opt]) == MatrixSolve(A, x) + x
def test_logaddexp_opt():
x, y = map(Symbol, 'x y'.split())
expr1 = log(exp(x) + exp(y))
opt1 = optimize(expr1, [logaddexp_opt])
assert logaddexp(x, y) - opt1 == 0
assert logaddexp(y, x) - opt1 == 0
assert opt1.rewrite(log) == expr1
def test_logaddexp2_opt():
x, y = map(Symbol, 'x y'.split())
expr1 = log(2**x + 2**y)/log(2)
opt1 = optimize(expr1, [logaddexp2_opt])
assert logaddexp2(x, y) - opt1 == 0
assert logaddexp2(y, x) - opt1 == 0
assert opt1.rewrite(log) == expr1
def test_sinc_opts():
def check(d):
for k, v in d.items():
assert optimize(k, sinc_opts) == v
x = Symbol('x')
check({
sin(x)/x : sinc(x),
sin(2*x)/(2*x) : sinc(2*x),
sin(3*x)/x : 3*sinc(3*x),
x*sin(x) : x*sin(x)
})
y = Symbol('y')
check({
sin(x*y)/(x*y) : sinc(x*y),
y*sin(x/y)/x : sinc(x/y),
sin(sin(x))/sin(x) : sinc(sin(x)),
sin(3*sin(x))/sin(x) : 3*sinc(3*sin(x)),
sin(x)/y : sin(x)/y
})
def test_optims_numpy():
def check(d):
for k, v in d.items():
assert optimize(k, optims_numpy) == v
x = Symbol('x')
check({
sin(2*x)/(2*x) + exp(2*x) - 1: sinc(2*x) + expm1(2*x),
log(x+3)/log(2) + log(x**2 + 1): log1p(x**2) + log2(x+3)
})
@XFAIL # room for improvement, ideally this test case should pass.
def test_optims_numpy_TODO():
def check(d):
for k, v in d.items():
assert optimize(k, optims_numpy) == v
x, y = map(Symbol, 'x y'.split())
check({
log(x*y)*sin(x*y)*log(x*y+1)/(log(2)*x*y): log2(x*y)*sinc(x*y)*log1p(x*y),
exp(x*sin(y)/y) - 1: expm1(x*sinc(y))
})
@may_xfail
def test_compiled_ccode_with_rewriting():
if not cython:
skip("cython not installed.")
if not has_c():
skip("No C compiler found.")
x = Symbol('x')
about_two = 2**(58/S(117))*3**(97/S(117))*5**(4/S(39))*7**(92/S(117))/S(30)*pi
# about_two: 1.999999999999581826
unchanged = 2*exp(x) - about_two
xval = S(10)**-11
ref = unchanged.subs(x, xval).n(19) # 2.0418173913673213e-11
rewritten = optimize(2*exp(x) - about_two, [expm1_opt])
# Unfortunately, we need to call ``.n()`` on our expressions before we hand them
# to ``ccode``, and we need to request a large number of significant digits.
# In this test, results converged for double precision when the following number
# of significant digits were chosen:
NUMBER_OF_DIGITS = 25 # TODO: this should ideally be automatically handled.
func_c = '''
#include <math.h>
double func_unchanged(double x) {
return %(unchanged)s;
}
double func_rewritten(double x) {
return %(rewritten)s;
}
''' % {"unchanged": ccode(unchanged.n(NUMBER_OF_DIGITS)),
"rewritten": ccode(rewritten.n(NUMBER_OF_DIGITS))}
func_pyx = '''
#cython: language_level=3
cdef extern double func_unchanged(double)
cdef extern double func_rewritten(double)
def py_unchanged(x):
return func_unchanged(x)
def py_rewritten(x):
return func_rewritten(x)
'''
with tempfile.TemporaryDirectory() as folder:
mod, info = compile_link_import_strings(
[('func.c', func_c), ('_func.pyx', func_pyx)],
build_dir=folder, compile_kwargs={"std": 'c99'}
)
err_rewritten = abs(mod.py_rewritten(1e-11) - ref)
err_unchanged = abs(mod.py_unchanged(1e-11) - ref)
assert 1e-27 < err_rewritten < 1e-25 # highly accurate.
assert 1e-19 < err_unchanged < 1e-16 # quite poor.
# Tolerances used above were determined as follows:
# >>> no_opt = unchanged.subs(x, xval.evalf()).evalf()
# >>> with_opt = rewritten.n(25).subs(x, 1e-11).evalf()
# >>> with_opt - ref, no_opt - ref
# (1.1536301877952077e-26, 1.6547074214222335e-18)
@@ -0,0 +1,44 @@
from itertools import product
from sympy.core.power import Pow
from sympy.core.symbol import symbols
from sympy.functions.elementary.exponential import exp, log
from sympy.functions.elementary.trigonometric import cos
from sympy.core.numbers import pi
from sympy.codegen.scipy_nodes import cosm1, powm1
x, y, z = symbols('x y z')
def test_cosm1():
cm1_xy = cosm1(x*y)
ref_xy = cos(x*y) - 1
for wrt, deriv_order in product([x, y, z], range(3)):
assert (
cm1_xy.diff(wrt, deriv_order) -
ref_xy.diff(wrt, deriv_order)
).rewrite(cos).simplify() == 0
expr_minus2 = cosm1(pi)
assert expr_minus2.rewrite(cos) == -2
assert cosm1(3.14).simplify() == cosm1(3.14) # cannot simplify with 3.14
assert cosm1(pi/2).simplify() == -1
assert (1/cos(x) - 1 + cosm1(x)/cos(x)).simplify() == 0
def test_powm1():
cases = {
powm1(x, y): x**y - 1,
powm1(x*y, z): (x*y)**z - 1,
powm1(x, y*z): x**(y*z)-1,
powm1(x*y*z, x*y*z): (x*y*z)**(x*y*z)-1
}
for pm1_e, ref_e in cases.items():
for wrt, deriv_order in product([x, y, z], range(3)):
der = pm1_e.diff(wrt, deriv_order)
ref = ref_e.diff(wrt, deriv_order)
delta = (der - ref).rewrite(Pow)
assert delta.simplify() == 0
eulers_constant_m1 = powm1(x, 1/log(x))
assert eulers_constant_m1.rewrite(Pow) == exp(1) - 1
assert eulers_constant_m1.simplify() == exp(1) - 1
@@ -0,0 +1,43 @@
from sympy.combinatorics.permutations import Permutation, Cycle
from sympy.combinatorics.prufer import Prufer
from sympy.combinatorics.generators import cyclic, alternating, symmetric, dihedral
from sympy.combinatorics.subsets import Subset
from sympy.combinatorics.partitions import (Partition, IntegerPartition,
RGS_rank, RGS_unrank, RGS_enum)
from sympy.combinatorics.polyhedron import (Polyhedron, tetrahedron, cube,
octahedron, dodecahedron, icosahedron)
from sympy.combinatorics.perm_groups import PermutationGroup, Coset, SymmetricPermutationGroup
from sympy.combinatorics.group_constructs import DirectProduct
from sympy.combinatorics.graycode import GrayCode
from sympy.combinatorics.named_groups import (SymmetricGroup, DihedralGroup,
CyclicGroup, AlternatingGroup, AbelianGroup, RubikGroup)
from sympy.combinatorics.pc_groups import PolycyclicGroup, Collector
from sympy.combinatorics.free_groups import free_group
__all__ = [
'Permutation', 'Cycle',
'Prufer',
'cyclic', 'alternating', 'symmetric', 'dihedral',
'Subset',
'Partition', 'IntegerPartition', 'RGS_rank', 'RGS_unrank', 'RGS_enum',
'Polyhedron', 'tetrahedron', 'cube', 'octahedron', 'dodecahedron',
'icosahedron',
'PermutationGroup', 'Coset', 'SymmetricPermutationGroup',
'DirectProduct',
'GrayCode',
'SymmetricGroup', 'DihedralGroup', 'CyclicGroup', 'AlternatingGroup',
'AbelianGroup', 'RubikGroup',
'PolycyclicGroup', 'Collector',
'free_group',
]

Some files were not shown because too many files have changed in this diff Show More