def test_clear(self):
     pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
     shuffle(pairs)
     od = OrderedDict(pairs)
     self.assertEqual(len(od), len(pairs))
     od.clear()
     self.assertEqual(len(od), 0)
 def test_clear(self):
     pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
     shuffle(pairs)
     od = OrderedDict(pairs)
     self.assertEqual(len(od), len(pairs))
     od.clear()
     self.assertEqual(len(od), 0)
 def test_delitem(self):
     pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
     od = OrderedDict(pairs)
     del od['a']
     self.assert_('a' not in od)
     self.assertRaises(KeyError, od.__delitem__, 'a')
     self.assertEqual(list(od.items()), pairs[:2] + pairs[3:])
 def test_delitem(self):
     pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
     od = OrderedDict(pairs)
     del od['a']
     self.assert_('a' not in od)
     self.assertRaises(KeyError, od.__delitem__, 'a')
     self.assertEqual(list(od.items()), pairs[:2] + pairs[3:])
Example #5
0
    def __init__(self, name=None, root=None):
        """Initialize.

        name    --  A name for this Equation.
        root    --  The root node of the Literal tree (default None). If root
                    is not passed here, you must call the 'setRoot' method to
                    set or change the root node.

        """
        # Operator stuff. We circumvent Operator.__init__ since we're using
        # args as a property. We cannot set it, as the Operator tries to do.
        if name is None and root is not None:
            name = "eq_%s" % root.name
        Literal.__init__(self, name)
        self.symbol = name
        self.nin = None
        self.nout = 1
        self.operation = self.__call__

        self.root = None
        self.argdict = OrderedDict()
        if root is not None:
            self.setRoot(root)

        return
Example #6
0
    def setRoot(self, root):
        """Set the root of the Literal tree.

        Raises:
        ValueError if errors are found in the Literal tree.

        """

        # Validate the new root
        validate(root)

        # Stop observing the old root
        if self.root is not None:
            self.root.removeObserver(self._flush)

        # Add the new root
        self.root = root
        self.root.addObserver(self._flush)
        self._flush(other=(self, ))

        # Get the args
        args = getArgs(root, getconsts=False)
        self.argdict = OrderedDict([(arg.name, arg) for arg in args])

        # Set Operator attributes
        self.nin = len(self.args)

        return
 def test_popitem(self):
     pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
     shuffle(pairs)
     od = OrderedDict(pairs)
     while pairs:
         self.assertEqual(od.popitem(), pairs.pop())
     self.assertRaises(KeyError, od.popitem)
     self.assertEqual(len(od), 0)
 def test_popitem(self):
     pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
     shuffle(pairs)
     od = OrderedDict(pairs)
     while pairs:
         self.assertEqual(od.popitem(), pairs.pop())
     self.assertRaises(KeyError, od.popitem)
     self.assertEqual(len(od), 0)
 def test_repr(self):
     od = OrderedDict([('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5),
                       ('f', 6)])
     self.assertEqual(
         repr(od),
         "OrderedDict([('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)])"
     )
     self.assertEqual(eval(repr(od)), od)
     self.assertEqual(repr(OrderedDict()), "OrderedDict()")
 def test_reinsert(self):
     # Given insert a, insert b, delete a, re-insert a,
     # verify that a is now later than b.
     od = OrderedDict()
     od['a'] = 1
     od['b'] = 2
     del od['a']
     od['a'] = 1
     self.assertEqual(list(od.items()), [('b', 2), ('a', 1)])
Example #11
0
 def test_reinsert(self):
     # Given insert a, insert b, delete a, re-insert a,
     # verify that a is now later than b.
     od = OrderedDict()
     od['a'] = 1
     od['b'] = 2
     del od['a']
     od['a'] = 1
     self.assertEqual(list(od.items()), [('b', 2), ('a', 1)])
Example #12
0
 def test_iterators(self):
     pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
     shuffle(pairs)
     od = OrderedDict(pairs)
     self.assertEqual(list(od), [t[0] for t in pairs])
     self.assertEqual(list(od.keys()), [t[0] for t in pairs])
     self.assertEqual(list(od.values()), [t[1] for t in pairs])
     self.assertEqual(list(od.items()), pairs)
     self.assertEqual(list(reversed(od)), [t[0] for t in reversed(pairs)])
 def test_iterators(self):
     pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
     shuffle(pairs)
     od = OrderedDict(pairs)
     self.assertEqual(list(od), [t[0] for t in pairs])
     self.assertEqual(list(od.keys()), [t[0] for t in pairs])
     self.assertEqual(list(od.values()), [t[1] for t in pairs])
     self.assertEqual(list(od.items()), pairs)
     self.assertEqual(list(reversed(od)),
                      [t[0] for t in reversed(pairs)])
Example #14
0
    def test_init(self):
        self.assertRaises(TypeError, OrderedDict, ([('a', 1), ('b', 2)], None))
        # too many args
        pairs = [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]
        self.assertEqual(sorted(OrderedDict(dict(pairs)).items()),
                         pairs)  # dict input
        self.assertEqual(sorted(OrderedDict(**dict(pairs)).items()),
                         pairs)  # kwds input
        self.assertEqual(list(OrderedDict(pairs).items()),
                         pairs)  # pairs input
        self.assertEqual(
            list(
                OrderedDict([('a', 1), ('b', 2), ('c', 9), ('d', 4)], c=3,
                            e=5).items()), pairs)  # mixed input

        # make sure no positional args conflict with possible kwdargs
        odargs = inspect.getargspec(OrderedDict.__dict__['__init__'])[0]
        self.assertTrue(odargs == [] or odargs == ['self'])

        # Make sure that direct calls to __init__ do not clear previous contents
        d = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 44), ('e', 55)])
        d.__init__([('e', 5), ('f', 6)], g=7, d=4)
        self.assertEqual(list(d.items()), [('a', 1), ('b', 2), ('c', 3),
                                           ('d', 4), ('e', 5), ('f', 6),
                                           ('g', 7)])
 def test_pop(self):
     pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
     shuffle(pairs)
     od = OrderedDict(pairs)
     shuffle(pairs)
     while pairs:
         k, v = pairs.pop()
         self.assertEqual(od.pop(k), v)
     self.assertRaises(KeyError, od.pop, 'xyz')
     self.assertEqual(len(od), 0)
     self.assertEqual(od.pop(k, 12345), 12345)
Example #16
0
 def test_pop(self):
     pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
     shuffle(pairs)
     od = OrderedDict(pairs)
     shuffle(pairs)
     while pairs:
         k, v = pairs.pop()
         self.assertEqual(od.pop(k), v)
     self.assertRaises(KeyError, od.pop, 'xyz')
     self.assertEqual(len(od), 0)
     self.assertEqual(od.pop(k, 12345), 12345)
Example #17
0
    def __init__(self, name):
        Observable.__init__(self)
        Configurable.__init__(self)
        validateName(name)
        self.name = name
        self._parameters = OrderedDict()

        self.__managed = []
        self._manage(self._parameters)

        return
Example #18
0
    def update(self):
        """Update the results according to the current state of the recipe."""
        ## Note that the order of these operations are chosen to reduce
        ## computation time.

        recipe = self.recipe

        if not recipe._contributions:
            return

        # Make sure everything is ready for calculation
        recipe._prepare()

        # Store the variable names and values
        self.varnames = recipe.getNames()
        self.varvals = recipe.getValues()
        fixedpars = recipe._tagmanager.union(recipe._fixedtag)
        fixedpars = [p for p in fixedpars if not p.constrained]
        self.fixednames = [p.name for p in fixedpars]
        self.fixedvals = [p.value for p in fixedpars]

        # Store the constraint information
        self.connames = [con.par.name for con in recipe._oconstraints]
        self.convals = [con.par.getValue() for con in recipe._oconstraints]

        if self.varnames:
            # Calculate the covariance
            self._calculateCovariance()

            # Get the variable uncertainties
            self.varunc = [self.cov[i,i]**0.5 for i in \
                    range(len(self.varnames))]

            # Get the constraint uncertainties
            self._calculateConstraintUncertainties()

        # Store the fitting arrays and metrics for each FitContribution.
        self.conresults = OrderedDict()
        for con, weight in zip(recipe._contributions.values(),
                               recipe._weights):
            self.conresults[con.name] = ContributionResults(con, weight, self)

        # Calculate the metrics
        res = recipe.residual()
        self.residual = numpy.dot(res, res)
        self._calculateMetrics()

        # Calcualte the restraints penalty
        w = self.chi2 / len(res)
        self.penalty = sum([r.penalty(w) for r in recipe._restraintlist])

        return
Example #19
0
 def test_equality(self):
     pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
     shuffle(pairs)
     od1 = OrderedDict(pairs)
     od2 = OrderedDict(pairs)
     self.assertEqual(od1, od2)  # same order implies equality
     pairs = pairs[2:] + pairs[:2]
     od2 = OrderedDict(pairs)
     self.assertNotEqual(od1, od2)  # different order implies inequality
     # comparison to regular dict is not order sensitive
     self.assertEqual(od1, dict(od2))
     self.assertEqual(dict(od2), od1)
     # different length implied inequality
     self.assertNotEqual(od1, OrderedDict(pairs[:-1]))
Example #20
0
    def setRoot(self, root):
        """Set the root of the Literal tree.

        Raises:
        ValueError if errors are found in the Literal tree.

        """

        # Validate the new root
        validate(root)

        # Stop observing the old root
        if self.root is not None:
            self.root.removeObserver(self._flush)

        # Add the new root
        self.root = root
        self.root.addObserver(self._flush)
        self._flush(self)

        # Get the args
        args = getArgs(root, getconsts=False)
        self.argdict = OrderedDict( [(arg.name, arg) for arg in args] )

        # Set Operator attributes
        self.nin = len(self.args)

        return
Example #21
0
    def __init__(self, name = None, root = None):
        """Initialize.

        name    --  A name for this Equation.
        root    --  The root node of the Literal tree (default None). If root
                    is not passed here, you must call the 'setRoot' method to
                    set or change the root node.

        """
        # Operator stuff. We circumvent Operator.__init__ since we're using
        # args as a property. We cannot set it, as the Operator tries to do.
        if name is None and root is not None:
            name = "eq_%s"%root.name
        Literal.__init__(self, name)
        self.symbol = name
        self.nin = None
        self.nout = 1
        self.operation = self.__call__

        self.root = None
        self.argdict = OrderedDict()
        if root is not None:
            self.setRoot(root)

        return
    def test_init(self):
        self.assertRaises(TypeError, OrderedDict, ([('a', 1), ('b', 2)], None))
                # too many args
        pairs = [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]
        self.assertEqual(sorted(OrderedDict(dict(pairs)).items()), pairs)           # dict input
        self.assertEqual(sorted(OrderedDict(**dict(pairs)).items()), pairs)         # kwds input
        self.assertEqual(list(OrderedDict(pairs).items()), pairs)                   # pairs input
        self.assertEqual(list(OrderedDict([('a', 1), ('b', 2), ('c', 9), ('d', 4)],
                                          c=3, e=5).items()), pairs)                # mixed input

        # make sure no positional args conflict with possible kwdargs
        self.assertEqual(inspect.getargspec(OrderedDict.__dict__['__init__'])[0],
                         ['self'])

        # Make sure that direct calls to __init__ do not clear previous contents
        d = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 44), ('e', 55)])
        d.__init__([('e', 5), ('f', 6)], g=7, d=4)
        self.assertEqual(list(d.items()),
            [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7)])
    def __init__(self, name):
        Observable.__init__(self)
        Configurable.__init__(self)
        validateName(name)
        self.name = name
        self._parameters = OrderedDict()

        self.__managed = []
        self._manage(self._parameters)

        return
