Coder Social home page Coder Social logo

hpac / matchpy Goto Github PK

View Code? Open in Web Editor NEW
159.0 14.0 25.0 926 KB

A library for pattern matching on symbolic expressions in Python.

License: MIT License

Makefile 0.09% Batchfile 0.16% Python 99.07% TeX 0.69%
pattern-matching term-rewriting python symbolic-expressions

matchpy's Introduction

MatchPy

MatchPy is a library for pattern matching on symbolic expressions in Python.

Work in progress

Latest version released on PyPi Latest version released via conda-forge Test coverage Build status of the master branch Documentation Status The Journal of Open Source Software Digital Object Identifier

Installation

MatchPy is available via PyPI, and for Conda via conda-forge. It can be installed with pip install matchpy or conda install -c conda-forge matchpy.

Overview

This package implements pattern matching in Python. Pattern matching is a powerful tool for symbolic computations, operating on symbolic expressions. Given a pattern and an expression (which is usually called subject), the goal of pattern matching is to find a substitution for all the variables in the pattern such that the pattern becomes the subject. As an example, consider the pattern f(x), where f is a function and x is a variable, and the subject f(a), where a is a constant symbol. Then the substitution that replaces x with a is a match. MatchPy supports associative and/or commutative function symbols, as well as sequence variables, similar to pattern matching in Mathematica.

A detailed example of how to use MatchPy can be found here.

MatchPy supports both one-to-one and many-to-one pattern matching. The latter makes use of similarities between patterns to efficiently find matches for multiple patterns at the same time.

A list of publications about MatchPy can be found below.

Expressions

Expressions are tree-like data structures, consisting of operations (functions, internal nodes) and symbols (constants, leaves):

>>> from matchpy import Operation, Symbol, Arity >>> f = Operation.new('f', Arity.binary) >>> a = Symbol('a') >>> print(f(a, a)) f(a, a)

Patterns are expressions which may contain wildcards (variables):

>>> from matchpy import Pattern, Wildcard >>> x = Wildcard.dot('x') >>> print(Pattern(f(a, x))) f(a, x)

In the previous example, x is the name of the variable. However, it is also possible to use wildcards without names:

>>> w = Wildcard.dot() >>> print(Pattern(f(w, w))) f(_, _)

It is also possible to assign variable names to entire subexpressions:

>>> print(Pattern(f(w, a, variable_name='y'))) y: f(_, a)

Pattern Matching

Given a pattern and an expression (which is usually called subject), the idea of pattern matching is to find a substitution that maps wildcards to expressions such that the pattern becomes the subject. In MatchPy, a substitution is a dict that maps variable names to expressions.

>>> from matchpy import match >>> y = Wildcard.dot('y') >>> b = Symbol('b') >>> subject = f(a, b) >>> pattern = Pattern(f(x, y)) >>> substitution = next(match(subject, pattern)) >>> print(substitution) {x ↦ a, y ↦ b}

Applying the substitution to the pattern results in the original expression.

>>> from matchpy import substitute >>> print(substitute(pattern, substitution)) f(a, b)

Sequence Wildcards

Sequence wildcards are wildcards that can match a sequence of expressions instead of just a single expression:

>>> z = Wildcard.plus('z') >>> pattern = Pattern(f(z)) >>> subject = f(a, b) >>> substitution = next(match(subject, pattern)) >>> print(substitution) {z ↦ (a, b)}

Associativity and Commutativity

MatchPy natively supports associative and/or commutative operations. Nested associative operators are automatically flattened, the operands in commutative operations are sorted:

>>> g = Operation.new('g', Arity.polyadic, associative=True, commutative=True) >>> print(g(a, g(b, a))) g(a, a, b)

Associativity and commutativity is also considered for pattern matching:

>>> pattern = Pattern(g(b, x)) >>> subject = g(a, a, b) >>> print(next(match(subject, pattern))) {x ↦ g(a, a)} >>> h = Operation.new('h', Arity.polyadic) >>> pattern = Pattern(h(b, x)) >>> subject = h(a, a, b) >>> list(match(subject, pattern)) []

Many-to-One Matching

When a fixed set of patterns is matched repeatedly against different subjects, matching can be sped up significantly by using many-to-one matching. The idea of many-to-one matching is to construct a so called discrimination net, a data structure similar to a decision tree or a finite automaton that exploits similarities between patterns. In MatchPy, there are two such data structures, implemented as classes: DiscriminationNet and ManyToOneMatcher. The DiscriminationNet class only supports syntactic pattern matching, that is, operations are neither associative nor commutative. Sequence variables are not supported either. The ManyToOneMatcher class supports associative and/or commutative matching with sequence variables. For syntactic pattern matching, the DiscriminationNet should be used, as it is usually faster.

>>> pattern1 = Pattern(f(a, x)) >>> pattern2 = Pattern(f(y, b)) >>> matcher = ManyToOneMatcher(pattern1, pattern2) >>> subject = f(a, b) >>> matches = matcher.match(subject) >>> for matched_pattern, substitution in sorted(map(lambda m: (str(m[0]), str(m[1])), matches)): ... print('{} matched with {}'.format(matched_pattern, substitution)) f(a, x) matched with {x ↦ b} f(y, b) matched with {y ↦ a}

Roadmap

Besides the existing features, we plan on adding the following to MatchPy:

  • Support for Mathematica's Alternatives: For example f(a | b) would match either f(a) or f(b).
  • Support for Mathematica's Repeated: For example f(a..) would match f(a), f(a, a), f(a, a, a), etc.
  • Support pattern sequences (PatternSequence in Mathematica). These are mainly useful in combination with Alternatives or Repeated, e.g. f(a | (b, c)) would match either f(a) or f(b, c). f((a a)..) would match any f with an even number of a arguments.
  • All these additional pattern features need to be supported in the ManyToOneMatcher as well.
  • Better integration with existing types such as dict.
  • Code generation for both one-to-one and many-to-one matching. There is already an experimental implementation, but it still has some dependencies on MatchPy which can probably be removed.
  • Improving the documentation with more examples.
  • Better test coverage with more randomized tests.
  • Implementation of the matching algorithms in a lower-level language, for example C, both for performance and to make MatchPy's functionality available in other languages.

Contributing

If you have some issue or want to contribute, please feel free to open an issue or create a pull request. Help is always appreciated!

The Makefile has several tasks to help development:

  • To install all needed packages, you can use make init .
  • To run the tests you can use make test. The tests use pytest.
  • To generate the documentation you can use make docs .
  • To run the style checker (pylint) you can use make check .

If you have any questions or need help with setting things up, please open an issue and we will try the best to assist you.

Publications

MatchPy: Pattern Matching in Python
Manuel Krebber and Henrik Barthels
Journal of Open Source Software, Volume 3(26), pp. 2, June 2018.
Efficient Pattern Matching in Python
Manuel Krebber, Henrik Barthels and Paolo Bientinesi
Proceedings of the 7th Workshop on Python for High-Performance and Scientific Computing, November 2017.
MatchPy: A Pattern Matching Library
Manuel Krebber, Henrik Barthels and Paolo Bientinesi
Proceedings of the 15th Python in Science Conference, July 2017.
Non-linear Associative-Commutative Many-to-One Pattern Matching with Sequence Variables
Manuel Krebber
Master Thesis, RWTH Aachen University, May 2017

If you want to cite MatchPy, please reference the JOSS paper:

@article{krebber2018,
    author    = {Manuel Krebber and Henrik Barthels},
    title     = {{M}atch{P}y: {P}attern {M}atching in {P}ython},
    journal   = {Journal of Open Source Software},
    year      = 2018,
    pages     = 2,
    month     = jun,
    volume    = {3},
    number    = {26},
    doi       = "10.21105/joss.00670",
    web       = "http://joss.theoj.org/papers/10.21105/joss.00670",
}

matchpy's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

matchpy's Issues

Unable to `.add()` rules in ManyToOneReplacer

