Initial import: grid-bot — grid trading bot for BTC-USDT on Cifra Markets
This commit is contained in:
@@ -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
|
||||
+35
@@ -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
|
||||
+336
@@ -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
Reference in New Issue
Block a user