Example #24
0
 def test_setdefault(self):
     pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
     shuffle(pairs)
     od = OrderedDict(pairs)
     pair_order = list(od.items())
     self.assertEqual(od.setdefault('a', 10), 3)
     # make sure order didn't change
     self.assertEqual(list(od.items()), pair_order)
     self.assertEqual(od.setdefault('x', 10), 10)
     # make sure 'x' is added to the end
     self.assertEqual(list(od.items())[-1], ('x', 10))
Example #25
0
    def update(self):
        """Update the results according to the current state of the recipe."""
        ## Note that the order of these operations are chosen to reduce
        ## computation time.

        recipe = self.recipe

        if not recipe._contributions:
            return

        # Make sure everything is ready for calculation
        recipe._prepare()

        # Store the variable names and values
        self.varnames = recipe.getNames()
        self.varvals = recipe.getValues()
        fixedpars = recipe._tagmanager.union(recipe._fixedtag)
        fixedpars = [p for p in fixedpars if not p.constrained]
        self.fixednames = [p.name for p in fixedpars]
        self.fixedvals = [p.value for p in fixedpars]

        # Store the constraint information
        self.connames = [con.par.name for con in recipe._oconstraints]
        self.convals = [con.par.getValue() for con in recipe._oconstraints]

        if self.varnames:
            # Calculate the covariance
            self._calculateCovariance()

            # Get the variable uncertainties
            self.varunc = [self.cov[i,i]**0.5 for i in \
                    range(len(self.varnames))]

            # Get the constraint uncertainties
            self._calculateConstraintUncertainties()

        # Store the fitting arrays and metrics for each FitContribution.
        self.conresults = OrderedDict()
        for con, weight in zip(recipe._contributions.values(), recipe._weights):
            self.conresults[con.name] = ContributionResults(con, weight, self)

        # Calculate the metrics
        res = recipe.residual()
        self.residual = numpy.dot(res, res)
        self._calculateMetrics()

        # Calcualte the restraints penalty
        w = self.chi2 / len(res)
        self.penalty = sum([res.penalty(w) for res in recipe._restraintlist])

        return
Example #26
0
    def __init__(self, recipe, update=True, showfixed=True, showcon=False):
        """Initialize the attributes.

        recipe   --  The recipe containing the results
        update  --  Flag indicating whether to do an immediate update (default
                    True).
        showcon --  Show fixed variables in the output (default True).
        showcon --  Show constraint values in the output (default False).

        """
        self.recipe = recipe
        self.conresults = OrderedDict()
        self.derivstep = 1e-8
        self.varnames = []
        self.varvals = []
        self.varunc = []
        self.fixednames = []
        self.fixedvals = []
        self.connames = []
        self.convals = []
        self.conunc = []
        self.cov = None
        self.residual = 0
        self.penalty = 0
        self.chi2 = 0
        self.rchi2 = 0
        self.rw = 0
        self.precision = 8
        self._dcon = []
        self.messages = []

        self.showfixed = bool(showfixed)
        self.showcon = bool(showcon)

        if update:
            self.update()
        return
 def test_setdefault(self):
     pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
     shuffle(pairs)
     od = OrderedDict(pairs)
     pair_order = list(od.items())
     self.assertEqual(od.setdefault('a', 10), 3)
     # make sure order didn't change
     self.assertEqual(list(od.items()), pair_order)
     self.assertEqual(od.setdefault('x', 10), 10)
     # make sure 'x' is added to the end
     self.assertEqual(list(od.items())[-1], ('x', 10))
Example #28
0
 def test_copying(self):
     # Check that ordered dicts are copyable, deepcopyable, picklable,
     # and have a repr/eval round-trip
     pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
     od = OrderedDict(pairs)
     update_test = OrderedDict()
     update_test.update(od)
     for i, dup in enumerate([
             od.copy(),
             copy.copy(od),
             copy.deepcopy(od),
             pickle.loads(pickle.dumps(od, 0)),
             pickle.loads(pickle.dumps(od, 1)),
             pickle.loads(pickle.dumps(od, 2)),
             pickle.loads(pickle.dumps(od, -1)),
             eval(repr(od)),
             update_test,
             OrderedDict(od),
     ]):
         self.assert_(dup is not od)
         self.assertEquals(dup, od)
         self.assertEquals(list(dup.items()), list(od.items()))
         self.assertEquals(len(dup), len(od))
         self.assertEquals(type(dup), type(od))
Example #29
0
    def __init__(self, name = "fit"):
        """Initialization."""
        RecipeOrganizer.__init__(self, name)
        self.fithooks = []
        self.pushFitHook(PrintFitHook())
        self._restraintlist = []
        self._oconstraints = []
        self._ready = False
        self._fixedtag = "__fixed"

        self._weights = []
        self._tagmanager = TagManager()

        self._parsets = {}
        self._manage(self._parsets)

        self._contributions = OrderedDict()
        self._manage(self._contributions)

        return
Example #30
0
    def __init__(self, recipe, update = True, showfixed = True, showcon =
            False):
        """Initialize the attributes.

        recipe   --  The recipe containing the results
        update  --  Flag indicating whether to do an immediate update (default
                    True).
        showcon --  Show fixed variables in the output (default True).
        showcon --  Show constraint values in the output (default False).

        """
        self.recipe = recipe
        self.conresults = OrderedDict()
        self.derivstep = 1e-8
        self.varnames = []
        self.varvals = []
        self.varunc = []
        self.fixednames = []
        self.fixedvals = []
        self.connames = []
        self.convals = []
        self.conunc = []
        self.cov = None
        self.residual = 0
        self.penalty = 0
        self.chi2 = 0
        self.rchi2 = 0
        self.rw = 0
        self.precision = 8
        self._dcon = []
        self.messages = []

        self.showfixed = bool(showfixed)
        self.showcon = bool(showcon)

        if update:
            self.update()
        return
 def test_copying(self):
     # Check that ordered dicts are copyable, deepcopyable, picklable,
     # and have a repr/eval round-trip
     pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
     od = OrderedDict(pairs)
     update_test = OrderedDict()
     update_test.update(od)
     for i, dup in enumerate([
                 od.copy(),
                 copy.copy(od),
                 copy.deepcopy(od),
                 pickle.loads(pickle.dumps(od, 0)),
                 pickle.loads(pickle.dumps(od, 1)),
                 pickle.loads(pickle.dumps(od, 2)),
                 pickle.loads(pickle.dumps(od, -1)),
                 eval(repr(od)),
                 update_test,
                 OrderedDict(od),
                 ]):
         self.assert_(dup is not od)
         self.assertEquals(dup, od)
         self.assertEquals(list(dup.items()), list(od.items()))
         self.assertEquals(len(dup), len(od))
         self.assertEquals(type(dup), type(od))
Example #32
0
    def test_update(self):
        self.assertRaises(TypeError,
                          OrderedDict().update, [('a', 1), ('b', 2)],
                          None)  # too many args
        pairs = [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]
        od = OrderedDict()
        od.update(dict(pairs))
        self.assertEqual(sorted(od.items()), pairs)  # dict input
        od = OrderedDict()
        od.update(**dict(pairs))
        self.assertEqual(sorted(od.items()), pairs)  # kwds input
        od = OrderedDict()
        od.update(pairs)
        self.assertEqual(list(od.items()), pairs)  # pairs input
        od = OrderedDict()
        od.update([('a', 1), ('b', 2), ('c', 9), ('d', 4)], c=3, e=5)
        self.assertEqual(list(od.items()), pairs)  # mixed input

        # Make sure that direct calls to update do not clear previous contents
        # add that updates items are not moved to the end
        d = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 44), ('e', 55)])
        d.update([('e', 5), ('f', 6)], g=7, d=4)
        self.assertEqual(list(d.items()), [('a', 1), ('b', 2), ('c', 3),
                                           ('d', 4), ('e', 5), ('f', 6),
                                           ('g', 7)])
 def test_setitem(self):
     od = OrderedDict([('d', 1), ('b', 2), ('c', 3), ('a', 4), ('e', 5)])
     od['c'] = 10           # existing element
     od['f'] = 20           # new element
     self.assertEqual(list(od.items()),
                      [('d', 1), ('b', 2), ('c', 10), ('a', 4), ('e', 5), ('f', 20)])
