Пример #1
0
    def testConstraint(self):
        """Test the Constraint class."""

        p1 = Parameter("p1", 1)
        p2 = Parameter("p2", 2)

        factory = EquationFactory()

        factory.registerArgument("p1", p1)
        factory.registerArgument("p2", p2)

        c = Constraint()
        # Constrain p1 = 2*p2
        eq = equationFromString("2*p2", factory)
        c.constrain(p1, eq)

        self.assertTrue(p1.constrained)
        self.assertFalse(p2.constrained)

        eq2 = equationFromString("2*p2+1", factory)
        c2 = Constraint()
        self.assertRaises(ValueError, c2.constrain, p1, eq2)
        p2.setConst()
        eq3 = equationFromString("p1", factory)
        self.assertRaises(ValueError, c2.constrain, p2, eq3)

        p2.setValue(2.5)
        c.update()
        self.assertEquals(5.0, p1.getValue())

        p2.setValue(8.1)
        self.assertEquals(5.0, p1.getValue())
        c.update()
        self.assertEquals(16.2, p1.getValue())
        return
Пример #2
0
    def testConstraint(self):
        """Test the Constraint class."""

        p1 = Parameter("p1", 1)
        p2 = Parameter("p2", 2)

        factory = EquationFactory()

        factory.registerArgument("p1", p1)
        factory.registerArgument("p2", p2)

        c = Constraint()
        # Constrain p1 = 2*p2
        eq = equationFromString("2*p2", factory)
        c.constrain(p1, eq)

        self.assertTrue(p1.constrained)
        self.assertFalse(p2.constrained)

        eq2 = equationFromString("2*p2+1", factory)
        c2 = Constraint()
        self.assertRaises(ValueError, c2.constrain, p1, eq2)
        p2.setConst()
        eq3 = equationFromString("p1", factory)
        self.assertRaises(ValueError, c2.constrain, p2, eq3)

        p2.setValue(2.5)
        c.update()
        self.assertEquals(5.0, p1.getValue())

        p2.setValue(8.1)
        self.assertEquals(5.0, p1.getValue())
        c.update()
        self.assertEquals(16.2, p1.getValue())
        return
Пример #3
0
def speedTest4(mutate = 2):
    """Test wrt sympy.

    Results - sympy is 10 to 24 times faster without using arrays (ouch!).
            - diffpy.srfit.equation is slightly slower when using arrays, but
              not considerably worse than versus numpy alone.

    """

    from diffpy.srfit.equation.builder import EquationFactory
    factory = EquationFactory()

    x = numpy.arange(0, 20, 0.05)
    qsig = 0.01
    sigma = 0.003

    eqstr = """\
    b1 + b2*x + b3*x**2 + b4*x**3 + b5*x**4 + b6*x**5 + b7*x**6 + b8*x**7\
    """
    factory.registerConstant("x", x)
    eq = factory.makeEquation(eqstr)

    from sympy import var, exp, lambdify
    from numpy import polyval
    b1, b2, b3, b4, b5, b6, b7, b8, xx = vars = var("b1 b2 b3 b4 b5 b6 b7 b8 xx")
    f = lambdify(vars, polyval([b1, b2, b3, b4, b5, b6, b7, b8], xx), "numpy")

    tnpy = 0
    teq = 0
    # Randomly change variables
    numargs = len(eq.args)
    choices = range(numargs)
    args = [1.0]*(len(eq.args))
    args.append(x)

    # The call-loop
    random.seed()
    numcalls = 1000
    for _i in xrange(numcalls):
        # Mutate values
        n = mutate
        if n == 0:
            n = random.choice(choices)
        c = choices[:]
        for _j in xrange(n):
            idx = random.choice(c)
            c.remove(idx)
            args[idx] = random.random()

        # Time the different functions with these arguments
        teq += timeFunction(eq, *(args[:-1]))
        tnpy += timeFunction(f, *args)

    print "Average call time (%i calls, %i mutations/call):" % (numcalls,
            mutate)
    print "sympy: ", tnpy/numcalls
    print "equation: ", teq/numcalls
    print "ratio: ", teq/tnpy

    return
Пример #4
0
def speedTest4(mutate=2):
    """Test wrt sympy.

    Results - sympy is 10 to 24 times faster without using arrays (ouch!).
            - diffpy.srfit.equation is slightly slower when using arrays, but
              not considerably worse than versus numpy alone.

    """

    from diffpy.srfit.equation.builder import EquationFactory
    factory = EquationFactory()

    x = numpy.arange(0, 20, 0.05)

    eqstr = """\
    b1 + b2*x + b3*x**2 + b4*x**3 + b5*x**4 + b6*x**5 + b7*x**6 + b8*x**7\
    """
    factory.registerConstant("x", x)
    eq = factory.makeEquation(eqstr)

    from sympy import var, lambdify
    from numpy import polyval
    b1, b2, b3, b4, b5, b6, b7, b8, xx = vars = var(
        "b1 b2 b3 b4 b5 b6 b7 b8 xx")
    f = lambdify(vars, polyval([b1, b2, b3, b4, b5, b6, b7, b8], xx), "numpy")

    tnpy = 0
    teq = 0
    # Randomly change variables
    numargs = len(eq.args)
    choices = range(numargs)
    args = [1.0] * (len(eq.args))
    args.append(x)

    # The call-loop
    random.seed()
    numcalls = 1000
    for _i in xrange(numcalls):
        # Mutate values
        n = mutate
        if n == 0:
            n = random.choice(choices)
        c = choices[:]
        for _j in xrange(n):
            idx = random.choice(c)
            c.remove(idx)
            args[idx] = random.random()

        # Time the different functions with these arguments
        teq += timeFunction(eq, *(args[:-1]))
        tnpy += timeFunction(f, *args)

    print("Average call time (%i calls, %i mutations/call):" %
          (numcalls, mutate))
    print("sympy: ", tnpy / numcalls)
    print("equation: ", teq / numcalls)
    print("ratio: ", teq / tnpy)

    return
Пример #5
0
    def __init__(self, name):
        RecipeContainer.__init__(self, name)
        self._restraints = set()
        self._constraints = {}
        self._eqfactory = EquationFactory()

        self._calculators = {}
        self._manage(self._calculators)
        return
Пример #6
0
def profileTest():

    from diffpy.srfit.builder import EquationFactory
    factory = EquationFactory()

    x = numpy.arange(0, 10, 0.001)
    qsig = 0.01
    sigma = 0.003

    eqstr = """\
    b1 + b2*x + b3*x**2 + b4*x**3 + b5*x**4 + b6*x**5 + b7*x**6 + b8*x**7\
    """
    factory.registerConstant("x", x)
    eq = factory.makeEquation(eqstr)

    eq.b1.setValue(0)
    eq.b2.setValue(1)
    eq.b3.setValue(2.0)
    eq.b4.setValue(2.0)
    eq.b5.setValue(2.0)
    eq.b6.setValue(2.0)
    eq.b7.setValue(2.0)
    eq.b8.setValue(2.0)

    mutate = 8
    numargs = len(eq.args)
    choices = range(numargs)
    args = [0.1]*numargs

    # The call-loop
    random.seed()
    numcalls = 1000
    for _i in xrange(numcalls):
        # Mutate values
        n = mutate
        if n == 0:
            n = random.choice(choices)
        c = choices[:]
        for _j in xrange(n):
            idx = random.choice(c)
            c.remove(idx)
            args[idx] = random.random()

        eq(*args)

    return
