Output of pulp to numpy arrays

Viewed 2383

I'm using pulp to solve a particular problem which has many variables, with a mixture of 1-D, 2-D and 3-D variables. When the solution is obtained, I would like to get these variables into k-dimensional numpy arrays.

Variables:

u = pulp.LpVariable.dicts("u", (ns, ns), 0, 1, "Integer")
v = pulp.LpVariable.dicts("v", (ns, ns), 0, 1, "Integer")
w = pulp.LpVariable.dicts("w", (ns, ns, ps), 0, 1, "Integer")
x = pulp.LpVariable.dicts("x", (ns, ps), 0, 1, "Integer")
y = pulp.LpVariable.dicts("y", (ms, ps), 0, 1, "Integer")
z = pulp.LpVariable.dicts("z", (ns), parameters.hold_duration_min,
                          parameters.hold_duration_max, "Continuous")

varnames = {"u": u, "v": v, "w": w, "x": x, "y": y, "z": z}

Input (after solving):

problem.variables()

Output:

[u_0_1, u_0_2, u_0_3, u_0_4, u_0_5, u_0_6, u_1_2, u_1_3, u_1_4, u_1_5, u_1_6, u_2_3, u_2_4, u_2_5, u_2_6, u_3_4, u_3_5, u_3_6, u_4_5, u_4_6, u_5_6, v_0_1, v_0_2, v_0_3, v_0_4, v_0_5, v_0_6, v_1_2, v_1_3, v_1_4, v_1_5, v_1_6, v_2_3, v_2_4, v_2_5, v_2_6, v_3_4, v_3_5, v_3_6, v_4_5, v_4_6, v_5_6, w_0_1_0, w_0_1_1, w_0_1_2, w_0_1_3, w_0_1_4, w_0_1_5, w_0_1_6, w_0_2_0, w_0_2_1, w_0_2_2, w_0_2_3, w_0_2_4, w_0_2_5, w_0_2_6, w_0_3_0, w_0_3_1, w_0_3_2, w_0_3_3, w_0_3_4, w_0_3_5, w_0_3_6, w_0_4_0, w_0_4_1, w_0_4_2, w_0_4_3, w_0_4_4, w_0_4_5, w_0_4_6, w_0_5_0, w_0_5_1, w_0_5_2, w_0_5_3, w_0_5_4, w_0_5_5, w_0_5_6, w_0_6_0, w_0_6_1, w_0_6_2, w_0_6_3, w_0_6_4, w_0_6_5, w_0_6_6, w_1_0_0, w_1_0_1, w_1_0_2, w_1_0_3, w_1_0_4, w_1_0_5, w_1_0_6, w_1_2_0, w_1_2_1, w_1_2_2, w_1_2_3, w_1_2_4, w_1_2_5, w_1_2_6, w_1_3_0, w_1_3_1, w_1_3_2, w_1_3_3, w_1_3_4, w_1_3_5, w_1_3_6, w_1_4_0, w_1_4_1, w_1_4_2, w_1_4_3, w_1_4_4, w_1_4_5, w_1_4_6, w_1_5_0, w_1_5_1, w_1_5_2, w_1_5_3, w_1_5_4, w_1_5_5, w_1_5_6, w_1_6_0, w_1_6_1, w_1_6_2, w_1_6_3, w_1_6_4, w_1_6_5, w_1_6_6, w_2_0_0, w_2_0_1, w_2_0_2, w_2_0_3, w_2_0_4, w_2_0_5, w_2_0_6, w_2_1_0, w_2_1_1, w_2_1_2, w_2_1_3, w_2_1_4, w_2_1_5, w_2_1_6, w_2_3_0, w_2_3_1, w_2_3_2, w_2_3_3, w_2_3_4, w_2_3_5, w_2_3_6, w_2_4_0, w_2_4_1, w_2_4_2, w_2_4_3, w_2_4_4, w_2_4_5, w_2_4_6, w_2_5_0, w_2_5_1, w_2_5_2, w_2_5_3, w_2_5_4, w_2_5_5, w_2_5_6, w_2_6_0, w_2_6_1, w_2_6_2, w_2_6_3, w_2_6_4, w_2_6_5, w_2_6_6, w_3_0_0, w_3_0_1, w_3_0_2, w_3_0_3, w_3_0_4, w_3_0_5, w_3_0_6, w_3_1_0, w_3_1_1, w_3_1_2, w_3_1_3, w_3_1_4, w_3_1_5, w_3_1_6, w_3_2_0, w_3_2_1, w_3_2_2, w_3_2_3, w_3_2_4, w_3_2_5, w_3_2_6, w_3_4_0, w_3_4_1, w_3_4_2, w_3_4_3, w_3_4_4, w_3_4_5, w_3_4_6, w_3_5_0, w_3_5_1, w_3_5_2, w_3_5_3, w_3_5_4, w_3_5_5, w_3_5_6, w_3_6_0, w_3_6_1, w_3_6_2, w_3_6_3, w_3_6_4, w_3_6_5, w_3_6_6, w_4_0_0, w_4_0_1, w_4_0_2, w_4_0_3, w_4_0_4, w_4_0_5, w_4_0_6, w_4_1_0, w_4_1_1, w_4_1_2, w_4_1_3, w_4_1_4, w_4_1_5, w_4_1_6, w_4_2_0, w_4_2_1, w_4_2_2, w_4_2_3, w_4_2_4, w_4_2_5, w_4_2_6, w_4_3_0, w_4_3_1, w_4_3_2, w_4_3_3, w_4_3_4, w_4_3_5, w_4_3_6, w_4_5_0, w_4_5_1, w_4_5_2, w_4_5_3, w_4_5_4, w_4_5_5, w_4_5_6, w_4_6_0, w_4_6_1, w_4_6_2, w_4_6_3, w_4_6_4, w_4_6_5, w_4_6_6, w_5_0_0, w_5_0_1, w_5_0_2, w_5_0_3, w_5_0_4, w_5_0_5, w_5_0_6, w_5_1_0, w_5_1_1, w_5_1_2, w_5_1_3, w_5_1_4, w_5_1_5, w_5_1_6, w_5_2_0, w_5_2_1, w_5_2_2, w_5_2_3, w_5_2_4, w_5_2_5, w_5_2_6, w_5_3_0, w_5_3_1, w_5_3_2, w_5_3_3, w_5_3_4, w_5_3_5, w_5_3_6, w_5_4_0, w_5_4_1, w_5_4_2, w_5_4_3, w_5_4_4, w_5_4_5, w_5_4_6, w_5_6_0, w_5_6_1, w_5_6_2, w_5_6_3, w_5_6_4, w_5_6_5, w_5_6_6, w_6_0_0, w_6_0_1, w_6_0_2, w_6_0_3, w_6_0_4, w_6_0_5, w_6_0_6, w_6_1_0, w_6_1_1, w_6_1_2, w_6_1_3, w_6_1_4, w_6_1_5, w_6_1_6, w_6_2_0, w_6_2_1, w_6_2_2, w_6_2_3, w_6_2_4, w_6_2_5, w_6_2_6, w_6_3_0, w_6_3_1, w_6_3_2, w_6_3_3, w_6_3_4, w_6_3_5, w_6_3_6, w_6_4_0, w_6_4_1, w_6_4_2, w_6_4_3, w_6_4_4, w_6_4_5, w_6_4_6, w_6_5_0, w_6_5_1, w_6_5_2, w_6_5_3, w_6_5_4, w_6_5_5, w_6_5_6, x_0_0, x_0_1, x_0_2, x_0_3, x_0_4, x_0_5, x_0_6, x_1_0, x_1_1, x_1_2, x_1_3, x_1_4, x_1_5, x_1_6, x_2_0, x_2_1, x_2_2, x_2_3, x_2_4, x_2_5, x_2_6, x_3_0, x_3_1, x_3_2, x_3_3, x_3_4, x_3_5, x_3_6, x_4_0, x_4_1, x_4_2, x_4_3, x_4_4, x_4_5, x_4_6, x_5_0, x_5_1, x_5_2, x_5_3, x_5_4, x_5_5, x_5_6, x_6_0, x_6_1, x_6_2, x_6_3, x_6_4, x_6_5, x_6_6, y_0_0, y_0_1, y_0_2, y_0_3, y_0_4, y_0_5, y_0_6, y_1_0, y_1_1, y_1_2, y_1_3, y_1_4, y_1_5, y_1_6, y_2_0, y_2_1, y_2_2, y_2_3, y_2_4, y_2_5, y_2_6, y_3_0, y_3_1, y_3_2, y_3_3, y_3_4, y_3_5, y_3_6, y_4_0, y_4_1, y_4_2, y_4_3, y_4_4, y_4_5, y_4_6, y_5_0, y_5_1, y_5_2, y_5_3, y_5_4, y_5_5, y_5_6, y_6_0, y_6_1, y_6_2, y_6_3, y_6_4, y_6_5, y_6_6, y_7_0, y_7_1, y_7_2, y_7_3, y_7_4, y_7_5, y_7_6, y_8_0, y_8_1, y_8_2, y_8_3, y_8_4, y_8_5, y_8_6, z_0, z_1, z_2, z_3, z_4, z_5, z_6]