Example #34
0
class FitRecipe(_fitrecipe_interface, RecipeOrganizer):
    """FitRecipe class.

    Attributes
    name            --  A name for this FitRecipe.
    fithooks        --  List of FitHook instances that can pass information out
                        of the system during a refinement. By default, the is
                        populated by a PrintFitHook instance.
    _constraints    --  A dictionary of Constraints, indexed by the constrained
                        Parameter. Constraints can be added using the
                        'constrain' method.
    _oconstraints   --  An ordered list of the constraints from this and all
                        sub-components.
    _calculators    --  A managed dictionary of Calculators.
    _contributions  --  A managed OrderedDict of FitContributions.
    _parameters     --  A managed OrderedDict of parameters (in this case the
                        parameters are varied).
    _parsets        --  A managed dictionary of ParameterSets.
    _eqfactory      --  A diffpy.srfit.equation.builder.EquationFactory
                        instance that is used to create constraints and
                        restraints from string
    _restraintlist  --  A list of restraints from this and all sub-components.
    _restraints     --  A set of Restraints. Restraints can be added using the
                        'restrain' or 'confine' methods.
    _ready          --  A flag indicating if all attributes are ready for the
                        calculation.
    _tagmanager     --  A TagManager instance for managing tags on Parameters.
    _weights        --  List of weighing factors for each FitContribution. The
                        weights are multiplied by the residual of the
                        FitContribution when determining the overall residual.
    _fixedtag       --  "__fixed", used for tagging variables as fixed. Don't
                        use this tag unless you want issues.

    Properties
    names           --  Variable names (read only). See getNames.
    values          --  Variable values (read only). See getValues.
    fixednames      --  Names of the fixed refinable variables (read only).
    fixedvalues     --  Values of the fixed refinable variables (read only).
    bounds          --  Bounds on parameters (read only). See getBounds.
    bounds2         --  Bounds on parameters (read only). See getBounds2.
    """

    fixednames = property(lambda self:
            [v.name for v in self._parameters.values()
                if not (self.isFree(v) or self.isConstrained(v))],
            doc='names of the fixed refinable variables')
    fixedvalues = property(lambda self:
            array([v.value for v in self._parameters.values()
                if not (self.isFree(v) or self.isConstrained(v))]),
            doc='values of the fixed refinable variables')
    bounds = property(lambda self: self.getBounds())
    bounds2 = property(lambda self: self.getBounds2())

    def __init__(self, name = "fit"):
        """Initialization."""
        RecipeOrganizer.__init__(self, name)
        self.fithooks = []
        self.pushFitHook(PrintFitHook())
        self._restraintlist = []
        self._oconstraints = []
        self._ready = False
        self._fixedtag = "__fixed"

        self._weights = []
        self._tagmanager = TagManager()

        self._parsets = {}
        self._manage(self._parsets)

        self._contributions = OrderedDict()
        self._manage(self._contributions)

        return

    def pushFitHook(self, fithook, index = None):
        """Add a FitHook to be called within the residual method.

        The hook is an object for reporting updates, or more fundamentally,
        passing information out of the system during a refinement. See the
        diffpy.srfit.fitbase.fithook.FitHook class for the required interface.
        Added FitHooks will be called sequentially during refinement.

        fithook --  FitHook instance to add to the sequence
        index   --  Index for inserting fithook into the list of fit hooks.  If
                    this is None (default), the fithook is added to the end.
        """
        if index is None:
            index = len(self.fithooks)
        self.fithooks.insert(index, fithook)
        # Make sure the added FitHook gets its reset method called.
        self._updateConfiguration()
        return

    def popFitHook(self, fithook = None, index = -1):
        """Remove a FitHook by index or reference.

        fithook --  FitHook instance to remove from the sequence. If this is
                    None (default), default to index.
        index   --  Index of FitHook instance to remove (default -1).

        Raises ValueError if fithook is not None, but is not present in the
        sequence.
        Raises IndexError if the sequence is empty or index is out of range.
        """
        if fithook is not None:
            self.fithooks.remove(fithook)
            return
        self.fithook.remove(index)
        return

    def getFitHooks(self):
        """Get the sequence of FitHook instances."""
        return self.fithooks[:]

    def clearFitHooks(self):
        """Clear the FitHook sequence."""
        del self.fithooks[:]
        return

    def addContribution(self, con, weight = 1.0):
        """Add a FitContribution to the FitRecipe.

        con     --  The FitContribution to be stored.

        Raises ValueError if the FitContribution has no name
        Raises ValueError if the FitContribution has the same name as some
        other managed object.
        """
        self._addObject(con, self._contributions, True)
        self._weights.append(weight)
        return

    def setWeight(self, con, weight):
        """Set the weight of a FitContribution."""
        idx = self._contributions.values().index(con)
        self._weights[idx] = weight
        return

    def addParameterSet(self, parset):
        """Add a ParameterSet to the hierarchy.

        parset  --  The ParameterSet to be stored.

        Raises ValueError if the ParameterSet has no name.
        Raises ValueError if the ParameterSet has the same name as some other
        managed object.
        """
        self._addObject(parset, self._parsets, True)
        return

    def removeParameterSet(self, parset):
        """Remove a ParameterSet from the hierarchy.

        Raises ValueError if parset is not managed by this object.
        """
        self._removeObject(parset, self._parsets)
        return

    def residual(self, p = []):
        """Calculate the vector residual to be optimized.

        Arguments
        p   --  The list of current variable values, provided in the same order
                as the '_parameters' list. If p is an empty iterable (default),
                then it is assumed that the parameters have already been
                updated in some other way, and the explicit update within this
                function is skipped.

        The residual is by default the weighted concatenation of each
        FitContribution's residual, plus the value of each restraint. The array
        returned, denoted chiv, is such that
        dot(chiv, chiv) = chi^2 + restraints.
        """

        # Prepare, if necessary
        self._prepare()

        for fithook in self.fithooks:
            fithook.precall(self)

        # Update the variable parameters.
        self._applyValues(p)

        # Update the constraints. These are ordered such that the list only
        # needs to be cycled once.
        for con in self._oconstraints:
            con.update()

        # Calculate the bare chiv
        chiv = concatenate([
            sqrt(self._weights[i])*\
                    self._contributions.values()[i].residual().flatten() \
                    for i in range(len(self._contributions))])

        # Calculate the point-average chi^2
        w = dot(chiv, chiv)/len(chiv)
        # Now we must append the restraints
        penalties = [ sqrt(res.penalty(w)) for res in self._restraintlist ]
        chiv = concatenate( [ chiv, penalties ] )

        for fithook in self.fithooks:
            fithook.postcall(self, chiv)

        return chiv

    def scalarResidual(self, p = []):
        """Calculate the scalar residual to be optimized.

        Arguments
        p   --  The list of current variable values, provided in the same order
                as the '_parameters' list. If p is an empty iterable (default),
                then it is assumed that the parameters have already been
                updated in some other way, and the explicit update within this
                function is skipped.

        The residual is by default the weighted concatenation of each
        FitContribution's residual, plus the value of each restraint. The array
        returned, denoted chiv, is such that
        dot(chiv, chiv) = chi^2 + restraints.
        """
        chiv = self.residual(p)
        return dot(chiv, chiv)

    def __call__(self, p = []):
        """Same as scalarResidual method."""
        return self.scalarResidual(p)

    def _prepare(self):
        """Prepare for the residual calculation, if necessary.

        This will prepare the data attributes to be used in the residual
        calculation.

        This updates the local restraints with those of the contributions.

        Raises AttributeError if there are variables without a value.
        """

        # Only prepare if the configuration has changed within the recipe
        # hierarchy.
        if self._ready:
            return

        # Inform the fit hooks that we're updating things
        for fithook in self.fithooks:
            fithook.reset(self)

        # Check Profiles
        self.__verifyProfiles()

        # Check parameters
        self.__verifyParameters()

        # Update constraints and restraints.
        self.__collectConstraintsAndRestraints()

        # We do this here so that the calculations that take place during the
        # validation use the most current values of the parameters. In most
        # cases, this will save us from recalculating them later.
        for con in self._oconstraints:
            con.update()

        # Validate!
        self._validate()

        self._ready = True

        return

    def __verifyProfiles(self):
        """Verify that each FitContribution has a Profile."""
        # Check for profile values
        for con in self._contributions.values():
            if con.profile is None:
                m = "FitContribution '%s' does not have a Profile"%con.name
                raise AttributeError(m)
            if con.profile.x is None or\
                con.profile.y is None or\
                con.profile.dy is None:

                    m = "Profile for '%s' is missing data"%con.name
                    raise AttributeError(m)
        return

    def __verifyParameters(self):
        """Verify that all Parameters have values."""

        # Get all parameters with a value of None
        badpars = []
        for par in self.iterPars():
            try:
                par.getValue()
            except ValueError:
                badpars.append(par)

        # Get the bad names
        badnames = []
        for par in badpars:
            objlist = self._locateManagedObject(par)
            names = [obj.name for obj in objlist]
            badnames.append( ".".join(names) )

        # Construct an error message, if necessary
        m = ""
        if len(badnames) == 1:
            m = "%s is not defined or needs an initial value" % badnames[0]
        elif len(badnames) > 0:
            s1 = ",".join(badnames[:-1])
            s2 = badnames[-1]
            m = "%s and %s are not defined or need initial values" % (s1, s2)

        if m:
            raise AttributeError(m)

        return

    def __collectConstraintsAndRestraints(self):
        """Collect the Constraints and Restraints from subobjects."""
        rset = set(self._restraints)
        cdict = {}

        for org in self._contributions.values() + self._parsets.values():
            rset.update( org._getRestraints() )
            cdict.update( org._getConstraints() )
        cdict.update(self._constraints)

        # The order of the restraint list does not matter
        self._restraintlist = list(rset)

        # Reorder the constraints. Constraints are ordered such that a given
        # constraint is placed before its dependencies.
        self._oconstraints = cdict.values()

        # Create a depth-1 map of the constraint dependencies
        depmap = {}
        for con in self._oconstraints:
            depmap[con] = set()
            # Now check the constraint's equation for constrained arguments
            for arg in con.eq.args:
                if arg in cdict:
                    depmap[con].add( cdict[arg] )

        # Turn the dependency map into multi-level map.
        def _extendDeps(con):
            deps = set(depmap[con])
            for dep in depmap[con]:
                deps.update(_extendDeps(dep))

            return deps

        for con in depmap:
            depmap[con] = _extendDeps(con)

        # Now sort the constraints based on the dependency map.
        def cmp(x, y):
            # x == y if neither of them have dependencies
            if not depmap[x] and not depmap[y]:
                return 0
            # x > y if y is a dependency of x
            # x > y if y has no dependencies
            if y in depmap[x] or not depmap[y]:
                return 1
            # x < y if x is a dependency of y
            # x < y if x has no dependencies
            if x in depmap[y] or not depmap[x]:
                return -1
            # If there are dependencies, but there is no relationship, the
            # constraints are equivalent
            return 0

        self._oconstraints.sort(cmp)

        return

    # Variable manipulation

    def addVar(self, par, value = None, name = None, fixed = False, tag = None,
            tags = []):
        """Add a variable to be refined.

        par     --  A Parameter that will be varied during a fit.
        value   --  An initial value for the variable. If this is None
                    (default), then the current value of par will be used.
        name    --  A name for this variable. If name is None (default), then
                    the name of the parameter will be used.
        fixed   --  Fix the variable so that it does not vary (default False).
        tag     --  A tag for the variable. This can be used to retrieve, fix
                    or free variables by tag (default None). Note that a
                    variable is automatically tagged with its name and "all".
        tags    --  A list of tags (default []). Both tag and tags can be
                    applied.

        Returns the ParameterProxy (variable) for the passed Parameter.

        Raises ValueError if the name of the variable is already taken by
        another managed object.
        Raises ValueError if par is constant.
        Raises ValueError if par is constrained.
        """
        name = name or par.name

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

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

        var = ParameterProxy(name, par)
        if value is not None:
            var.setValue(value)

        self._addParameter(var)

        if fixed:
            self.fix(var)

        # Tag with passed tags and by name
        self._tagmanager.tag(var, var.name)
        self._tagmanager.tag(var, "all")
        self._tagmanager.tag(var, *tags)
        if tag is not None:
            self._tagmanager.tag(var, tag)
        return var

    def delVar(self, var):
        """Remove a variable.

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

        var     --  A variable of the FitRecipe.

        Raises ValueError if var is not part of the FitRecipe.
        """

        self._removeParameter(var)
        self._tagmanager.untag(var)
        return

    def __delattr__(self, name):
        if name in self._parameters:
            self.delVar( self._parameters[name] )
            return
        super(FitRecipe, self).__delattr__(name)
        return


    def newVar(self, name, value = None, fixed = False, tag = None, tags = []):
        """Create a new variable of the fit.

        This method lets new variables be created that are not tied to a
        Parameter.  Orphan variables may cause a fit to fail, depending on the
        optimization routine, and therefore should only be created to be used
        in contraint or restraint equations.

        name    --  The name of the variable. The variable will be able to be
                    used by this name in restraint and constraint equations.
        value   --  An initial value for the variable. If this is None
                    (default), then the variable will be given the value of the
                    first non-None-valued Parameter constrained to it. If this
                    fails, an error will be thrown when 'residual' is called.
        fixed   --  Fix the variable so that it does not vary (default False).
                    The variable will still be managed by the FitRecipe.
        tag     --  A tag for the variable. This can be used to fix and free
                    variables by tag (default None). Note that a variable is
                    automatically tagged with its name and "all".
        tags    --  A list of tags (default []). Both tag and tags can be
                    applied.

        Returns the new variable (Parameter instance).
        """
        # This will fix the Parameter
        var = self._newParameter(name, value)

        # We may explicitly free it
        if not fixed:
            self.free(var)

        # Tag with passed tags
        self._tagmanager.tag(var, *tags)
        if tag is not None:
            self._tagmanager.tag(var, tag)

        return var

    def _newParameter(self, name, value, check=True):
        """Overloaded to tag variables.

        See RecipeOrganizer._newParameter
        """
        par = RecipeOrganizer._newParameter(self, name, value, check)
        # tag this
        self._tagmanager.tag(par, par.name)
        self._tagmanager.tag(par, "all")
        self.fix(par.name)
        return par


    def __getVarAndCheck(self, var):
        """Get the actual variable from var

        var     --  A variable of the FitRecipe, or the name of a variable.

        Returns the variable or None if the variable cannot be found in the
        _parameters list.
        """
        if isinstance(var, basestring):
            var = self._parameters.get(var)

        if var not in self._parameters.values():
            raise ValueError("Passed variable is not part of the FitRecipe")

        return var

    def __getVarsFromArgs(self, *args, **kw):
        """Get a list of variables from passed arguments.

        This method accepts string or variable arguments. An argument of "all"
        selects all variables. Keyword arguments must be parameter names,
        followed by a value to assign to the fixed variable. This method is
        used by the fix and free methods.

        Raises ValueError if an unknown variable, name or tag is passed, or if
        a tag is passed in a keyword.
        """
        # Process args. Each variable is tagged with its name, so this is easy.
        strargs = set([arg for arg in args if isinstance(arg, basestring)])
        varargs = set(args) - strargs
        # Check that the tags are valid
        alltags = set(self._tagmanager.alltags())
        badtags = strargs - alltags
        if badtags:
            names = ",".join(badtags)
            raise ValueError("Variables or tags cannot be found (%s)"% names)

        # Check that variables are valid
        allvars = set(self._parameters.values())
        badvars = varargs - allvars
        if badvars:
            names = ",".join(v.name for v in badvars)
            raise ValueError("Variables cannot be found (%s)"% names)

        # Make sure that we only have parameters in kw
        kwnames = set(kw.keys())
        allnames = set(self._parameters.keys())
        badkw = kwnames - allnames
        if badkw:
            names = ",".join(badkw)
            raise ValueError("Tags cannot be passed as keywords (%s)"% names)

        # Now get all the objects referred to in the arguments.
        varargs |= self._tagmanager.union(*strargs)
        varargs |= self._tagmanager.union(*kw.keys())
        return varargs

    def fix(self, *args, **kw):
        """Fix a parameter by reference, name or tag.

        A fixed variable is not refined.  Variables are free by default.

        This method accepts string or variable arguments. An argument of "all"
        selects all variables. Keyword arguments must be parameter names,
        followed by a value to assign to the fixed variable.

        Raises ValueError if an unknown Parameter, name or tag is passed, or if
        a tag is passed in a keyword.
        """
        # Check the inputs and get the variables from them
        varargs = self.__getVarsFromArgs(*args, **kw)


        # Fix all of these
        for var in varargs:
            self._tagmanager.tag(var, self._fixedtag)

        # Set the kw values
        for name, val in kw.items():
            self.get(name).value = val

        return

    def free(self, *args, **kw):
        """Free a parameter by reference, name or tag.

        A free variable is refined.  Variables are free by default. Constrained
        variables are not free.

        This method accepts string or variable arguments. An argument of "all"
        selects all variables. Keyword arguments must be parameter names,
        followed by a value to assign to the fixed variable.

        Raises ValueError if an unknown Parameter, name or tag is passed, or if
        a tag is passed in a keyword.
        """
        # Check the inputs and get the variables from them
        varargs = self.__getVarsFromArgs(*args, **kw)

        # Free all of these
        for var in varargs:
            if not var.constrained:
                self._tagmanager.untag(var, self._fixedtag)

        # Set the kw values
        for name, val in kw.items():
            self.get(name).value = val

        return

    def isFree(self, var):
        """Check if a variable is fixed."""
        return (not self._tagmanager.hasTags(var, self._fixedtag))

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

        This removes any constraints on a Parameter. If the Parameter is also a
        variable of the recipe, it will be freed as well.

        *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 par in self._parameters.values():
                self._tagmanager.untag(par, self._fixedtag)

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

        return

    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.

        This is overloaded to set the value of con if it represents a variable
        and its current value is None. A constrained variable will be set as
        fixed.

        par     --  The 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 eqstr, but not part of this object (default {}).

        Raises ValueError if ns uses a name that is already used for a
        variable.
        Raises ValueError if eqstr depends on a Parameter that is not part of
        the FitRecipe and that is not defined 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 '%s' cannot be found"%name)

        if con in self._parameters.keys():
            con = self._parameters[con]

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

        # This will pass the value of a constrained parameter to the initial
        # value of a parameter constraint.
        if con in self._parameters.values():
            val = con.getValue()
            if val is None:
                val = par.getValue()
                con.setValue(val)

        if par in self._parameters.values():
            self.fix(par)

        RecipeOrganizer.constrain(self, par, con, ns)
        return


    def getValues(self):
        """Get the current values of the variables in a list."""
        return array([v.value for v in self._parameters.values() if
            self.isFree(v)])

    def getNames(self):
        """Get the names of the variables in a list."""
        return [v.name for v in self._parameters.values() if self.isFree(v)]

    def getBounds(self):
        """Get the bounds on variables in a list.

        Returns a list of (lb, ub) pairs, where lb is the lower bound and ub is
        the upper bound.
        """
        return [v.bounds for v in self._parameters.values() if self.isFree(v)]

    def getBounds2(self):
        """Get the bounds on variables in two lists.

        Returns lower- and upper-bound lists of variable bounds.
        """
        bounds = self.getBounds()
        lb = array([b[0] for b in bounds])
        ub = array([b[1] for b in bounds])
        return lb, ub

    def boundsToRestraints(self, sig = 1, scaled = False):
        """Turn all bounded parameters into restraints.

        The bounds become limits on the restraint.

        sig     --  The uncertainty on the bounds (scalar or iterable,
                    default 1).
        scaled  --  Scale the restraints, see restrain.
        """
        pars = self._parameters.values()
        if not hasattr(sig, "__iter__"):
            sig = [sig] * len(pars)
        for par, x in zip(pars, sig):
            self.restrain(par, par.bounds[0], par.bounds[1], sig = x,
                    scaled = scaled)
        return

    def _applyValues(self, p):
        """Apply variable values to the variables."""
        if len(p) == 0: return
        vargen = (v for v in self._parameters.values() if self.isFree(v))
        for var, pval in zip(vargen, p):
            var.setValue(pval)
        return

    def _updateConfiguration(self):
        """Notify RecipeContainers in hierarchy of configuration change."""
        self._ready = False
        return