Пример #7
0
def profileTest():

    from diffpy.srfit.builder import EquationFactory
    factory = EquationFactory()

    x = numpy.arange(0, 10, 0.001)
    qsig = 0.01
    sigma = 0.003

    eqstr = """\
    b1 + b2*x + b3*x**2 + b4*x**3 + b5*x**4 + b6*x**5 + b7*x**6 + b8*x**7\
    """
    factory.registerConstant("x", x)
    eq = factory.makeEquation(eqstr)

    eq.b1.setValue(0)
    eq.b2.setValue(1)
    eq.b3.setValue(2.0)
    eq.b4.setValue(2.0)
    eq.b5.setValue(2.0)
    eq.b6.setValue(2.0)
    eq.b7.setValue(2.0)
    eq.b8.setValue(2.0)

    mutate = 8
    numargs = len(eq.args)
    choices = range(numargs)
    args = [0.1] * numargs

    # The call-loop
    random.seed()
    numcalls = 1000
    for _i in xrange(numcalls):
        # Mutate values
        n = mutate
        if n == 0:
            n = random.choice(choices)
        c = choices[:]
        for _j in xrange(n):
            idx = random.choice(c)
            c.remove(idx)
            args[idx] = random.random()

        eq(*args)

    return
Пример #8
0
    def __init__(self, name):
        RecipeContainer.__init__(self, name)
        self._restraints = set()
        self._constraints = {}
        self._eqfactory = EquationFactory()

        self._calculators = {}
        self._manage(self._calculators)
        return
    def testEquationFromString(self):
        """Test the equationFromString method."""

        p1 = Parameter("p1", 1)
        p2 = Parameter("p2", 2)
        p3 = Parameter("p3", 3)
        p4 = Parameter("p4", 4)

        factory = EquationFactory()

        factory.registerArgument("p1", p1)
        factory.registerArgument("p2", p2)

        # Check usage where all parameters are registered with the factory
        eq = equationFromString("p1+p2", factory)

        self.assertEqual(2, len(eq.args))
        self.assertTrue(p1 in eq.args)
        self.assertTrue(p2 in eq.args)
        self.assertEqual(3, eq())

        # Try to use a parameter that is not registered
        self.assertRaises(ValueError, equationFromString, "p1+p2+p3", factory)

        # Pass that argument in the ns dictionary
        eq = equationFromString("p1+p2+p3", factory, {"p3":p3})
        self.assertEqual(3, len(eq.args))
        self.assertTrue(p1 in eq.args)
        self.assertTrue(p2 in eq.args)
        self.assertTrue(p3 in eq.args)
        self.assertEqual(6, eq())

        # Make sure that there are no remnants of p3 in the factory
        self.assertTrue("p3" not in factory.builders)

        # Pass and use an unregistered parameter
        self.assertRaises(ValueError, equationFromString, "p1+p2+p3+p4",
                factory, {"p3":p3})

        # Try to overload a registered parameter
        self.assertRaises(ValueError, equationFromString, "p1+p2",
                factory, {"p2":p3})

        return
Пример #10
0
    def testEquationFromString(self):
        """Test the equationFromString method."""

        p1 = Parameter("p1", 1)
        p2 = Parameter("p2", 2)
        p3 = Parameter("p3", 3)
        p4 = Parameter("p4", 4)

        factory = EquationFactory()

        factory.registerArgument("p1", p1)
        factory.registerArgument("p2", p2)

        # Check usage where all parameters are registered with the factory
        eq = equationFromString("p1+p2", factory)

        self.assertEqual(2, len(eq.args))
        self.assertTrue(p1 in eq.args)
        self.assertTrue(p2 in eq.args)
        self.assertEqual(3, eq())

        # Try to use a parameter that is not registered
        self.assertRaises(ValueError, equationFromString, "p1+p2+p3", factory)

        # Pass that argument in the ns dictionary
        eq = equationFromString("p1+p2+p3", factory, {"p3": p3})
        self.assertEqual(3, len(eq.args))
        self.assertTrue(p1 in eq.args)
        self.assertTrue(p2 in eq.args)
        self.assertTrue(p3 in eq.args)
        self.assertEqual(6, eq())

        # Make sure that there are no remnants of p3 in the factory
        self.assertTrue("p3" not in factory.builders)

        # Pass and use an unregistered parameter
        self.assertRaises(ValueError, equationFromString, "p1+p2+p3+p4",
                          factory, {"p3": p3})

        # Try to overload a registered parameter
        self.assertRaises(ValueError, equationFromString, "p1+p2", factory,
                          {"p2": p4})

        return
Пример #11
0
    def testRestraint(self):
        """Test the Restraint class."""

        p1 = Parameter("p1", 1)
        p2 = Parameter("p2", 2)

        factory = EquationFactory()

        factory.registerArgument("p1", p1)
        factory.registerArgument("p2", p2)

        # Restrain 1 <  p1 + p2 < 5
        eq = equationFromString("p1 + p2", factory)
        r = Restraint(eq, 1, 5)

        # This should have no penalty
        p1.setValue(1)
        p2.setValue(1)
        self.assertEquals(0, r.penalty())

        # Make p1 + p2 = 0
        # This should have a penalty of 1*(1 - 0)**2 = 1
        p1.setValue(-1)
        p2.setValue(1)
        self.assertEquals(1, r.penalty())

        # Make p1 + p2 = 8
        # This should have a penalty of 1*(8 - 5)**2 = 9
        p1.setValue(4)
        p2.setValue(4)
        self.assertEquals(9, r.penalty())

        # Set the chi^2 to get a dynamic penalty
        r.scaled = True
        self.assertEquals(13.5, r.penalty(1.5))

        # Make a really large number to check the upper bound
        import numpy
        r.ub = numpy.inf
        p1.setValue(1e100)
        self.assertEquals(0, r.penalty())

        return
Пример #12
0
    def testRestraint(self):
        """Test the Restraint class."""

        p1 = Parameter("p1", 1)
        p2 = Parameter("p2", 2)

        factory = EquationFactory()

        factory.registerArgument("p1", p1)
        factory.registerArgument("p2", p2)

        # Restrain 1 <  p1 + p2 < 5
        eq = equationFromString("p1 + p2", factory)
        r = Restraint(eq, 1, 5)

        # This should have no penalty
        p1.setValue(1)
        p2.setValue(1)
        self.assertEqual(0, r.penalty())

        # Make p1 + p2 = 0
        # This should have a penalty of 1*(1 - 0)**2 = 1
        p1.setValue(-1)
        p2.setValue(1)
        self.assertEqual(1, r.penalty())

        # Make p1 + p2 = 8
        # This should have a penalty of 1*(8 - 5)**2 = 9
        p1.setValue(4)
        p2.setValue(4)
        self.assertEqual(9, r.penalty())

        # Set the chi^2 to get a dynamic penalty
        r.scaled = True
        self.assertEqual(13.5, r.penalty(1.5))

        # Make a really large number to check the upper bound
        import numpy
        r.ub = numpy.inf
        p1.setValue(1e100)
        self.assertEqual(0, r.penalty())

        return