I am receiving the following error while adding rule to ManyToOneReplacer:

>>> pattern1 = Pattern(Int(Mul(a_, Pow(x_, -1)), x), FreeQ((a,), x))
>>> rule1 = ReplacementRule(pattern1, lambda a, x: Mul(a, Log(x)))
>>> subject1 = Int(Mul(a, Pow(x, -1)), x)
>>>
>>> pattern2 = Pattern(Int(Pow(x_, m_), x), FreeQ((m,), x), NonzeroQ(Add(m_, one), (m,)))
>>> rule2 = ReplacementRule(pattern2, lambda m, x: Mul(Pow(x, Add(m, one)), Pow(Add(m, one), m_one)))
>>> subject2 = Int(Pow(x, one), x)
>>>
>>> replace_all(subject1, [rule1])
Mul(Log(VariableSymbol('x')), VariableSymbol('a'))
>>> replace_all(subject2, [rule2])
Mul(Pow(Add(ConstantSymbol('1'), ConstantSymbol('1')), ConstantSymbol('-1')), Pow(VariableSymbol('x'), Add(ConstantSymbol('1'), ConstantSymbol('1'))))
>>> rubi = ManyToOneReplacer(rule1)
>>> rubi.add(rule2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3-py3.6.egg/matchpy/matching/many_to_one.py", line 329, in add
AttributeError: 'ReplacementRule' object has no attribute 'expression'

AttributeError during import on Python 3.6.3

During import of the MatchPy on Python 3.6.3, following error occurs:

Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import matchpy
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python36\lib\site-packages\matchpy\__init__.py", line 5, in <module>
    from . import expressions
  File "C:\Python36\lib\site-packages\matchpy\expressions\__init__.py", line 4, in <module>
    from . import expressions
  File "C:\Python36\lib\site-packages\matchpy\expressions\expressions.py", line 225, in <module>
    class Arity(_ArityBase, Enum, metaclass=_ArityMeta):
  File "C:\Python36\lib\typing.py", line 977, in __new__
    self = super().__new__(cls, name, bases, namespace, _root=True)
  File "C:\Python36\lib\typing.py", line 137, in __new__
    return super().__new__(cls, name, bases, namespace)
  File "C:\Python36\lib\abc.py", line 133, in __new__
    cls = super().__new__(mcls, name, bases, namespace, **kwargs)
  File "C:\Python36\lib\enum.py", line 154, in __new__
    enum_class._member_names_ = []               # names in definition order
  File "C:\Python36\lib\typing.py", line 1177, in __setattr__
    super(GenericMeta, self._gorg).__setattr__(attr, value)
  File "C:\Python36\lib\enum.py", line 324, in __getattr__
    raise AttributeError(name) from None
AttributeError: _gorg

Bug in `ManyToOneReplacer`

is_match is returning True but ManyToOneReplacer is not able to match the subject:

>>> a_, b_, c_, d_, e_, f_, g_, h_ = map(Wildcard.dot, 'abcdefgh')
>>> n_, m_ = map(Wildcard.dot, 'nm')
>>> x_, u_ = map(Wildcard.symbol, 'xu')
>>> pattern533 = Pattern(Int(Mul(Add(a_, Mul(b_, x_)), Pow(x_, n_)), x_))
>>> rule533 = ReplacementRule(pattern533, lambda x, a, n, b : a)
>>> rubi = ManyToOneReplacer()
>>> rubi.add(rule533)
>>> sub = Int(Mul(Add(Mul(b, x), a), Pow(x, Integer(3))), x)
>>> is_match(sub, pattern533)
True
>>> rubi.replace(sub)
Int(Mul(Add(Mul(VariableSymbol('b'), VariableSymbol('x')), VariableSymbol('a')), Pow(VariableSymbol('x'), Integer('3'))), VariableSymbol('x'))

Operators are:

class Int(Operation):
    name = "Int"
    arity = Arity.binary

class Mul(Operation):
    name = "Mul"
    arity = Arity.variadic
    associative = True
    commutative = True
    one_identity = True

class Add(Operation):
    name = "Add"
    arity = Arity.variadic
    associative = True
    commutative = True
    one_identity = True

class Pow(Operation):
    name = "Pow"
    arity = Arity.binary

Bug in ManyToOneReplacer

Adding multiple rules is leading to mismatch of subject.

Code

import matchpy
Pattern, ReplacementRule, ManyToOneReplacer = matchpy.Pattern, matchpy.ReplacementRule, matchpy.ManyToOneReplacer

from matchpy import replace_all, is_match, Wildcard, CustomConstraint
from sympy.integrals import Integral
from sympy import Symbol, Pow, cacheit, Basic, Add, Mul, S
from matchpy.expressions.functions import register_operation_iterator, register_operation_factory
from matchpy import Operation, CommutativeOperation, AssociativeOperation, OneIdentityOperation
from sympy.integrals.rubi.utility_function import *

class WC(Wildcard, Symbol):
    def __init__(self, min_length, fixed_size, variable_name=None, optional=None, **assumptions):
        Wildcard.__init__(self, min_length, fixed_size, str(variable_name), optional)

    def __new__(cls, min_length, fixed_size, variable_name=None, optional=None, **assumptions):
        cls._sanitize(assumptions, cls)
        return WC.__xnew__(cls, min_length, fixed_size, variable_name, optional, **assumptions)

    def __getnewargs__(self):
        return (self.min_length, self.fixed_size, self.variable_name, self.optional)

    @staticmethod
    def __xnew__(cls, min_length, fixed_size, variable_name=None, optional=None, **assumptions):
        obj = Symbol.__xnew__(cls, variable_name, **assumptions)
        return obj

    def _hashable_content(self):
        if self.optional:
            return super()._hashable_content() + (self.min_count, self.fixed_size, self.variable_name, self.optional)
        else:
            return super()._hashable_content() + (self.min_count, self.fixed_size, self.variable_name)

Operation.register(Integral)
register_operation_iterator(Integral, lambda a: (a._args[0],) + a._args[1], lambda a: len((a._args[0],) + a._args[1]))

Operation.register(Add)
OneIdentityOperation.register(Add)
CommutativeOperation.register(Add)
AssociativeOperation.register(Add)
register_operation_iterator(Add, lambda a: a._args, lambda a: len(a._args))

Operation.register(Mul)
OneIdentityOperation.register(Mul)
CommutativeOperation.register(Mul)
AssociativeOperation.register(Mul)
register_operation_iterator(Mul, lambda a: a._args, lambda a: len(a._args))

def sympy_op_factory(old_operation, new_operands, variable_name):
     return type(old_operation)(*new_operands)

register_operation_factory(Basic, sympy_op_factory)

A_, B_, C_, F_, G_, H_, a_, b_, c_, d_, e_, f_, g_, h_, i_, j_, k_, l_, m_, n_, p_, q_, r_, t_, u_, v_, s_, w_, x_, y_, z_ = [WC(1, True, i) for i in 'ABCFGHabcdefghijklmnpqrtuvswxyz']
Pm_ = WC(1, True, 'Pm')

rubi = ManyToOneReplacer()

pattern7 = Pattern(Integral(Pm_**p_*WC(1, True,'u', S(1)), x_), CustomConstraint(lambda p, x: FreeQ(p, x)), CustomConstraint(lambda x, Pm: PolyQ(Pm, x)), CustomConstraint(lambda p: Not(RationalQ(p))), CustomConstraint(lambda p: RationalQ(p)))
rule7 = ReplacementRule(pattern7, lambda x, p, u, Pm : Int(Pm**p*u, x))
rubi.add(rule7)

pattern8 = Pattern(Integral(a_, x_), CustomConstraint(lambda a, x: FreeQ(a, x)))
rule8 = ReplacementRule(pattern8, lambda x, a : a*x)
rubi.add(rule8)

pattern30 = Pattern(Integral((x_**WC(1, True,'p', S(1))*WC(1, True,'a', S(1)) + x_**WC(1, True,'q', S(1))*WC(1, True,'b', S(1)) + x_**WC(1, True,'r', S(1))*WC(1, True,'c', S(1)))**WC(1, True,'m', S(1))*WC(1, True,'u', S(1)), x_), CustomConstraint(lambda a, x: FreeQ(a, x)), CustomConstraint(lambda b, x: FreeQ(b, x)), CustomConstraint(lambda c, x: FreeQ(c, x)), CustomConstraint(lambda p, x: FreeQ(p, x)), CustomConstraint(lambda q, x: FreeQ(q, x)), CustomConstraint(lambda r, x: FreeQ(r, x)), CustomConstraint(lambda m: IntegerQ(m)), CustomConstraint(lambda q, p: PosQ(-p + q)), CustomConstraint(lambda p, r: PosQ(-p + r)))
rule30 = ReplacementRule(pattern30, lambda x, p, m, c, u, q, b, a, r : Int(u*x**(m*p)*(a + b*x**(-p + q) + c*x**(-p + r))**m, x))
rubi.add(rule30)

from sympy.abc import a, x
subject = Integral(x + a, x)
print(rubi.replace(subject))

returns s*(x + a).

rule8 is being applied when all the patterns are added. When rule8 alone is added in ManyToOneReplacer, it does not match as expected.

ManyToOneMatcher: Position of constraints logic

This issue regards the discussion at: sympy/sympy#14988

The generated graph of the many-to-one matcher appears like this:
https://drive.google.com/file/d/1s2CewkIWhoUsKrSEE4vly1gOzDudg3Xn/view

In the transition between state 2212 and 2213 the constraint c0 appears. It's pretty curious, for two reasons:

  • Constraint c0 requires two parameters, but at transition 2212->2213 only i2 has been read.
  • The transition 2212->2213 is also shared by pattern p0, which does not have any constraint associated with it.

If you use the code generator, the presence of c0 at the transition 2212->2213 produces a wrong decision tree. If I manually remove the constraint c0 from that transition, the generated code works fine:

state2212.transitions[None][0].check_constraints.discard(0)

At this point, there are two possible fixes:

  1. modify the generated code to exactly mimic the way _Matcher visits the tree, so that the generated decision tree matches in the correct way.
  2. modify the way constraints are added to the transitions, so that transition 2212->2213 would not have c0.

I've got the impression that the constraints are being placed in the wrong transitions and somehow _Matcher is able to ignore this issue during the tree visit. But maybe I'm wrong, so I open this issue to ask for suggestion.

If it's OK, I will try to fix this issue myself.

Doubt in `ManyToOneReplacer`

I am wondering what happens when we add two ReplacementRule to ManyToOneReplacer whose Pattern and Constraint are same but have different replacement expression?

Expression matching with patterns without wildcards

import matchpy as m

f = m.Operation.new("f", arity=m.Arity.variadic, commutative=True,
                    associative=True)
a, b, c = m.Symbol("a"), m.Symbol("b"), m.Symbol("c")
expr = f(a, b, c)
matches = m.match_anywhere(expr, m.Pattern(f(a, b)))
print(list(matches))  # prints '[]'

In the above snippet f(a, b) appears in the expression as expr can be realized as f(f(a, b), c), however match_anywhere and replace_all are unable to find those.

Is this a missing feature or just a wrong usage of the API?

Thanks in advance!

One-to-one matching: symbols with variable_name do not work in commutative functions

In one-to-one matching, symbols that have a variable_name do not work in commutative functions. They seem to work correctly in many-to-one matching. Example:

from matchpy import Operation, Symbol, Arity, Pattern, match, ManyToOneMatcher

f = Operation.new('f', Arity.binary, commutative=True)
a_x = Symbol('a', variable_name='x')
a = Symbol('a')
b = Symbol('b')

subject = f(a, b)
pattern = Pattern(f(a_x, b))

print(list(match(subject, pattern)))

matcher = ManyToOneMatcher(pattern)
print(list(matcher.match(subject)))

This results in

[]
[(Pattern(f(a: x, b)), {'x': Symbol('a')})]

but it should result in

[{'x': Symbol('a')}]
[(Pattern(f(a: x, b)), {'x': Symbol('a')})]

So far, we don't have any tests that verify this functionality. Once it is fixed, we should add some.

New MatchPy version?

Hi,

can we have a new MatchPy version in order to definitely have all GPL dependencies removed?

Bug in ManyToOneReplacer

Hi, I am receiving the following error while using ManyToOneReplacer. the same pattern is working properly with replace_all.

I checked the substitution in __call__ of Constraint and it is giving wrong substitution while using ManyToOneReplacer.

Substitution by ManyToOneReplacer is {i2 ↦ x, i3 ↦ 3} (i2 and i3 are not even in the variables).
Whereas Substitution by replace_all is {m ↦ 3, x ↦ x}

>>> pattern2 = Pattern(Int(Pow(x_, m_), x_), cons(And(FreeQ(m_, x_), NonzeroQ(Add(ConstantSymbol(1), m_))), (m, x)))
>>> rule2 = ReplacementRule(pattern2, lambda m, x : Mul(Pow(Add(ConstantSymbol(1), m), ConstantSymbol(-1)), Pow(x, Add(ConstantSymbol(1), m))))
>>> subject = Int(Pow(x, ConstantSymbol(3)), x)
>>> replace_all(subject, [rule2])
Mul(Pow(Add(ConstantSymbol('1'), ConstantSymbol('3')), ConstantSymbol('-1')), Pow(VariableSymbol('x'), Add(ConstantSymbol('1'), ConstantSymbol('3'))))
>>> ManyToOneReplacer(rule2).replace(subject)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 754, in replace
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 89, in __iter__
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 137, in _match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 147, in _match_transition
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 276, in _match_regular_operation
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 179, in _check_transition
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 137, in _match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 147, in _match_transition
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 276, in _match_regular_operation
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 179, in _check_transition
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 137, in _match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 159, in _match_transition
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 179, in _check_transition
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 137, in _match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 159, in _match_transition
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 175, in _check_transition
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.3.1-py3.6.egg/matchpy/matching/many_to_one.py", line 205, in _check_constraints
  File "/Users/parsoyaarihant/sympy/sympy/rubi/constraint.py", line 13, in __call__
    return sympify(str(substitute(self.expr, substitution)))
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/sympy/core/sympify.py", line 322, in sympify
    expr = parse_expr(a, local_dict=locals, transformations=transformations, evaluate=evaluate)
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/sympy/parsing/sympy_parser.py", line 894, in parse_expr
    return eval_expr(code, local_dict, global_dict)
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/sympy/parsing/sympy_parser.py", line 807, in eval_expr
    code, global_dict, local_dict)  # take local objects in preference
  File "<string>", line 1, in <module>