Example #35
0
class FitResults(object):
    """Class for processing, presenting and storing results of a fit.

    Attributes
    recipe      --  The recipe containing the results.
    cov         --  The covariance matrix from the recipe.
    conresults  --  An ordered dictionary of ContributionResults for each
                    FitContribution, indexed by the FitContribution name.
    derivstep   --  The fractional step size for calculating numeric
                    derivatives. Default 1e-8.
    varnames    --  Names of the variables in the recipe.
    varvals     --  Values of the variables in the recipe.
    varunc      --  Uncertainties in the variable values.
    showfixed   --  Show fixed variables (default True).
    fixednames  --  Names of the fixed variables of the recipe.
    fixedvals   --  Values of the fixed variables of the recipe.
    showcon     --  Show constraint values in the output (default False).
    connames    --  Names of the constrained parameters.
    convals     --  Values of the constrained parameters.
    conunc      --  Uncertainties in the constraint values.
    residual    --  The scalar residual of the recipe.
    penalty     --  The penalty to residual from the restraints.
    chi2        --  The chi2 of the recipe.
    cumchi2     --  The cumulative chi2 of the recipe.
    rchi2       --  The reduced chi2 of the recipe.
    rw          --  The Rw of the recipe.
    cumrw       --  The cumulative Rw of the recipe.
    messages    --  A list of messages about the results.
    precision   --  The precision of numeric output (default 8).
    _dcon       --  The derivatives of the constraint equations with respect to
                    the variables. This is used internally.

    Each of these attributes, except the recipe, are created or updated when
    the update method is called.

    """
    def __init__(self, recipe, update=True, showfixed=True, showcon=False):
        """Initialize the attributes.

        recipe   --  The recipe containing the results
        update  --  Flag indicating whether to do an immediate update (default
                    True).
        showcon --  Show fixed variables in the output (default True).
        showcon --  Show constraint values in the output (default False).

        """
        self.recipe = recipe
        self.conresults = OrderedDict()
        self.derivstep = 1e-8
        self.varnames = []
        self.varvals = []
        self.varunc = []
        self.fixednames = []
        self.fixedvals = []
        self.connames = []
        self.convals = []
        self.conunc = []
        self.cov = None
        self.residual = 0
        self.penalty = 0
        self.chi2 = 0
        self.rchi2 = 0
        self.rw = 0
        self.precision = 8
        self._dcon = []
        self.messages = []

        self.showfixed = bool(showfixed)
        self.showcon = bool(showcon)

        if update:
            self.update()
        return

    def update(self):
        """Update the results according to the current state of the recipe."""
        ## Note that the order of these operations are chosen to reduce
        ## computation time.

        recipe = self.recipe

        if not recipe._contributions:
            return

        # Make sure everything is ready for calculation
        recipe._prepare()

        # Store the variable names and values
        self.varnames = recipe.getNames()
        self.varvals = recipe.getValues()
        fixedpars = recipe._tagmanager.union(recipe._fixedtag)
        fixedpars = [p for p in fixedpars if not p.constrained]
        self.fixednames = [p.name for p in fixedpars]
        self.fixedvals = [p.value for p in fixedpars]

        # Store the constraint information
        self.connames = [con.par.name for con in recipe._oconstraints]
        self.convals = [con.par.getValue() for con in recipe._oconstraints]

        if self.varnames:
            # Calculate the covariance
            self._calculateCovariance()

            # Get the variable uncertainties
            self.varunc = [self.cov[i,i]**0.5 for i in \
                    range(len(self.varnames))]

            # Get the constraint uncertainties
            self._calculateConstraintUncertainties()

        # Store the fitting arrays and metrics for each FitContribution.
        self.conresults = OrderedDict()
        for con, weight in zip(recipe._contributions.values(),
                               recipe._weights):
            self.conresults[con.name] = ContributionResults(con, weight, self)

        # Calculate the metrics
        res = recipe.residual()
        self.residual = numpy.dot(res, res)
        self._calculateMetrics()

        # Calcualte the restraints penalty
        w = self.chi2 / len(res)
        self.penalty = sum([r.penalty(w) for r in recipe._restraintlist])

        return

    def _calculateCovariance(self):
        """Calculate the covariance matrix. This is called by update.

        This code borrowed from PARK. It finds the pseudo-inverse of the
        Jacobian using the singular value decomposition.

        """
        try:
            J = self._calculateJacobian()
            u, s, vh = numpy.linalg.svd(J, 0)
            self.cov = numpy.dot(vh.T.conj() / s**2, vh)
        except numpy.linalg.LinAlgError:
            self.messages.append("Cannot compute covariance matrix.")
            l = len(self.varvals)
            self.cov = numpy.zeros((l, l), dtype=float)
        return

    def _calculateJacobian(self):
        """Calculate the Jacobian for the fitting.

        Adapted from PARK.
        Returns the derivative wrt the fit variables at point p.

        This also calculates the derivatives of the constrained parameters
        while we're at it.

        Numeric derivatives are calculated based on step, where step is the
        portion of variable value. E.g. step = dv/v.

        """
        recipe = self.recipe
        step = self.derivstep

        # Make sure the input vector is an array
        pvals = numpy.asarray(self.varvals)
        # Compute the numeric derivative using the center point formula.
        delta = step * pvals

        # Center point formula:
        #     df/dv = lim_{h->0} ( f(v+h)-f(v-h) ) / ( 2h )
        #

        r = []
        # The list of constraint derivatives with respect to variables
        # The forward difference would be faster, but perhaps not as accurate.
        conr = []
        for k, v in enumerate(pvals):
            h = delta[k]
            pvals[k] = v + h
            rk = self.recipe.residual(pvals)

            # The constraints derivatives
            cond = []
            for con in recipe._oconstraints:
                con.update()
                cond.append(con.par.getValue())

            pvals[k] = v - h
            rk -= self.recipe.residual(pvals)

            # FIXME - constraints are used for vectors as well!
            for i, con in enumerate(recipe._oconstraints):
                con.update()
                val = con.par.getValue()
                if numpy.isscalar(val):
                    cond[i] -= con.par.getValue()
                    cond[i] /= 2 * h
                else:
                    cond[i] = 0.0

            conr.append(cond)

            pvals[k] = v
            r.append(rk / (2 * h))

        # Reset the constrained parameters to their original values
        for con in recipe._oconstraints:
            con.update()

        self._dcon = numpy.vstack(conr).T

        # return the jacobian
        jac = numpy.vstack(r).T
        return jac

    def _calculateMetrics(self):
        """Calculate chi2, cumchi2, rchi2, rw and cumrw for the recipe."""
        cumchi2 = numpy.array([], dtype=float)
        # total weighed denominator for the ratio in the Rw formula
        yw2tot = 0.0
        numpoints = 0
        for con in self.conresults.values():
            cc2w = con.weight * con.cumchi2
            c2last = cumchi2[-1:].sum()
            cumchi2 = numpy.concatenate([cumchi2, c2last + cc2w])
            yw2tot += con.weight * (con.chi2 / con.rw**2)
            numpoints += len(con.x)

        chi2 = cumchi2[-1:].sum()
        cumrw = numpy.sqrt(cumchi2 / yw2tot)
        rw = cumrw[-1:].sum()

        numpoints += len(self.recipe._restraintlist)
        rchi2 = chi2 / (numpoints - len(self.varnames))

        self.chi2 = chi2
        self.rchi2 = rchi2
        self.rw = rw
        self.cumchi2 = cumchi2
        self.cumrw = cumrw
        return

    def _calculateConstraintUncertainties(self):
        """Calculate the uncertainty on the constrained parameters."""
        vu = self.varunc

        # sig^2(c) = sum_i sum_j sig(v_i) sig(v_j) (dc/dv_i)(dc/dv_j)
        # sig^2(c) = sum_i sum_j [sig(v_i)(dc/dv_i)][sig(v_j)(dc/dv_j)]
        # sig^2(c) = sum_i sum_j u_i u_j
        self.conunc = []
        for dci in self._dcon:

            # Create sig(v_i) (dc/dv_i) array.
            u = dci * vu
            # The outer product is all possible pairings of u_i and u_j
            # uu_ij = u_i u_j
            uu = numpy.outer(u, u)
            # Sum these pairings to get sig^2(c)
            sig2c = sum(uu.flatten())

            self.conunc.append(sig2c**0.5)
        return

    def formatResults(self, header="", footer="", update=False):
        """Format the results and return them in a string.

        This function is called by printResults and saveResults. Overloading
        the formatting here will change all three functions.

        header  --  A header to add to the output (default "")
        footer  --  A footer to add to the output (default "")
        update  --  Flag indicating whether to call update() (default False).

        Returns a string containing the formatted results.

        """
        if update:
            self.update()

        lines = []
        corrmin = 0.25
        p = self.precision
        pe = "%-" + "%i.%ie" % (p + 6, p)
        pet = "%" + ".%ie" % (p, )
        # Check to see if the uncertainty values are reliable.
        certain = True
        for con in self.conresults.values():
            if (con.dy == 1).all():
                certain = False
                break

        # User-defined header
        if header:
            lines.append(header)

        if not certain:
            l = "Some quantities invalid due to missing profile uncertainty"
            if not l in self.messages:
                self.messages.append(l)

        lines.extend(self.messages)

        ## Overall results
        l = "Overall"
        if not certain:
            l += " (Chi2 and Reduced Chi2 invalid)"
        lines.append(l)
        dashedline = 79 * '-'
        lines.append(dashedline)
        formatstr = "%-14s %.8f"
        lines.append(formatstr % ("Residual", self.residual))
        lines.append(formatstr %
                     ("Contributions", self.residual - self.penalty))
        lines.append(formatstr % ("Restraints", self.penalty))
        lines.append(formatstr % ("Chi2", self.chi2))
        lines.append(formatstr % ("Reduced Chi2", self.rchi2))
        lines.append(formatstr % ("Rw", self.rw))

        ## Per-FitContribution results
        if len(self.conresults) > 1:
            keys = self.conresults.keys()
            numericStringSort(keys)

            lines.append("")
            l = "Contributions"
            if not certain:
                l += " (Chi2 and Reduced Chi2 invalid)"
            lines.append(l)
            lines.append(dashedline)
            formatstr = "%-10s %-42.8f"
            for name in keys:
                res = self.conresults[name]
                lines.append("")
                namestr = name + " (%f)" % res.weight
                lines.append(namestr)
                lines.append("-" * len(namestr))
                lines.append(formatstr % ("Residual", res.residual))
                lines.append(formatstr % ("Chi2", res.chi2))
                lines.append(formatstr % ("Rw", res.rw))

        ## The variables
        if self.varnames:
            lines.append("")
            l = "Variables"
            if not certain:
                m = "Uncertainties invalid"
                l += " (%s)" % m
            lines.append(l)
            lines.append(dashedline)

            varnames = self.varnames
            varvals = self.varvals
            varunc = self.varunc
            varlines = []

            w = max(map(len, varnames))
            w = str(w + 1)
            # Format the lines
            formatstr = "%-" + w + "s " + pe + " +/- " + pet
            for name, val, unc in zip(varnames, varvals, varunc):
                varlines.append(formatstr % (name, val, unc))

            varlines.sort()
            lines.extend(varlines)

        # Fixed variables
        if self.showfixed and self.fixednames:
            varlines = []
            lines.append("")
            lines.append("Fixed Variables")
            lines.append(dashedline)
            w = max(map(len, self.fixednames))
            w = str(w + 1)
            formatstr = "%-" + w + "s " + pet
            for name, val in zip(self.fixednames, self.fixedvals):
                varlines.append(formatstr % (name, val))
            varlines.sort()
            lines.extend(varlines)

        ## The constraints
        if self.connames and self.showcon:
            lines.append("")
            l = "Constrained Parameters"
            if not certain:
                l += " (Uncertainties invalid)"
            lines.append(l)
            lines.append(dashedline)

            w = 0
            keys = []
            vals = {}
            for con in self.conresults.values():
                for i, loc in enumerate(con.conlocs):
                    names = [obj.name for obj in loc]
                    name = ".".join(names)
                    w = max(w, len(name))
                    val = con.convals[i]
                    unc = con.conunc[i]
                    keys.append(name)
                    vals[name] = (val, unc)

            numericStringSort(keys)
            w = str(w + 1)
            formatstr = "%-" + w + "s %- 15f +/- %-15f"
            for name in keys:
                val, unc = vals[name]
                lines.append(formatstr % (name, val, unc))

        ## Variable correlations
        lines.append("")
        corint = int(corrmin * 100)
        l = "Variable Correlations greater than %i%%" % corint
        if not certain:
            l += " (Correlations invalid)"
        lines.append(l)
        lines.append(dashedline)
        tup = []
        cornames = []
        n = len(self.varnames)
        for i in xrange(n):
            for j in xrange(i + 1, n):
                name = "corr(%s, %s)" % (varnames[i], varnames[j])
                val = (self.cov[i, j] / (self.cov[i, i] * self.cov[j, j])**0.5)
                if abs(val) > corrmin:
                    cornames.append(name)
                    tup.append((val, name))

        tup.sort(key=lambda vn: abs(vn[0]))
        tup.reverse()

        if cornames:
            w = max(map(len, cornames))
            w = str(w + 1)
            formatstr = "%-" + w + "s  %.4f"
            for val, name in tup:
                lines.append(formatstr % (name, val))
        else:
            lines.append("No correlations greater than %i%%" % corint)

        # User-defined footer
        if footer:
            lines.append(footer)

        out = "\n".join(lines) + '\n'
        return out

    def printResults(self, header="", footer="", update=False):
        """Format and print the results.

        header  --  A header to add to the output (default "")
        footer  --  A footer to add to the output (default "")
        update  --  Flag indicating whether to call update() (default False).

        """
        print self.formatResults(header, footer, update).rstrip()
        return

    def __str__(self):
        return self.formatResults()

    def saveResults(self, filename, header="", footer="", update=False):
        """Format and save the results.

        filename -  Name of the save file.
        header  --  A header to add to the output (default "")
        footer  --  A footer to add to the output (default "")
        update  --  Flag indicating whether to call update() (default False).

        """
        # Save the time and user
        from time import ctime
        from getpass import getuser
        myheader = "Results written: " + ctime() + "\n"
        myheader += "produced by " + getuser() + "\n"
        header = myheader + header

        res = self.formatResults(header, footer, update)
        f = file(filename, 'w')
        f.write(res)
        f.close()
        return
    def test_update(self):
        self.assertRaises(TypeError, OrderedDict().update, [('a', 1), ('b',
            2)], None)                        # too many args
        pairs = [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)]
        od = OrderedDict()
        od.update(dict(pairs))
        self.assertEqual(sorted(od.items()), pairs)                                 # dict input
        od = OrderedDict()
        od.update(**dict(pairs))
        self.assertEqual(sorted(od.items()), pairs)                                 # kwds input
        od = OrderedDict()
        od.update(pairs)
        self.assertEqual(list(od.items()), pairs)                                   # pairs input
        od = OrderedDict()
        od.update([('a', 1), ('b', 2), ('c', 9), ('d', 4)], c=3, e=5)
        self.assertEqual(list(od.items()), pairs)                                   # mixed input

        # Make sure that direct calls to update do not clear previous contents
        # add that updates items are not moved to the end
        d = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 44), ('e', 55)])
        d.update([('e', 5), ('f', 6)], g=7, d=4)
        self.assertEqual(list(d.items()),
            [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7)])