Пример #13
0
def weightedTest(mutate=2):
    """Show the benefits of a properly balanced equation tree."""

    from diffpy.srfit.equation.builder import EquationFactory
    factory = EquationFactory()

    x = numpy.arange(0, 10, 0.01)
    qsig = 0.01
    sigma = 0.003

    eqstr = """\
    b1 + b2*x + b3*x**2 + b4*x**3 + b5*x**4 + b6*x**5 + b7*x**6 + b8*x**7\
    """
    factory.registerConstant("x", x)
    eq = factory.makeEquation(eqstr)

    eq.b1.setValue(0)
    eq.b2.setValue(1)
    eq.b3.setValue(2.0)
    eq.b4.setValue(2.0)
    eq.b5.setValue(2.0)
    eq.b6.setValue(2.0)
    eq.b7.setValue(2.0)
    eq.b8.setValue(2.0)

    #scale = visitors.NodeWeigher()
    #eq.root.identify(scale)
    #print scale.output

    from numpy import polyval

    def f(b1, b2, b3, b4, b5, b6, b7, b8):
        return polyval([b8, b7, b6, b5, b4, b3, b2, b1], x)

    tnpy = 0
    teq = 0
    # Randomly change variables
    numargs = len(eq.args)
    choices = range(numargs)
    args = [0.1] * numargs

    # The call-loop
    random.seed()
    numcalls = 1000
    for _i in xrange(numcalls):
        # Mutate values
        n = mutate
        if n == 0:
            n = random.choice(choices)
        c = choices[:]
        for _j in xrange(n):
            idx = random.choice(c)
            c.remove(idx)
            args[idx] = random.random()

        #print args

        # Time the different functions with these arguments
        teq += timeFunction(eq, *args)
        tnpy += timeFunction(f, *args)

    print "Average call time (%i calls, %i mutations/call):" % (numcalls,
                                                                mutate)
    print "numpy: ", tnpy / numcalls
    print "equation: ", teq / numcalls
    print "ratio: ", teq / tnpy

    return
Пример #14
0
def speedTest3(mutate=2):
    """Test wrt sympy.

    Results - sympy is 10 to 24 times faster without using arrays (ouch!).
            - diffpy.srfit.equation is slightly slower when using arrays, but
              not considerably worse than versus numpy alone.

    """

    from diffpy.srfit.equation.builder import EquationFactory
    factory = EquationFactory()

    x = numpy.arange(0, 20, 0.05)
    qsig = 0.01
    sigma = 0.003

    eqstr = """\
    A0*exp(-(x*qsig)**2)*(exp(-((x-1.0)/sigma1)**2)+exp(-((x-2.0)/sigma2)**2))\
    + polyval(list(b1, b2, b3, b4, b5, b6, b7, b8), x)\
    """
    factory.registerConstant("x", x)
    eq = factory.makeEquation(eqstr)
    eq.qsig.setValue(qsig)
    eq.sigma1.setValue(sigma)
    eq.sigma2.setValue(sigma)
    eq.A0.setValue(1.0)
    eq.b1.setValue(0)
    eq.b2.setValue(1)
    eq.b3.setValue(2.0)
    eq.b4.setValue(2.0)
    eq.b5.setValue(2.0)
    eq.b6.setValue(2.0)
    eq.b7.setValue(2.0)
    eq.b8.setValue(2.0)

    from sympy import var, exp, lambdify
    from numpy import polyval
    A0, qsig, sigma1, sigma2, b1, b2, b3, b4, b5, b6, b7, b8, xx = vars = var(
        "A0 qsig sigma1 sigma2 b1 b2 b3 b4 b5 b6 b7 b8 xx")
    f = lambdify(
        vars,
        A0 * exp(-(xx * qsig)**2) *
        (exp(-((xx - 1.0) / sigma1)**2) + exp(-((xx - 2.0) / sigma2)**2)) +
        polyval([b1, b2, b3, b4, b5, b6, b7, b8], xx), "numpy")

    tnpy = 0
    teq = 0
    # Randomly change variables
    numargs = len(eq.args)
    choices = range(numargs)
    args = [1.0] * (len(eq.args))
    args.append(x)

    # The call-loop
    random.seed()
    numcalls = 1000
    for _i in xrange(numcalls):
        # Mutate values
        n = mutate
        if n == 0:
            n = random.choice(choices)
        c = choices[:]
        for _j in xrange(n):
            idx = random.choice(c)
            c.remove(idx)
            args[idx] = random.random()

        # Time the different functions with these arguments
        teq += timeFunction(eq, *(args[:-1]))
        tnpy += timeFunction(f, *args)

    print "Average call time (%i calls, %i mutations/call):" % (numcalls,
                                                                mutate)
    print "sympy: ", tnpy / numcalls
    print "equation: ", teq / numcalls
    print "ratio: ", teq / tnpy

    return
Пример #15
0
def speedTest2(mutate=2):

    from diffpy.srfit.equation.builder import EquationFactory
    factory = EquationFactory()

    x = numpy.arange(0, 20, 0.05)
    qsig = 0.01
    sigma = 0.003

    eqstr = """\
    A0*exp(-(x*qsig)**2)*(exp(-((x-1.0)/sigma1)**2)+exp(-((x-2.0)/sigma2)**2))\
    + polyval(list(b1, b2, b3, b4, b5, b6, b7, b8), x)\
    """
    factory.registerConstant("x", x)
    eq = factory.makeEquation(eqstr)
    eq.qsig.setValue(qsig)
    eq.sigma1.setValue(sigma)
    eq.sigma2.setValue(sigma)
    eq.A0.setValue(1.0)
    eq.b1.setValue(0)
    eq.b2.setValue(1)
    eq.b3.setValue(2.0)
    eq.b4.setValue(2.0)
    eq.b5.setValue(2.0)
    eq.b6.setValue(2.0)
    eq.b7.setValue(2.0)
    eq.b8.setValue(2.0)

    from numpy import exp
    from numpy import polyval

    def f(A0, qsig, sigma1, sigma2, b1, b2, b3, b4, b5, b6, b7, b8):
        return A0 * exp(-(x * qsig)**2) * (exp(-(
            (x - 1.0) / sigma1)**2) + exp(-((x - 2.0) / sigma2)**2)) + polyval(
                [b8, b7, b6, b5, b4, b3, b2, b1], x)

    tnpy = 0
    teq = 0
    # Randomly change variables
    numargs = len(eq.args)
    choices = range(numargs)
    args = [0.0] * (len(eq.args))

    # The call-loop
    random.seed()
    numcalls = 1000
    for _i in xrange(numcalls):
        # Mutate values
        n = mutate
        if n == 0:
            n = random.choice(choices)
        c = choices[:]
        for _j in xrange(n):
            idx = random.choice(c)
            c.remove(idx)
            args[idx] = random.random()

        # Time the different functions with these arguments
        tnpy += timeFunction(f, *args)
        teq += timeFunction(eq, *args)

    print "Average call time (%i calls, %i mutations/call):" % (numcalls,
                                                                mutate)
    print "numpy: ", tnpy / numcalls
    print "equation: ", teq / numcalls
    print "ratio: ", teq / tnpy

    return