What I'm looking to generate is a dictionary of numpy arrays to hold results, e.g. results such that, e.g.:

results["x"][1][4] == pulp.value(x[1][4])
type(results["x"]) == numpy.ndarray
results["z"][2] == pulp.value(z[2])
results(["w"][6][0][2]) == pulp.value(w[6][0][2])

I've tried some things using e.g. pandas, but can't get it to work for my mix of 1-D, 2-D and 3-D arrays:

results = {}
vfunc = numpy.vectorize(lambda i: pulp.value(i))
for k, v in varnames.items():
    try:
        results[k] = vfunc(pandas.DataFrame(v).values)
    except:
        pass

...the above works for the 2D variables, but not the 1-D or 3-D ones.

EDIT: I've returned to this problem more recently on another project and have solved it far more neatly by defining a new class for multidimensional variables:

import pulp
import numpy


class MultiDimensionalLpVariable:
    def __init__(self, name, dimensions, low_bound, up_bound, cat):
        self.name = name
        try:
            self.dimensions = (*dimensions,)
        except:
            self.dimensions = (dimensions,)
        self.low_bound = low_bound
        self.up_bound = up_bound
        assert cat in pulp.LpCategories, 'cat must be one of ("{}").'.format(
            '", "'.join(pulp.LpCategories)
        )
        self.cat = cat
        self.variables = self._build_variables_array()
        self.values = None

    def __getitem__(self, index):
        return self.variables[index]

    def _build_variables_array(self):
        f = numpy.vectorize(self._define_variable)
        return numpy.fromfunction(f, self.dimensions, dtype="int")

    def _define_variable(self, *index):
        name = "_".join(map(str, (self.name, *index)))
        return pulp.LpVariable(name, self.low_bound, self.up_bound, self.cat)

    def evaluate(self):
        f = numpy.vectorize(lambda i: pulp.value(i))
        self.values = f(self.variables)

