1

I am using the ojAlgo linear/quadratic solver via ExpressionsBasedModel to solve the layout of graphical elements in a plotting library so that they fit neatly into the screen boundaries. Specifically, I want to solve for scale and translation so that the coordinates of a scatter plot fill up the screen space. I do that by declaring scale and translation variables of the ExpressionsBasedModel and transform the scatter plot coordinates to the screen using those variables and then construct linear constraints that the transformed coordinates should project inside the screen. I also add a negative cost to the scale variables, so that they are maximized and the scatter plot covers as much screen space as possible. My problem is that in some special cases, for example if I have only one point to plot, this results in an unbounded problem where the scale goes towards infinity without any constraint being active. How can I detect the scale variables for which this would happen and fix them to some default values?

To illustrate the above problem, I constructed a toy plotting library (the full library that I am working on is too big to fit in this question). To help layout the graphical elements, I have a problem class:

class Problem {
    private ArrayList<Variable> _scale_variables = new ArrayList<Variable>();
    private ExpressionsBasedModel _model = new ExpressionsBasedModel();

    Variable freeVariable() {
        return _model.addVariable();
    }

    Variable scaleVariable() {
        Variable x = _model.addVariable();
        x.lower(0.0); // Negative scale not allowed
        _scale_variables.add(x);
        return x;
    }

    Expression expr() {
        return _model.addExpression();
    }

    Result solve() {
        for (Variable scale_var: _scale_variables) {

            // This is may result in unbounded solution for degenerate cases.
            Expression expr = _model.addExpression("Encourage-larger-scale");
            expr.set(scale_var, -1.0);
            expr.weight(1.0);
        }
        return _model.minimise();
    }
}

It wraps an ExpressionsBasedModel and has some facilities to create variables. For the transform that I will use to map my scatter point coordinates to screen coordinates, I have this class:

class Transform2d {
    Variable x_scale;
    Variable y_scale;
    Variable x_translation;
    Variable y_translation;

    Transform2d(Problem problem) {
        x_scale = problem.scaleVariable();
        y_scale = problem.scaleVariable();
        x_translation = problem.freeVariable();
        y_translation = problem.freeVariable();
    }

    void respectBounds(double x, double y, double marker_size,
        double width, double height,
        Problem problem) {
        // Respect left and right screen bounds
        {
            Expression expr = problem.expr();
            expr.set(x_scale, x);
            expr.set(x_translation, 1.0);
            expr.lower(marker_size);
            expr.upper(width - marker_size);
        }

        // Respect top and bottom screen bounds
        {
            Expression expr = problem.expr();
            expr.set(y_scale, y);
            expr.set(y_translation, 1.0);
            expr.lower(marker_size);
            expr.upper(height - marker_size);
        }            
    }
}

The respectBounds method is used to add the constraints of a single point in the scatter plot the the Problem class mentioned before. To add all the points of a scatter plot, I have this function:

void addScatterPoints(
    double[] xy_pairs,

    // How much space every marker occupies
    double marker_size,

    Transform2d transform_to_screen,

    // Screen size
    double width, double height,

    Problem problem) {

    int data_count = xy_pairs.length/2;
    for (int i = 0; i < data_count; i++) {
        int offset = 2*i;
        double x = xy_pairs[offset + 0];
        double y = xy_pairs[offset + 1];
        transform_to_screen.respectBounds(x, y, marker_size, width, height, problem);
    }
}

First, let's look at what a non-degenerate case looks like. I specify the screen size and the size of the markers used for the scatter plot. I also specify the data to plot, build the problem and solve it. Here is the code

    Problem problem = new Problem();
    double marker_size = 4;
    double width = 800;
    double height = 600;

    double[] data_to_plot = new double[] {
        1.0, 2.0,
        4.0, 9.3,
        7.0, 4.5};

    Transform2d transform = new Transform2d(problem);
    addScatterPoints(data_to_plot, marker_size, transform, width, height, problem);

    Result result = problem.solve();
    System.out.println("Solution: " + result);

which prints out Solution: OPTIMAL -81.0958904109589 @ { 0, 81.0958904109589, 795.99999999999966, -158.19178082191794 }.

This is what a degenerate case looks like, plotting two points with the same y-coordinate:

    Problem problem = new Problem();

    double marker_size = 4;
    double width = 800;
    double height = 600;

    double[] data_to_plot = new double[] {
        1, 1,
        9, 1
    };

    Transform2d transform = new Transform2d(problem);
    addScatterPoints(data_to_plot, marker_size, transform, width, height, problem);

    Result result = problem.solve();
    System.out.println("Solution: " + result);

It displays Solution: UNBOUNDED -596.0 @ { 88.44444444444444, 596, 0, 0 }. As mentioned before, my question is: How can I detect the scale variables whose negative cost would result in an unbounded solution and constraint them to some default value, so that my solution is not unbounded?

Rulle
  • 4,496
  • 1
  • 15
  • 21
  • In that case with "only one point to plot" what solution do you want? – apete Apr 17 '20 at 14:54
  • In that case, I want to identify the problematic scale variables (that go towards infinity and result in an unbounded solution) and add the equality constraint that they should take the value 1. The solution that I want is the solution of this modified optimization problem with the added equality constraints. – Rulle Apr 17 '20 at 16:10
  • 1
    Isn't the problem when there is only 1 x-value or 1 y-value among the points? If so, then check that before you generate the model and adjust for it. – apete Apr 19 '20 at 07:21
  • 1
    Yes, that is one possible problem that could cause it. In reality though, the plot could potentially be built up in a more complex way with more coordinate systems, constraints between coordinate systems, etc, which would make this degenerate case harder to detect. I haven't really looked at the implementation of ojAlgo but thought that maybe the API could point out these sorts of problems from the final simplex tableau, maybe. The naïve way to detect this problem would be to put a huge upper bound on the scale variables to make the problem bounded and help identify unbounded scale variables. – Rulle Apr 20 '20 at 05:53
  • 1
    ojAlgo will tell you if the LP is unbounded or infeasible, and you can get the dual variables 'result.getMultipliers()' in addition to the primal. – apete Apr 20 '20 at 07:11

0 Answers0