Example #37
0
class Equation(Operator):
    """Class for holding and evaluating a Literal tree.

    Instances have attributes that are the non-const Arguments of the tree
    (accessed by name) and a __call__ method that evaluates the tree.  It is
    assumed, but not enforced that Arguments have unique names.  If this is not
    the case, then one should keep its own list of Arguments.

    The tree is scanned for errors when it is added. Thus, the tree should be
    complete before putting it inside an Equation.

    Equations can act as Operator nodes within a literal tree. In this context,
    they evaluate as the root node, but provide a calling interface that
    accepts new argument values for the literal tree.

    Attributes
    root    --  The root Literal of the equation tree
    argdict --  An OrderedDict of Arguments from the root.
    args    --  Property that gets the values of argdict.

    Operator Attributes
    args    --  List of Literal arguments, set with 'addLiteral'
    name    --  A name for this operator. e.g. "add" or "sin"
    nin     --  Number of inputs (<1 means this is variable)
    nout    --  Number of outputs
    operation   --  Function that performs the operation. e.g. numpy.add.
    symbol  --  The symbolic representation. e.g. "+" or "sin".
    _value  --  The value of the Operator.
    value   --  Property for 'getValue'.

    """

    def __init__(self, name = None, root = None):
        """Initialize.

        name    --  A name for this Equation.
        root    --  The root node of the Literal tree (default None). If root
                    is not passed here, you must call the 'setRoot' method to
                    set or change the root node.

        """
        # Operator stuff. We circumvent Operator.__init__ since we're using
        # args as a property. We cannot set it, as the Operator tries to do.
        if name is None and root is not None:
            name = "eq_%s"%root.name
        Literal.__init__(self, name)
        self.symbol = name
        self.nin = None
        self.nout = 1
        self.operation = self.__call__

        self.root = None
        self.argdict = OrderedDict()
        if root is not None:
            self.setRoot(root)

        return

    def _getArgs(self):
        return self.argdict.values()

    args = property(_getArgs)

    def __getattr__(self, name):
        """Gives access to the Arguments as attributes."""
        arg = self.argdict.get(name)
        if arg is None:
            raise AttributeError("No argument named '%s' here"%name)
        return arg

    def setRoot(self, root):
        """Set the root of the Literal tree.

        Raises:
        ValueError if errors are found in the Literal tree.

        """

        # Validate the new root
        validate(root)

        # Stop observing the old root
        if self.root is not None:
            self.root.removeObserver(self._flush)

        # Add the new root
        self.root = root
        self.root.addObserver(self._flush)
        self._flush(self)

        # Get the args
        args = getArgs(root, getconsts=False)
        self.argdict = OrderedDict( [(arg.name, arg) for arg in args] )

        # Set Operator attributes
        self.nin = len(self.args)

        return

    def __call__(self, *args, **kw):
        """Call the equation.

        New Argument values are acceped as arguments or keyword assignments (or
        both).  The order of accepted arguments is given by the args
        attribute.  The Equation will remember values set in this way.

        Raises
        ValueError when a passed argument cannot be found

        """
        # Process args
        for idx, val in enumerate(args):
            if idx > len(self.argdict):
                raise ValueError("Too many arguments")
            arg = self.args[idx]
            arg.setValue(val)

        # Process kw
        for name, val in kw.items():
            arg = self.argdict.get(name)
            if arg is None:
                raise ValueError("No argument named '%s' here"%name)
            arg.setValue(val)

        self._value = self.root.getValue()
        return self._value

    def swap(self, oldlit, newlit):
        """Swap a literal in the equation for another.

        Note that this may change the root and the operation interface

        """
        newroot = swap(self.root, oldlit, newlit)
        self.setRoot(newroot)
        return

    # Operator methods

    def addLiteral(self, literal):
        """Cannot add a literal to an Equation."""
        raise AttributeError("Cannot add literals to an Equation.")

    # Visitors can treat us differently than an Operator.

    def identify(self, visitor):
        """Identify self to a visitor."""
        return visitor.onEquation(self)