TypeError: 'Symbol' object is not subscriptable

'Integer' object is not iterable in `ManyToOneReplacer`

Pattern is matching but .replace in ManyToOneReplacer is showing error.

Code

import matchpy
Pattern, ReplacementRule, ManyToOneReplacer = matchpy.Pattern, matchpy.ReplacementRule, matchpy.ManyToOneReplacer

from matchpy import replace_all, is_match, Wildcard
from sympy.integrals import Integral
from sympy import Symbol, Pow, cacheit, Basic, S, Add, Mul, srepr, Or, And
from matchpy.expressions.functions import register_operation_iterator, register_operation_factory
from matchpy import Operation, CommutativeOperation, AssociativeOperation, OneIdentityOperation, match, CustomConstraint

class WC(Wildcard, Symbol):
    def __init__(self, min_length, fixed_size, variable_name=None, optional=None, **assumptions):
        Wildcard.__init__(self, min_length, fixed_size, variable_name, optional)

    def __new__(cls, min_length, fixed_size, variable_name=None, optional=None, **assumptions):
        cls._sanitize(assumptions, cls)
        return WC.__xnew__(cls, min_length, fixed_size, variable_name, optional, **assumptions)

    def __getnewargs__(self):
        return (self.min_length, self.fixed_size, self.variable_name, self.optional)

    @staticmethod
    def __xnew__(cls, min_length, fixed_size, variable_name=None, optional=None, **assumptions):
        obj = Symbol.__xnew__(cls, variable_name, **assumptions)
        return obj

    def _hashable_content(self):
        return super()._hashable_content() + (self.min_count, self.fixed_size, self.variable_name, self.optional)

