Skip to content

Commit 3552d36

Browse files
committed
Removed the constraintsdef dict and instead has each Fitter subclass manually specify what constraints they support as a class attribute supported_constraints. If in the future we do need a registry of which fitters support which constraints we can do that through a metaclass, but I didn't notice any current use case for that.
1 parent e8857f8 commit 3552d36

1 file changed

Lines changed: 34 additions & 30 deletions

File tree

‎astropy/modeling/fitting.py‎

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,6 @@
3636
EPS = np.sqrt(np.finfo(float).eps)
3737

3838

39-
# supported constraints
40-
# TODO: Register these automatically through a metaclass
41-
constraintsdef = {
42-
'NonLinearLSQFitter': ['fixed', 'tied', 'bounds'],
43-
'SLSQPFitter': ['bounds', 'eqcons', 'ineqcons', 'fixed', 'tied'],
44-
'LinearLSQFitter': ['fixed']}
45-
46-
4739
def _convert_input(x, y, z=None):
4840
x = np.asarray(x)
4941
y = np.asarray(y)
@@ -69,7 +61,7 @@ class ModelLinearityError(ModelsError):
6961
"""
7062

7163

72-
class UnsupportedConstraintError(ModelsError):
64+
class UnsupportedConstraintError(ModelsError, ValueError):
7365
"""
7466
Raised when a fitter does not support a type of constraint.
7567
"""
@@ -84,6 +76,12 @@ class Fitter(object):
8476

8577
__metaclass__ = abc.ABCMeta
8678

79+
# The base Fitter does not support any constraints by default; individual
80+
# fitters should explicitly set this list to the specific constraints
81+
# it supports
82+
# May contain any of 'bounds', 'eqcons', 'ineqcons', 'fixed', or 'tied'
83+
supported_constraints = []
84+
8785
def __init__(self, model):
8886
if not model.fittable:
8987
raise ValueError("Model must be a subclass of ParametricModel")
@@ -257,28 +255,28 @@ def _wrap_deriv(self, p, x, y, z=None):
257255
return [np.ravel(_) for _ in self.model.deriv(x, y, *params)]
258256

259257
def _validate_constraints(self):
260-
fname = self.__class__.__name__
261-
try:
262-
c = constraintsdef[fname]
263-
except KeyError:
264-
raise UnsupportedConstraintError("{0} does not support fitting",
265-
"with constraints".format(fname))
266-
if any(self.fixed) and 'fixed' not in c:
267-
raise ValueError("{0} cannot handle fixed parameter",
268-
"constraints .".format(fname))
269-
if any(self.tied) and 'tied' not in c:
270-
raise ValueError("{0} cannot handle tied parameter",
271-
"constraints ".format(fname))
258+
message = '{0} cannot handle {{0}} constraints.'.format(
259+
self.__class__.__name__)
260+
261+
if any(self.fixed) and 'fixed' not in self.supported_constraints:
262+
raise UnsupportedConstraintError(
263+
message.format('fixed parameter'))
264+
265+
if any(self.tied) and 'tied' not in self.supported_constraints:
266+
raise UnsupportedConstraintError(
267+
message.format('tied parameter'))
268+
272269
if (any([tuple(b) != (-1E12, 1E12) for b in self.bounds]) and
273-
'bounds' not in c):
274-
raise ValueError("{0} cannot handle bound parameter",
275-
"constraints".format(fname))
276-
if self.model.eqcons and 'eqcons' not in c:
277-
raise ValueError("{0} cannot handle equality constraints but ",
278-
"eqcons given".format(fname))
279-
if self.model.ineqcons and 'ineqcons' not in c:
280-
raise ValueError("{0} cannot handle inequality constraints but ",
281-
"ineqcons given".format(fname))
270+
'bounds' not in self.supported_constraints):
271+
raise UnsupportedConstraintError(
272+
message.format('bound parameter'))
273+
274+
if self.model.eqcons and 'eqcons' not in self.supported_constraints:
275+
raise UnsupportedConstraintError(message.format('equality'))
276+
277+
if (self.model.ineqcons and
278+
'ineqcons' not in self.supported_constraints):
279+
raise UnsupportedConstraintError(message.format('inequality'))
282280

283281
@property
284282
def covar(self):
@@ -330,6 +328,8 @@ class LinearLSQFitter(Fitter):
330328
A nonlinear model is passed to a linear fitter
331329
"""
332330

331+
supported_constraints = ['fixed']
332+
333333
def __init__(self, model):
334334
super(LinearLSQFitter, self).__init__(model)
335335
if not self.model.linear:
@@ -502,6 +502,8 @@ class NonLinearLSQFitter(Fitter):
502502
A linear model is passed to a nonlinear fitter
503503
"""
504504

505+
supported_constraints = ['fixed', 'tied', 'bounds']
506+
505507
def __init__(self, model):
506508

507509
self.fit_info = {'nfev': None,
@@ -636,6 +638,8 @@ class SLSQPFitter(Fitter):
636638
.. [1] http://www.netlib.org/toms/733
637639
"""
638640

641+
supported_constraints = ['bounds', 'eqcons', 'ineqcons', 'fixed', 'tied']
642+
639643
def __init__(self, model):
640644
super(SLSQPFitter, self).__init__(model)
641645
if self.model.linear:

0 commit comments

Comments
 (0)