question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Fitting of CompoundModel is slow

See original GitHub issue

I’m playing with some photometry computation with photutils, which is horribly slow, and this appears to be because of astropy.modeling trying to know if the model has some units. From what I understand with the traceback below (after interrupting the process), accessing self.input_units triggers a recursive lookup that takes a lot of time, exponentially increasing with the number of sources in the CompoundModel. And this for a model that has no units, and for each iteration if I get it correctly.

https://github.com/astropy/astropy/blob/master/astropy/modeling/core.py#L2861

I cannot provide reliable timing for now as it takes just too long, but I measured one call to prepare_units to 17sec, with a model with 24 (gaussian) sources. Replacing ._validate_input_units by a no-op method makes thing work, fitting the 24 sources group in 5 seconds (I will try to get the timing without this tomorrow).

---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-43-b96405ebf871> in <module>
----> 1 photometry_results = basic_photometry(subim)
      2 photometry_results

~/dev/photutils/photutils/psf/photometry.py in __call__(self, image, init_guesses)
    185         including the `__call__` signature.
    186         """
--> 187 
    188         return self.do_photometry(image, init_guesses)
    189 

~/dev/photutils/photutils/psf/photometry.py in do_photometry(self, image, init_guesses)
    288                                     [getattr(self.psf_model, param).value])
    289 
--> 290         star_groups = self.group_maker(init_guesses)
    291         output_tab, self._residual_image = self.nstar(image, star_groups)
    292 

~/dev/photutils/photutils/psf/photometry.py in nstar(self, image, star_groups)
    348                                         mode='trim')[0]] = True
    349 
--> 350             fit_model = self.fitter(group_psf, x[usepixel], y[usepixel],
    351                                     image[usepixel])
    352             param_table = self._model_params2table(fit_model,

~/dev/astropy/astropy/modeling/fitting.py in wrapper(self, model, x, y, z, **kwargs)
    176         else:
    177 
--> 178             return func(self, model, x, y, z=z, **kwargs)
    179 
    180     return wrapper

~/dev/astropy/astropy/modeling/fitting.py in __call__(self, model, x, y, z, weights, maxiter, acc, epsilon, estimate_jacobian)
    885             self.objective_function, init_values, args=farg, Dfun=dfunc,
    886             col_deriv=model_copy.col_fit_deriv, maxfev=maxiter, epsfcn=epsilon,
--> 887             xtol=acc, full_output=True)
    888         _fitter_to_model_params(model_copy, fitparams)
    889         self.fit_info.update(dinfo)

~/.pyenv/versions/3.7.0/lib/python3.7/site-packages/scipy/optimize/minpack.py in leastsq(func, x0, args, Dfun, full_output, col_deriv, ftol, xtol, gtol, maxfev, epsfcn, factor, diag)
    392             maxfev = 200*(n + 1)
    393         retval = _minpack._lmdif(func, x0, args, full_output, ftol, xtol,
--> 394                                  gtol, maxfev, epsfcn, factor, diag)
    395     else:
    396         if col_deriv:

~/dev/astropy/astropy/modeling/fitting.py in objective_function(self, fps, *args)
    823         meas = args[-1]
    824         if weights is None:
--> 825             return np.ravel(model(*args[2: -1]) - meas)
    826         else:
    827             return np.ravel(weights * (model(*args[2: -1]) - meas))

~/dev/astropy/astropy/modeling/core.py in __call__(self, x, y, model_set_axis, with_bounding_box, fill_value, equivalencies)
    380                                      ('with_bounding_box', False),
    381                                      ('fill_value', np.nan),
--> 382                                      ('equivalencies', None)])
    383 
    384             # The following makes it look like __call__ was defined in the class

~/dev/astropy/astropy/modeling/core.py in __call__(self, *inputs, **kwargs)
    361             def __call__(self, *inputs, **kwargs):
    362                 """Evaluate this model on the supplied inputs."""
--> 363                 return super(cls, self).__call__(*inputs, **kwargs)
    364 
    365             # When called, models can take two optional keyword arguments:

~/dev/astropy/astropy/modeling/core.py in __call__(self, *inputs, **kwargs)
    755         that were specified when the model was instantiated.
    756         """
--> 757         inputs, format_info = self.prepare_inputs(*inputs, **kwargs)
    758 
    759         parameters = self._param_sets(raw=True, units=True)

~/dev/astropy/astropy/modeling/core.py in prepare_inputs(self, model_set_axis, equivalencies, *inputs, **kwargs)
   1477                                model_set_axis, self.standard_broadcasting)
   1478 
-> 1479         inputs = self._validate_input_units(inputs, equivalencies)
   1480 
   1481         # The input formatting required for single models versus a multiple

~/dev/astropy/astropy/modeling/core.py in _validate_input_units(self, inputs, equivalencies)
   1495         # Check that the units are correct, if applicable
   1496 
-> 1497         if self.input_units is not None:
   1498 
   1499             # We combine any instance-level input equivalencies with user

~/dev/astropy/astropy/modeling/core.py in input_units(self)
   2860     @property
   2861     def input_units(self):
-> 2862         return self._generate_input_output_units_dict(self._tree.inputs_map, 'input_units')
   2863 
   2864     @property

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

~/dev/astropy/astropy/modeling/utils.py in inputs_map(self)
     83         else:
     84             for inp in self.left.inputs:
---> 85                 m, inp2 = self._recursive_lookup(self.left, self.left.inputs_map, inp)
     86                 inputs_map[inp] = m, inp2
     87 

KeyboardInterrupt: 

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:15 (15 by maintainers)

github_iconTop GitHub Comments

2reactions
perrygreenfieldcommented, Jan 11, 2019

Right. They should make that button harder to get to 😃

1reaction
perrygreenfieldcommented, Jan 11, 2019

The problem was tracked down to util.py ExpressionTree property inputs_map. This only shows up when there are more than one input coordinate and results in a doubling of calls to this property at every level of the expression tree. At 24 levels that basically becomes something on the order of 2**24 calls, a moderately substantial number, even for a simple routine 😉 I have a fix for this that I will later submit. I’ll need to test it more thoroughly.

Read more comments on GitHub >

github_iconTop Results From Across the Web

NonlinearModelFit runs too slow when fitting data to a function ...
The non-linear fitting takes more than 7-10 min to run, depending of the noise of the data. Reducing WorkingPrecision to 5 instead of...
Read more >
BayesicFitting Manual
The BayesicFitting toolbox can be used to fit data to a model and to find the ... A compound model ia a Model,...
Read more >
1 4 07-13 - NHTSA
5. Run the “EGR Actuator Slow Learn” procedure in DDDL or DDRS. 3 Installation of the DD15 and DD16 Exhaust Gas Recirculation Valve...
Read more >
(PDF) Reducing Sensitivity Analysis Time-Cost of Compound Model
gradient-based sensitivity analysis of a compound model. First ... method, this one is three to four times slower (Table II,. column 2).
Read more >
Models of cardiac cell - Scholarpedia
The model uses three transmembrane currents, a fast inward, slow ... was fitted using data available from guinea pig ventricular myocytes.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found