sherpa / sherpa

Fit models to your data in Python with Sherpa.
https://sherpa.readthedocs.io
GNU General Public License v3.0
146 stars 49 forks source link

RecursionError when trying to regrid "complex" models #1802

Open DougBurke opened 1 year ago

DougBurke commented 1 year ago

With CIAO 4.15 we have

sherpa In [1]: from sherpa.models.basic import Const1D

sherpa In [2]: (Const1D() + Const1D()).regrid([1, 2, 3])
Out[2]: <RegridWrappedModel model instance 'regrid1d((const1d + const1d))'>

sherpa In [3]: (Const1D() * Const1D()).regrid([1, 2, 3])
Out[3]: <RegridWrappedModel model instance 'regrid1d((const1d * const1d))'>

sherpa In [4]: (Const1D() * (1 + Const1D())).regrid([1, 2, 3])
Out[4]: <RegridWrappedModel model instance 'regrid1d((const1d * (1.0 + const1d)))'>

sherpa In [5]: (Const1D() * Const1D() * (1 + Const1D())).regrid([1, 2, 3])
RecursionError: maximum recursion depth exceeded while calling a Python object

Where is this recursion coming from? I have also see a similar issue when dealing with the integrate flag (when trying to get it to be supported for *OpModel classes).

If I try with python 3.11 / latest main I get the following:

>>> (Const1D() * Const1D() * (1 + Const1D())).regrid([1, 2, 3])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/dburke/sherpa/sherpa-main/sherpa/models/model.py", line 1381, in regrid
    return part.__class__.regrid(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dburke/sherpa/sherpa-main/sherpa/models/model.py", line 1381, in regrid
    return part.__class__.regrid(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dburke/sherpa/sherpa-main/sherpa/models/model.py", line 1381, in regrid
    return part.__class__.regrid(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded

I can get a slightly-different error if I try the less-well-used division operator

>>> (1 / Const1D()).regrid([1, 2, 3])
<RegridWrappedModel model instance 'regrid1d((1.0 / const1d))'>
>>> (1 / (1 + Const1D())).regrid([1, 2, 3])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/dburke/sherpa/sherpa-main/sherpa/models/model.py", line 1381, in regrid
    return part.__class__.regrid(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dburke/sherpa/sherpa-main/sherpa/models/model.py", line 1381, in regrid
    return part.__class__.regrid(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dburke/sherpa/sherpa-main/sherpa/models/model.py", line 1381, in regrid
    return part.__class__.regrid(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  [Previous line repeated 994 more times]
  File "/home/dburke/sherpa/sherpa-main/sherpa/models/model.py", line 1378, in regrid
    if not hasattr(part, 'regrid'):
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dburke/sherpa/sherpa-main/sherpa/models/model.py", line 577, in __getattr__
    lowered_name = name.lower()
                   ^^^^^^^^^^^^
RecursionError: maximum recursion depth exceeded while calling a Python object

but note that I can say:

>>> (Const1D() / (1 + Const1D())).regrid([1, 2, 3])
<RegridWrappedModel model instance 'regrid1d((const1d / (1.0 + const1d)))'>

Hmm, it's not due to ArithmeticConstantModel - which is used to represent numeric terms like 1 in the above, since this also fails

>>> (Const1D() * Const1D() * (Const1D() + Const1D())).regrid([1, 2, 3])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/dburke/sherpa/sherpa-main/sherpa/models/model.py", line 1381, in regrid
    return part.__class__.regrid(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dburke/sherpa/sherpa-main/sherpa/models/model.py", line 1381, in regrid
    return part.__class__.regrid(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dburke/sherpa/sherpa-main/sherpa/models/model.py", line 1381, in regrid
    return part.__class__.regrid(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded
DougBurke commented 1 year ago

Aha: we have, for BinaryOpModel:

    def regrid(self, *args, **kwargs):
        for part in self.parts:
            # ArithmeticConstantModel does not support regrid by design
            if not hasattr(part, 'regrid'):
                continue
            # The full model expression must be used
            return part.__class__.regrid(self, *args, **kwargs)
        raise ModelErr('Neither component supports regrid method')

Unfortunately, if one of the parts is also a BinaryOpModel - such as the case of Const1D() * Const1D() * Const1D(), which has

>>> mdl.parts
(<BinaryOpModel model instance '(const1d + const1d)'>, <Const1D model instance 'const1d'>)

then part.__class__ is BinnaryOpModel, which leads to the recursion. This was added by Dan in #919 based on exploratory work I'd done. So I need to do some thinking.