Пример #16
0
class RecipeOrganizer(_recipeorganizer_interface, RecipeContainer):
    """Extended base class for organizing pieces of a FitRecipe.

    This class extends RecipeContainer by organizing constraints and
    Restraints, as well as Equations that can be used in Constraint and
    Restraint equations.  These constraints and Restraints can be placed at any
    level and a flattened list of them can be retrieved with the
    _getConstraints and _getRestraints methods.

    Attributes
    name            --  A name for this organizer. Names should be unique
                        within a RecipeOrganizer and should be valid attribute
                        names.
    _calculators    --  A managed dictionary of Calculators, indexed by name.
    _parameters     --  A managed OrderedDict of contained Parameters.
    _constraints    --  A dictionary of Constraints, indexed by the constrained
                        Parameter. Constraints can be added using the
                        'constrain' method.
    _restraints     --  A set of Restraints. Restraints can be added using the
                        'restrain' method.
    _eqfactory      --  A diffpy.srfit.equation.builder.EquationFactory
                        instance that is used create Equations from string.

    Properties
    names           --  Variable names (read only). See getNames.
    values          --  Variable values (read only). See getValues.

    Raises ValueError if the name is not a valid attribute identifier
    """

    def __init__(self, name):
        RecipeContainer.__init__(self, name)
        self._restraints = set()
        self._constraints = {}
        self._eqfactory = EquationFactory()

        self._calculators = {}
        self._manage(self._calculators)
        return

    # Parameter management

    def _newParameter(self, name, value, check=True):
        """Add a new Parameter to the container.

        This creates a new Parameter and adds it to the container using the
        _addParameter method.

        Returns the Parameter.
        """
        p = Parameter(name, value)
        self._addParameter(p, check)
        return p

    def _addParameter(self, par, check=True):
        """Store a Parameter.

        Parameters added in this way are registered with the _eqfactory.

        par     --  The Parameter to be stored.
        check   --  If True (default), a ValueError is raised a Parameter of
                    the specified name has already been inserted.

        Raises ValueError if the Parameter has no name.
        Raises ValueError if the Parameter has the same name as a contained
        RecipeContainer.
        """

        # Store the Parameter
        RecipeContainer._addObject(self, par, self._parameters, check)

        # Register the Parameter
        self._eqfactory.registerArgument(par.name, par)
        return

    def _removeParameter(self, par):
        """Remove a parameter.

        This de-registers the Parameter with the _eqfactory. The Parameter will
        remain part of built equations.

        Note that constraints and restraints involving the Parameter are not
        modified.

        Raises ValueError if par is not part of the RecipeOrganizer.
        """
        self._removeObject(par, self._parameters)
        self._eqfactory.deRegisterBuilder(par.name)
        return

    def registerCalculator(self, f, argnames=None):
        """Register a Calculator so it can be used within equation strings.

        A Calculator is an elaborate function that can organize Parameters.
        This creates a function with this class that can be used within string
        equations. The resulting equation can be used in a string with
        arguments like a function or without, in which case the values of the
        Parameters created from argnames will be be used to compute the value.

        f           --  The Calculator to register.
        argnames    --  The names of the arguments to f (list or None).
                        If this is None, then the argument names will be
                        extracted from the function.
        """
        self._eqfactory.registerOperator(f.name, f)
        self._addObject(f, self._calculators)
        # Register arguments of the calculator
        if argnames is None:
            func_code = f.__call__.im_func.func_code
            argnames = list(func_code.co_varnames)
            argnames = argnames[1 : func_code.co_argcount]

        for pname in argnames:
            if pname not in self._eqfactory.builders:
                par = self._newParameter(pname, 0)
            else:
                par = self.get(pname)
            f.addLiteral(par)

        # Now return an equation object
        eq = self._eqfactory.makeEquation(f.name)
        return eq

    def registerFunction(self, f, 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
        passed in the equation string, as this will be handled automatically.

        f           --  The callable to register. If this is an Equation
                        instance, then all that needs to be provied is a name.
        name        --  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).
                        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
        classes.

        Raises TypeError if name or argnames cannot be automatically
        extracted.
        Raises TypeError if an automatically extracted name is '<lambda>'.
        Raises ValueError if f is an Equation object and name is None.

        Returns 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 name is None:
                m = "Equation must be given a name"
                raise ValueError(m)
            self._eqfactory.registerOperator(name, f)
            return f

        #### Introspection code
        if name is None or argnames is None:

            import inspect

            func_code = None

            # This will let us offset the argument list to eliminate 'self'
            offset = 0

            # check regular functions
            if inspect.isfunction(f):
                func_code = f.func_code
            # check class method
            elif inspect.ismethod(f):
                func_code = f.im_func.func_code
                offset = 1
            # check functor
            elif hasattr(f, "__call__") and hasattr(f.__call__, "im_func"):
                func_code = f.__call__.im_func.func_code
                offset = 1
            else:
                m = "Cannot extract name or argnames"
                raise ValueError(m)

            # Extract the name
            if name is None:
                name = func_code.co_name
                if name == "<lambda>":
                    m = "You must supply a name name for a lambda function"
                    raise ValueError(m)

            # Extract the arguments
            if argnames is None:
                argnames = list(func_code.co_varnames)
                argnames = argnames[offset : func_code.co_argcount]

        #### End introspection code

        # Make missing Parameters
        for pname in argnames:
            if pname not in self._eqfactory.builders:
                self._newParameter(pname, 0)

        # Initialize and register
        from diffpy.srfit.fitbase.calculator import Calculator

        if isinstance(f, Calculator):
            for pname in argnames:
                par = self.get(pname)
                f.addLiteral(par)
            self._eqfactory.registerOperator(name, f)
        else:
            self._eqfactory.registerFunction(name, f, argnames)

        # Now we can create the Equation and return it to the user.
        eq = self._eqfactory.makeEquation(name)

        return eq

    def registerStringFunction(self, fstr, name, ns={}):
        """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
        passed in the function string, as this will be handled automatically.

        fstr        --  A string equation to register.
        name        --  The name of the function to be used in equations.
        ns          --  A dictionary of Parameters, indexed by name, that are
                        used in fstr, 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.

        Returns the callable Equation object.
        """

        # Build the equation instance.
        eq = equationFromString(fstr, self._eqfactory, ns=ns, buildargs=True)
        eq.name = name

        # Register any new Parameters.
        for par in self._eqfactory.newargs:
            self._addParameter(par)

        # Register the equation as a callable function.
        argnames = eq.argdict.keys()
        return self.registerFunction(eq, name, argnames)

    def evaluateEquation(self, eqstr, ns={}):
        """Evaluate a string equation.

        eqstr   --  A 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 {}).

        Raises ValueError if ns uses a name that is already used for a
        variable.
        """
        eq = equationFromString(eqstr, self._eqfactory, ns)
        return eq()

    def constrain(self, par, con, ns={}):
        """Constrain a parameter to an equation.

        Note that only one constraint can exist on a Parameter at a time.

        par     --  The name of a Parameter or a Parameter to constrain.
        con     --  A string representation of the constraint equation or a
                    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      --  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.
        """
        if isinstance(par, basestring):
            name = par
            par = self.get(name)
            if par is None:
                par = ns.get(name)

        if par is None:
            raise ValueError("The parameter cannot be found")

        if par.const:
            raise ValueError("The parameter '%s' is constant" % par)

        if isinstance(con, basestring):
            eqstr = con
            eq = equationFromString(con, self._eqfactory, ns)
        else:
            eq = Equation(root=con)
            eqstr = con.name

        eq.name = "_constraint_%s" % par.name

        # Make and store the constraint
        con = Constraint()
        con.constrain(par, eq)
        # Store the equation string so it can be shown later.
        con.eqstr = eqstr
        self._constraints[par] = con

        # Our configuration changed
        self._updateConfiguration()

        return

    def isConstrained(self, par):
        """Determine if a Parameter is constrained in this object.

        par     --  The name of a Parameter or a Parameter to check.
        """
        if isinstance(par, basestring):
            name = par
            par = self.get(name)

        return par in self._constraints

    def unconstrain(self, *pars):
        """Unconstrain a Parameter.

        This removes any constraints on a Parameter.

        *pars   --  The names of Parameters or Parameters to unconstrain.


        Raises ValueError if the Parameter is not constrained.
        """
        update = False
        for par in pars:
            if isinstance(par, basestring):
                name = par
                par = self.get(name)

            if par is None:
                raise ValueError("The parameter cannot be found")

            if par in self._constraints:
                self._constraints[par].unconstrain()
                del self._constraints[par]
                update = True

        if update:
            # Our configuration changed
            self._updateConfiguration()

        else:

            raise ValueError("The parameter is not constrained")

        return

    def getConstrainedPars(self, recurse=False):
        """Get a list of constrained managed Parameters in this object.

        recurse --  Recurse into managed objects and retrive their constrained
                    Parameters as well (default False).
        """
        const = self._getConstraints(recurse)
        return const.keys()

    def clearConstraints(self, recurse=False):
        """Clear all constraints managed by this organizer.

        recurse --  Recurse into managed objects and clear all constraints
                    found there as well.

        This removes constraints that are held in this organizer, no matter
        where the constrained parameters are from.
        """
        if self._constraints:
            self.unconstrain(*self._constraints)

        if recurse:
            f = lambda m: hasattr(m, "clearConstraints")
            for m in ifilter(f, self._iterManaged()):
                m.clearConstraints(recurse)
        return

    def restrain(self, res, lb=-inf, ub=inf, sig=1, scaled=False, ns={}):
        """Restrain an expression to specified bounds

        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 {}).

        The penalty is calculated as
        (max(0, lb - val, val - ub)/sig)**2
        and val is the value of the calculated equation.  This is multipled by
        the average chi^2 if scaled is True.

        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.

        Returns the Restraint object for use with the 'unrestrain' method.
        """

        if isinstance(res, basestring):
            eqstr = res
            eq = equationFromString(res, self._eqfactory, ns)
        else:
            eq = Equation(root=res)
            eqstr = res.name

        # Make and store the restraint
        res = Restraint(eq, lb, ub, sig, scaled)
        res.eqstr = eqstr
        self.addRestraint(res)
        return res

    def addRestraint(self, res):
        """Add a Restraint instance to the RecipeOrganizer.

        res     --  A Restraint instance.
        """
        self._restraints.add(res)
        # Our configuration changed. Notify observers.
        self._updateConfiguration()
        return

    def unrestrain(self, *ress):
        """Remove a Restraint from the RecipeOrganizer.

        *ress   --  Restraints returned from the 'restrain' method or added
                    with the 'addRestraint' method.
        """
        update = False
        restuple = tuple(self._restraints)
        for res in ress:
            if res in restuple:
                self._restraints.remove(res)
                update = True

        if update:
            # Our configuration changed
            self._updateConfiguration()

        return

    def clearRestraints(self, recurse=False):
        """Clear all restraints.

        recurse --  Recurse into managed objects and clear all restraints
                    found there as well.
        """
        self.unrestrain(*self._restraints)

        if recurse:
            f = lambda m: hasattr(m, "clearRestraints")
            for m in ifilter(f, self._iterManaged()):
                m.clearRestraints(recurse)
        return

    def _getConstraints(self, recurse=True):
        """Get the constrained Parameters for this and managed sub-objects."""
        constraints = {}
        if recurse:
            f = lambda m: hasattr(m, "_getConstraints")
            for m in ifilter(f, self._iterManaged()):
                constraints.update(m._getConstraints(recurse))

        constraints.update(self._constraints)

        return constraints

    def _getRestraints(self, recurse=True):
        """Get the Restraints for this and embedded ParameterSets.

        This returns a set of Restraint objects.
        """
        restraints = set(self._restraints)
        if recurse:
            f = lambda m: hasattr(m, "_getRestraints")
            for m in ifilter(f, self._iterManaged()):
                restraints.update(m._getRestraints(recurse))

        return restraints

    def _validate(self):
        """Validate my state.

        This performs RecipeContainer validations.
        This validates contained Restraints and Constraints.

        Raises AttributeError if validation fails.
        """
        RecipeContainer._validate(self)
        iterable = chain(iter(self._restraints), self._constraints.itervalues())
        self._validateOthers(iterable)
        return

    # For printing the configured recipe to screen

    def _formatManaged(self, indent=""):
        """Format fit hierarchy for showing.

        Returns the lines of the formatted string in a list.
        """
        dashedline = 79 * "-"
        lines = []
        formatstr = "%-20s %s"

        lines.append((indent + self.name)[:79])
        # Show parameters
        if self._parameters:
            lines.append((indent + dashedline)[:79])
            items = self._parameters.items()
            items.sort()
            lines.extend((indent + formatstr % (n, p.value))[:79] for n, p in items)

        indent += "  "

        for obj in self._iterManaged():
            if hasattr(obj, "_formatManaged"):
                tlines = obj._formatManaged(indent)
                lines.append("")
                lines.extend(tlines)

        return lines

    def _formatConstraints(self):
        """Format constraints for showing.

        This collects constraints on all levels of the hierarchy and displays
        them with respect to this level.

        Returns the lines of the formatted string in a list.
        """
        cdict = self._getConstraints()
        # Find each constraint and format the equation
        clines = []
        for par, con in cdict.items():
            loc = self._locateManagedObject(par)
            if loc:
                locstr = ".".join(o.name for o in loc)
                clines.append("%s <-- %s" % (locstr, con.eqstr))
            else:
                clines.append("%s <-- %s" % (par.name, con.eqstr))

        if clines:
            clines.sort()
            dashedline = 79 * "-"
            clines.insert(0, dashedline)
            clines.insert(0, "Constraints")
        return clines

    def _formatRestraints(self):
        """Format restraints for showing.

        This collects restraints on all levels of the hierarchy and displays
        them with respect to this level.

        Returns the lines of the formatted string in a list.
        """
        rset = self._getRestraints()
        rlines = []
        for res in rset:
            line = "%s: lb = %f, ub = %f, sig = %f, scaled = %s" % (res.eqstr, res.lb, res.ub, res.sig, res.scaled)
            rlines.append(line)

        if rlines:
            rlines.sort()
            dashedline = 79 * "-"
            rlines.insert(0, dashedline)
            rlines.insert(0, "Restraints")
        return rlines

    def show(self):
        """Show the configuration on screen.

        This will print a summary of all contained objects.
        """
        # Show sub objects and their parameters
        lines = []
        tlines = self._formatManaged()
        lines.extend(tlines)

        # FIXME - parameter names in equations not particularly informative
        # Show constraints
        tlines = self._formatConstraints()
        lines.append("")
        lines.extend(tlines)

        # FIXME - parameter names in equations not particularly informative
        # Show restraints
        tlines = self._formatRestraints()
        lines.append("")
        lines.extend(tlines)

        print "\n".join(lines)

        return