I can now define a new variable as follows:

x = MultiDimensionalLpVariable("x",(10,20), 0, 1, "Binary")

Individual variables can be accessed by doing e.g.

>>> x[1,2]
x_1_2

or:

>>> x[1][2]
x_1_2

This allows constraints to be implemented in a clear syntax, e.g. the equation:

enter image description here

can be written as:

for n in range(N):
        problem += sum([x[n][p] for p in range(P)]) == 1

This approach is generalizable to any number of dimensions.

Once the problem has been solved, the values of the variables can be obtained

>>> x.evaluate()
>>> x.values
array([[1., 0., 0., 0., 0.],
       [0., 0., 1., 0., 0.],
       [1., 0., 0., 0., 0.],
       [0., 0., 1., 0., 0.],
       [0., 0., 0., 1., 0.]])

Individual variable values can be accessed by doing e.g.

>>> x.values[1,2]
1.0

or:

>>> x.values[1][2]
1.0
3 Answers

Old question now, but for my own benefit there is a better (or at least another) way to do this. It's actually to use the objects to which you assigned the variable dictionaries:

u = pulp.LpVariable.dicts("u", (ns, ns), 0, 1, "Integer") v = pulp.LpVariable.dicts("v", (ns, ns), 0, 1, "Integer") w = pulp.LpVariable.dicts("w", (ns, ns, ps), 0, 1, "Integer") x = pulp.LpVariable.dicts("x", (ns, ps), 0, 1, "Integer") y = pulp.LpVariable.dicts("y", (ms, ps), 0, 1, "Integer") z = pulp.LpVariable.dicts("z", (ns), parameters.hold_duration_min, parameters.hold_duration_max, "Continuous")

After you have solved the problem, you can get arrays of the solutions by:

u_soln = np.array([[u[i][j].varValue for i in ns] for j in ns])

Etc.

Related