``CompoundModel.n_left_params` counts parameter values, not parameters, so array-valued parameters and model sets break `evaluate()` and every `parallel_fit_dask` fit · Issue #20554 · astropy/astropy · GitHub
You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
`CompoundModel.n_left_params counts parameter values, not parameters, so array-valued parameters and model sets break evaluate() and every parallel_fit_dask fit #20554
In my glorious attempt to make the lives of my users a nightmare, I have come up with fixed compound models for them to use as starting points for their spectral fitting.
This means having parameters with array and I hit this bug.
parallel_fit_dask documents that a model's parameters may be arrays shaped like the iterating axes, so each spectrum gets its own start. With a compound model such as Const1D + Gaussian1D, every fit raises TypeError: Const1D.evaluate() takes 2 positional arguments but 5 were given and every parameter comes back NaN. The same error hits any compound model with array-valued parameters or a model set (n_models > 1) whenever evaluate(*inputs, *parameters) is called, which is what the fitters do.
Expected behavior
n_left_params should be the number of parameters of the left model, len(self.left.param_names), regardless of the shape of their values.
importtempfilefrompathlibimportPathimportnumpyasnpfromastropy.modelingimportmodelsfromastropy.modeling.fittingimportTRFLSQFitter, parallel_fit_daskscalar=models.Const1D(1.0) +models.Gaussian1D(1.0, 0.0, 1.0)
array=models.Const1D(np.ones((3, 4))) +models.Gaussian1D(np.ones((3, 4)), np.zeros((3, 4)), np.ones((3, 4)))
print("scalar parameters: n_left_params =", scalar.n_left_params)
print("(3, 4) parameters: n_left_params =", array.n_left_params, "for", len(array.left.param_names), "parameter")
# __call__ works; evaluate(*inputs, *parameters), which the fitters use, does not.x=np.linspace(-1, 1, 5)
print("array(x).shape =", array(x[:, None, None]).shape)
try:
array.evaluate(x[:, None, None], *(getattr(array, name).valuefornameinarray.param_names))
exceptTypeErroraserror:
print("array.evaluate(...) ->", error)
# Model sets are affected too.model_set=models.Const1D([1.0, 2.0], n_models=2) +models.Gaussian1D([1.0, 1.0], [0.0, 0.0], [1.0, 1.0], n_models=2)
try:
model_set.evaluate(x[:, None], *(getattr(model_set, name).valuefornameinmodel_set.param_names))
exceptTypeErroraserror:
print("model_set.evaluate(...) ->", error)
# parallel_fit_dask with one start per pixel: every fit raises and comes back NaN.rng=np.random.default_rng(0)
xs=np.linspace(-1, 1, 50)
data=2+5*np.exp(-0.5* ((xs-rng.normal(0, 0.05, (3, 4))[..., None]) /0.2) **2) +rng.normal(0, 0.1, (3, 4, 50))
start=models.Const1D(np.ones((3, 4))) +models.Gaussian1D(np.full((3, 4), 4.0), np.zeros((3, 4)), np.full((3, 4), 0.3))
withtempfile.TemporaryDirectory() aspath:
fitted=parallel_fit_dask(model=start, fitter=TRFLSQFitter(), data=data, world=(xs,), fitting_axes=2,
scheduler="single-threaded", diagnostics="error", diagnostics_path=path)
log=sorted(Path(path).rglob("error.log"))[0].read_text().strip().splitlines()[-1]
print(f"parallel_fit_dask: {np.isnan(fitted.mean_1.value).sum()} of 12 fits NaN; error.log ends with: {log}")
# The proposed fix, applied by hand to this instance:start.n_left_params=len(start.left.param_names)
fitted=parallel_fit_dask(model=start, fitter=TRFLSQFitter(), data=data, world=(xs,), fitting_axes=2, scheduler="single-threaded")
print(f"with n_left_params = len(left.param_names): {np.isnan(fitted.mean_1.value).sum()} of 12 fits NaN")
Output:
scalar parameters: n_left_params = 1
(3, 4) parameters: n_left_params = 12 for 1 parameter
array(x).shape = (5, 3, 4)
array.evaluate(...) -> Const1D.evaluate() takes 2 positional arguments but 5 were given
model_set.evaluate(...) -> Const1D.evaluate() takes 2 positional arguments but 3 were given
parallel_fit_dask: 12 of 12 fits NaN; error.log ends with: TypeError: Const1D.evaluate() takes 2 positional arguments but 5 were given
with n_left_params = len(left.param_names): 0 of 12 fits NaN
changed the title [-]CompoundModel.n_left_params counts parameter values, not parameters, so array-valued parameters and model sets break evaluate() and every parallel_fit_dask fit[/-][+]``CompoundModel.n_left_params` counts parameter values, not parameters, so array-valued parameters and model sets break `evaluate()` and every `parallel_fit_dask` fit[/+]on Oct 6, 2026
Activity
[-]CompoundModel.n_left_params counts parameter values, not parameters, so array-valued parameters and model sets break evaluate() and every parallel_fit_dask fit[/-][+]``CompoundModel.n_left_params` counts parameter values, not parameters, so array-valued parameters and model sets break `evaluate()` and every `parallel_fit_dask` fit[/+]Count parameters, not parameter values, in CompoundModel.n_left_params