Example #38
0
class FitResults(object):
    """Class for processing, presenting and storing results of a fit.

    Attributes
    recipe      --  The recipe containing the results.
    cov         --  The covariance matrix from the recipe.
    conresults  --  An ordered dictionary of ContributionResults for each
                    FitContribution, indexed by the FitContribution name.
    derivstep   --  The fractional step size for calculating numeric
                    derivatives. Default 1e-8.
    varnames    --  Names of the variables in the recipe.
    varvals     --  Values of the variables in the recipe.
    varunc      --  Uncertainties in the variable values.
    showfixed   --  Show fixed variables (default True).
    fixednames  --  Names of the fixed variables of the recipe.
    fixedvals   --  Values of the fixed variables of the recipe.
    showcon     --  Show constraint values in the output (default False).
    connames    --  Names of the constrained parameters.
    convals     --  Values of the constrained parameters.
    conunc      --  Uncertainties in the constraint values.
    residual    --  The scalar residual of the recipe.
    penalty     --  The penalty to residual from the restraints.
    chi2        --  The chi2 of the recipe.
    cumchi2     --  The cumulative chi2 of the recipe.
    rchi2       --  The reduced chi2 of the recipe.
    rw          --  The Rw of the recipe.
    cumrw       --  The cumulative Rw of the recipe.
    messages    --  A list of messages about the results.
    precision   --  The precision of numeric output (default 8).
    _dcon       --  The derivatives of the constraint equations with respect to
                    the variables. This is used internally.

    Each of these attributes, except the recipe, are created or updated when
    the update method is called.

    """

    def __init__(self, recipe, update = True, showfixed = True, showcon =
            False):
        """Initialize the attributes.

        recipe   --  The recipe containing the results
        update  --  Flag indicating whether to do an immediate update (default
                    True).
        showcon --  Show fixed variables in the output (default True).
        showcon --  Show constraint values in the output (default False).

        """
        self.recipe = recipe
        self.conresults = OrderedDict()
        self.derivstep = 1e-8
        self.varnames = []
        self.varvals = []
        self.varunc = []
        self.fixednames = []
        self.fixedvals = []
        self.connames = []
        self.convals = []
        self.conunc = []
        self.cov = None
        self.residual = 0
        self.penalty = 0
        self.chi2 = 0
        self.rchi2 = 0
        self.rw = 0
        self.precision = 8
        self._dcon = []
        self.messages = []

        self.showfixed = bool(showfixed)
        self.showcon = bool(showcon)

        if update:
            self.update()
        return

    def update(self):
        """Update the results according to the current state of the recipe."""
        ## Note that the order of these operations are chosen to reduce
        ## computation time.

        recipe = self.recipe

        if not recipe._contributions:
            return

        # Make sure everything is ready for calculation
        recipe._prepare()

        # Store the variable names and values
        self.varnames = recipe.getNames()
        self.varvals = recipe.getValues()
        fixedpars = recipe._tagmanager.union(recipe._fixedtag)
        fixedpars = [p for p in fixedpars if not p.constrained]
        self.fixednames = [p.name for p in fixedpars]
        self.fixedvals = [p.value for p in fixedpars]

        # Store the constraint information
        self.connames = [con.par.name for con in recipe._oconstraints]
        self.convals = [con.par.getValue() for con in recipe._oconstraints]

        if self.varnames:
            # Calculate the covariance
            self._calculateCovariance()

            # Get the variable uncertainties
            self.varunc = [self.cov[i,i]**0.5 for i in \
                    range(len(self.varnames))]

            # Get the constraint uncertainties
            self._calculateConstraintUncertainties()

        # Store the fitting arrays and metrics for each FitContribution.
        self.conresults = OrderedDict()
        for con, weight in zip(recipe._contributions.values(), recipe._weights):
            self.conresults[con.name] = ContributionResults(con, weight, self)

        # Calculate the metrics
        res = recipe.residual()
        self.residual = numpy.dot(res, res)
        self._calculateMetrics()

        # Calcualte the restraints penalty
        w = self.chi2 / len(res)
        self.penalty = sum([res.penalty(w) for res in recipe._restraintlist])

        return

    def _calculateCovariance(self):
        """Calculate the covariance matrix. This is called by update.

        This code borrowed from PARK. It finds the pseudo-inverse of the
        Jacobian using the singular value decomposition.

        """
        try:
            J = self._calculateJacobian()
            u,s,vh = numpy.linalg.svd(J,0)
            self.cov = numpy.dot(vh.T.conj()/s**2,vh)
        except numpy.linalg.LinAlgError:
            self.messages.append("Cannot compute covariance matrix.")
            l = len(self.varvals)
            self.cov = numpy.zeros((l, l), dtype=float)
        return

    def _calculateJacobian(self):
        """Calculate the Jacobian for the fitting.

        Adapted from PARK.
        Returns the derivative wrt the fit variables at point p.

        This also calculates the derivatives of the constrained parameters
        while we're at it.

        Numeric derivatives are calculated based on step, where step is the
        portion of variable value. E.g. step = dv/v.

        """
        recipe = self.recipe
        step = self.derivstep

        # Make sure the input vector is an array
        pvals = numpy.asarray(self.varvals)
        # Compute the numeric derivative using the center point formula.
        delta = step * pvals

        # Center point formula:
        #     df/dv = lim_{h->0} ( f(v+h)-f(v-h) ) / ( 2h )
        #

        r = []
        # The list of constraint derivatives with respect to variables
        # The forward difference would be faster, but perhaps not as accurate.
        conr = []
        for k,v in enumerate(pvals):
            h = delta[k]
            pvals[k] = v + h
            rk = self.recipe.residual(pvals)

            # The constraints derivatives
            cond = []
            for con in recipe._oconstraints:
                con.update()
                cond.append(con.par.getValue())

            pvals[k] = v - h
            rk -= self.recipe.residual(pvals)

            # FIXME - constraints are used for vectors as well!
            for i, con in enumerate(recipe._oconstraints):
                con.update()
                val = con.par.getValue()
                if numpy.isscalar(val):
                    cond[i] -= con.par.getValue()
                    cond[i] /= 2*h
                else:
                    cond[i] = 0.0

            conr.append(cond)

            pvals[k] = v
            r.append(rk/(2*h))

        # Reset the constrained parameters to their original values
        for con in recipe._oconstraints:
            con.update()

        self._dcon = numpy.vstack(conr).T

        # return the jacobian
        jac = numpy.vstack(r).T
        return jac

    def _calculateMetrics(self):
        """Calculate chi2, cumchi2, rchi2, rw and cumrw for the recipe."""
        cumchi2 = numpy.array([], dtype=float)
        # total weighed denominator for the ratio in the Rw formula
        yw2tot = 0.0
        numpoints = 0
        for con in self.conresults.values():
            cc2w = con.weight * con.cumchi2
            c2last = cumchi2[-1:].sum()
            cumchi2 = numpy.concatenate([cumchi2, c2last + cc2w])
            yw2tot += con.weight * (con.chi2 / con.rw**2)
            numpoints += len(con.x)

        chi2 = cumchi2[-1:].sum()
        cumrw = numpy.sqrt(cumchi2 / yw2tot)
        rw = cumrw[-1:].sum()

        numpoints += len(self.recipe._restraintlist)
        rchi2 = chi2 / (numpoints - len(self.varnames))

        self.chi2 = chi2
        self.rchi2 = rchi2
        self.rw = rw
        self.cumchi2 = cumchi2
        self.cumrw = cumrw
        return

    def _calculateConstraintUncertainties(self):
        """Calculate the uncertainty on the constrained parameters."""
        vu = self.varunc

        # sig^2(c) = sum_i sum_j sig(v_i) sig(v_j) (dc/dv_i)(dc/dv_j)
        # sig^2(c) = sum_i sum_j [sig(v_i)(dc/dv_i)][sig(v_j)(dc/dv_j)]
        # sig^2(c) = sum_i sum_j u_i u_j
        self.conunc = []
        for dci in self._dcon:

            # Create sig(v_i) (dc/dv_i) array.
            u = dci * vu
            # The outer product is all possible pairings of u_i and u_j
            # uu_ij = u_i u_j
            uu = numpy.outer(u, u)
            # Sum these pairings to get sig^2(c)
            sig2c = sum(uu.flatten())

            self.conunc.append(sig2c**0.5)
        return

    def formatResults(self, header = "", footer = "", update = False):
        """Format the results and return them in a string.

        This function is called by printResults and saveResults. Overloading
        the formatting here will change all three functions.

        header  --  A header to add to the output (default "")
        footer  --  A footer to add to the output (default "")
        update  --  Flag indicating whether to call update() (default False).

        Returns a string containing the formatted results.

        """
        if update:
            self.update()

        lines = []
        corrmin = 0.25
        p = self.precision
        pe = "%-" + "%i.%ie" % (p+6, p)
        pet = "%" + ".%ie" % (p,)
        # Check to see if the uncertainty values are reliable.
        certain = True
        for con in self.conresults.values():
            if (con.dy == 1).all():
                certain = False
                break

        # User-defined header
        if header:
            lines.append(header)

        if not certain:
            l = "Some quantities invalid due to missing profile uncertainty"
            if not l in self.messages:
                self.messages.append(l)

        lines.extend(self.messages)

        ## Overall results
        l = "Overall"
        if not certain:
            l += " (Chi2 and Reduced Chi2 invalid)"
        lines.append(l)
        dashedline = 79 * '-'
        lines.append(dashedline)
        formatstr = "%-14s %.8f"
        lines.append(formatstr%("Residual",self.residual))
        lines.append(formatstr%("Contributions", self.residual - self.penalty))
        lines.append(formatstr%("Restraints", self.penalty))
        lines.append(formatstr%("Chi2",self.chi2))
        lines.append(formatstr%("Reduced Chi2",self.rchi2))
        lines.append(formatstr%("Rw",self.rw))

        ## Per-FitContribution results
        if len(self.conresults) > 1:
            keys = self.conresults.keys()
            numericStringSort(keys)

            lines.append("")
            l = "Contributions"
            if not certain:
                l += " (Chi2 and Reduced Chi2 invalid)"
            lines.append(l)
            lines.append(dashedline)
            formatstr = "%-10s %-42.8f"
            for name in keys:
                res = self.conresults[name]
                lines.append("")
                namestr = name + " (%f)"%res.weight
                lines.append(namestr)
                lines.append("-"*len(namestr))
                lines.append(formatstr%("Residual",res.residual))
                lines.append(formatstr%("Chi2",res.chi2))
                lines.append(formatstr%("Rw",res.rw))

        ## The variables
        if self.varnames:
            lines.append("")
            l = "Variables"
            if not certain:
                m = "Uncertainties invalid"
                l += " (%s)"%m
            lines.append(l)
            lines.append(dashedline)

            varnames = self.varnames
            varvals = self.varvals
            varunc = self.varunc
            varlines = []

            w = max(map(len, varnames))
            w = str(w+1)
            # Format the lines
            formatstr = "%-"+w+"s " + pe + " +/- " + pet
            for name, val, unc in zip(varnames, varvals, varunc):
                varlines.append(formatstr%(name, val, unc))

            varlines.sort()
            lines.extend(varlines)

        # Fixed variables
        if self.showfixed and self.fixednames:
            varlines = []
            lines.append("")
            lines.append("Fixed Variables")
            lines.append(dashedline)
            w = max(map(len, self.fixednames))
            w = str(w+1)
            formatstr = "%-"+w+"s " + pet
            for name, val in zip(self.fixednames, self.fixedvals):
                varlines.append(formatstr%(name, val))
            varlines.sort()
            lines.extend(varlines)


        ## The constraints
        if self.connames and self.showcon:
            lines.append("")
            l = "Constrained Parameters"
            if not certain:
                l += " (Uncertainties invalid)"
            lines.append(l)
            lines.append(dashedline)

            w = 0
            keys = []
            vals = {}
            for con in self.conresults.values():
                for i, loc in enumerate(con.conlocs):
                    names = [obj.name for obj in loc]
                    name = ".".join(names)
                    w = max(w, len(name))
                    val = con.convals[i]
                    unc = con.conunc[i]
                    keys.append(name)
                    vals[name] = (val, unc)

            numericStringSort(keys)
            w = str(w+1)
            formatstr = "%-"+w+"s %- 15f +/- %-15f"
            for name in keys:
                val, unc = vals[name]
                lines.append(formatstr%(name, val, unc))

        ## Variable correlations
        lines.append("")
        corint = int(corrmin*100)
        l = "Variable Correlations greater than %i%%"%corint
        if not certain:
            l += " (Correlations invalid)"
        lines.append(l)
        lines.append(dashedline)
        tup = []
        cornames = []
        n = len(self.varnames)
        for i in xrange(n):
            for j in xrange(i+1, n):
                name = "corr(%s, %s)"%(varnames[i], varnames[j])
                val = (self.cov[i,j]/(self.cov[i,i] * self.cov[j,j])**0.5)
                if val > corrmin:
                    cornames.append(name)
                    tup.append((val, name))

        tup.sort()
        tup.reverse()

        if cornames:
            w = max(map(len, cornames))
            w = str(w + 1)
            formatstr = "%-"+w+"s  %.4f"
            for val, name in tup:
                lines.append(formatstr%(name, val))
        else:
            lines.append("No correlations greater than %i%%"%corint)


        # User-defined footer
        if footer:
            lines.append(footer)

        out = "\n".join(lines) + '\n'
        return out

    def printResults(self, header = "", footer = "", update = False):
        """Format and print the results.

        header  --  A header to add to the output (default "")
        footer  --  A footer to add to the output (default "")
        update  --  Flag indicating whether to call update() (default False).

        """
        print self.formatResults(header, footer, update).rstrip()
        return

    def __str__(self):
        return self.formatResults()


    def saveResults(self, filename, header = "", footer = "", update = False):
        """Format and save the results.

        filename -  Name of the save file.
        header  --  A header to add to the output (default "")
        footer  --  A footer to add to the output (default "")
        update  --  Flag indicating whether to call update() (default False).

        """
        # Save the time and user
        from time import ctime
        from getpass import getuser
        myheader = "Results written: " + ctime() + "\n"
        myheader += "produced by " + getuser() + "\n"
        header = myheader + header

        res = self.formatResults(header, footer, update)
        f = file(filename, 'w')
        f.write(res)
        f.close()
        return