Пример #17
0
class RecipeOrganizer(_recipeorganizer_interface, RecipeContainer):
    """Extended base class for organizing pieces of a FitRecipe.

    This class extends RecipeContainer by organizing constraints and
    Restraints, as well as Equations that can be used in Constraint and
    Restraint equations.  These constraints and Restraints can be placed at any
    level and a flattened list of them can be retrieved with the
    _getConstraints and _getRestraints methods.

    Attributes
    name            --  A name for this organizer. Names should be unique
                        within a RecipeOrganizer and should be valid attribute
                        names.
    _calculators    --  A managed dictionary of Calculators, indexed by name.
    _parameters     --  A managed OrderedDict of contained Parameters.
    _constraints    --  A dictionary of Constraints, indexed by the constrained
                        Parameter. Constraints can be added using the
                        'constrain' method.
    _restraints     --  A set of Restraints. Restraints can be added using the
                        'restrain' method.
    _eqfactory      --  A diffpy.srfit.equation.builder.EquationFactory
                        instance that is used create Equations from string.

    Properties
    names           --  Variable names (read only). See getNames.
    values          --  Variable values (read only). See getValues.

    Raises ValueError if the name is not a valid attribute identifier
    """
    def __init__(self, name):
        RecipeContainer.__init__(self, name)
        self._restraints = set()
        self._constraints = {}
        self._eqfactory = EquationFactory()

        self._calculators = {}
        self._manage(self._calculators)
        return

    # Parameter management

    def _newParameter(self, name, value, check=True):
        """Add a new Parameter to the container.

        This creates a new Parameter and adds it to the container using the
        _addParameter method.

        Returns the Parameter.
        """
        p = Parameter(name, value)
        self._addParameter(p, check)
        return p

    def _addParameter(self, par, check=True):
        """Store a Parameter.

        Parameters added in this way are registered with the _eqfactory.

        par     --  The Parameter to be stored.
        check   --  If True (default), a ValueError is raised a Parameter of
                    the specified name has already been inserted.

        Raises ValueError if the Parameter has no name.
        Raises ValueError if the Parameter has the same name as a contained
        RecipeContainer.
        """

        # Store the Parameter
        RecipeContainer._addObject(self, par, self._parameters, check)

        # Register the Parameter
        self._eqfactory.registerArgument(par.name, par)
        return

    def _removeParameter(self, par):
        """Remove a parameter.

        This de-registers the Parameter with the _eqfactory. The Parameter will
        remain part of built equations.

        Note that constraints and restraints involving the Parameter are not
        modified.

        Raises ValueError if par is not part of the RecipeOrganizer.
        """
        self._removeObject(par, self._parameters)
        self._eqfactory.deRegisterBuilder(par.name)
        return

    def registerCalculator(self, f, argnames=None):
        """Register a Calculator so it can be used within equation strings.

        A Calculator is an elaborate function that can organize Parameters.
        This creates a function with this class that can be used within string
        equations. The resulting equation can be used in a string with
        arguments like a function or without, in which case the values of the
        Parameters created from argnames will be be used to compute the value.

        f           --  The Calculator to register.
        argnames    --  The names of the arguments to f (list or None).
                        If this is None, then the argument names will be
                        extracted from the function.
        """
        self._eqfactory.registerOperator(f.name, f)
        self._addObject(f, self._calculators)
        # Register arguments of the calculator
        if argnames is None:
            func_code = f.__call__.im_func.func_code
            argnames = list(func_code.co_varnames)
            argnames = argnames[1:func_code.co_argcount]

        for pname in argnames:
            if pname not in self._eqfactory.builders:
                par = self._newParameter(pname, 0)
            else:
                par = self.get(pname)
            f.addLiteral(par)

        # Now return an equation object
        eq = self._eqfactory.makeEquation(f.name)
        return eq

    def registerFunction(self, f, 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
        passed in the equation string, as this will be handled automatically.

        f           --  The callable to register. If this is an Equation
                        instance, then all that needs to be provied is a name.
        name        --  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).
                        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
        classes.

        Raises TypeError if name or argnames cannot be automatically
        extracted.
        Raises TypeError if an automatically extracted name is '<lambda>'.
        Raises ValueError if f is an Equation object and name is None.

        Returns 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 name is None:
                m = "Equation must be given a name"
                raise ValueError(m)
            self._eqfactory.registerOperator(name, f)
            return f

        #### Introspection code
        if name is None or argnames is None:

            import inspect

            func_code = None

            # This will let us offset the argument list to eliminate 'self'
            offset = 0

            # check regular functions
            if inspect.isfunction(f):
                func_code = f.func_code
            # check class method
            elif inspect.ismethod(f):
                func_code = f.im_func.func_code
                offset = 1
            # check functor
            elif hasattr(f, "__call__") and hasattr(f.__call__, 'im_func'):
                func_code = f.__call__.im_func.func_code
                offset = 1
            else:
                m = "Cannot extract name or argnames"
                raise ValueError(m)

            # Extract the name
            if name is None:
                name = func_code.co_name
                if name == '<lambda>':
                    m = "You must supply a name name for a lambda function"
                    raise ValueError(m)

            # Extract the arguments
            if argnames is None:
                argnames = list(func_code.co_varnames)
                argnames = argnames[offset:func_code.co_argcount]

        #### End introspection code

        # Make missing Parameters
        for pname in argnames:
            if pname not in self._eqfactory.builders:
                self._newParameter(pname, 0)

        # Initialize and register
        from diffpy.srfit.fitbase.calculator import Calculator
        if isinstance(f, Calculator):
            for pname in argnames:
                par = self.get(pname)
                f.addLiteral(par)
            self._eqfactory.registerOperator(name, f)
        else:
            self._eqfactory.registerFunction(name, f, argnames)

        # Now we can create the Equation and return it to the user.
        eq = self._eqfactory.makeEquation(name)

        return eq

    def registerStringFunction(self, fstr, name, ns={}):
        """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
        passed in the function string, as this will be handled automatically.

        fstr        --  A string equation to register.
        name        --  The name of the function to be used in equations.
        ns          --  A dictionary of Parameters, indexed by name, that are
                        used in fstr, 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.

        Returns the callable Equation object.
        """

        # Build the equation instance.
        eq = equationFromString(fstr, self._eqfactory, ns=ns, buildargs=True)
        eq.name = name

        # Register any new Parameters.
        for par in self._eqfactory.newargs:
            self._addParameter(par)

        # Register the equation as a callable function.
        argnames = eq.argdict.keys()
        return self.registerFunction(eq, name, argnames)

    def evaluateEquation(self, eqstr, ns={}):
        """Evaluate a string equation.

        eqstr   --  A 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 {}).

        Raises ValueError if ns uses a name that is already used for a
        variable.
        """
        eq = equationFromString(eqstr, self._eqfactory, ns)
        return eq()

    def constrain(self, par, con, ns={}):
        """Constrain a parameter to an equation.

        Note that only one constraint can exist on a Parameter at a time.

        par     --  The name of a Parameter or a Parameter to constrain.
        con     --  A string representation of the constraint equation or a
                    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      --  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.
        """
        if isinstance(par, basestring):
            name = par
            par = self.get(name)
            if par is None:
                par = ns.get(name)

        if par is None:
            raise ValueError("The parameter cannot be found")

        if par.const:
            raise ValueError("The parameter '%s' is constant" % par)

        if isinstance(con, basestring):
            eqstr = con
            eq = equationFromString(con, self._eqfactory, ns)
        else:
            eq = Equation(root=con)
            eqstr = con.name

        eq.name = "_constraint_%s" % par.name

        # Make and store the constraint
        con = Constraint()
        con.constrain(par, eq)
        # Store the equation string so it can be shown later.
        con.eqstr = eqstr
        self._constraints[par] = con

        # Our configuration changed
        self._updateConfiguration()

        return

    def isConstrained(self, par):
        """Determine if a Parameter is constrained in this object.

        par     --  The name of a Parameter or a Parameter to check.
        """
        if isinstance(par, basestring):
            name = par
            par = self.get(name)

        return (par in self._constraints)

    def unconstrain(self, *pars):
        """Unconstrain a Parameter.

        This removes any constraints on a Parameter.

        *pars   --  The names of Parameters or Parameters to unconstrain.


        Raises ValueError if the Parameter is not constrained.
        """
        update = False
        for par in pars:
            if isinstance(par, basestring):
                name = par
                par = self.get(name)

            if par is None:
                raise ValueError("The parameter cannot be found")

            if par in self._constraints:
                self._constraints[par].unconstrain()
                del self._constraints[par]
                update = True

        if update:
            # Our configuration changed
            self._updateConfiguration()

        else:

            raise ValueError("The parameter is not constrained")

        return

    def getConstrainedPars(self, recurse=False):
        """Get a list of constrained managed Parameters in this object.

        recurse --  Recurse into managed objects and retrive their constrained
                    Parameters as well (default False).
        """
        const = self._getConstraints(recurse)
        return const.keys()

    def clearConstraints(self, recurse=False):
        """Clear all constraints managed by this organizer.

        recurse --  Recurse into managed objects and clear all constraints
                    found there as well.

        This removes constraints that are held in this organizer, no matter
        where the constrained parameters are from.
        """
        if self._constraints:
            self.unconstrain(*self._constraints)

        if recurse:
            f = lambda m: hasattr(m, "clearConstraints")
            for m in ifilter(f, self._iterManaged()):
                m.clearConstraints(recurse)
        return

    def restrain(self, res, lb=-inf, ub=inf, sig=1, scaled=False, ns={}):
        """Restrain an expression to specified bounds

        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 {}).

        The penalty is calculated as
        (max(0, lb - val, val - ub)/sig)**2
        and val is the value of the calculated equation.  This is multipled by
        the average chi^2 if scaled is True.

        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.

        Returns the Restraint object for use with the 'unrestrain' method.
        """

        if isinstance(res, basestring):
            eqstr = res
            eq = equationFromString(res, self._eqfactory, ns)
        else:
            eq = Equation(root=res)
            eqstr = res.name

        # Make and store the restraint
        res = Restraint(eq, lb, ub, sig, scaled)
        res.eqstr = eqstr
        self.addRestraint(res)
        return res

    def addRestraint(self, res):
        """Add a Restraint instance to the RecipeOrganizer.

        res     --  A Restraint instance.
        """
        self._restraints.add(res)
        # Our configuration changed. Notify observers.
        self._updateConfiguration()
        return

    def unrestrain(self, *ress):
        """Remove a Restraint from the RecipeOrganizer.

        *ress   --  Restraints returned from the 'restrain' method or added
                    with the 'addRestraint' method.
        """
        update = False
        restuple = tuple(self._restraints)
        for res in ress:
            if res in restuple:
                self._restraints.remove(res)
                update = True

        if update:
            # Our configuration changed
            self._updateConfiguration()

        return

    def clearRestraints(self, recurse=False):
        """Clear all restraints.

        recurse --  Recurse into managed objects and clear all restraints
                    found there as well.
        """
        self.unrestrain(*self._restraints)

        if recurse:
            f = lambda m: hasattr(m, "clearRestraints")
            for m in ifilter(f, self._iterManaged()):
                m.clearRestraints(recurse)
        return

    def _getConstraints(self, recurse=True):
        """Get the constrained Parameters for this and managed sub-objects."""
        constraints = {}
        if recurse:
            f = lambda m: hasattr(m, "_getConstraints")
            for m in ifilter(f, self._iterManaged()):
                constraints.update(m._getConstraints(recurse))

        constraints.update(self._constraints)

        return constraints

    def _getRestraints(self, recurse=True):
        """Get the Restraints for this and embedded ParameterSets.

        This returns a set of Restraint objects.
        """
        restraints = set(self._restraints)
        if recurse:
            f = lambda m: hasattr(m, "_getRestraints")
            for m in ifilter(f, self._iterManaged()):
                restraints.update(m._getRestraints(recurse))

        return restraints

    def _validate(self):
        """Validate my state.

        This performs RecipeContainer validations.
        This validates contained Restraints and Constraints.

        Raises AttributeError if validation fails.
        """
        RecipeContainer._validate(self)
        iterable = chain(iter(self._restraints),
                         self._constraints.itervalues())
        self._validateOthers(iterable)
        return

    # For printing the configured recipe to screen

    def _formatManaged(self, indent=""):
        """Format fit hierarchy for showing.

        Returns the lines of the formatted string in a list.
        """
        dashedline = 79 * '-'
        lines = []
        formatstr = "%-20s %s"

        lines.append((indent + self.name)[:79])
        # Show parameters
        if self._parameters:
            lines.append((indent + dashedline)[:79])
            items = self._parameters.items()
            items.sort()
            lines.extend(
                (indent + formatstr % (n, p.value))[:79] for n, p in items)

        indent += "  "

        for obj in self._iterManaged():
            if hasattr(obj, "_formatManaged"):
                tlines = obj._formatManaged(indent)
                lines.append("")
                lines.extend(tlines)

        return lines

    def _formatConstraints(self):
        """Format constraints for showing.

        This collects constraints on all levels of the hierarchy and displays
        them with respect to this level.

        Returns the lines of the formatted string in a list.
        """
        cdict = self._getConstraints()
        # Find each constraint and format the equation
        clines = []
        for par, con in cdict.items():
            loc = self._locateManagedObject(par)
            if loc:
                locstr = ".".join(o.name for o in loc)
                clines.append("%s <-- %s" % (locstr, con.eqstr))
            else:
                clines.append("%s <-- %s" % (par.name, con.eqstr))

        if clines:
            clines.sort()
            dashedline = 79 * '-'
            clines.insert(0, dashedline)
            clines.insert(0, "Constraints")
        return clines

    def _formatRestraints(self):
        """Format restraints for showing.

        This collects restraints on all levels of the hierarchy and displays
        them with respect to this level.

        Returns the lines of the formatted string in a list.
        """
        rset = self._getRestraints()
        rlines = []
        for res in rset:
            line = "%s: lb = %f, ub = %f, sig = %f, scaled = %s"%\
                    (res.eqstr, res.lb, res.ub, res.sig, res.scaled)
            rlines.append(line)

        if rlines:
            rlines.sort()
            dashedline = 79 * '-'
            rlines.insert(0, dashedline)
            rlines.insert(0, "Restraints")
        return rlines

    def show(self):
        """Show the configuration on screen.

        This will print a summary of all contained objects.
        """
        # Show sub objects and their parameters
        lines = []
        tlines = self._formatManaged()
        lines.extend(tlines)

        # FIXME - parameter names in equations not particularly informative
        # Show constraints
        tlines = self._formatConstraints()
        lines.append("")
        lines.extend(tlines)

        # FIXME - parameter names in equations not particularly informative
        # Show restraints
        tlines = self._formatRestraints()
        lines.append("")
        lines.extend(tlines)

        print "\n".join(lines)

        return