Operation.register(Integral)
register_operation_iterator(Integral, lambda a: (a._args[0],) + a._args[1], lambda a: len((a._args[0],) + a._args[1]))

Operation.register(Pow)
OneIdentityOperation.register(Pow)
register_operation_iterator(Pow, lambda a: a._args, lambda a: len(a._args))

Operation.register(Add)
OneIdentityOperation.register(Add)
CommutativeOperation.register(Add)
AssociativeOperation.register(Add)
register_operation_iterator(Add, lambda a: a._args, lambda a: len(a._args))

Operation.register(Mul)
OneIdentityOperation.register(Mul)
CommutativeOperation.register(Mul)
AssociativeOperation.register(Mul)
register_operation_iterator(Mul, lambda a: a._args, lambda a: len(a._args))

def sympy_op_factory(old_operation, new_operands, variable_name):
     return type(old_operation)(*new_operands)

register_operation_factory(Basic, sympy_op_factory)

m_ = WC(1, True, 'm')
x_ = WC(1, True, 'x')
c_ = WC(1, True, 'c')
n_ = WC(1, True, 'n')
x = Symbol('x')
b = Symbol('b')
a = Symbol('a')
c = Symbol('c')
d = Symbol('d')
m = Symbol('m')
n = Symbol('n')

subject = Integral((b*x)**m*(d*x + S(2))**n, x)

rubi = ManyToOneReplacer()

pattern45 = Pattern(Integral(Mul(Pow(Mul(WC(1, True, 'b', S('1')), x_), m_), Pow(Add(c_, Mul(WC(1, True, 'd', S('1')), x_)), n_)), x_))
rule45 = ReplacementRule(pattern45, lambda d, x, b, c, m, n : Mul(Pow(c, n), Mul(Pow(Mul(b, x), Add(m, S('1'))), Pow(Mul(b, Add(m, S('1'))), S('-1'))), Hypergeometric2F1(Mul(S('-1'), n), Add(m, S('1')), Add(m, S('2')), Mul(Mul(S('-1'), d), Mul(x, Pow(c, S('-1')))))))
rubi.add(rule45)

print(is_match(subject, pattern45))
print(rubi.replace(subject))

Error

True
Traceback (most recent call last):
  File "r.py", line 70, in <module>
    print(rubi.replace(subject))
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 796, in replace
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 99, in __iter__
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 148, in _match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 162, in _match_transition
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 310, in _match_regular_operation
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 209, in _check_transition
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 148, in _match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 160, in _match_transition
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 281, in _match_commutative_operation
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 849, in add_subject
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 839, in get_match_iter
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 148, in _match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 162, in _match_transition
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 310, in _match_regular_operation
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 209, in _check_transition
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 148, in _match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 160, in _match_transition
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 282, in _match_commutative_operation
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 865, in match
TypeError: 'Integer' object is not iterable

Error while replace in `ManyToOneReplacer`

Pattern is matching but .replace in ManyToOneReplacer is showing error.

Code

import matchpy
Pattern, ReplacementRule, ManyToOneReplacer = matchpy.Pattern, matchpy.ReplacementRule, matchpy.ManyToOneReplacer

from matchpy import replace_all, is_match, Wildcard
from sympy.integrals import Integral
from sympy import Symbol, Pow, cacheit, Basic, S, Add, Mul, srepr, Or, And
from matchpy.expressions.functions import register_operation_iterator, register_operation_factory
from matchpy import Operation, CommutativeOperation, AssociativeOperation, OneIdentityOperation, match, CustomConstraint

class WC(Wildcard, Symbol):
    def __init__(self, min_length, fixed_size, variable_name=None, optional=None, **assumptions):
        Wildcard.__init__(self, min_length, fixed_size, variable_name, optional)

    def __new__(cls, min_length, fixed_size, variable_name=None, optional=None, **assumptions):
        cls._sanitize(assumptions, cls)
        return WC.__xnew__(cls, min_length, fixed_size, variable_name, optional, **assumptions)

    def __getnewargs__(self):
        return (self.min_length, self.fixed_size, self.variable_name, self.optional)

    @staticmethod
    def __xnew__(cls, min_length, fixed_size, variable_name=None, optional=None, **assumptions):
        obj = Symbol.__xnew__(cls, variable_name, **assumptions)
        return obj

    def _hashable_content(self):
        return super()._hashable_content() + (self.min_count, self.fixed_size, self.variable_name, self.optional)

Operation.register(Integral)
register_operation_iterator(Integral, lambda a: (a._args[0],) + a._args[1], lambda a: len((a._args[0],) + a._args[1]))

Operation.register(Pow)
OneIdentityOperation.register(Pow)
register_operation_iterator(Pow, lambda a: a._args, lambda a: len(a._args))

Operation.register(Add)
OneIdentityOperation.register(Add)
CommutativeOperation.register(Add)
AssociativeOperation.register(Add)
register_operation_iterator(Add, lambda a: a._args, lambda a: len(a._args))

Operation.register(Mul)
OneIdentityOperation.register(Mul)
CommutativeOperation.register(Mul)
AssociativeOperation.register(Mul)
register_operation_iterator(Mul, lambda a: a._args, lambda a: len(a._args))

def sympy_op_factory(old_operation, new_operands, variable_name):
     return type(old_operation)(*new_operands)

register_operation_factory(Basic, sympy_op_factory)

m_ = WC(1, True, 'm')
x_ = WC(1, True, 'x')
c_ = WC(1, True, 'c')
n_ = WC(1, True, 'n')
x = Symbol('x')
b = Symbol('b')
a = Symbol('a')
c = Symbol('c')
d = Symbol('d')
m = Symbol('m')
n = Symbol('n')

subject = Integral(x**(S(4)/3)/(a + b*x)**2, x)

rubi = ManyToOneReplacer()

pattern32 = Pattern(Integral(Mul(Pow(Add(WC(1, True, 'a', S('0')), Mul(WC(1, True, 'b', S('1')), x_)), m_), Pow(Add(WC(1, True, 'c', S('0')), Mul(WC(1, True, 'd', S('1')), x_)), n_)), x_))
rule32 = ReplacementRule(pattern32, lambda a, d, x, b, c, m, n : Add(Mul(Pow(Add(a, Mul(b, x)), Add(m, S('1'))), Mul(Pow(Add(c, Mul(d, x)), n), Pow(Mul(b, Add(m, S('1'))), S('-1')))), Mul(S('-1'), Mul(d, Mul(n, Pow(Mul(b, Add(m, S('1'))), S('-1'))), Integral(Mul(Pow(Add(a, Mul(b, x)), Add(m, S('1'))), Pow(Add(c, Mul(d, x)), Add(n, S('-1')))), x)))))
rubi.add(rule32)

print(is_match(subject, pattern32))
print(rubi.replace(subject))

Error:

Traceback (most recent call last):
  File "r1.py", line 74, in <module>
    print(rubi.replace(subject))
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/matching/many_to_one.py", line 798, in replace
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev56+g9b820f8-py3.6.egg/matchpy/functions.py", line 126, in replace
TypeError: object of type 'Add' has no len()

Not able to match nested commutative operators

is_match is giving wrong answers and raising TypeError with Optional Wildcards.

Code

import matchpy
Pattern, ReplacementRule, ManyToOneReplacer = matchpy.Pattern, matchpy.ReplacementRule, matchpy.ManyToOneReplacer

