- Added and documented debugging tools
- Added and documented vectorization tools
- Documented modular model construction
- 3200 lines of code, 1800 lines of tests, 1700 lines of docstring. (not counting `interactive`)
Changes to named models / Model inheritance
We are deprecating the creation of named submodels with custom `__init__` methods. Previously, variables created during `__init__` in any class inheriting from Model were replaced by a copy with `__class__.__name__` added as varkey metadata. This was slow, a bit irregular, and hacky.
We're moving to an explicitly-irregular `setup` method, which (if declared for a class inheriting from Model) is automatically called during `Model.__init__` inside a `NamedVariables(self.__class__.__name__)` environment. This 1) handles the naming of variables more explicitly and efficiently, and 2) allows us to capture variables created within `setup`, so that constants that are not a part of any constraint can be used directly (several examples of such template models are in the new _Building Complex Models_ documentation).
`Model.__init__` calls `setup` with the arguments given to the constructor, with the exception of `substitutions`, which is a reserved keyword. This allows for the easy creation of a named model with custom parameter values (as in the documentation's Beam example). `setup` methods should return an iterable (list, tuple, ConstraintSet, ...) of constraints or nothing if the model contains no constraints. To declare a submodel cost, set `self.cost` during `setup`. However, we often find declaring a model's cost explicitly just before solving to be a more legible practice.
In addition to permitting us to name variables at creation, and include unconstrained variables in a model, we hope that `setup` methods will clarify the side effects of named model creation.