Пример #18
0
def weightedTest(mutate = 2):
    """Show the benefits of a properly balanced equation tree."""

    from diffpy.srfit.equation.builder import EquationFactory
    factory = EquationFactory()

    x = numpy.arange(0, 10, 0.01)
    qsig = 0.01
    sigma = 0.003

    eqstr = """\
    b1 + b2*x + b3*x**2 + b4*x**3 + b5*x**4 + b6*x**5 + b7*x**6 + b8*x**7\
    """
    factory.registerConstant("x", x)
    eq = factory.makeEquation(eqstr)

    eq.b1.setValue(0)
    eq.b2.setValue(1)
    eq.b3.setValue(2.0)
    eq.b4.setValue(2.0)
    eq.b5.setValue(2.0)
    eq.b6.setValue(2.0)
    eq.b7.setValue(2.0)
    eq.b8.setValue(2.0)

    #scale = visitors.NodeWeigher()
    #eq.root.identify(scale)
    #print scale.output

    from numpy import polyval
    def f(b1, b2, b3, b4, b5, b6, b7, b8):
        return polyval([b8, b7, b6, b5,b4,b3,b2,b1],x)

    tnpy = 0
    teq = 0
    import random
    # Randomly change variables
    numargs = len(eq.args)
    choices = range(numargs)
    args = [0.1]*numargs

    # The call-loop
    random.seed()
    numcalls = 1000
    for _i in xrange(numcalls):
        # Mutate values
        n = mutate
        if n == 0:
            n = random.choice(choices)
        c = choices[:]
        for _j in xrange(n):
            idx = random.choice(c)
            c.remove(idx)
            args[idx] = random.random()

        #print args

        # Time the different functions with these arguments
        teq += timeFunction(eq, *args)
        tnpy += timeFunction(f, *args)

    print "Average call time (%i calls, %i mutations/call):" % (numcalls,
            mutate)
    print "numpy: ", tnpy/numcalls
    print "equation: ", teq/numcalls
    print "ratio: ", teq/tnpy

    return
