Inequality constraints
======================

The usual pattern for constraints within bumps is to set the value for one
parameter to be some function of the other parameters.  This does not
allow contraints of the form $a < b$ for parameters $a$ and paramter $b$.

Instead, along with the fit problem definition, you can supply your
own penalty constraints function which adds an artificial value to the
probability function for points outside the feasible region.  The
ideal constraints function will incorporate the distance from the
boundary of the feasible region so that if the fitter is started
outside forces the fit back into the feasible region.

The *soft_limit* value can be used in conjunction with the penalty to
avoid evaluating the function outside the feasible region.  For example,
the function $\log(a-b)$ is only defined for $a > b$, so setting a
constraint such as $10^6 + (a-b)^2$ for $a <= b$ and $0$ along with a
soft limit of $10^6$ will keep the function defined everywhere.  With
the penalty value sufficiently large, the probability of any evaluation
in the infeasible region will be neglible, and will not skew the
posterior distribution statistics.

Define the model as usual

::

    from bumps.names import *
    
    def line(x, m, b):
        return m*x + b
    
    x = [1, 2, 3, 4, 5, 6]
    y = [2.1, 4.0, 6.3, 8.03, 9.6, 11.9]
    dy = [0.05, 0.05, 0.2, 0.05, 0.2, 0.2]
    
    M = Curve(line, x, y, dy, m=2, b=0)
    M.m.range(0, 4)
    M.b.range(0, 5)
    
Define the constraints as a function which takes no parameters and returns
a floating point value.  Note the value *1e6* in the penalty condition:
this is the soft limit value which we will use to avoid evaluating the
curve in the infeasible region.

::

    def constraints():
        m, b = M.m.value, M.b.value
        return 0 if m < b else 1e6 + (m-b)**6
    
Attach the constraints to the problem.  Give the soft limit value that is
used for the constraints.  Without the soft limit, the fit would stall
since we started it at a deep local minimum near the true solution without
constraints.

::

    problem = FitProblem(M, constraints=constraints, soft_limit=1e6)
    
The constraint relies on the ability for python to access the parameters
from the module.  Furthermore, the parameters still "boxed", and so you
need to reference the value attribute to get the parameter value at the
time the constraint is evaluated.  Not an elegant solution, but it works.
Eventually we will add constraint expressions such as *M.m < M.b* or
*M.m + M.b < 10* using the same infrastructure as equality constraints.


.. only:: html

   Download: :download:`inequality.py <inequality.py>`.
