#!/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.
#
##############################################################################
"""Operator classes.
Operators are combined with other Literals to create an equation.
Operators are non-leaf nodes on a Literal tree. These trees can be
evaluated by the Evaluator visitor, or otherwise inspected.
The Operator class contains all the information necessary to be
identified and evaluated by a Visitor. Thus, a single onOperator method
exists in the Visitor base class. Other Operators can be derived from
Operator (see AdditionOperator), but they all identify themselves with
the Visitor.onOperator method.
"""
__all__ = [
"Operator",
"AdditionOperator",
"SubtractionOperator",
"MultiplicationOperator",
"DivisionOperator",
"ExponentiationOperator",
"RemainderOperator",
"NegationOperator",
"ConvolutionOperator",
"SumOperator",
"UFuncOperator",
"ArrayOperator",
"PolyvalOperator",
]
import numpy
from diffpy.srfit.equation.literals.abcs import OperatorABC
from diffpy.srfit.equation.literals.literal import Literal
[docs]
class Operator(Literal, OperatorABC):
"""Abstract class for specifying a general operator.
This class provides several methods that are common to a derived
classes for concrete operations.
Class Attributes
----------------
nin : int, abstract
Number of input arguments for the operator. Any number of
arguments is allowed when -1. This attribute must be defined
in a derived class.
nout : int, abstract
Number of outputs returned by the `operation`. This attribute
must be defined in a derived class.
operation : callable, abstract
Function that performs the operation, e.g., `numpy.add`.
This must be defined in a derived class.
symbol : str, abstract
The symbolic representation for the operator such as
"+" or "sin". This attribute must be defined in a derived
class.
Attributes
----------
args : list
The list of `Literal` arguments. Read-only, use the
`addLiteral` method to change its content.
"""
# Private Attributes
# ------------------
# _value : float, numpy.ndarray or None
# The last value of the operator or None.
# We must declare the abstract `args` here.
args = None
# default for the value
_value = None
def __init__(self, name=None):
"""Initialize the operator object with the specified name.
Parameters
----------
name : str, optional
Name for the operator object. When not specified,
use the class attribute `name`.
"""
Literal.__init__(self, name)
self.args = []
return
[docs]
def identify(self, visitor):
"""Identify self to a visitor."""
return visitor.onOperator(self)
[docs]
def addLiteral(self, literal):
"""Add a literal to this operator.
Note that order of operation matters. The first literal added is
the leftmost argument. The last is the rightmost.
Raises ValueError if the literal causes a self-reference.
"""
# Make sure we don't have self-reference
self._loopCheck(literal)
self.args.append(literal)
literal.addObserver(self._flush)
self._flush(other=(self,))
return
[docs]
def getValue(self):
"""Get or evaluate the value of the operator."""
if self._value is None:
vals = [arg.value for arg in self.args]
self._value = self.operation(*vals)
return self._value
value = property(lambda self: self.getValue())
def _loopCheck(self, literal):
"""Check if a literal causes self-reference."""
if literal is self:
raise ValueError("'%s' causes self-reference" % literal)
# Check to see if I am a dependency of the literal.
if hasattr(literal, "args"):
for lit_arg in literal.args:
self._loopCheck(lit_arg)
return
class UnaryOperator(Operator):
"""Abstract class for an unary operator with one input and one result.
This base class defines the `nin` and `nout` attributes. The derived
concrete operator must provide the remaining abstract attributes
of the `Operator` class.
"""
nin = 1
nout = 1
pass
[docs]
class BinaryOperator(Operator):
"""Abstract class for a binary operator with two inputs and one result.
This base class defines the `nin` and `nout` attributes. The derived
concrete operator must define the remaining abstract attributes
of the `Operator` class.
"""
nin = 2
nout = 1
pass
[docs]
class CustomOperator(Operator):
"""Concrete class for a user-defined Operator.
Use the `makeOperator` factory function to create an instance.
"""
# declare all abstract attributes from the Operator base.
nin = None
nout = None
operation = None
symbol = None
pass
[docs]
def makeOperator(name, symbol, operation, nin, nout):
"""Return a new custom operator object.
Parameters
----------
name : str
Name of the custom operator object.
symbol : str
The symbolic representation for the operator such as
"+" or "sin".
operation : callable
Function that performs the operation, e.g., `numpy.add`.
nin : int
Number of input arguments for the operator. Any number of
arguments is allowed when -1.
nout : in
Number of outputs returned by the `operation`.
Returns
-------
CustomOperator
The new custom operator object.
"""
op = CustomOperator(name=name)
op.symbol = symbol
op.operation = operation
op.nin = nin
op.nout = nout
return op
# Some specified operators
[docs]
class AdditionOperator(BinaryOperator):
"""Addition operator."""
name = "add"
symbol = "+"
operation = staticmethod(numpy.add)
pass
[docs]
class SubtractionOperator(BinaryOperator):
"""Subtraction operator."""
name = "subtract"
symbol = "-"
operation = staticmethod(numpy.subtract)
pass
[docs]
class MultiplicationOperator(BinaryOperator):
"""Multiplication operator."""
name = "multiply"
symbol = "*"
operation = staticmethod(numpy.multiply)
pass
[docs]
class DivisionOperator(BinaryOperator):
"""Division operator."""
name = "divide"
symbol = "/"
operation = staticmethod(numpy.divide)
pass
[docs]
class ExponentiationOperator(BinaryOperator):
"""Exponentiation operator."""
name = "power"
symbol = "**"
operation = staticmethod(numpy.power)
pass
[docs]
class RemainderOperator(BinaryOperator):
"""Remainder operator."""
name = "mod"
symbol = "%"
operation = staticmethod(numpy.mod)
pass
[docs]
class NegationOperator(UnaryOperator):
"""Negation operator."""
name = "negative"
symbol = "-"
operation = staticmethod(numpy.negative)
pass
def _conv(v1, v2):
# Get the full convolution
c = numpy.convolve(v1, v2, mode="full")
# Find the centroid of the first signal
s1 = sum(v1)
x1 = numpy.arange(len(v1), dtype=float)
c1idx = numpy.sum(v1 * x1) / s1
# Find the centroid of the convolution
xc = numpy.arange(len(c), dtype=float)
ccidx = numpy.sum(c * xc) / sum(c)
# Interpolate the convolution such that the centroids line up. This
# uses linear interpolation.
shift = ccidx - c1idx
x1 += shift
c = numpy.interp(x1, xc, c)
# Normalize
sc = sum(c)
if sc > 0:
c *= s1 / sc
return c
[docs]
class ConvolutionOperator(BinaryOperator):
"""Convolve two signals.
This convolves two signals such that centroid of the first array is
not altered by the convolution. Furthermore, the integrated
amplitude of the convolution is scaled to be that of the first
signal. This is mean to act as a convolution of a signal by a
probability distribution.
Note that this is only possible when the signals are computed over
the same range.
"""
name = "convolve"
symbol = "convolve"
operation = staticmethod(_conv)
pass
[docs]
class SumOperator(UnaryOperator):
"""numpy.sum operator."""
name = "sum"
symbol = "sum"
operation = staticmethod(numpy.sum)
[docs]
class UFuncOperator(Operator):
"""A operator wrapper around a numpy ufunc.
The name and symbol attributes are set equal to the ufunc.__name__
attribute. nin and nout are also taken from the ufunc.
"""
symbol = None
nin = None
nout = None
operation = None
def __init__(self, op):
"""Initialization.
Arguments
Attributes
----------
op
A numpy ufunc
"""
Operator.__init__(self, name=op.__name__)
self.symbol = op.__name__
self.nin = op.nin
self.nout = op.nout
self.operation = op
return
[docs]
class ArrayOperator(Operator):
"""Operator that will take parameters and turn them into an array."""
name = "array"
symbol = "array"
nin = -1
nout = 1
[docs]
@staticmethod
def operation(*args):
return numpy.array(args)
[docs]
class PolyvalOperator(BinaryOperator):
"""Operator for numpy polyval."""
name = "polyval"
symbol = "polyval"
operation = staticmethod(numpy.polyval)
pass
# End of file