tBuLi / symfit

Symbolic Fitting; fitting as it should be.
http://symfit.readthedocs.org
MIT License
233 stars 17 forks source link

ODEModel with constraints gives error #282

Open JohnGoertz opened 4 years ago

JohnGoertz commented 4 years ago

Adding constraints to the fitting of an ODEModel raises an error, __init__() missing 1 required positional argument: 'initial'

Taking the simple coupled ODE model from the docs and adding an equality constraint to the variables (one that will necessarily be true anyways) gives the following output:

tdata = np.array([10, 26, 44, 70, 120])
adata = 10e-4 * np.array([44, 34, 27, 20, 14])
a, b, t = sf.variables('a, b, t')
k = sf.Parameter('k', 0.1)
a0 = 54 * 10e-4

model_dict = {
    sf.D(a, t): - k * a**2,
    sf.D(b, t): k * a**2,
}

ode_model = sf.ODEModel(model_dict, initial={t: 0.0, a: a0, b: 0.0})

constraints = [sf.Eq(a+b,0.055)]

fit = sf.Fit(ode_model, t=tdata, a=adata, b=None, constraints=constraints)
fit_result = fit.execute()
print(fit_result)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-133-bcdec22720dd> in <module>
     14 constraints = [sf.Eq(a+b,0.055)]
     15 
---> 16 fit = sf.Fit(ode_model, t=tdata, a=adata, b=None, constraints=constraints)
     17 fit_result = fit.execute()
     18 print(fit_result)

C:\ProgramData\Anaconda3\envs\sf_env\lib\site-packages\symfit\core\support.py in wrapped_func(*args, **kwargs)
    421                     else:
    422                         bound_args.arguments[param.name] = param.default
--> 423             return func(*bound_args.args, **bound_args.kwargs)
    424         return wrapped_func
    425 

C:\ProgramData\Anaconda3\envs\sf_env\lib\site-packages\symfit\core\fit.py in __init__(self, model, *ordered_data, **named_data)
    374 
    375         self.constraints = self._init_constraints(constraints=constraints,
--> 376                                                   model=self.model)
    377 
    378         # Bind as much as possible the provided arguments.

C:\ProgramData\Anaconda3\envs\sf_env\lib\site-packages\symfit\core\fit.py in _init_constraints(self, constraints, model)
    565                 else:
    566                     con_models.append(
--> 567                         model.__class__.as_constraint(constraint, model)
    568                     )
    569         return con_models

C:\ProgramData\Anaconda3\envs\sf_env\lib\site-packages\symfit\core\models.py in as_constraint(cls, constraint, model, constraint_type, **init_kwargs)
    157         instance = cls.with_dependencies(constraint,
    158                                          dependency_model=model,
--> 159                                          **init_kwargs)
    160 
    161         # Check if the constraint_type is allowed, and flip the sign if needed

C:\ProgramData\Anaconda3\envs\sf_env\lib\site-packages\symfit\core\models.py in with_dependencies(cls, model_expr, dependency_model, **init_kwargs)
    207         :return: A stand-alone :class:`~symfit.core.models.BaseModel` subclass.
    208         """
--> 209         model = cls(model_expr, **init_kwargs)  # Initiate model instance.
    210         if any(var in dependency_model for var in model.independent_vars):
    211             # This model depends on the output of the dependency_model,

TypeError: __init__() missing 1 required positional argument: 'initial'
pckroon commented 4 years ago

Constraints should not always be of the same type as their parent model apparently.

As a workaround, could you try to create a constraint like this?

cons = Model.as_constraint(sf.Eq(a+b, 0.055), ode_model)
constraints = [cons]

I'm unable to try it myself at the moment, unfortunately.

As an aside, this is not the best way of solving this: you're better of expressing b = 0.055 - a. But I assume this is a toy problem.

JohnGoertz commented 4 years ago

Hm, that gives a key error:

import numpy as np
import symfit as sf

tdata = np.array([10, 26, 44, 70, 120])
adata = 10e-4 * np.array([44, 34, 27, 20, 14])
a, b, t = sf.variables('a, b, t')
k = sf.Parameter('k', 0.1)
a0 = 54 * 10e-4

model_dict = {
    sf.D(a, t): - k * a**2,
    sf.D(b, t): k * a**2,
}

ode_model = sf.ODEModel(model_dict, initial={t: 0.0, a: a0, b: 0.0})

cons = sf.Model.as_constraint(sf.Eq(a, 0.055-b), ode_model)
constraints = [cons]

fit = sf.Fit(ode_model, t=tdata, a=adata, b=None, constraints=constraints)
fit_result = fit.execute()
print(fit_result)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-106-04170c57c285> in <module>
     12 ode_model = sf.ODEModel(model_dict, initial={t: 0.0, a: a0, b: 0.0})
     13 
---> 14 cons = sf.Model.as_constraint(sf.Eq(a, 0.055-b), ode_model)
     15 constraints = [cons]
     16 

C:\ProgramData\Anaconda3\envs\sf_env\lib\site-packages\symfit\core\models.py in as_constraint(cls, constraint, model, constraint_type, **init_kwargs)
    157         instance = cls.with_dependencies(constraint,
    158                                          dependency_model=model,
--> 159                                          **init_kwargs)
    160 
    161         # Check if the constraint_type is allowed, and flip the sign if needed

C:\ProgramData\Anaconda3\envs\sf_env\lib\site-packages\symfit\core\models.py in with_dependencies(cls, model_expr, dependency_model, **init_kwargs)
    226                             if symbol not in model_dict:
    227                                 model_dict[symbol] = dependency_model[symbol]
--> 228                                 connectivity_mapping[symbol] = dependency_model.connectivity_mapping[symbol]
    229                         if symbol == var:
    230                             break

KeyError: k

(And yes, this is a toy problem. The real problem is related to this: https://github.com/tBuLi/symfit/issues/284