lmfit/lmfit-py — pull request #160 by newville (2014-11-26 03:12:23+00:00)
Title: Make a separate class for CompositeModel, simplifying Model, and supporting add, subtract, multiply, and divide for Models 

This change introduces a `CompositeModel` class, a subclass of `Model` that holds two `Model`s and a binary operator for them.
This is meant to replace and expand the current mechanism for adding two `Model`s. The PR passes tests, but is put forth mainly
for discussion. The main features of the PR are:

1. the `Model` class itself simplified. Many of the internal methods in `Model` had conditional code for whether the `Model` had
*components* or not, often with the distinction between the two cases (is a composite model, is not a composite model) being
hard to follow. With this PR, a basic `Model` wraps a model function, but cannot hold sub-components. Instead, adding or
multiplying 2 models creates a `CompositeModel`. By itself, this simplification of `Model` seems like a good idea to me.

2. While the current code supports adding `Model`s, this PR also supports subtraction, multiplication, and division of `Model`s.
Admittedly, the need for these is less urgent and obvious than adding `Model`s, but it is useful to have.

3. Because the code to support a binary operation is minimal, and relies of Python builtins. That is, the
code to support division is:

```Python
def __div__(self, other): # in class Model: how to divide Models
return CompositeModel(self, other, '/')
```

and

```Python
def eval(self, params=None, **kwargs): # in class CompositeModel
lval = self.left.eval(params=params, **kwargs)
rval = self.right.eval(params=params, **kwargs)
# ....
elif self.op == '/': return lval / rval
```

Normal precedence rules are enforced by Python, and parentheses can be used as needed, so that one could do

```Python
full_model = Model1 * (Model_A + Model_B/Model_C)
```

With this approach, it would be trivial to add support for other of Python many binary operators. For example, it
would be possible to override one of these (perhaps `%`, '^', or '&') to signify convolution, or some other operation.
I think it's an open question whether that is a good idea or not.

In short -- non-trivial features are added, supporting more mathematical operations of `Model`s, with additional
supported operators very easy to add. Furthermore, the `Model` class is simpler, and the code in model.py is
only a few lines longer than the current version. The PR currently passes all tests, but no new tests or docs have
been added. This is mostly a request for comments and suggestions on how to improve this idea.