3.2.4. TConstrLHS example

This section will discuss the way to produce a constrained LHS design-of-experiments from scratch, focusing on the main problematic part: defining one or more constraints and on which variable to apply them. The logic behind the heuristic is supposed to be known, so if it’s not the case, please have a look at the dedicated section in [Bla17]. This section will mostly rely on the way to define the constraints and the variables on which these should be applied on which are specified by the method addConstraint. This methods takes 4 arguments:

  1. a pointer to the function that will compute the constraints values;

  2. the number of constraint defined in the function discussed above;

  3. the number of parameters that are provided to the function discussed above;

  4. the values of the parameters that are provided to the function discussed above;

The main object is indeed the constraint function and the way it is defined is discussed here. It is a C++ function (for convenience as the platform is C++-coded) but this should not be an issue even for python users. The followings lines are showing the example of the constraint function used to produce the plot in Macro “samplingConstrLHSLinear.C”.

void Linear(double *p, double *y)
{
    double p1=p[0], p2=p[1];
    double p3=p[2], p4=p[3];

    // Linear constaint
    y[0] = (( (p1 + p2>=2.5) || (p1-p2<=0) ) ? 0 : 1);
    y[1] = ((p3 - p4<0) ? 0 : 1);
}

Here are few elements to discuss and explain this function:

  • its prototype is the usual C++-ROOT one with a pointer to the input parameter p and a pointer to the output (here the constraint results) y;

  • the first lines are defining the parameters, meaning the couples \((x_{row}, x_{col})\) for all the constraints. By convention, the first element of every line (\(p1\) and \(p3\)) are of the row type (they will not change in the design-of-experiments through this constraint, see [Bla17] for clarification) while the second parameters (\(p2\) and \(p4\)) are of the column type, meaning their order in the design-of-experiments will change through permutations through this constraint.

  • the rest of the lines are showing the way to compute the constraints and to interpret them thanks to the trilinear operator (even though the classical if, else would perfectly do the trick as well. Let’s focus first on the second constraint:

    y[1] = ((p3 - p4<0) ? 0 : 1);
    

    if p3 is lower than p4 (p3-p4<0) then the function will put 0 in y[1] (stating that this configuration is not fulfilling the constraint), and it will put 1 otherwise (stating that the constraint is fulfilled). The other line is defining another constraint which is composed of two tests on the same couple of variables:

    y[0] = (( (p1 + p2>=2.5) || (p1-p2<=0) ) ? 0 : 1);
    

    Here, two constraints are combined in once, as they affect the same couple of variables, the configuration will be rejected either if p1+p2 is greater than 2.5 or if p1 is lower than p2.

Once done, this function needs to be plugged into our code in order to state what variables are p1, p2, p3 and p4 so that the rest of the procedure discussed in [Bla17] can be run. This is done in the addConstraint method, thanks to the third and fourth paramaters which are taken from a single object: a vector<int> in C++ and a numpy.array in python. It defines a list of indices (integers) that corresponds to the number of the input attributes as it is has been added into the TDataServer object. For instance in our case, the list of input attributes is "x0:x1:x2" while the constraints are coupling \((x_1,x_0)\) and \((x_2,x_1)\) respectively. Once translated in term of indices, the constraints are coupling respectively \((1,0)\) and \((2,1)\), so the list of parameter should reflect this which is shown below:

vector<int> inputs = {1,0,2,1};
constrlhs->addConstraint(Linear, 2, inputs.size(), &inputs[0]);

Warning

The consistency between the function and the list of parameters is up to you and you should keep a carefull watch over it. It is true that an inequality can be written in two ways, as

\[x_1 - x_0 < 0 \;\; \Leftrightarrow \;\; x_0 - x_1 > 0\]

but when the constraint is defined as

y[0] = ((p[0] - p[1]<0) ? 0 : 1);

the results will be drastically different if the list of parameters is \((1,0)\) (which should fulfill the constraint shown above) or \((0,1)\) which would results in the exact opposite behaviour (and might make the heuristic crash if the constraint cannot be fulfilled).