tBuLi / symfit

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

Models as components for `CallableNumericalModel` #251

Open tBuLi opened 5 years ago

tBuLi commented 5 years ago

Taking the example from #248, we realized that the syntax for CallableNumericalModel's could be improved a bit from

ode_model = ODEModel({D(y, x): a * y}, initial={x: 0.0, y: 1.0})
model_dict = {
    z: 2 * y + b,
    y: lambda x, a: ode_model(x=x, a=a).y,
}

to

ode_model = ODEModel({D(y, x): a * y}, initial={x: 0.0, y: 1.0})
model_dict = {
    z: 2 * y + b,
    y: ode_model
}

if we make CallableNumericalModel's aware of Ans objects to automate the unpacking. (ODEModel is used here for no particular reason, could be any model.) I think this looks a lot cleaner. However, this could potentially open a pandora's box I haven't thought about, but for now I think it's save. An interesting extension would be tuple unpacking for multicomponent models:

ode_model = ODEModel({D(y1, x): a * y1, D(y2, x): a * y2}, initial={x: 0.0, y1: 1.0, y2: 0.0})
model_dict = {
    z: 2 * y1 + b,
    (y1, y2): ode_model
}
model = CallableNumericalModel(model_dict, connectivity_mapping={z: {y2, b}})

fit = Fit(model, x=xdata, z=zdata, y2=None)

where y2 is set to None so it is ignored in the optimization as usual. The general invariant here would be {ode_model.dependent_vars: ode_model}.

For general models this might not be a good idea because of the extra work involved for calculating jacobians etc, but for CallableNumericalModel this might in fact be a powerful addition.

pckroon commented 5 years ago

My only issue with this is that it makes it a little bit harder for users to create a CallableNumericalModel with a function that just returns a number. Unless you want the CallableNumericalModel to do the wrapping of the answer, but I see problems on the horizon there: you'll need to know the symbols, and only do it if the function doesn't return Ans objects already.

tBuLi commented 5 years ago

Why is it harder for functions that only return a number? We can already do that now, right? Like,

{y: lambda x: x}

is valid input to CallableNumericalModel. What am I missing here.

pckroon commented 5 years ago

Maybe I'm missing something. I was under the impression you'd have to provide {y: lambda x: Ans(x=x)}

tBuLi commented 5 years ago

Ah, I see what you mean. No, I'm not intending to always use the Ans object, that would indeed be worse. I'm just proposing that if a component is evaluated, and it happens to return an Ans object, than we realize what has happened (the component was one of our own models) and so we unpack it.