diff --git a/docs/examples/coreshellnp.py b/docs/examples/coreshellnp.py index afb2a339..430726b1 100644 --- a/docs/examples/coreshellnp.py +++ b/docs/examples/coreshellnp.py @@ -76,8 +76,8 @@ def makeRecipe(stru1, stru2, datname): # very little to the PDF. from diffpy.srfit.pdf.characteristicfunctions import shellCF, sphericalCF - contribution.registerFunction(sphericalCF, name="f_CdS") - contribution.registerFunction(shellCF, name="f_ZnS") + contribution.register_function(sphericalCF, name="f_CdS") + contribution.register_function(shellCF, name="f_ZnS") # Write the fitting equation. We want to sum the PDFs from each phase and # multiply it by a scaling factor. diff --git a/docs/examples/debyemodel.py b/docs/examples/debyemodel.py index 3ec7002d..cd2dcbbd 100644 --- a/docs/examples/debyemodel.py +++ b/docs/examples/debyemodel.py @@ -106,14 +106,14 @@ def makeRecipe(): contribution.set_profile(profile, xname="T") # We now need to create the fitting equation. We tell the FitContribution - # to use the 'debye' function defined below. The 'registerFunction' method + # to use the 'debye' function defined below. The 'register_function' method # will let us do this. Since we haven't told it otherwise, - # 'registerFunction' will extract the name of the function ('debye') and + # 'register_function' will extract the name of the function ('debye') and # the names of the arguments ('T', 'm', 'thetaD'). These arguments will # become Parameters of the FitContribution. Since we named the x-variable # 'T' above, the 'T' in the 'debye' equation will refer to this x-variable # whenever it is used. - contribution.registerFunction(debye) + contribution.register_function(debye) # Now we can create the fitting equation. We want to extend the 'debye' # equation by adding a vertical offset. We could wrap 'debye' in a new diff --git a/docs/examples/debyemodelII.py b/docs/examples/debyemodelII.py index 5e82aaee..4817d11d 100644 --- a/docs/examples/debyemodelII.py +++ b/docs/examples/debyemodelII.py @@ -107,14 +107,14 @@ def plotResults(recipe): recipe.highT.profile.set_calculation_range(xmin="obs", xmax="obs") T = recipe.lowT.profile.x U = recipe.lowT.profile.y - # We can use a FitContribution's 'evaluateEquation' method to evaluate + # We can use a FitContribution's 'evaluate_equation' method to evaluate # expressions involving the Parameters and other aspects of the # FitContribution. Here we evaluate the fitting equation, which is always # accessed using the name "eq". We access it this way (rather than through # the Profile's ycalc attribute) because we changed the calculation range # above, and we therefore need to recalculate the profile. - lowU = recipe.lowT.evaluateEquation("eq") - highU = recipe.highT.evaluateEquation("eq") + lowU = recipe.lowT.evaluate_equation("eq") + highU = recipe.highT.evaluate_equation("eq") # Now we can plot this. import pylab diff --git a/docs/examples/npintensity.py b/docs/examples/npintensity.py index fce0e7bd..b9d33075 100644 --- a/docs/examples/npintensity.py +++ b/docs/examples/npintensity.py @@ -231,7 +231,7 @@ def makeRecipe(strufile, datname): # This creates a callable equation named "bkgd" within the FitContribution, # and turns the polynomial coefficients into Parameters. - contribution.registerStringFunction(bkgdstr, "bkgd") + contribution.register_string_function(bkgdstr, "bkgd") # We will create the broadening function that we need by creating a python # function and registering it with the FitContribution. @@ -247,7 +247,7 @@ def gaussian(q, q0, width): # This registers the python function and extracts the name and creates # Parameters from the arguments. - contribution.registerFunction(gaussian) + contribution.register_function(gaussian) # Center the Gaussian so it is not truncated. contribution.q0.value = x[len(x) // 2] @@ -342,7 +342,7 @@ def plotResults(recipe): Imeas = recipe.bucky.profile.y Icalc = recipe.bucky.profile.ycalc - bkgd = recipe.bucky.evaluateEquation("bkgd") + bkgd = recipe.bucky.evaluate_equation("bkgd") diff = Imeas - Icalc import pylab diff --git a/docs/examples/npintensityII.py b/docs/examples/npintensityII.py index 2791a1b3..92e5e899 100644 --- a/docs/examples/npintensityII.py +++ b/docs/examples/npintensityII.py @@ -105,8 +105,8 @@ def makeRecipe(strufile, datname1, datname2): bkgdstr = "b0 + b1*q + b2*q**2 + b3*q**3 + b4*q**4 + b5*q**5 + b6*q**6 +\ b7*q**7 +b8*q**8 + b9*q**9" - contribution1.registerStringFunction(bkgdstr, "bkgd") - contribution2.registerStringFunction(bkgdstr, "bkgd") + contribution1.register_string_function(bkgdstr, "bkgd") + contribution2.register_string_function(bkgdstr, "bkgd") # We will create the broadening function by registering a python function. pi = numpy.pi @@ -119,8 +119,8 @@ def gaussian(q, q0, width): * exp(-0.5 * ((q - q0) / width) ** 2) ) - contribution1.registerFunction(gaussian) - contribution2.registerFunction(gaussian) + contribution1.register_function(gaussian) + contribution2.register_function(gaussian) # Center the gaussian contribution1.q0.value = x[len(x) // 2] contribution2.q0.value = x[len(x) // 2] @@ -196,11 +196,11 @@ def plotResults(recipe): # Plot this for fun. I1 = recipe.bucky1.profile.y Icalc1 = recipe.bucky1.profile.ycalc - bkgd1 = recipe.bucky1.evaluateEquation("bkgd") + bkgd1 = recipe.bucky1.evaluate_equation("bkgd") diff1 = I1 - Icalc1 I2 = recipe.bucky2.profile.y Icalc2 = recipe.bucky2.profile.ycalc - bkgd2 = recipe.bucky2.evaluateEquation("bkgd") + bkgd2 = recipe.bucky2.evaluate_equation("bkgd") diff2 = I2 - Icalc2 offset = 1.2 * max(I2) * numpy.ones_like(I2) I1 += offset diff --git a/docs/examples/nppdfcrystal.py b/docs/examples/nppdfcrystal.py index a8e0dc9d..95cca7e3 100644 --- a/docs/examples/nppdfcrystal.py +++ b/docs/examples/nppdfcrystal.py @@ -59,7 +59,7 @@ def makeRecipe(ciffile, grdata): # Register the nanoparticle shape factor. from diffpy.srfit.pdf.characteristicfunctions import sphericalCF - pdfcontribution.registerFunction(sphericalCF, name="f") + pdfcontribution.register_function(sphericalCF, name="f") # Now we set up the fitting equation. pdfcontribution.set_equation("f * G") @@ -91,10 +91,10 @@ def plotResults(recipe): diffzero = -0.8 * max(g) * numpy.ones_like(g) diff = g - gcalc + diffzero - gcryst = recipe.pdf.evaluateEquation("G") + gcryst = recipe.pdf.evaluate_equation("G") gcryst /= recipe.scale.value - fr = recipe.pdf.evaluateEquation("f") + fr = recipe.pdf.evaluate_equation("f") fr *= max(g) / fr[0] import pylab diff --git a/docs/examples/nppdfsas.py b/docs/examples/nppdfsas.py index 6626c603..69386a8b 100644 --- a/docs/examples/nppdfsas.py +++ b/docs/examples/nppdfsas.py @@ -87,7 +87,7 @@ def makeRecipe(ciffile, grdata, iqdata): # Register the calculator with the pdf contribution and define the fitting # equation. - pdfcontribution.registerCalculator(cfcalculator) + pdfcontribution.register_calculator(cfcalculator) # The PDF for a nanoscale crystalline is approximated by # Gnano = f * Gcryst pdfcontribution.set_equation("f * G") @@ -156,10 +156,10 @@ def plotResults(recipe): diffzero = -0.8 * max(g) * numpy.ones_like(g) diff = g - gcalc + diffzero - gcryst = recipe.pdf.evaluateEquation("G") + gcryst = recipe.pdf.evaluate_equation("G") gcryst /= recipe.scale.value - fr = recipe.pdf.evaluateEquation("f") + fr = recipe.pdf.evaluate_equation("f") fr *= max(g) / fr[0] import pylab diff --git a/docs/examples/threedoublepeaks.py b/docs/examples/threedoublepeaks.py index d6c70202..4a3d7eb6 100644 --- a/docs/examples/threedoublepeaks.py +++ b/docs/examples/threedoublepeaks.py @@ -59,7 +59,7 @@ def makeRecipe(): def gaussian(t, mu, sig): return 1 / (2 * pi * sig**2) ** 0.5 * exp(-0.5 * ((t - mu) / sig) ** 2) - contribution.registerFunction(gaussian, name="peakshape") + contribution.register_function(gaussian, name="peakshape") def delta(t, mu): """Calculate a delta-function. @@ -70,12 +70,12 @@ def delta(t, mu): sig = t[1] - t[0] return gaussian(t, mu, sig) - contribution.registerFunction(delta) + contribution.register_function(delta) # Here is another one bkgdstr = "b0 + b1*t + b2*t**2 + b3*t**3 + b4*t**4 + b5*t**5 + b6*t**6" - contribution.registerStringFunction(bkgdstr, "bkgd") + contribution.register_string_function(bkgdstr, "bkgd") # Now define our fitting equation. We will hardcode the peak ratios. contribution.set_equation( @@ -120,7 +120,7 @@ def peakloc(mu): l2 = 1.0 return 180 / pi * arcsin(pi / 180 * l2 * sin(mu) / l1) - recipe.registerFunction(peakloc) + recipe.register_function(peakloc) recipe.constrain(contribution.mu12, "peakloc(mu11)") recipe.constrain(contribution.mu22, "peakloc(mu21)") recipe.constrain(contribution.mu32, "peakloc(mu31)") @@ -134,7 +134,7 @@ def sig(sig0, dsig, mu): """Calculate the peak broadening with respect to position.""" return sig0 * (1 - dsig * mu**2) - recipe.registerFunction(sig) + recipe.register_function(sig) recipe.fix("mu") # Now constrain the peak widths to this recipe.sig0.value = 0.001 diff --git a/news/recipeorg-dep1.rst b/news/recipeorg-dep1.rst new file mode 100644 index 00000000..aff30369 --- /dev/null +++ b/news/recipeorg-dep1.rst @@ -0,0 +1,37 @@ +**Added:** + +* Added ``iterate_over_parameters`` method. +* Added ``register_calculator`` method. +* Added ``register_function`` method. +* Added ``register_string_function`` method. +* Added ``evaluate_equation`` method. +* Added ``is_constrained`` method. +* Added ``get_constrained_parameters`` method. +* Added ``clear_all_constraints`` method. + +**Changed:** + +* + +**Deprecated:** + +* Deprecated ``iterPars`` method. Use ``iterate_over_parameters`` instead. +* Deprecated ``registerCalculator`` method. Use ``register_calculator`` instead. +* Deprecated ``registerFunction`` method. Use ``register_function`` instead. +* Deprecated ``registerStringFunction`` method. Use ``register_string_function`` instead. +* Deprecated ``evaluateEquation`` method. Use ``evaluate_equation`` instead. +* Deprecated ``isConstrained`` method. Use ``is_constrained`` instead. +* Deprecated ``getConstrainedPars`` method. Use ``get_constrained_parameters`` instead. +* Deprecated ``clearConstraints`` method. Use ``clear_all_constraints`` instead. + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/src/diffpy/srfit/equation/builder.py b/src/diffpy/srfit/equation/builder.py index 87a56b63..0c9ffafb 100644 --- a/src/diffpy/srfit/equation/builder.py +++ b/src/diffpy/srfit/equation/builder.py @@ -106,6 +106,16 @@ _builders = {} +EquationFactory_base = "diffpy.srfit.equation.builder.EquationFactory" +removal_version = "4.0.0" + +registerFunction_dep_msg = build_deprecation_message( + EquationFactory_base, + "registerFunction", + "register_function", + removal_version, +) + class EquationFactory(object): """A Factory for equations. @@ -208,23 +218,26 @@ def registerOperator(self, name, op): opbuilder = wrapOperator(name, op) return self.registerBuilder(name, opbuilder) - def registerFunction(self, name, func, argnames): + def register_function(self, name, func, argnames): """Register a named function with the factory. This will register a builder for the function. - Attributes + Parameters ---------- - name + name : str The name of the function - func - A callable python object - argnames + func : callable + The callable python object + argnames : list of str The argument names for func. If these names do not correspond to builders, then new constants with value 0 will be created for each name. - Returns the registered builder. + Returns + ------- + registered_builder : OperatorBuilder + The registered builder. """ for n in argnames: if n not in self.builders: @@ -234,8 +247,19 @@ def registerFunction(self, name, func, argnames): builder = self.builders[argname] argliteral = builder.literal opbuilder.literal.addLiteral(argliteral) + registered_builder = self.registerBuilder(name, opbuilder) + return registered_builder - return self.registerBuilder(name, opbuilder) + @deprecated(registerFunction_dep_msg) + def registerFunction(self, name, func, argnames): + """This function has been deprecated and will be removed in + version 4.0.0. + + Please use + diffpy.srfit.equation.builder.EquationFactory.register_function + instead. + """ + return self.register_function(name, func, argnames) def registerBuilder(self, name, builder): """Register builder in this module so it can be used in diff --git a/src/diffpy/srfit/fitbase/calculator.py b/src/diffpy/srfit/fitbase/calculator.py index 22e96540..41ebde60 100644 --- a/src/diffpy/srfit/fitbase/calculator.py +++ b/src/diffpy/srfit/fitbase/calculator.py @@ -20,7 +20,7 @@ overloaded to accept external arguments. Calculators are used to wrap registered functions so that the function's Parameters are contained in an object specific to the function. A custom Calculator can be added to -another RecipeOrganizer with the 'registerCalculator' method. +another RecipeOrganizer with the 'register_calculator' method. """ __all__ = ["Calculator"] diff --git a/src/diffpy/srfit/fitbase/fitcontribution.py b/src/diffpy/srfit/fitbase/fitcontribution.py index 21a7aed9..d5af4709 100644 --- a/src/diffpy/srfit/fitbase/fitcontribution.py +++ b/src/diffpy/srfit/fitbase/fitcontribution.py @@ -283,8 +283,8 @@ def set_equation(self, eqstr, ns={}): eqstr A string representation of the equation. Any Parameter registered by addParameter or setProfile, or function - registered by setCalculator, registerFunction or - registerStringFunction can be can be used in the equation + registered by setCalculator, register_function or + register_string_function can be can be used in the equation by name. Other names will be turned into Parameters of this FitContribution. ns diff --git a/src/diffpy/srfit/fitbase/fitrecipe.py b/src/diffpy/srfit/fitbase/fitrecipe.py index 2ebaa777..95021384 100644 --- a/src/diffpy/srfit/fitbase/fitrecipe.py +++ b/src/diffpy/srfit/fitbase/fitrecipe.py @@ -195,7 +195,7 @@ class FitRecipe(_fitrecipe_interface, RecipeOrganizer): lambda self: [ v.name for v in self._parameters.values() - if not (self.is_free(v) or self.isConstrained(v)) + if not (self.is_free(v) or self.is_constrained(v)) ], doc="names of the fixed refinable variables", ) @@ -204,7 +204,7 @@ class FitRecipe(_fitrecipe_interface, RecipeOrganizer): [ v.value for v in self._parameters.values() - if not (self.is_free(v) or self.isConstrained(v)) + if not (self.is_free(v) or self.is_constrained(v)) ] ), doc="values of the fixed refinable variables", @@ -622,7 +622,7 @@ def __verify_parameters(self): # Get all parameters with a value of None badpars = [] - for par in self.iterPars(): + for par in self.iterate_over_parameters(): try: par.getValue() except ValueError: diff --git a/src/diffpy/srfit/fitbase/parameterset.py b/src/diffpy/srfit/fitbase/parameterset.py index e5e51e39..6ebc4fc7 100644 --- a/src/diffpy/srfit/fitbase/parameterset.py +++ b/src/diffpy/srfit/fitbase/parameterset.py @@ -156,7 +156,7 @@ def setConst(self, const=True): Flag indicating if the parameter is constant (default True). """ - for par in self.iterPars(): + for par in self.iterate_over_parameters(): par.setConst(const) return diff --git a/src/diffpy/srfit/fitbase/recipeorganizer.py b/src/diffpy/srfit/fitbase/recipeorganizer.py index 3daf5674..495b3706 100644 --- a/src/diffpy/srfit/fitbase/recipeorganizer.py +++ b/src/diffpy/srfit/fitbase/recipeorganizer.py @@ -27,7 +27,6 @@ from functools import partial from itertools import chain, groupby -import six from numpy import inf from diffpy.srfit.equation import Equation @@ -61,6 +60,64 @@ removal_version, ) +iterPars_deprecation_msg = build_deprecation_message( + recipecontainer_base, + "iterPars", + "iterate_over_parameters", + removal_version, +) + +recipeorganizer_base = "diffpy.srfit.fitbase.recipeorganizer.RecipeOrganizer" + +registerCalculator_deprecation_msg = build_deprecation_message( + recipeorganizer_base, + "registerCalculator", + "register_calculator", + removal_version, +) + +registerFunction_deprecation_msg = build_deprecation_message( + recipeorganizer_base, + "registerFunction", + "register_function", + removal_version, +) + +registerStringFunction_deprecation_msg = build_deprecation_message( + recipeorganizer_base, + "registerStringFunction", + "register_string_function", + removal_version, +) + +evaluateEquation_deprecation_msg = build_deprecation_message( + recipeorganizer_base, + "evaluateEquation", + "evaluate_equation", + removal_version, +) + +isConstrained_deprecation_msg = build_deprecation_message( + recipeorganizer_base, + "isConstrained", + "is_constrained", + removal_version, +) + +getConstrainedPars_deprecation_msg = build_deprecation_message( + recipeorganizer_base, + "getConstrainedPars", + "get_constrained_parmeters", + removal_version, +) + +clearConstraints_deprecation_msg = build_deprecation_message( + recipeorganizer_base, + "clearConstraints", + "clear_all_constraints", + removal_version, +) + class RecipeContainer(Observable, Configurable, Validatable): """Base class for organizing pieces of a FitRecipe. @@ -134,21 +191,35 @@ def _iter_managed(self): """Get iterator over managed objects.""" return chain(*(d.values() for d in self.__managed)) - def iterPars(self, pattern="", recurse=True): + def iterate_over_parameters(self, pattern="", recurse=True): """Iterate over the Parameters contained in this object. Parameters ---------- - pattern : str - Iterate over parameters with names matching this regular - expression (all parameters by default). - recurse : bool - Recurse into managed objects when True (default). + pattern : str, optional + The regular expression pattern to match parameter + names against. Only parameters with names matching + this pattern will be returned. Default is an empty + string, which matches all parameter names. + recurse : bool, optional + The flag indicating whether to recurse into managed + objects when iterating over parameters. If True + (default), the method will also iterate over + parameters in managed sub-objects. If False, only + top-level parameters will be iterated over. + + Example + ------- + + .. + for param in recipe.iterate_over_parameters(pattern="scale_"): + # print the name and value of parameters containing "scale_" + print(f"{param.name}={param.value}") """ regexp = re.compile(pattern) - for par in list(self._parameters.values()): - if regexp.search(par.name): - yield par + for parameter in list(self._parameters.values()): + if regexp.search(parameter.name): + yield parameter if not recurse: return # Iterate over objects within the managed dictionaries. @@ -156,11 +227,24 @@ def iterPars(self, pattern="", recurse=True): managed.remove(self._parameters) for m in managed: for obj in m.values(): - if hasattr(obj, "iterPars"): - for par in obj.iterPars(pattern=pattern): - yield par + if hasattr(obj, "iterate_over_parameters"): + for parameter in obj.iterate_over_parameters( + pattern=pattern + ): + yield parameter return + @deprecated(iterPars_deprecation_msg) + def iterPars(self, pattern="", recurse=True): + """This function has been deprecated and will be removed in + version 4.0.0. + + Please use + diffpy.srfit.fitbase.recipeorganizer.RecipeContainer.iterate_over_parameters + instead. + """ + return self.iterate_over_parameters(pattern=pattern, recurse=recurse) + def __iter__(self): """Iterate over top-level parameters.""" return iter(self._parameters.values()) @@ -206,11 +290,11 @@ def __dir__(self): def __setattr__(self, name, value): """Parameter access and object checking.""" if name in self._parameters: - par = self._parameters[name] + parameter = self._parameters[name] if isinstance(value, Parameter): - par.value = value.value + parameter.value = value.value else: - par.value = value + parameter.value = value return m = self.get(name) @@ -472,14 +556,14 @@ def _new_parameter(self, name, value, check=True): self._add_parameter(p, check) return p - def _add_parameter(self, par, check=True): + def _add_parameter(self, parameter, check=True): """Store a Parameter. Parameters added in this way are registered with the _eqfactory. Attributes ---------- - par + parameter The Parameter to be stored. check If True (default), a ValueError is raised a Parameter of @@ -492,13 +576,13 @@ def _add_parameter(self, par, check=True): """ # Store the Parameter - RecipeContainer._add_object(self, par, self._parameters, check) + RecipeContainer._add_object(self, parameter, self._parameters, check) # Register the Parameter - self._eqfactory.registerArgument(par.name, par) + self._eqfactory.registerArgument(parameter.name, parameter) return - def _remove_parameter(self, par): + def _remove_parameter(self, parameter): """Remove a parameter. This de-registers the Parameter with the _eqfactory. The @@ -507,13 +591,14 @@ def _remove_parameter(self, par): Note that constraints and restraints involving the Parameter are not modified. - Raises ValueError if par is not part of the RecipeOrganizer. + Raises ValueError if parameter is not part of the + RecipeOrganizer. """ - self._remove_object(par, self._parameters) - self._eqfactory.deRegisterBuilder(par.name) + self._remove_object(parameter, self._parameters) + self._eqfactory.deRegisterBuilder(parameter.name) return - def registerCalculator(self, f, argnames=None): + def register_calculator(self, calculator, argnames=None): """Register a Calculator so it can be used within equation strings. @@ -525,78 +610,99 @@ def registerCalculator(self, f, argnames=None): Attributes ---------- - f + calculator : Calculator object The Calculator to register. - argnames - The names of the arguments to f (list or None). + argnames : list or None, optional + The names of the arguments to `calculator` (list or None). If this is None, then the argument names will be extracted from the function. """ - self._eqfactory.registerOperator(f.name, f) - self._add_object(f, self._calculators) + self._eqfactory.registerOperator(calculator.name, calculator) + self._add_object(calculator, self._calculators) # Register arguments of the calculator if argnames is None: - fncode = f.__call__.__func__.__code__ + fncode = calculator.__call__.__func__.__code__ argnames = list(fncode.co_varnames) argnames = argnames[1 : fncode.co_argcount] for pname in argnames: if pname not in self._eqfactory.builders: - par = self._new_parameter(pname, 0) + parameter = self._new_parameter(pname, 0) else: - par = self.get(pname) - f.addLiteral(par) + parameter = self.get(pname) + calculator.addLiteral(parameter) # Now return an equation object - eq = self._eqfactory.makeEquation(f.name) + eq = self._eqfactory.makeEquation(calculator.name) return eq - def registerFunction(self, f, name=None, argnames=None): + @deprecated(registerCalculator_deprecation_msg) + def registerCalculator(self, f, argnames=None): + """This function has been deprecated and will be removed in + version 4.0.0. + + Please use + diffpy.srfit.fitbase.recipeorganizer.RecipeOrganizer.register_calculator + instead. + """ + return self.register_calculator(f, argnames=argnames) + + def register_function(self, function, name=None, argnames=None): """Register a function so it can be used within equation strings. This creates a function with this class that can be used within string - equations. The resulting equation does not require the arguments to be + equations. The resulting equation does not require the arguments to be passed in the equation string, as this will be handled automatically. - Attributes + Parameters ---------- - f + function : callable The callable to register. If this is an Equation instance, then all that needs to be provided is a name. - name + name : str or None, optional The name of the function to be used in equations. If this is None (default), the method will try to determine the name of the function automatically. - argnames - The names of the arguments to f (list or None). + argnames : list or None, optional + The names of the arguments to `function` (list or None). If this is None (default), then the argument names will be extracted from the function. - - Note that name and argnames can be extracted from regular python - functions (of type 'function'), bound class methods and callable + Note + ---- + The `name` and `argnames` args can be extracted from regular Python + functions (of type ), bound class methods, and callable classes. + Raises + ------ + TypeError + If name or argnames cannot be automatically extracted. + TypeError + If an automatically extracted name is ''. + ValueError + If function is an Equation object and name is None. - Raises TypeError if name or argnames cannot be automatically - extracted. - Raises TypeError if an automatically extracted name is ''. - Raises ValueError if f is an Equation object and name is None. - - Returns the callable Equation object. + Returns + ------- + equation_object : Equation + The callable Equation object. """ # If the function is an equation, we treat it specially. This is # required so that the objects observed by the root get observed if the # Equation is used within another equation. It is assumed that a plain # function is not observable. - if isinstance(f, Equation): + if isinstance(function, Equation): if name is None: - m = "Equation must be given a name" + m = ( + "The equation must be given a name. " + "Specify a name with the 'name' argument." + ) raise ValueError(m) - self._eqfactory.registerOperator(name, f) - return f + self._eqfactory.registerOperator(name, function) + return function # Introspection code if name is None or argnames is None: @@ -609,15 +715,17 @@ def registerFunction(self, f, name=None, argnames=None): offset = 0 # check regular functions - if inspect.isfunction(f): - fncode = f.__code__ + if inspect.isfunction(function): + fncode = function.__code__ # check class method - elif inspect.ismethod(f): - fncode = f.__func__.__code__ + elif inspect.ismethod(function): + fncode = function.__func__.__code__ offset = 1 # check functor - elif hasattr(f, "__call__") and hasattr(f.__call__, "__func__"): - fncode = f.__call__.__func__.__code__ + elif hasattr(function, "__call__") and hasattr( + function.__call__, "__func__" + ): + fncode = function.__call__.__func__.__code__ offset = 1 else: m = "Cannot extract name or argnames" @@ -645,153 +753,229 @@ def registerFunction(self, f, name=None, argnames=None): # Initialize and register from diffpy.srfit.fitbase.calculator import Calculator - if isinstance(f, Calculator): + if isinstance(function, Calculator): for pname in argnames: - par = self.get(pname) - f.addLiteral(par) - self._eqfactory.registerOperator(name, f) + parameter = self.get(pname) + function.addLiteral(parameter) + self._eqfactory.registerOperator(name, function) else: - self._eqfactory.registerFunction(name, f, argnames) + self._eqfactory.register_function(name, function, argnames) # Now we can create the Equation and return it to the user. - eq = self._eqfactory.makeEquation(name) + equation_object = self._eqfactory.makeEquation(name) - return eq + return equation_object - def registerStringFunction(self, fstr, name, ns={}): + @deprecated(registerFunction_deprecation_msg) + def registerFunction(self, f, name=None, argnames=None): + """This function has been deprecated and will be removed in + version 4.0.0. + + Please use + diffpy.srfit.fitbase.recipeorganizer.RecipeOrganizer.register_function + instead. + """ + return self.register_function(f, name=name, argnames=argnames) + + def register_string_function(self, function_str, name, func_params={}): """Register a string function. This creates a function with this class that can be used within string - equations. The resulting equation does not require the arguments to be + equations. The resulting equation does not require the arguments to be passed in the function string, as this will be handled automatically. - Attributes + Parameters ---------- - fstr + function_str : str A string equation to register. - name + name : str The name of the function to be used in equations. - ns + func_params : dict, optional A dictionary of Parameters, indexed by name, that are - used in fstr, but not part of the FitRecipe (default - {}). - + used in `function_str`, but not part of the FitRecipe (default {}). - Raises ValueError if ns uses a name that is already used for another - managed object. - Raises ValueError if the function name is the name of another managed - object. + Raises + ------ + ValueError + If `func_params` uses a name that is already used for another + managed object. + ValueError + If the function name is the name of another managed object. - Returns the callable Equation object. + Returns + ------- + equation_object : Equation + The callable Equation object. """ # Build the equation instance. - eq = equationFromString(fstr, self._eqfactory, ns=ns, buildargs=True) + eq = equationFromString( + function_str, self._eqfactory, ns=func_params, buildargs=True + ) eq.name = name # Register any new Parameters. - for par in self._eqfactory.newargs: - self._add_parameter(par) + for parameter in self._eqfactory.newargs: + self._add_parameter(parameter) # Register the equation as a callable function. argnames = eq.argdict.keys() - return self.registerFunction(eq, name, argnames) + equation_object = self.register_function( + eq, name=name, argnames=argnames + ) + return equation_object - def evaluateEquation(self, eqstr, ns={}): + @deprecated(registerStringFunction_deprecation_msg) + def registerStringFunction(self, fstr, name, ns={}): + """This function has been deprecated and will be removed in + version 4.0.0. + + Please use + diffpy.srfit.fitbase.recipeorganizer.RecipeOrganizer.register_string_function + instead. + """ + return self.register_string_function(fstr, name, func_params=ns) + + def evaluate_equation(self, equation_str, func_params={}): """Evaluate a string equation. - Attributes + This method takes a string representation of a mathematical equation + and evaluates it using the current values of the registered Parameters + in the FitRecipe. Additional parameters not part of the FitRecipe can + also be provided via the `func_params` dictionary. + + Parameters ---------- - eqstr - A string equation to evaluate. The equation is evaluated at + equation_str + The string equation to evaluate. The equation is evaluated at the current value of the registered Parameters. - ns - A dictionary of Parameters, indexed by name, that are - used in fstr, but not part of the FitRecipe (default {}). - + func_params : dict, optional + The dictionary of Parameters, indexed by name, that are + used in `equation_str`, but not part of the FitRecipe + (default `{}`). - Raises ValueError if ns uses a name that is already used for a - variable. + Returns + ------- + returned_value : float + The value of the evaluated equation. + + Raises + ------ + ValueError + If `func_params` uses a name that is already used for a + variable. """ - eq = equationFromString(eqstr, self._eqfactory, ns) + eq = equationFromString(equation_str, self._eqfactory, func_params) try: - rv = eq() + returned_value = eq() finally: self._eqfactory.wipeout(eq) - return rv + return returned_value + + @deprecated(evaluateEquation_deprecation_msg) + def evaluateEquation(self, eqstr, ns={}): + """This function has been deprecated and will be removed in + version 4.0.0. + + Please use + diffpy.srfit.fitbase.recipeorganizer.RecipeOrganizer.evaluate_equation + instead. + """ + return self.evaluate_equation(eqstr, func_params=ns) - def constrain(self, par, con, ns={}): + def constrain(self, parameter, constraint_eq, params={}): """Constrain a parameter to an equation. Note that only one constraint can exist on a Parameter at a time. - Attributes + Parameters ---------- - par + parameter : str or Parameter The name of a Parameter or a Parameter to constrain. - con + constraint_eq : str or Equation A string representation of the constraint equation or a - Parameter to constrain to. A constraint equation must + Parameter to constrain to. A constraint equation must consist of numpy operators and "known" Parameters. - Parameters are known if they are in the ns argument, or if - they are managed by this object. - ns + Parameters are known if they are in the `params` + argument, or if they are managed by this object. + params : dict, optional A dictionary of Parameters, indexed by name, that are used - in the parameter, but not part of this object (default {}). - - - Raises ValueError if ns uses a name that is already used for a - variable. - Raises ValueError if par is a string but not part of this object or in - ns. - Raises ValueError if par is marked as constant. + in `parameter`, but not part of this object (default {}). + + Raises + ------ + ValueError + If `params` uses a name that is already used for a + variable. + ValueError + If `parameter` is a string but not part of this object or + in `params`. + ValueError + If `parameter` is marked as constant. """ - if isinstance(par, six.string_types): - name = par - par = self.get(name) - if par is None: - par = ns.get(name) + if isinstance(parameter, str): + name = parameter + parameter = self.get(name) + if parameter is None: + parameter = params.get(name) - if par is None: + if parameter is None: raise ValueError("The parameter cannot be found") - if par.const: - raise ValueError("The parameter '%s' is constant" % par) + if parameter.const: + raise ValueError("The parameter '%s' is constant" % parameter) - if isinstance(con, six.string_types): - eqstr = con - eq = equationFromString(con, self._eqfactory, ns) + if isinstance(constraint_eq, str): + eqstr = constraint_eq + eq = equationFromString(constraint_eq, self._eqfactory, params) else: - eq = Equation(root=con) - eqstr = con.name + eq = Equation(root=constraint_eq) + eqstr = constraint_eq.name - eq.name = "_constraint_%s" % par.name + eq.name = "_constraint_%s" % parameter.name # Make and store the constraint - con = Constraint() - con.constrain(par, eq) + constraint_eq = Constraint() + constraint_eq.constrain(parameter, eq) # Store the equation string so it can be shown later. - con.eqstr = eqstr - self._constraints[par] = con + constraint_eq.eqstr = eqstr + self._constraints[parameter] = constraint_eq # Our configuration changed self._update_configuration() return - def isConstrained(self, par): + def is_constrained(self, parameter): """Determine if a Parameter is constrained in this object. - Attributes + Parameters ---------- - par + parameter : str or Parameter The name of a Parameter or a Parameter to check. + + Returns + ------- + bool + True if the Parameter is constrained in this object, False + otherwise. """ - if isinstance(par, six.string_types): - name = par - par = self.get(name) + if isinstance(parameter, str): + name = parameter + parameter = self.get(name) + + return parameter in self._constraints - return par in self._constraints + @deprecated(isConstrained_deprecation_msg) + def isConstrained(self, parameter): + """This function has been deprecated and will be removed in + version 4.0.0. + + Please use + diffpy.srfit.fitbase.recipeorganizer.RecipeOrganizer.is_constrained + instead. + """ + return self.is_constrained(parameter) def unconstrain(self, *pars): """Unconstrain a Parameter. @@ -807,17 +991,17 @@ def unconstrain(self, *pars): Raises ValueError if the Parameter is not constrained. """ update = False - for par in pars: - if isinstance(par, six.string_types): - name = par - par = self.get(name) + for parameter in pars: + if isinstance(parameter, str): + name = parameter + parameter = self.get(name) - if par is None: + if parameter is None: raise ValueError("The parameter cannot be found") - if par in self._constraints: - self._constraints[par].unconstrain() - del self._constraints[par] + if parameter in self._constraints: + self._constraints[parameter].unconstrain() + del self._constraints[parameter] update = True if update: @@ -830,88 +1014,130 @@ def unconstrain(self, *pars): return - def getConstrainedPars(self, recurse=False): + def get_constrained_parmeters(self, recurse=False): """Get a list of constrained managed Parameters in this object. - Attributes + Parameters ---------- - recurse - Recurse into managed objects and retrieve their constrained - Parameters as well (default False). + recurse : bool, optional + If False (default), only constrained Parameters in + this object are returned. If True, constrained + Parameters in managed sub-objects are also included. + + Return + ------ + constrained_params : list of Parameter + A list of constrained managed Parameters in this object. """ const = self._get_constraints(recurse) - return const.keys() + constrained_params = const.keys() + return constrained_params - def clearConstraints(self, recurse=False): - """Clear all constraints managed by this organizer. + @deprecated(getConstrainedPars_deprecation_msg) + def getConstrainedPars(self, recurse=False): + """Get a list of constrained managed Parameters in this object. Attributes ---------- recurse - Recurse into managed objects and clear all constraints - found there as well. + Recurse into managed objects and retrieve their constrained + Parameters as well (default False). + """ + return self.get_constrained_parmeters(recurse=recurse) + def clear_all_constraints(self, recurse=False): + """Clear all constraints managed by this organizer. This removes constraints that are held in this organizer, no matter where the constrained parameters are from. + + Parameters + ---------- + recurse : bool, optional + If False (default), only constraints in this object + are cleared. If True, constraints in managed + sub-objects are also cleared. """ if self._constraints: self.unconstrain(*self._constraints) if recurse: for m in filter(_has_clear_constraints, self._iter_managed()): - m.clearConstraints(recurse) + m.clear_all_constraints(recurse) return - def restrain(self, res, lb=-inf, ub=inf, sig=1, scaled=False, ns={}): - """Restrain an expression to specified bounds. - - Attributes - ---------- - res - An equation string or Parameter to restrain. - lb - The lower bound on the restraint evaluation (default -inf). - ub - The lower bound on the restraint evaluation (default inf). - sig - The uncertainty on the bounds (default 1). - scaled - A flag indicating if the restraint is scaled (multiplied) - by the unrestrained point-average chi^2 (chi^2/numpoints) - (default False). - ns - A dictionary of Parameters, indexed by name, that are used - in the equation string, but not part of the RecipeOrganizer - (default {}). - + @deprecated(clearConstraints_deprecation_msg) + def clearConstraints(self, recurse=False): + """This function has been deprecated and will be removed in + version 4.0.0. - The penalty is calculated as - (max(0, lb - val, val - ub)/sig)**2 - and val is the value of the calculated equation. This is multiplied by - the average chi^2 if scaled is True. + Please use + diffpy.srfit.fitbase.recipeorganizer.RecipeOrganizer.clear_all_constraints + instead. + """ + return self.clear_all_constraints(recurse=recurse) + def restrain( + self, param_or_eq, lb=-inf, ub=inf, sig=1, scaled=False, params={} + ): + """Restrain an expression to specified bounds. - Raises ValueError if ns uses a name that is already used for a - Parameter. - Raises ValueError if res depends on a Parameter that is not part of - the RecipeOrganizer and that is not defined in ns. + Parameters + ---------- + param_or_eq : str or Parameter + The equation string or a Parameter object to restrain. + lb : float, optional + The lower bound for the restraint evaluation (default is -inf). + ub : float, optional + The upper bound for the restraint evaluation (default is inf). + sig : float, optional + The uncertainty associated with the bounds (default is 1). + scaled : bool, optional + If True, the restraint penalty is scaled by the unrestrained + point-average chi^2 (chi^2/numpoints) (default is False). + params : dict, optional + The dictionary of Parameters, indexed by name, that are used in the + equation string but are not part of the RecipeOrganizer + (default is {}). - Returns the Restraint object for use with the 'unrestrain' method. + Returns + ------- + Restraint + The created Restraint object, which can be used with the + 'unrestrain' method. + + Notes + ----- + The penalty is calculated as: + + .. + (max(0, lb - val, val - ub) / sig) ** 2 + + where `val` is the value of the evaluated equation. + If `scaled` is True, this penalty is multiplied by + the average chi^2. + + Raises + ------ + ValueError + If `func_params` contains a name that is already used + for a Parameter. + ValueError + If `param_or_eq` depends on a Parameter that is not part of the + RecipeOrganizer and is not defined in `func_params`. """ - - if isinstance(res, six.string_types): - eqstr = res - eq = equationFromString(res, self._eqfactory, ns) + if isinstance(param_or_eq, str): + eqstr = param_or_eq + eq = equationFromString(param_or_eq, self._eqfactory, params) else: - eq = Equation(root=res) - eqstr = res.name + eq = Equation(root=param_or_eq) + eqstr = param_or_eq.name # Make and store the restraint - res = Restraint(eq, lb, ub, sig, scaled) - res.eqstr = eqstr - self.addRestraint(res) - return res + param_or_eq = Restraint(eq, lb, ub, sig, scaled) + param_or_eq.eqstr = eqstr + self.addRestraint(param_or_eq) + return param_or_eq def addRestraint(self, res): """Add a Restraint instance to the RecipeOrganizer. @@ -1050,13 +1276,13 @@ def _format_constraints(self): cdict = self._get_constraints() # Find each constraint and format the equation clines = [] - for par, con in cdict.items(): - loc = self._locate_managed_object(par) + for parameter, con in cdict.items(): + loc = self._locate_managed_object(parameter) if loc: locstr = ".".join(o.name for o in loc[1:]) clines.append("%s <-- %s" % (locstr, con.eqstr)) else: - clines.append("%s <-- %s" % (par.name, con.eqstr)) + clines.append("%s <-- %s" % (parameter.name, con.eqstr)) clines.sort(key=numstr) return clines @@ -1200,7 +1426,7 @@ def equationFromString( def _has_clear_constraints(msg): - return hasattr(msg, "clearConstraints") + return hasattr(msg, "clear_all_constraints") def _has_clear_restraints(msg): diff --git a/src/diffpy/srfit/pdf/characteristicfunctions.py b/src/diffpy/srfit/pdf/characteristicfunctions.py index 9846e131..ca24dfc1 100644 --- a/src/diffpy/srfit/pdf/characteristicfunctions.py +++ b/src/diffpy/srfit/pdf/characteristicfunctions.py @@ -21,7 +21,7 @@ function and Gcryst(f) is the crystal PDF. These functions are meant to be imported and added to a FitContribution -using the 'registerFunction' method of that class. +using the 'register_function' method of that class. """ __all__ = [ diff --git a/src/diffpy/srfit/structure/sgconstraints.py b/src/diffpy/srfit/structure/sgconstraints.py index 81c71de9..8e1e198d 100644 --- a/src/diffpy/srfit/structure/sgconstraints.py +++ b/src/diffpy/srfit/structure/sgconstraints.py @@ -390,7 +390,7 @@ def _clear_constraints(self): for scatterer in scatterers: for par in [scatterer.x, scatterer.y, scatterer.z]: - if scatterer.isConstrained(par): + if scatterer.is_constrained(par): scatterer.unconstrain(par) par.setConst(False) @@ -407,7 +407,7 @@ def _clear_constraints(self): lattice.gamma, ] for par in latpars: - if lattice.isConstrained(par): + if lattice.is_constrained(par): lattice.unconstrain(par) par.setConst(False) @@ -417,14 +417,14 @@ def _clear_constraints(self): if isosymbol: par = scatterer.get(isosymbol) if par is not None: - if scatterer.isConstrained(par): + if scatterer.is_constrained(par): scatterer.unconstrain(par) par.setConst(False) for pname in adpsymbols: par = scatterer.get(pname) if par is not None: - if scatterer.isConstrained(par): + if scatterer.is_constrained(par): scatterer.unconstrain(par) par.setConst(False) @@ -596,7 +596,9 @@ def _constrain_adps(self, positions): continue isoidx.append(j) scatterer = scatterers[j] - scatterer.constrain(isosymbol, isoname, ns=self._parameters) + scatterer.constrain( + isosymbol, isoname, params=self._parameters + ) fadp = g.UFormulas(adpnames) @@ -809,7 +811,7 @@ def _makeconstraint(parname, formula, scatterer, idx, ns={}): # If we got here, then we have a constraint equation # Fix any division issues formula = formula.replace("/", "*1.0/") - scatterer.constrain(par, formula, ns=ns) + scatterer.constrain(par, formula, params=ns) return diff --git a/tests/test_builder.py b/tests/test_builder.py index 0cdf6d20..cf1c3681 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -97,7 +97,7 @@ def g2(v1): factory.registerArgument("v2", v2) factory.registerArgument("v3", v3) factory.registerArgument("v4", v4) - b = factory.registerFunction("g", g1, ["v1", "v2", "v3", "v4"]) + b = factory.register_function("g", g1, ["v1", "v2", "v3", "v4"]) # Now associate args with the wrapped function op = b.literal @@ -121,7 +121,7 @@ def g2(v1): assert round(abs(24 - eq1()), 7) == 0 # Now swap out the function - b = factory.registerFunction("g", g2, ["v1"]) + b = factory.register_function("g", g2, ["v1"]) op = b.literal assert op.operation == g2 assert v1 in op.args @@ -181,7 +181,7 @@ def testParseEquation(noObserversInGlobalBuilders): assert eq.args == [eq.sigma] # Equation with user-defined functions - factory.registerFunction("myfunc", eq, ["sigma"]) + factory.register_function("myfunc", eq, ["sigma"]) eq2 = factory.makeEquation("c*myfunc(sigma)") assert numpy.allclose(eq2(c=2, sigma=sigma), 2 * gaussian_test(x, sigma)) assert "sigma" in eq2.argdict diff --git a/tests/test_contribution.py b/tests/test_contribution.py index e30bf7c4..813ef9c8 100644 --- a/tests/test_contribution.py +++ b/tests/test_contribution.py @@ -192,11 +192,11 @@ def test_releaseOldEquations(self): self.assertEqual(2, len(fc._eqfactory.equations)) return - def test_registerFunction(self): + def test_register_function(self): """Ensure registered function works after second set_equation call.""" fc = self.fitcontribution - fc.registerFunction(_fsquare, name="fsquare") + fc.register_function(_fsquare, name="fsquare") fc.set_equation("fsquare") fc.x.set_value(5) self.assertEqual(25, fc.evaluate()) diff --git a/tests/test_pdf.py b/tests/test_pdf.py index 37839b01..d8e86e54 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -308,7 +308,9 @@ def test_pickling( pc2 = pickle.loads(pickle.dumps(pc)) res0 = pc.residual() assert numpy.array_equal(res0, pc2.residual()) - for p in chain(pc.iterPars("Uiso"), pc2.iterPars("Uiso")): + for p in chain( + pc.iterate_over_parameters("Uiso"), pc2.iterate_over_parameters("Uiso") + ): p.value = 0.004 res1 = pc.residual() assert not numpy.allclose(res0, res1) diff --git a/tests/test_recipeorganizer.py b/tests/test_recipeorganizer.py index 15a284af..bdd38058 100644 --- a/tests/test_recipeorganizer.py +++ b/tests/test_recipeorganizer.py @@ -259,7 +259,7 @@ def testRemoveParameter(self): self.assertRaises(ValueError, m._remove_parameter, c) return - def testConstrain(self): + def test_constrain(self): """Test the constrain method.""" p1 = self.m._new_parameter("p1", 1) @@ -270,10 +270,22 @@ def testConstrain(self): self.assertEqual(0, len(self.m._constraints)) self.m.constrain(p1, "2*p2") + actual_constrained_params = self.m.getConstrainedPars() + actual_constrained_params = [p.name for p in actual_constrained_params] + expected_constrained_params = [p1.name] + + assert actual_constrained_params == expected_constrained_params + + actual_constrained_params = self.m.get_constrained_parmeters() + actual_constrained_params = [p.name for p in actual_constrained_params] + expected_constrained_params = [p1.name] + + assert actual_constrained_params == expected_constrained_params + self.assertTrue(p1.constrained) self.assertTrue(p1 in self.m._constraints) self.assertEqual(1, len(self.m._constraints)) - self.assertTrue(self.m.isConstrained(p1)) + self.assertTrue(self.m.is_constrained(p1)) p2.set_value(10) self.m._constraints[p1].update() @@ -294,6 +306,26 @@ def testConstrain(self): p2.set_value(7) self.m._constraints[p1].update() self.assertEqual(7, p1.getValue()) + + self.m.clear_all_constraints() + actual_constrained_params = self.m.get_constrained_parmeters() + actual_constrained_params = [p.name for p in actual_constrained_params] + expected_constrained_params = [] + assert actual_constrained_params == expected_constrained_params + + # add constraint back and test the old function name `clearConstraints` + self.m.constrain(p1, p2) + actual_constrained_params = self.m.get_constrained_parmeters() + actual_constrained_params = [p.name for p in actual_constrained_params] + expected_constrained_params = [p1.name] + assert actual_constrained_params == expected_constrained_params + + self.m.clearConstraints() + actual_constrained_params = self.m.get_constrained_parmeters() + actual_constrained_params = [p.name for p in actual_constrained_params] + expected_constrained_params = [] + assert actual_constrained_params == expected_constrained_params + return def testRestrain(self): @@ -320,7 +352,9 @@ def testRestrain(self): # Check errors on unregistered parameters self.assertRaises(ValueError, self.m.restrain, "2*p3") - self.assertRaises(ValueError, self.m.restrain, "2*p2", ns={"p2": p3}) + self.assertRaises( + ValueError, self.m.restrain, "2*p2", params={"p2": p3} + ) return def testGetConstraints(self): @@ -377,6 +411,51 @@ def testGetRestraints(self): self.assertEqual(2, len(res)) return + def test_register_calculator(self): + + class GCalc(Calculator): + + def __init__(self, name): + Calculator.__init__(self, name) + self.newParameter("A", 1.0) + self.newParameter("center", 0.0) + self.newParameter("width", 0.1) + return + + def __call__(self, x): + A = self.A.getValue() + c = self.center.getValue() + w = self.width.getValue() + return A * numpy.exp(-0.5 * ((x - c) / w) ** 2) + + # End class GCalc + + g = GCalc("g") + + self.m.register_calculator(g) + + x = numpy.arange(0.5, 10, 0.5) + self.m.x.set_value(x) + + self.m.g.center.set_value(3.0) + + self.assertTrue( + numpy.array_equal(numpy.exp(-0.5 * ((x - 3.0) / 0.1) ** 2), g(x)) + ) + + self.m.g.center.set_value(5.0) + + self.assertTrue( + numpy.array_equal(numpy.exp(-0.5 * ((x - 5.0) / 0.1) ** 2), g(x)) + ) + + # Use this in another equation + + eq = self.m.register_string_function("g/x - 1", "pdf") + self.assertTrue(numpy.array_equal(g(x) / x - 1, eq())) + + return + def testRegisterCalculator(self): class GCalc(Calculator): @@ -417,7 +496,7 @@ def __call__(self, x): # Use this in another equation - eq = self.m.registerStringFunction("g/x - 1", "pdf") + eq = self.m.register_string_function("g/x - 1", "pdf") self.assertTrue(numpy.array_equal(g(x) / x - 1, eq())) return @@ -431,7 +510,7 @@ def g1(A, c, w, x): def g2(A): return A + 1 - eq = self.m.registerFunction(g1, "g") + eq = self.m.register_function(g1, "g") for p in eq.args: self.assertTrue(p in self.m._parameters.values()) @@ -447,11 +526,11 @@ def g2(A): ) # Use this in another equation - eq2 = self.m.registerStringFunction("g/x - 1", "pdf") + eq2 = self.m.register_string_function("g/x - 1", "pdf") self.assertTrue(numpy.array_equal(eq() / x - 1, eq2())) # Make sure we can swap out "g". - self.m.registerFunction(g2, "g") + self.m.register_function(g2, "g") self.assertAlmostEqual(2.0, eq()) # Try a bound method @@ -463,7 +542,7 @@ def __call__(self): return 4.56 t = temp() - eq = self.m.registerFunction(t.eval, "eval") + eq = self.m.register_function(t.eval, "eval") self.assertAlmostEqual(1.23, eq()) # Now the callable @@ -472,11 +551,11 @@ def __call__(self): return - def testRegisterStringFunction(self): + def test_register_string_function(self): """Test registering string functions in various ways.""" # Make an equation. - eq1 = self.m.registerStringFunction("x**2 + 3", "eq1") + eq1 = self.m.register_string_function("x**2 + 3", "eq1") eq1.x.set_value(0) for p in eq1.args: @@ -492,9 +571,9 @@ def testRegisterStringFunction(self): # Use eq1 in some equations # x**2 (x**2 + 3 - 3) - eq2 = self.m.registerStringFunction("eq1 - 3", "eq2") + eq2 = self.m.register_string_function("eq1 - 3", "eq2") # y**2 - eq3 = self.m.registerStringFunction("eq1(y) - 3", "eq3") + eq3 = self.m.register_string_function("eq1(y) - 3", "eq3") # Test these equations. self.assertEqual(0, eq2()) @@ -510,6 +589,13 @@ def testRegisterStringFunction(self): return + def test_release_old_equations(self): + """Verify EquationFactory does not hold temporary equations.""" + self.m._new_parameter("x", 12) + self.assertEqual(36, self.m.evaluate_equation("3 * x")) + self.assertEqual(0, len(self.m._eqfactory.equations)) + return + def test_releaseOldEquations(self): """Verify EquationFactory does not hold temporary equations.""" self.m._new_parameter("x", 12)