from matchpy import replace_all, is_match, Wildcard
from sympy.integrals import Integral
from sympy import Symbol, Pow, cacheit, Basic, S, Add, Mul, srepr
from matchpy.expressions.functions import register_operation_iterator, register_operation_factory
from matchpy import Operation, CommutativeOperation, AssociativeOperation, OneIdentityOperation, match, CustomConstraint

class WC(Wildcard, Symbol):
    def __init__(self, min_length, fixed_size, variable_name=None, optional=None, **assumptions):
        Wildcard.__init__(self, min_length, fixed_size, variable_name, optional)

    def __new__(cls, min_length, fixed_size, variable_name=None, optional=None, **assumptions):
        cls._sanitize(assumptions, cls)
        return WC.__xnew__(cls, min_length, fixed_size, variable_name, optional, **assumptions)

    def __getnewargs__(self):
        return (self.min_length, self.fixed_size, self.variable_name, self.optional)

    @staticmethod
    def __xnew__(cls, min_length, fixed_size, variable_name=None, optional=None, **assumptions):
        obj = Symbol.__xnew__(cls, variable_name, **assumptions)
        return obj

    def _hashable_content(self):
        return super()._hashable_content() + (self.min_count, self.fixed_size, self.variable_name, self.optional)

Operation.register(Integral)
register_operation_iterator(Integral, lambda a: (a._args[0],) + a._args[1], lambda a: len((a._args[0],) + a._args[1]))

Operation.register(Pow)
OneIdentityOperation.register(Pow)
register_operation_iterator(Pow, lambda a: a._args, lambda a: len(a._args))

Operation.register(Add)
OneIdentityOperation.register(Add)
CommutativeOperation.register(Add)
AssociativeOperation.register(Add)
register_operation_iterator(Add, lambda a: a._args, lambda a: len(a._args))

Operation.register(Mul)
OneIdentityOperation.register(Mul)
CommutativeOperation.register(Mul)
AssociativeOperation.register(Mul)
register_operation_iterator(Mul, lambda a: a._args, lambda a: len(a._args))

def sympy_op_factory(old_operation, new_operands, variable_name):
     return type(old_operation)(*new_operands)

register_operation_factory(Basic, sympy_op_factory)

m_ = WC(1, True, 'm')
x_ = WC(1, True, 'x')
x = Symbol('x')
b = Symbol('b')
a = Symbol('a')
c = Symbol('c')
d = Symbol('d')

subject = Integral((a + b*x)**S(2)*(c + d*x)**S(3), x)

pattern1 = Pattern(Integral(Mul(Pow(Add(WC(1, True, 'a'), Mul(WC(1, True, 'b'), x_)), WC(1, True, 'm')), Pow(Add(WC(1, True, 'c'), Mul(WC(1, True, 'd'), x_)), WC(1, True, 'n'))), x_))
print(is_match(subject, pattern1))

pattern1 = Pattern(Integral(Mul(Pow(Add(WC(1, True, 'a', S('0')), Mul(WC(1, True, 'b', S('1')), x_)), WC(1, True, 'm', S('1'))), Pow(Add(WC(1, True, 'c', S('0')), Mul(WC(1, True, 'd', S('1')), x_)), WC(1, True, 'n', S('1')))), x_))
print(is_match(subject, pattern1))

Output

False
Traceback (most recent call last):
  File "r.py", line 67, in <module>
    print(is_match(subject, pattern1))
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/functions.py", line 278, in is_match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/functions.py", line 278, in <genexpr>
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 45, in match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 129, in _match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 251, in _match_operation
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 231, in _non_commutative_match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/utils.py", line 518, in generator_chain
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 153, in factory
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 129, in _match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 254, in _match_operation
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 313, in _match_commutative_operation
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/utils.py", line 518, in generator_chain
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 358, in factory
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 129, in _match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 251, in _match_operation
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 231, in _non_commutative_match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/utils.py", line 518, in generator_chain
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 153, in factory
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 129, in _match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 254, in _match_operation
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 313, in _match_commutative_operation
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/utils.py", line 518, in generator_chain
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 358, in factory
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 129, in _match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 254, in _match_operation
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 281, in _match_commutative_operation
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/multiset.py", line 70, in __init__
    for element in iterable:
TypeError: 'Mul' object is not iterable

GPL'd dependency: hopcroftkarp library

The hopcroftkarp library upon which this project depends is GPL'd.