Example #39
0
class Equation(Operator):
    """Class for holding and evaluating a Literal tree.

    Instances have attributes that are the non-const Arguments of the tree
    (accessed by name) and a __call__ method that evaluates the tree.  It is
    assumed, but not enforced that Arguments have unique names.  If this is not
    the case, then one should keep its own list of Arguments.

    The tree is scanned for errors when it is added. Thus, the tree should be
    complete before putting it inside an Equation.

    Equations can act as Operator nodes within a literal tree. In this context,
    they evaluate as the root node, but provide a calling interface that
    accepts new argument values for the literal tree.

    Attributes
    root    --  The root Literal of the equation tree
    argdict --  An OrderedDict of Arguments from the root.
    args    --  Property that gets the values of argdict.

    Operator Attributes
    args    --  List of Literal arguments, set with 'addLiteral'
    name    --  A name for this operator. e.g. "add" or "sin"
    nin     --  Number of inputs (<1 means this is variable)
    nout    --  Number of outputs
    operation   --  Function that performs the operation. e.g. numpy.add.
    symbol  --  The symbolic representation. e.g. "+" or "sin".
    _value  --  The value of the Operator.
    value   --  Property for 'getValue'.
    """
    def __init__(self, name=None, root=None):
        """Initialize.

        name    --  A name for this Equation.
        root    --  The root node of the Literal tree (default None). If root
                    is not passed here, you must call the 'setRoot' method to
                    set or change the root node.

        """
        # Operator stuff. We circumvent Operator.__init__ since we're using
        # args as a property. We cannot set it, as the Operator tries to do.
        if name is None and root is not None:
            name = "eq_%s" % root.name
        Literal.__init__(self, name)
        self.symbol = name
        self.nin = None
        self.nout = 1
        self.operation = self.__call__

        self.root = None
        self.argdict = OrderedDict()
        if root is not None:
            self.setRoot(root)

        return

    def _getArgs(self):
        return self.argdict.values()

    args = property(_getArgs)

    def __getattr__(self, name):
        """Gives access to the Arguments as attributes."""
        # Avoid infinite loop on argdict lookup.
        argdict = object.__getattribute__(self, 'argdict')
        if not name in argdict:
            raise AttributeError("No argument named '%s' here" % name)
        return argdict[name]

    def setRoot(self, root):
        """Set the root of the Literal tree.

        Raises:
        ValueError if errors are found in the Literal tree.

        """

        # Validate the new root
        validate(root)

        # Stop observing the old root
        if self.root is not None:
            self.root.removeObserver(self._flush)

        # Add the new root
        self.root = root
        self.root.addObserver(self._flush)
        self._flush(other=(self, ))

        # Get the args
        args = getArgs(root, getconsts=False)
        self.argdict = OrderedDict([(arg.name, arg) for arg in args])

        # Set Operator attributes
        self.nin = len(self.args)

        return

    def __call__(self, *args, **kw):
        """Call the equation.

        New Argument values are acceped as arguments or keyword assignments (or
        both).  The order of accepted arguments is given by the args
        attribute.  The Equation will remember values set in this way.

        Raises
        ValueError when a passed argument cannot be found

        """
        # Process args
        for idx, val in enumerate(args):
            if idx > len(self.argdict):
                raise ValueError("Too many arguments")
            arg = self.args[idx]
            arg.setValue(val)

        # Process kw
        for name, val in kw.items():
            arg = self.argdict.get(name)
            if arg is None:
                raise ValueError("No argument named '%s' here" % name)
            arg.setValue(val)

        self._value = self.root.getValue()
        return self._value

    def swap(self, oldlit, newlit):
        """Swap a literal in the equation for another.

        Note that this may change the root and the operation interface

        """
        newroot = swap(self.root, oldlit, newlit)
        self.setRoot(newroot)
        return

    # Operator methods

    def addLiteral(self, literal):
        """Cannot add a literal to an Equation."""
        raise AttributeError("Cannot add literals to an Equation.")

    # Visitors can treat us differently than an Operator.

    def identify(self, visitor):
        """Identify self to a visitor."""
        return visitor.onEquation(self)
Example #40
0
 def test_setitem(self):
     od = OrderedDict([('d', 1), ('b', 2), ('c', 3), ('a', 4), ('e', 5)])
     od['c'] = 10  # existing element
     od['f'] = 20  # new element
     self.assertEqual(list(od.items()), [('d', 1), ('b', 2), ('c', 10),
                                         ('a', 4), ('e', 5), ('f', 20)])