Пример #19
0
def speedTest3(mutate = 2):
    """Test wrt sympy.

    Results - sympy is 10 to 24 times faster without using arrays (ouch!).
            - diffpy.srfit.equation is slightly slower when using arrays, but
              not considerably worse than versus numpy alone.

    """

    from diffpy.srfit.equation.builder import EquationFactory
    factory = EquationFactory()

    x = numpy.arange(0, 20, 0.05)
    qsig = 0.01
    sigma = 0.003

    eqstr = """\
    A0*exp(-(x*qsig)**2)*(exp(-((x-1.0)/sigma1)**2)+exp(-((x-2.0)/sigma2)**2))\
    + polyval(list(b1, b2, b3, b4, b5, b6, b7, b8), x)\
    """
    factory.registerConstant("x", x)
    eq = factory.makeEquation(eqstr)
    eq.qsig.setValue(qsig)
    eq.sigma1.setValue(sigma)
    eq.sigma2.setValue(sigma)
    eq.A0.setValue(1.0)
    eq.b1.setValue(0)
    eq.b2.setValue(1)
    eq.b3.setValue(2.0)
    eq.b4.setValue(2.0)
    eq.b5.setValue(2.0)
    eq.b6.setValue(2.0)
    eq.b7.setValue(2.0)
    eq.b8.setValue(2.0)

    from sympy import var, exp, lambdify
    from numpy import polyval
    A0, qsig, sigma1, sigma2, b1, b2, b3, b4, b5, b6, b7, b8, xx = vars = var("A0 qsig sigma1 sigma2 b1 b2 b3 b4 b5 b6 b7 b8 xx")
    f = lambdify(vars, A0*exp(-(xx*qsig)**2)*(exp(-((xx-1.0)/sigma1)**2)+exp(-((xx-2.0)/sigma2)**2)) + polyval([b1, b2, b3, b4, b5, b6, b7, b8], xx), "numpy")

    tnpy = 0
    teq = 0
    import random
    # Randomly change variables
    numargs = len(eq.args)
    choices = range(numargs)
    args = [1.0]*(len(eq.args))
    args.append(x)

    # The call-loop
    random.seed()
    numcalls = 1000
    for _i in xrange(numcalls):
        # Mutate values
        n = mutate
        if n == 0:
            n = random.choice(choices)
        c = choices[:]
        for _j in xrange(n):
            idx = random.choice(c)
            c.remove(idx)
            args[idx] = random.random()

        # Time the different functions with these arguments
        teq += timeFunction(eq, *(args[:-1]))
        tnpy += timeFunction(f, *args)

    print "Average call time (%i calls, %i mutations/call):" % (numcalls,
            mutate)
    print "sympy: ", tnpy/numcalls
    print "equation: ", teq/numcalls
    print "ratio: ", teq/tnpy

    return