I have re-implemented the Hopcroft-Karp algorithm in C++ as part of an attempt to make MatchPy usable in SymEngine (that is, SymPy's core rewritten in C++). I didn't perform extensive testing, so there may be bugs. That code can be translated into Python.

Not Compatible with Python 3.7

Traceback (most recent call last):
  File "uarray.py", line 1, in <module>
    import matchpy
  File "/usr/local/miniconda3/envs/uarray/lib/python3.7/site-packages/matchpy/__init__.py", line 5, in <module>
    from . import expressions
  File "/usr/local/miniconda3/envs/uarray/lib/python3.7/site-packages/matchpy/expressions/__init__.py", line 4, in <module>
    from . import expressions
  File "/usr/local/miniconda3/envs/uarray/lib/python3.7/site-packages/matchpy/expressions/expressions.py", line 54, in <module>
    from typing import (Callable, Iterator, List, NamedTuple, Optional, Set, Tuple, TupleMeta, Type, Union)
ImportError: cannot import name 'TupleMeta' from 'typing' (/usr/local/miniconda3/envs/uarray/lib/python3.7/typing.py)

See RussBaz/enforce#71 and https://github.com/ilevkivskyi/typing_inspect/blob/168fa6f7c5c55f720ce6282727211cf4cf6368f6/typing_inspect.py#L16-L24

unexpected keyword argument error in `ManyToOneReplacer`

When adding multiple rules in ManyToOneReplacer along with CustomConstraint, I recieve TypeError: <lambda>() got an unexpected keyword argument 'i2'

Code:

import matchpy
Pattern, ReplacementRule, ManyToOneReplacer = matchpy.Pattern, matchpy.ReplacementRule, matchpy.ManyToOneReplacer

from matchpy import replace_all, is_match, Wildcard, CustomConstraint
from sympy.integrals import Integral
from sympy import Symbol, Pow, cacheit, Basic
from matchpy.expressions.functions import register_operation_iterator, register_operation_factory
from matchpy import Operation, CommutativeOperation, AssociativeOperation, OneIdentityOperation

class WC(Wildcard, Symbol):
    def __init__(self, min_length, fixed_size, variable_name=None, default=None, **assumptions):
        Wildcard.__init__(self, min_length, fixed_size, variable_name, default)

    def __new__(cls, min_length, fixed_size, variable_name=None, default=None, **assumptions):
        cls._sanitize(assumptions, cls)
        return WC.__xnew__(cls, min_length, fixed_size, variable_name, default, **assumptions)

    def __getnewargs__(self):
        return (self.min_length, self.fixed_size, self.variable_name, self.optional)

    @staticmethod
    @cacheit
    def __xnew__(cls, min_length, fixed_size, variable_name=None, default=None, **assumptions):
        obj = Symbol.__xnew__(cls, variable_name, **assumptions)
        return obj

    def _hashable_content(self):
        return super()._hashable_content() + (self.min_count, self.fixed_size, self.variable_name, self.optional)

Operation.register(Integral)
register_operation_iterator(Integral, lambda a: (a._args[0],) + a._args[1], lambda a: len(a._args))

Operation.register(Pow)
OneIdentityOperation.register(Pow)
register_operation_iterator(Pow, lambda a: a._args, lambda a: len(a._args))

def sympy_op_factory(old_operation, new_operands, variable_name):
     return type(old_operation)(*new_operands)

register_operation_factory(Basic, sympy_op_factory)

a_ = WC(1, True, 'a')
m_ = WC(1, True, 'm')
x_ = WC(1, True, 'x')
x = Symbol('x')
m = Symbol('m')

subject = Integral(x**2, x)

rubi = ManyToOneReplacer()

pattern1 = Pattern(Integral(a_, x_), CustomConstraint(lambda a, x: not a.has(x)))
rule1 = ReplacementRule(pattern1, lambda x, a : Mul(a, x))
rubi.add(rule1)

pattern3 = Pattern(Integral(Pow(x_, WC(1, True, 'm')), x_), CustomConstraint(lambda m, x: not m.has(x)))
rule3 = ReplacementRule(pattern3, lambda x, m : m)
rubi.add(rule3)

print(rubi.replace(subject))

Error:

Traceback (most recent call last):
  File "r1.py", line 60, in <module>
    print(rubi.replace(subject))
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/many_to_one.py", line 797, in replace
TypeError: <lambda>() got an unexpected keyword argument 'i2'

ANN: MatchPyCpp port to C++

ANNOUNCEMENT:

I have ported parts of MatchPy into C++:
symengine/symengine#1497

  • symengine library has been used for expression trees,
  • only the parts of MatchPy needed by the generated code have been ported into C++,
  • the C++ code generator is written in Python and still relies on MatchPy library.

add `__version__` to `__init__`

It is nice to be able to check quickly which version of a library you have installed by doing:

matchpy.__version__

This is normally just a string like '0.4.4'

Inconsistent output of match_anywhere

Function match_anywhere in one_to_one.py produces different result when using patterns refering to the same subexpression but with different amount of hierarchical context:

using these functions:

plus = Operation.new('plus', Arity.variadic, associative=True, commutative=True)
f_B = Operation.new('B', Arity.nullary, associative=False, commutative=True)

and having the subject:

plus( f_B(), f_B(), f_B() );

these to pattern result differently:

pattern_1 = Pattern(f_B(), rest[0] );
pattern_2 = Pattern(plus( f_B(), rest[0] ) );

using pattern_1 results in:

{} matched with (0,)
{} matched with (1,)
{} matched with (2,)

using pattern_2 results in:

{rest0 ↦ {B(), B()}} matched with ()

I would expect pattern_2 to also result in 3 times the same match. One for every f_B() pattern_2 can refer to.

Need Help in code generation.

I could not find any documentation related to code generation.
Last year @wheerd had written a code generation script for rubi in sympy.
https://gist.github.com/wheerd/13ac5a0e9b560db6201d96136fa0bbcc

But code generated was huge so, It cannot be used. This year I have removed the repeated definition of constraints. So code generation should work probably.

However, I am unable to write the code generation script. Here is the new structure of rubi in sympy. Can someone update the gist accordingly?

rules = [] #list to keep track which rules has been applied
def cons_f1(m, x):
    return FreeQ(m, x)
cons1 = CustomConstraint(cons_f1)

def cons_f2(m):
    return NonzeroQ(m + S(1))
cons2 = CustomConstraint(cons_f2)

pattern1 = Pattern(Integral(x_**WC('m', S(1)), x_), cons1, cons2)
def replacement1(m, x):
    rules.append(1)
    return Simp(x**(m + S(1))/(m + S(1)), x)
rule1 = ReplacementRule(pattern1, replacement1)

[Feature] Visualization of Expressions with Graphviz

I have been working on graphviz visualization tool this morning to quickly look at matchpy expressions that I will be adding to uarray. Was wondering if I could upstream it in a PR. The idea is to add an _repr_svg_(self) and _repr_gviz_(self) method to both Symbol and Operation. Within jupyter notebooks/lab if the returned value is an object with a _repr_svg_ method it will attempt to render the expression. Graphviz takes advantage of this.

Minimal working example.

import itertools
import matchpy

from graphviz import Digraph

class VizSymbol(matchpy.Symbol):
    def _repr_svg_(self):
        dot = graph_visualize(self)
        return dot.pipe(format='svg').decode(dot._encoding)
    
    def _repr_gviz_(self):
        return (
            f'Symbol: {self.__class__.__name__}\na b c', 
            dict(shape='box', color='#AC6C82', fillcolor='#cfaab7', style='filled')
        )
    
class VizOperation(matchpy.Operation):
    def _repr_svg_(self):
        dot = graph_visualize(self)
        return dot.pipe(format='svg').decode(dot._encoding)
    
    def _repr_gviz_(self):
        return (
            f'Operation: {self.__class__.__name__}\nq w e', 
            dict(shape='oval', color='#455C7B', fillcolor='#9db0c8', style='filled')
        )

def graph_visualize(expr, comment='Matchpy Expression', with_attrs=True):
    dot = Digraph(comment=comment)
    counter = itertools.count()
    
    def _label_node(dot, expr):
        unique_id = str(next(counter))
        
        if hasattr(expr, '_repr_gviz_'):
            description, attr = expr._repr_gviz_()
        else:
            raise ValueError(f'matchpy expr does not have _repr_gviz_: "{repr(self)}"')
        
        if with_attrs:
            dot.attr('node', **attr)
        dot.node(unique_id, description)
        return unique_id
    
    def _visualize_node(dot, expr):
        expr_id = _label_node(dot, expr)
        
        if isinstance(expr, matchpy.Operation):
            for sub_expr in expr:
                sub_expr_id = _visualize_node(dot, sub_expr)
                dot.edge(expr_id, sub_expr_id)
        return expr_id
            
    _visualize_node(dot, expr)
    return dot

Lets look at a simple expression.

class Int(VizSymbol):
    pass

class List(VizOperation):
    name = "List"
    arity = matchpy.Arity(0, False)

List(Int('asdf'), List(Int('A'), Int('B')))

example

Would you like this as a PR? If so I would appreciate input on formatting

  • what colors to use for nodes by default
  • which properties should be shown for each node (is it associative? does it have a variable_name? etc.)
  • also would love input on if you have a good idea on visualizing replacements that take place. Ideally I would like to visualize the replacements as a gif.

Remove dependency on pkg_resources/setuptools

We recently started using pkg_resources/setuptools to store the version number in __version__ . Unfortunately, this introduced a problem with getting the latest version of MatchPy into conda-forge, see here. @asmeurer recommended to instead use something like versioneer to get the version. I created the branch dev-versioneer and set it up there. I'm not sure how to thoroughly test it, but it seems to work when I locally install MatchPy.

I don't have strong opinions regarding pkg_resources/setuptools or versioneer, but I think it would be good to fix the problem with conda-forge. Any opinions?

Overridable node visitors instead of typed-nodes in expression trees

The current MatchPy implementation defines expression trees and allows expression trees to be overloaded by external library (as it is done in SymPy's connector to MatchPy). Unfortunately, the current MatchPy implementation relies on types and isinstance( ) calls to determine the expression node.
For example:

  • isinstance(label, Wildcard) to check if a label is a wildcard,
  • isinstance(expression, Operation)
  • isinstance(subpattern, CommutativeOperation)
  • isinstance(expression, AssociativeOperation)
  • isinstance(subpattern, OneIdentityOperation)

The first one is quite tricky in SymPy because it needs to define a new class that is both a MatchPy and SymPy object.

There are other approaches to expression tree that do not defined typed nodes (i.e. classes that represent a particular type of node). For example, protosym has a unique class to define many types of nodes.

Does it make sense to create an overridable method is check whether a node is a wildcard/operation/associative operation/commutative operation/etc... so that custom libraries may define their way to visit their expression trees?

Repeated definition of the same CustomConstraint

In the current RUBI rules patterns in SymPy, the same constraint is defined many times. For example: CustomConstraint(lambda a, x: FreeQ(a, x)) has its definition repeated more and more, like in:

https://github.com/sympy/sympy/blob/628286fb5cc2248002bb0b141a911757a2e81fa7/sympy/integrals/rubi/rules/miscellaneous_algebraic.py#L22

and in the next rule the same constraint is redefined:
https://github.com/sympy/sympy/blob/628286fb5cc2248002bb0b141a911757a2e81fa7/sympy/integrals/rubi/rules/miscellaneous_algebraic.py#L26

Python defines a new object for every new lambda definition, even if the functions return the same identical value.

Does this practice have some potential negative consequences, like:

  • the size of the generated decision tree?
  • the overall speed of MatchPy?

Match arbitrary function

Is there any way to match a function with a certain number of arguments? Suppose I've defined a binary function sum, is there a way to create a pattern f(a, b) that matches sum(x, y)?

Usage of type unions

I have translated some parts of MatchPy into C++. I am finding difficulties implementing the type Union[TLeft, TRight]. I could use C++17 and variant<TLeft, TRight>, but this would break compatibility with older compilers.

I have translated parts of BipartiteGraph into C++ and I have temporarily used the template TEdgeValue only:
https://github.com/symengine/symengine/blob/9cb6da759fb95e2ffece407e0cf2bad136a3933b/matchpygen/bipartite.h#L29

Could the dictionary _graph be split into _graph_left and _graph_right to keep type consistency without type unions?

Bug in `ManyToOneReplacer()`

I added two ReplacementRule to ManyToOneReplacer and it does not match the subject. Whereas adding a single(Rule23) rule matches correctly.

Rule22 does not match the subject as expected but adding Rule22 is preventing Rule23 to transform the subject.

Does not match

rubi = ManyToOneReplacer()

pattern22 = Pattern(Int(Mul(Pow(Add(Wildcard.optional('a', Integer(0)), Mul(Wildcard.optional('b', Integer(1)), Wildcard.dot('x'))), Wildcard.optional('m', Integer(1))), Pow(Add(Wildcard.optional('c', Integer(0)), Mul(Wildcard.optional('d', Integer(1)), Wildcard.dot('x'))), Wildcard.optional('n', Integer(1)))), Wildcard.dot('x')), FreeQ(a, x), FreeQ(b, x), FreeQ(c, x), FreeQ(d, x), FreeQ(n, x), cons(And(PositiveIntegerQ(Wildcard.dot('m')), NonzeroQ(Add(Mul(Integer(-1), Wildcard.dot('a'), Wildcard.dot('d')), Mul(Wildcard.dot('b'), Wildcard.dot('c')))), Or(Not(IntegerQ(Wildcard.dot('n'))), Greater(Add(Wildcard.dot('m'), Wildcard.dot('n'), Integer(2)), Integer(0)), Less(Add(Mul(Integer(9), Wildcard.dot('m')), Mul(Integer(5), Wildcard.dot('n')), Integer(5)), Integer(0)), And(ZeroQ(Wildcard.dot('c')), LessEqual(Add(Mul(Integer(7), Wildcard.dot('m')), Mul(Integer(4), Wildcard.dot('n'))), Integer(0))))), (n, b, m, a, x, d, c)))
rule22 = ReplacementRule(pattern22, lambda n, b, m, a, x, d, c : Int(ExpandIntegrand(Mul(Pow(Add(a, Mul(b, x)), m), Pow(Add(c, Mul(d, x)), n)), x), x))
rubi.add(rule22)

pattern23 = Pattern(Int(Mul(Pow(Add(Wildcard.dot('a'), Mul(Wildcard.optional('b', Integer(1)), Wildcard.dot('x'))), Wildcard.optional('m', Integer(1))), Pow(Add(Wildcard.optional('c', Integer(0)), Mul(Wildcard.optional('d', Integer(1)), Wildcard.dot('x'))), Wildcard.optional('n', Integer(1)))), Wildcard.dot('x')), FreeQ(a, x), FreeQ(b, x), FreeQ(c, x), FreeQ(d, x), FreeQ(n, x), cons(And(IntegerQ(Wildcard.dot('n')), NegativeIntegerQ(Wildcard.dot('m')), NonzeroQ(Add(Mul(Integer(-1), Wildcard.dot('a'), Wildcard.dot('d')), Mul(Wildcard.dot('b'), Wildcard.dot('c')))), Not(And(PositiveIntegerQ(Wildcard.dot('n')), Less(Add(Wildcard.dot('m'), Wildcard.dot('n'), Integer(2)), Integer(0))))), (n, b, m, a, x, d, c)))
rule23 = ReplacementRule(pattern23, lambda n, b, m, a, x, d, c : Int(ExpandIntegrand(Mul(Pow(Add(a, Mul(b, x)), m), Pow(Add(c, Mul(d, x)), n)), x), x))
rubi.add(rule23)

subject = Int(Mul(Pow(Add(Mul(b, x), a), Integer(-2)), Pow(x, Integer(-2))), x)
print(rubi.replace(subject))

Matches

rubi = ManyToOneReplacer()

pattern23 = Pattern(Int(Mul(Pow(Add(Wildcard.dot('a'), Mul(Wildcard.optional('b', Integer(1)), Wildcard.dot('x'))), Wildcard.optional('m', Integer(1))), Pow(Add(Wildcard.optional('c', Integer(0)), Mul(Wildcard.optional('d', Integer(1)), Wildcard.dot('x'))), Wildcard.optional('n', Integer(1)))), Wildcard.dot('x')), FreeQ(a, x), FreeQ(b, x), FreeQ(c, x), FreeQ(d, x), FreeQ(n, x), cons(And(IntegerQ(Wildcard.dot('n')), NegativeIntegerQ(Wildcard.dot('m')), NonzeroQ(Add(Mul(Integer(-1), Wildcard.dot('a'), Wildcard.dot('d')), Mul(Wildcard.dot('b'), Wildcard.dot('c')))), Not(And(PositiveIntegerQ(Wildcard.dot('n')), Less(Add(Wildcard.dot('m'), Wildcard.dot('n'), Integer(2)), Integer(0))))), (n, b, m, a, x, d, c)))
rule23 = ReplacementRule(pattern23, lambda n, b, m, a, x, d, c : Int(ExpandIntegrand(Mul(Pow(Add(a, Mul(b, x)), m), Pow(Add(c, Mul(d, x)), n)), x), x))
rubi.add(rule23)

subject = Int(Mul(Pow(Add(Mul(b, x), a), Integer(-2)), Pow(x, Integer(-2))), x)
print(rubi.replace(subject))

Use more intelligent sequence wildcards

Currently the last sequence wildcard within an operation will try all possible lengths despite only one being possible. This could be sped up for both one-to-one matching by using the information about the remaining length of the pattern.

Using MatchPy lambda constraint with SymPy

I tried using Pattern constraint using lambda and it is giving errors:

Rule

pattern1 = Pattern(Integral(x_**m_, x_), lambda x, m: FreeQ(m, x))
rule1 = ReplacementRule(pattern1, lambda x, m: x**(m + 1)/(m + 1))

Error:

Traceback (most recent call last):
  File "t.py", line 4, in <module>
    print(rubi_integrate(expr, x))
  File "/Users/parsoyaarihant/sympy/sympy/integrals/rubi/rubi.py", line 46, in rubi_integrate
    result = replace_all(Integral(expr, var), [rubi])
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/functions.py", line 251, in replace_all
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 43, in match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.5.dev51+ge514171-py3.6.egg/matchpy/matching/one_to_one.py", line 43, in <listcomp>
AttributeError: 'function' object has no attribute 'variables'

clarify statement of need

Opening this issue as part of the JOSS paper review:

The statement of need in the documentation/README isn't very clear. For people unfamiliar with mathematica's pattern matching the overview doesn't really explain what this does. I would suggest putting something like the first paragraph of the summary section from the JOSS paper in the overview section of the README.

Also, if you just go the the docs page without first reading the README you would probably be a little lost. I would recommend making the README the index of documentation. You can make sure they stay in sync and still have the table of contents by using: .. include:: ../../README.rst in index.rst which will copy the readme into file.

Create `new` class method on Symbol

It would be nice to have a new classmethod on Symbol like the one on Operation to make a subclass more succinctly. I think it would just take one argument, the classname.

using `int` as Wildcard

Hi, I am trying to define class ConstantWild (similar to ConstantSymbol you suggested earlier).

Here is the code:

from matchpy import Wildcard

class ConstantWild(Wildcard):
    def __init__(self, value):
        super(self.__class__, self).__init__(min_count=1, fixed_size=True, variable_name=str(value))
        self.value = value

    def __str__(self):
        return str(self.value)

The above code have same attributes as Wildcard.dot but I don't understand why it is giving errors.

Do performance evalutation

Check the performance of many-to-one, one-to-one and generated code and see if there is optimization potential.

Unable to replace matched pattern in `ManyToOneReplacer`

import matchpy
Pattern, ReplacementRule, ManyToOneReplacer = matchpy.Pattern, matchpy.ReplacementRule, matchpy.ManyToOneReplacer

from matchpy import replace_all, is_match, Wildcard
from sympy.integrals import Integral
from sympy import Symbol, Pow, cacheit, Basic, S
from matchpy.expressions.functions import register_operation_iterator, register_operation_factory
from matchpy import Operation, CommutativeOperation, AssociativeOperation, OneIdentityOperation, match

class WC(Wildcard, Symbol):
    def __init__(self, min_length, fixed_size, variable_name=None, default=None, **assumptions):
        Wildcard.__init__(self, min_length, fixed_size, variable_name, default)

    def __new__(cls, min_length, fixed_size, variable_name=None, default=None, **assumptions):
        cls._sanitize(assumptions, cls)
        return WC.__xnew__(cls, min_length, fixed_size, variable_name, default, **assumptions)

    def __getnewargs__(self):
        return (self.min_length, self.fixed_size, self.variable_name, self.optional)

    @staticmethod
    def __xnew__(cls, min_length, fixed_size, variable_name=None, default=None, **assumptions):
        obj = Symbol.__xnew__(cls, variable_name, **assumptions)
        return obj

    def _hashable_content(self):
        return super()._hashable_content() + (self.min_count, self.fixed_size, self.variable_name, self.optional)

Operation.register(Integral)
register_operation_iterator(Integral, lambda a: (a._args[0],) + a._args[1], lambda a: len(a._args))

Operation.register(Pow)
OneIdentityOperation.register(Pow)
register_operation_iterator(Pow, lambda a: a._args, lambda a: len(a._args))

def sympy_op_factory(old_operation, new_operands, variable_name):
     return type(old_operation)(*new_operands)

register_operation_factory(Basic, sympy_op_factory)

m_ = WC(1, True, 'm', 1)
x_ = WC(1, True, 'x')
x = Symbol('x')

subject = Integral(x, x)

pattern1 = Pattern(Integral(x_**m_, x_))
rule1 = ReplacementRule(pattern1, lambda x, m: x**(m + 1)/(m + 1))

rubi = ManyToOneReplacer()
rubi.add(rule1)

print(is_match(subject, pattern1))
print(next(match(subject, pattern1)))
print(rubi.replace(subject))

Output:

True
{m1, xx}
Integral(x, x)

`ManyToOneReplacer` for Rubi

Hi, I implemented ~80 rules using ManyToOneReplacer for Rubi integration and MatchPy was able to match the subjects very quickly. However, when I added ~550 rules, there was significant decrease in speed while matching even smaller subjects.

It would be great if you could advice us on how to solve this issue.

Here are the files for our project:

Patterns: https://github.com/parsoyaarihant/sympy/blob/rubi4/sympy/rubi/patterns.py
Constraint: https://github.com/parsoyaarihant/sympy/blob/rubi4/sympy/rubi/constraint.py
All Rubi Files: https://github.com/parsoyaarihant/sympy/tree/rubi4/sympy/rubi

FreeQ equivalent in MatchPy?

Mathematica has FreeQ to test whether an expression contains a symbol. This is very useful in pattern matching for equations as you can specify that, for example, in a * x + b == 0 the variables a and b should not contain the variable x.

In SymPy we are currently using things like CustomConstraint(lambda a, x: not a.has(x)), where a.has(x) is a SymPy expression that tells you if x is contained in the expression tree of a.

Would it make sense to add an optimized FreeQ-like tester that checks whether the variable is contained in the expression during the matching iteration of MatchPy?

Optional wildcards to match the identity element of the current node

When using the optional=value argument in Wildcard, one can specify which value to get in case no matching is found. The value has to be given for every wildcard.

In case of addition and multiplication, the usual optional values are the identity elements, zero and one respectively.

It would be nice to have the possibility to have the optional value to return the identity element of the current node, if available.

Using `int` objects in ReplacementRule

I am unable to use int objects in ReplacementRule:

>>> from matchpy import *
>>> a, b = map(Symbol, 'ab')
>>> Plus = Operation.new('+', Arity.variadic, 'Plus', associative=True, one_identity=True, infix=True)
>>> e = Plus(a,b)
>>> a_ = Wildcard.dot('a')
>>> b_ = Wildcard.dot('b')
>>> p = Pattern(Plus(a_,b_))
>>> rule = ReplacementRule(p, lambda a, b: Plus(a, 1)) # Ubable to apply rule due to the presence of `1`
>>> replace_all(e, [rule])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.2-py3.6.egg/matchpy/functions.py", line 251, in replace_all
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.2-py3.6.egg/matchpy/matching/one_to_one.py", line 36, in match
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.2-py3.6.egg/matchpy/utils.py", line 584, in __get__
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.2-py3.6.egg/matchpy/expressions/expressions.py", line 131, in is_constant
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.2-py3.6.egg/matchpy/expressions/expressions.py", line 507, in _is_constant
  File "/Users/parsoyaarihant/anaconda/lib/python3.6/site-packages/matchpy-0.2-py3.6.egg/matchpy/expressions/expressions.py", line 507, in <genexpr>
AttributeError: 'int' object has no attribute 'is_constant'

Is there any alternative way to use int or float in expressions?

substitute not working with SymPy

Calling sorted( ) in

new_operands.extend(sorted(result))

fails with SymPy, as SymPy objects are not comparable with operators (<, <=, >, >=). Operators are overloaded in SymPy to create instances of inequality objects.

SymPy has a function called default_sort_key meant to deal with this problem:

from sympy import symbols
x, y, z = symbols("x y z")
from sympy.core.compatibility import default_sort_key
sorted([z, x, y], key=default_sort_key)

The question is, is it possible to modify MatchPy to specify a custom sorting key so that SymPy can specify the way its objects should be sorted?

Or is it an issue with Multiset?

Alternate way to define constraint

I wanted to know if there is alternatative way to define constraint in matchpy. Example:

def FreeQ(vars, x): # returns True of any `var` contains `x`
	if any(i.contains(x) for i in vars):
		return False
	return True

def NonzeroQ(e): # returns True if `e` is not zero.
	return e != 0

pattern = Pattern(Int(Pow(Add(a_, Mul(b_, x_)), m_), x_), FreeQ(list(a, b, m), x), NonzeroQ(Add(m, 1)))

Thanks.

Cannot import matchpy

import matchpy
Traceback (most recent call last):
File "", line 1, in
File "matchpy/init.py", line 5, in
from . import expressions
File "matchpy/expressions/init.py", line 4, in
from . import expressions
File "matchpy/expressions/expressions.py", line 90
def variables(self) -> MultisetOfVariables:
^
SyntaxError: invalid syntax

Wildcard that matches multiple symbols

Would it be possible to have a wildcard that matches any number of symbols?

Right now I am doing this by matching any number of items and having a custom constraint that asserts all are of instance of the Symbol I care about.

This is useful when you have some operation that needs all of it's arguments to be replaced into reduced symbolic form and can only itself be replaced when they all have been.

Improve checking correctness of input

To avoid issues such as #60, we should check that objects are created and functions are used correctly, at least in those cases where so far, incorrect input does not produce any errors.

New release?

Hi,

I've seen there were some changes in the way classes are registered. The current SymPy master branch does not support this.

I have added a fixing PR to SymPy: sympy/sympy#16356

It requires a new release of MatchPy in order to be mergeable.

Will there be a new release in the future?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.