Example #41
0
class RecipeContainer(Observable, Configurable, Validatable):
    """Base class for organizing pieces of a FitRecipe.

    RecipeContainers are hierarchical organizations of Parameters and other
    RecipeContainers. This class provides attribute-access to these contained
    objects.  Parameters and other RecipeContainers can be found within the
    hierarchy with the _locateManagedObject method.

    A RecipeContainer can manage dictionaries for that store various objects.
    These dictionaries can be added to the RecipeContainer using the _manage
    method. RecipeContainer methods that add, remove or retrieve objects will
    work with any managed dictionary. This makes it easy to add new types of
    objects to be contained by a RecipeContainer. By default, the
    RecipeContainer is configured to manage an OrderedDict of Parameter
    objects.

    RecipeContainer is an Observable, and observes its managed objects and
    Parameters. This allows hierarchical calculation elements, such as
    ProfileGenerator, to detect changes in Parameters and Restraints on which
    it may depend.

    Attributes
    name            --  A name for this RecipeContainer. Names should be unique
                        within a RecipeContainer and should be valid attribute
                        names.
    _parameters     --  A managed OrderedDict of contained Parameters.
    __managed       --  A list of managed dictionaries. This is used for
                        attribute access, addition and removal.
    _configobjs     --  A set of configurable objects that must know of
                        configuration changes within this object.

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

    names = property(lambda self: self.getNames())
    values = property(lambda self: self.getValues())

    def __init__(self, name):
        Observable.__init__(self)
        Configurable.__init__(self)
        validateName(name)
        self.name = name
        self._parameters = OrderedDict()

        self.__managed = []
        self._manage(self._parameters)

        return

    def _manage(self, d):
        """Manage a dictionary of objects.

        This adds the dictionary to the __managed list. Dictionaries in
        __managed are used for attribute access, addition, and removal.
        """
        self.__managed.append(d)
        return

    def _iterManaged(self):
        """Get iterator over managed objects."""
        return chain(*(d.values() for d in self.__managed))

    def iterPars(self, name=".", recurse=True):
        """Iterate over Parameters.

        name    --  Select parameters with this name (regular expression,
                    default ".").
        recurse --  Recurse into managed objects (default True)
        """
        for par in self._parameters.itervalues():
            if re.match(name, par.name):
                yield par

        if not recurse:
            return

        # Iterate over objects within the managed dictionaries.
        managed = self.__managed[:]
        managed.remove(self._parameters)
        for m in managed:
            for obj in m.values():
                if hasattr(obj, "iterPars"):
                    for par in obj.iterPars(name=name):
                        yield par

        return

    def __iter__(self):
        """Iterate over top-level parameters."""
        return self._parameters.itervalues()

    def __len__(self):
        """Get number of top-level parameters."""
        return len(self._parameters)

    def __getitem__(self, idx):
        """Get top-level parameters by index."""
        return self._parameters.values()[idx]

    def __getattr__(self, name):
        """Gives access to the contained objects as attributes."""
        arg = self.get(name)
        if arg is None:
            raise AttributeError(name)
        return arg

    # Ensure there is no __dir__ override in the base class.
    assert (getattr(Observable, '__dir__', None) is getattr(
        Configurable, '__dir__', None) is getattr(Validatable, '__dir__', None)
            is getattr(object, '__dir__', None))

    def __dir__(self):
        "Return sorted list of attributes for this object."
        rv = set(dir(type(self)))
        rv.update(self.__dict__)
        # self.get fetches looks up for items in all managed dictionaries.
        # Add keys from each dictionary in self.__managed.
        rv.update(*self.__managed)
        rv = sorted(rv)
        return rv

    # Needed by __setattr__
    _parameters = {}
    __managed = {}

    def __setattr__(self, name, value):
        """Parameter access and object checking."""
        if name in self._parameters:
            par = self._parameters[name]
            if isinstance(value, Parameter):
                par.value = value.value
            else:
                par.value = value
            return

        m = self.get(name)
        if m is not None:
            raise AttributeError("Cannot set '%s'" % name)

        super(RecipeContainer, self).__setattr__(name, value)
        return

    def __delattr__(self, name):
        """Delete parameters with del.

        This does not allow deletion of non-parameters, as this may require
        configuration changes that are not yet handled in a general way.
        """
        if name in self._parameters:
            self._removeParameter(self._parameters[name])
            return

        m = self.get(name)
        if m is not None:
            raise AttributeError("Cannot delete '%s'" % name)

        super(RecipeContainer, self).__delattr__(name)
        return

    def get(self, name, default=None):
        """Get a managed object."""
        for d in self.__managed:
            arg = d.get(name)
            if arg is not None:
                return arg

        return default

    def getNames(self):
        """Get the names of managed parameters."""
        return [p.name for p in self._parameters.values()]

    def getValues(self):
        """Get the values of managed parameters."""
        return [p.value for p in self._parameters.values()]

    def _addObject(self, obj, d, check=True):
        """Add an object to a managed dictionary.

        obj     --  The object to be stored.
        d       --  The managed dictionary to store the object in.
        check   --  If True (default), a ValueError is raised an object of the
                    given name already exists.

        Raises ValueError if the object has no name.
        Raises ValueError if the object has the same name as some other managed
        object.
        """

        # Check name
        if not obj.name:
            message = "%s has no name" % obj.__class__.__name__
            raise ValueError(message)

        # Check for extant object in d with same name
        oldobj = d.get(obj.name)
        if check and oldobj is not None:
            message = "%s with name '%s' already exists"%\
                    (obj.__class__.__name__, obj.name)
            raise ValueError(message)

        # Check for object with same name in other dictionary.
        if oldobj is None and self.get(obj.name) is not None:
            message = "Non-%s with name '%s' already exists"%\
                    (obj.__class__.__name__, obj.name)
            raise ValueError(message)

        # Detach the old object, if there is one
        if oldobj is not None:
            oldobj.removeObserver(self._flush)

        # Add the object
        d[obj.name] = obj

        # Observe the object
        obj.addObserver(self._flush)

        # Store this as a configurable object
        self._storeConfigurable(obj)
        return

    def _removeObject(self, obj, d):
        """Remove an object from a managed dictionary.

        Raises ValueError if obj is not part of the dictionary.
        """
        if obj not in d.values():
            m = "'%s' is not part of the %s" % (obj, self.__class__.__name__)
            raise ValueError(m)

        del d[obj.name]
        obj.removeObserver(self._flush)

        return

    def _locateManagedObject(self, obj):
        """Find the location a managed object within the hierarchy.

        obj     --  The object to find.

        Returns a list of objects. The first member of the list is this object,
        and each subsequent member is a sub-object of the previous one.  The
        last entry in the list is obj. If obj cannot be found, the list is
        empty.
        """
        loc = [self]

        # This handles the case that an object is asked to locate itself.
        if obj is self:
            return loc

        for m in self._iterManaged():

            # Check locally for the object
            if m is obj:
                loc.append(obj)
                return loc

            # Check within managed objects
            if hasattr(m, "_locateManagedObject"):

                subloc = m._locateManagedObject(obj)
                if subloc:
                    return loc + subloc

        return []

    def _flush(self, other):
        """Invalidate cached state.

        This will force any observer to invalidate its state. By default this
        does nothing.
        """
        self.notify(other)
        return

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

        This validates that contained Parameters and managed objects are valid.

        Raises AttributeError if validation fails.
        """
        iterable = chain(self.__iter__(), self._iterManaged())
        self._validateOthers(iterable)
        return
class RecipeContainer(Observable, Configurable, Validatable):
    """Base class for organizing pieces of a FitRecipe.

    RecipeContainers are hierarchical organizations of Parameters and other
    RecipeContainers. This class provides attribute-access to these contained
    objects.  Parameters and other RecipeContainers can be found within the
    hierarchy with the _locateManagedObject method.

    A RecipeContainer can manage dictionaries for that store various objects.
    These dictionaries can be added to the RecipeContainer using the _manage
    method. RecipeContainer methods that add, remove or retrieve objects will
    work with any managed dictionary. This makes it easy to add new types of
    objects to be contained by a RecipeContainer. By default, the
    RecipeContainer is configured to manage an OrderedDict of Parameter
    objects.

    RecipeContainer is an Observable, and observes its managed objects and
    Parameters. This allows hierarchical calculation elements, such as
    ProfileGenerator, to detect changes in Parameters and Restraints on which
    it may depend.

    Attributes
    name            --  A name for this RecipeContainer. Names should be unique
                        within a RecipeContainer and should be valid attribute
                        names.
    _parameters     --  A managed OrderedDict of contained Parameters.
    __managed       --  A list of managed dictionaries. This is used for
                        attribute access, addition and removal.
    _configobjs     --  A set of configurable objects that must know of
                        configuration changes within this object.

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

    names = property(lambda self: self.getNames())
    values = property(lambda self: self.getValues())

    def __init__(self, name):
        Observable.__init__(self)
        Configurable.__init__(self)
        validateName(name)
        self.name = name
        self._parameters = OrderedDict()

        self.__managed = []
        self._manage(self._parameters)

        return

    def _manage(self, d):
        """Manage a dictionary of objects.

        This adds the dictionary to the __managed list. Dictionaries in
        __managed are used for attribute access, addition, and removal.
        """
        self.__managed.append(d)
        return

    def _iterManaged(self):
        """Get iterator over managed objects."""
        return chain(*(d.values() for d in self.__managed))

    def iterPars(self, name=".", recurse=True):
        """Iterate over Parameters.

        name    --  Select parameters with this name (regular expression,
                    default ".").
        recurse --  Recurse into managed objects (default True)
        """
        for par in self._parameters.itervalues():
            if re.match(name, par.name):
                yield par

        if not recurse:
            return

        # Iterate over objects within the managed dictionaries.
        managed = self.__managed[:]
        managed.remove(self._parameters)
        for m in managed:
            for obj in m.values():
                if hasattr(obj, "iterPars"):
                    for par in obj.iterPars(name=name):
                        yield par

        return

    def __iter__(self):
        """Iterate over top-level parameters."""
        return self._parameters.itervalues()

    def __len__(self):
        """Get number of top-level parameters."""
        return len(self._parameters)

    def __getitem__(self, idx):
        """Get top-level parameters by index."""
        return self._parameters.values()[idx]

    def __getattr__(self, name):
        """Gives access to the contained objects as attributes."""
        arg = self.get(name)
        if arg is None:
            raise AttributeError(name)
        return arg

    # Needed by __setattr__
    _parameters = {}
    __managed = {}

    def __setattr__(self, name, value):
        """Parameter access and object checking."""
        if name in self._parameters:
            par = self._parameters[name]
            if isinstance(value, Parameter):
                par.value = value.value
            else:
                par.value = value
            return

        m = self.get(name)
        if m is not None:
            raise AttributeError("Cannot set '%s'" % name)

        super(RecipeContainer, self).__setattr__(name, value)
        return

    def __delattr__(self, name):
        """Delete parameters with del.

        This does not allow deletion of non-parameters, as this may require
        configuration changes that are not yet handled in a general way.
        """
        if name in self._parameters:
            self._removeParameter(self._parameters[name])
            return

        m = self.get(name)
        if m is not None:
            raise AttributeError("Cannot delete '%s'" % name)

        super(RecipeContainer, self).__delattr__(name)
        return

    def get(self, name, default=None):
        """Get a managed object."""
        for d in self.__managed:
            arg = d.get(name)
            if arg is not None:
                return arg

        return default

    def getNames(self):
        """Get the names of managed parameters."""
        return [p.name for p in self._parameters.values()]

    def getValues(self):
        """Get the values of managed parameters."""
        return [p.value for p in self._parameters.values()]

    def _addObject(self, obj, d, check=True):
        """Add an object to a managed dictionary.

        obj     --  The object to be stored.
        d       --  The managed dictionary to store the object in.
        check   --  If True (default), a ValueError is raised an object of the
                    given name already exists.

        Raises ValueError if the object has no name.
        Raises ValueError if the object has the same name as some other managed
        object.
        """

        # Check name
        if not obj.name:
            message = "%s has no name" % obj.__class__.__name__
            raise ValueError(message)

        # Check for extant object in d with same name
        oldobj = d.get(obj.name)
        if check and oldobj is not None:
            message = "%s with name '%s' already exists" % (obj.__class__.__name__, obj.name)
            raise ValueError(message)

        # Check for object with same name in other dictionary.
        if oldobj is None and self.get(obj.name) is not None:
            message = "Non-%s with name '%s' already exists" % (obj.__class__.__name__, obj.name)
            raise ValueError(message)

        # Detach the old object, if there is one
        if oldobj is not None:
            oldobj.removeObserver(self._flush)

        # Add the object
        d[obj.name] = obj

        # Observe the object
        obj.addObserver(self._flush)

        # Store this as a configurable object
        self._storeConfigurable(obj)
        return

    def _removeObject(self, obj, d):
        """Remove an object from a managed dictionary.

        Raises ValueError if obj is not part of the dictionary.
        """
        if obj not in d.values():
            m = "'%s' is not part of the %s" % (obj, self.__class__.__name__)
            raise ValueError(m)

        del d[obj.name]
        obj.removeObserver(self._flush)

        return

    def _locateManagedObject(self, obj):
        """Find the location a managed object within the hierarchy.

        obj     --  The object to find.

        Returns a list of objects. The first member of the list is this object,
        and each subsequent member is a sub-object of the previous one.  The
        last entry in the list is obj. If obj cannot be found, the list is
        empty.
        """
        loc = [self]

        # This handles the case that an object is asked to locate itself.
        if obj is self:
            return loc

        for m in self._iterManaged():

            # Check locally for the object
            if m is obj:
                loc.append(obj)
                return loc

            # Check within managed objects
            if hasattr(m, "_locateManagedObject"):

                subloc = m._locateManagedObject(obj)
                if subloc:
                    return loc + subloc

        return []

    def _flush(self, other):
        """Invalidate cached state.

        This will force any observer to invalidate its state. By default this
        does nothing.
        """
        self.notify()
        return

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

        This validates that contained Parameters and managed objects are valid.

        Raises AttributeError if validation fails.
        """
        iterable = chain(self.__iter__(), self._iterManaged())
        self._validateOthers(iterable)
        return