Skip to main content

Solution

Solution​

Returned by Model.solve. Primal and dual values, returned over the sets they were declared over.

MemberReturns
statusthe outcome the solver reported
feasiblewhether the solver reports a primal-feasible point
objectivethe objective value of that point
boundthe bound on the optimal objective the solver proved, or None
gapthe relative distance from the objective to the bound, or None
primal(name)the named variable's values over its own sets
dual(name, kind=None)a constraint's duals over its free sets, or a variable's reduced costs over its own sets
has_dualswhether dual returns values: status optimal, and a solver that reports duals

status and feasible are readable whatever the solver reported. objective and primal raise ValueError where feasible is False. They raise at status unbounded and unbounded_or_infeasible whatever feasible reports. An unbounded model has no optimal value, and bound and gap are None there. A solve stopped at a limit reports feasible True where the solver found a point, and those reads then return it. dual raises ValueError where status is not optimal, and for a model with integer columns. Read status first, or has_duals before dual.

primal and dual raise KeyError for a name the model does not declare. primal takes a variable, and its message reports a constraint name as one dual reads. dual takes either, and its message lists the declared constraints and variables. Both raise KeyError for a name declared after the solve.

A model declares its constraints and its variables in two registries, so one name identifies one of each. dual raises ValueError for such a name and reads it under kind="constraint" or kind="variable". Any other kind raises ValueError.

dual returns a reduced cost for a variable: its objective coefficient less the duals of the rows it appears in, weighted by its coefficients in them, in the model's own objective under either sense. nimopt derives the value from the row duals the solver reports, so the convention does not vary by solver. The values follow the dual solution the solver returns. A degenerate model has more than one such solution, and two solvers can report different reduced costs for it. A reduced cost follows the variable's members by the rule primal follows: a DenseArray over a full product, a SparseArray over a subset.

bound is a lower bound on the optimal objective under sense min and an upper bound under sense max. It is None where the solver reports none. For a model without integer columns it is the objective at status optimal and None at any other status. bound is readable at every status, and the solvers report none at status unbounded and unbounded_or_infeasible. gap is abs(objective - bound) / abs(objective). It is None where feasible is False, where bound is None, and at status unbounded and unbounded_or_infeasible.

import numpy as np
from nimopt import Model, Param, Set, Sum

P = Set("P", np.array(["lisbon", "porto"]))
W = Set("W", np.array(["berlin", "paris", "rome"]))
cost = Param.from_dense("cost", (P, W), np.array([[2.0, 4.0, 5.0], [3.0, 1.0, 6.0]]))
supply = Param.from_dense("supply", (P,), np.array([30.0, 25.0]))
demand = Param.from_dense("demand", (W,), np.array([20.0, 15.0, 15.0]))

m = Model("transport")
x = m.var("x", (P, W))
m.constraint("supply", Sum(W, x[P, W]) <= supply[P])
m.constraint("demand", Sum(P, x[P, W]) >= demand[W])
m.set_objective(Sum(P, W, cost[P, W] * x[P, W]))
solution = m.solve()

print(solution.status)
print(solution.objective)
print(solution.primal("x").to_dense())
print(solution.dual("demand").to_dense())
Output
optimal
135.0
[[20. 0. 10.]
[ 0. 15. 5.]]
[3. 1. 6.]

Reading a value where the solver reports no feasible point raises ValueError; the message gives the status.

import numpy as np
from nimopt import Model, Set, Sum

P = Set("P", np.array(["lisbon", "porto"]))
W = Set("W", np.array(["berlin", "paris", "rome"]))

m = Model("infeasible")
x = m.var("x", (P, W))
m.constraint("floor", Sum(W, x[P, W]) >= 10.0)
m.constraint("ceiling", Sum(W, x[P, W]) <= 1.0)
m.set_objective(Sum(P, W, x[P, W]))

m.solve().objective
Raises ValueError
ValueError: status is 'infeasible' and the solver reports no feasible point; read `status` before reading values

The array type of a value​

A variable over a full product has a value at every cell of its frame. The solver returns those values in column order, and they reshape into a DenseArray with no index built. A variable over a subset has values at its members alone. A dense frame would be the grid the declaration avoids, and those values remain a SparseArray. A dual follows the rows of its constraint by the same rule.

Every array declares absence="unknown". A coordinate the model does not have has no value, and combining the results of two models adds no zero for it.