#!/usr/bin/env python
##############################################################################
#
# diffpy.srfit by DANSE Diffraction group
# Simon J. L. Billinge
# (c) 2008 The Trustees of Columbia University
# in the City of New York. All rights reserved.
#
# File coded by: Chris Farrow
#
# See AUTHORS.txt for a list of people who contributed.
# See LICENSE_DANSE.txt for license information.
#
##############################################################################
"""The FitHook class for inspecting the progress of a FitRecipe
refinement.
FitHooks are called by a FitRecipe at various stages of the residual
evaluation. The default FitHook of a FitRecipe, PrintFitHook, counts the
number of times the residual is called, and reports that number every
time the residual is calculated. Depending on the verbosity, it will
also report the residual and the current variable values.
Custom FitHooks can be added to a FitRecipe with the
FitRecipe.push_fit_hook method.
"""
from __future__ import print_function
__all__ = ["FitHook"]
import numpy
from diffpy.srfit.util import sort_key_for_numeric_string
[docs]
class FitHook(object):
"""Base class for inspecting the progress of a FitRecipe refinement.
A FitHook can be added to a FitRecipe with the
``FitRecipe.push_fit_hook`` method. The methods in this class are
called during the preparation of the FitRecipe for refinement, and
during the residual call. The base implementations do nothing; derived
classes overload them as needed.
"""
[docs]
def reset(self, recipe):
"""Reset the hook data.
This is called by ``FitRecipe._prepare`` whenever a
configurational change to the fit hierarchy takes place, such as
adding a new ParameterSet, constraint or restraint.
Parameters
----------
recipe : FitRecipe
The FitRecipe that is being prepared.
"""
return
[docs]
def precall(self, recipe):
"""Inspect the recipe before the residual is calculated.
This is called within ``FitRecipe.residual``, before the
variable values are applied and the residual is calculated.
Parameters
----------
recipe : FitRecipe
The FitRecipe whose residual is being calculated.
"""
return
[docs]
def postcall(self, recipe, chiv):
"""Inspect the recipe after the residual is calculated.
This is called within ``FitRecipe.residual``, after the residual
is calculated.
Parameters
----------
recipe : FitRecipe
The FitRecipe whose residual was calculated.
chiv : ndarray
The residual vector, including the restraint penalties.
"""
return
# End class FitHook
class PrintFitHook(FitHook):
"""Print the progress of a FitRecipe refinement.
This FitHook prints out a running count of the number of times the residual
has been called, or other information, based on the verbosity.
Attributes
----------
count : int
The number of times the residual has been called (default 0).
verbose : int
The verbosity level (default 1). Each level also prints what the
lower levels print.
0
Print nothing.
1
Print the count during the precall.
2
Print the residual during the postcall.
>=3
Print the variables during the postcall.
"""
def __init__(self):
self.count = 0
self.verbose = 1
return
def reset(self, recipe):
"""Reset the residual call count to 0.
This is called by ``FitRecipe._prepare`` whenever a
configurational change to the fit hierarchy takes place, such as
adding a new ParameterSet, constraint or restraint.
Parameters
----------
recipe : FitRecipe
The FitRecipe that is being prepared.
"""
self.count = 0
return
def precall(self, recipe):
"""Increment the residual call count and print it.
The count is printed when ``verbose`` is at least 1.
Parameters
----------
recipe : FitRecipe
The FitRecipe whose residual is being calculated.
"""
self.count += 1
if self.verbose > 0:
print(self.count)
return
def postcall(self, recipe, chiv):
"""Print the residual and, optionally, the variable values.
When ``verbose`` is at least 2, print the total residual and, if
the recipe has restraints, its FitContribution and restraint
parts. When ``verbose`` is at least 3, also print the name and
value of each variable.
Parameters
----------
recipe : FitRecipe
The FitRecipe whose residual was calculated.
chiv : ndarray
The residual vector, including the restraint penalties.
"""
if self.verbose < 2:
return
# Get the number of restraints
numres = len(recipe._restraintlist)
chi2tot = numpy.dot(chiv, chiv)
chi2 = 0
res = 0
if numres > 0:
chi = chiv[:-numres]
resv = chiv[-numres:]
chi2 = numpy.dot(chi, chi)
res = numpy.dot(resv, resv)
print("Residual:", chi2tot)
if numres:
print("FitContributions:", chi2)
print("Restraints:", res)
if self.verbose >= 3:
print("Variables")
vnames = recipe.get_names()
vals = recipe.get_values()
# byname = _byname()
items = sorted(zip(vnames, vals), key=_byname)
for name, val in items:
print(" %s = %f" % (name, val))
return
def _byname(nv):
return sort_key_for_numeric_string(nv[0])
# End class PrintFitHook
# TODO - Display the chi^2 on the plot during refinement.
[docs]
class PlotFitHook(FitHook):
"""Live-plot the progress of a FitRecipe refinement.
Each FitContribution of the recipe gets a subplot showing the
observed profile and the calculated profile, which is updated after
every residual call. This requires matplotlib.
Attributes
----------
_plots : list of tuple
The (observed, calculated) line pair for each FitContribution.
It is created by ``reset``.
"""
[docs]
def reset(self, recipe):
"""Set up the plot.
Clear the current figure, turn on interactive mode and draw one
subplot per FitContribution of the recipe.
Parameters
----------
recipe : FitRecipe
The FitRecipe that is being prepared.
"""
FitHook.reset(self, recipe)
self._plots = []
import pylab
pylab.clf()
pylab.ion()
nc = len(recipe._contributions)
if nc > 1:
ncols = 2
nrows = (nc + 1) / 2
for idx, c in enumerate(recipe._contributions.values()):
name = c.name
xname = c._xname
yname = c._yname
p = c.profile
# Create a subplot
if nc > 1:
pylab.subplot(nrows, ncols, idx + 1)
pdata = pylab.plot(p.x, p.y, "bo")[0]
pfit = pylab.plot(p.x, p.y, "r-")[0]
self._plots.append((pdata, pfit))
pylab.xlabel(xname)
pylab.ylabel(yname)
pylab.title(name)
# Set up some event handling, so things behave nicely.
# def redraw(event):
# canvas = event.canvas
# canvas.draw()
# return
# pylab.connect('resize_event', redraw)
return
[docs]
def postcall(self, recipe, chiv):
"""Update the plots with the latest profiles.
This is called within ``FitRecipe.residual``, after the residual
is calculated.
Parameters
----------
recipe : FitRecipe
The FitRecipe whose residual was calculated.
chiv : ndarray
The residual vector, including the restraint penalties.
"""
FitHook.postcall(self, recipe, chiv)
import pylab
for c, plottup in zip(recipe._contributions.values(), self._plots):
p = c.profile
pdata = plottup[0]
pfit = plottup[1]
pdata.set_data(p.x, p.y)
pfit.set_data(p.x, p.ycalc)
pylab.draw()
return