Пример #20
0
def speedTest2(mutate = 2):

    from diffpy.srfit.equation.builder import EquationFactory
    factory = EquationFactory()

    x = numpy.arange(0, 20, 0.05)
    qsig = 0.01
    sigma = 0.003

    eqstr = """\
    A0*exp(-(x*qsig)**2)*(exp(-((x-1.0)/sigma1)**2)+exp(-((x-2.0)/sigma2)**2))\
    + polyval(list(b1, b2, b3, b4, b5, b6, b7, b8), x)\
    """
    factory.registerConstant("x", x)
    eq = factory.makeEquation(eqstr)
    eq.qsig.setValue(qsig)
    eq.sigma1.setValue(sigma)
    eq.sigma2.setValue(sigma)
    eq.A0.setValue(1.0)
    eq.b1.setValue(0)
    eq.b2.setValue(1)
    eq.b3.setValue(2.0)
    eq.b4.setValue(2.0)
    eq.b5.setValue(2.0)
    eq.b6.setValue(2.0)
    eq.b7.setValue(2.0)
    eq.b8.setValue(2.0)

    from numpy import exp
    from numpy import polyval
    def f(A0, qsig, sigma1, sigma2, b1, b2, b3, b4, b5, b6, b7, b8):
        return A0*exp(-(x*qsig)**2)*(exp(-((x-1.0)/sigma1)**2)+exp(-((x-2.0)/sigma2)**2)) + polyval([b8, b7, b6, b5,b4,b3,b2,b1],x)

    tnpy = 0
    teq = 0
    import random
    # Randomly change variables
    numargs = len(eq.args)
    choices = range(numargs)
    args = [0.0]*(len(eq.args))

    # The call-loop
    random.seed()
    numcalls = 1000
    for _i in xrange(numcalls):
        # Mutate values
        n = mutate
        if n == 0:
            n = random.choice(choices)
        c = choices[:]
        for _j in xrange(n):
            idx = random.choice(c)
            c.remove(idx)
            args[idx] = random.random()

        # Time the different functions with these arguments
        tnpy += timeFunction(f, *args)
        teq += timeFunction(eq, *args)

    print "Average call time (%i calls, %i mutations/call):" % (numcalls,
            mutate)
    print "numpy: ", tnpy/numcalls
    print "equation: ", teq/numcalls
    print "ratio: ", teq/tnpy

    return