Skip to content

Commit 54b3012

Browse files
authored
Merge pull request #2524 from mathgeekcoder/highspy-getobj
[highspy] Add support for getObjective()
2 parents 419685e + e99ef79 commit 54b3012

File tree

10 files changed

+312
-72
lines changed

10 files changed

+312
-72
lines changed

examples/callback_gap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# An example of solving the Generalized Assignment Problem (GAP) using highspy
22
# Also demonstrates how to use callbacks to print cuts as they are found
3-
from highspy import *
3+
from highspy import Highs
44
import numpy as np
55

66
#

examples/chip.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,35 @@
99
h = highspy.Highs()
1010
h.silent()
1111

12-
items = ['Tables', 'Sets of chairs']
13-
x = h.addVariables(items, obj = [10, 20], name = items)
12+
items = ['Tables', 'SetsOfChairs']
13+
x = h.addVariables(items, obj = [10, 25], name = items)
1414

1515
constrNames = ['Assembly', 'Finishing']
16-
cons = h.addConstrs(x['Tables'] + 2*x['Sets of chairs'] <= 80,
17-
x['Tables'] + 4*x['Sets of chairs'] <= 120, name = constrNames)
18-
16+
cons = h.addConstrs(x['Tables'] + 2*x['SetsOfChairs'] <= 80,
17+
x['Tables'] + 4*x['SetsOfChairs'] <= 120, name = constrNames)
1918
h.setMaximize()
2019

2120
status = h.writeModel('Chip.lp')
22-
print('writeModel(\'Chip.lp\') status =', status)
21+
print(f"writeModel('Chip.lp') status = {status}")
2322
status = h.writeModel('Chip.mps')
24-
print('writeModel(\'Chip.mps\') status =', status)
23+
print(f"writeModel('Chip.mps') status = {status}\n")
2524

2625
h.solve()
2726

28-
2927
for n, var in x.items():
30-
print('Make', h.variableValue(var), h.variableName(var), ': Reduced cost', h.variableDual(var))
31-
32-
print('Make', h.variableValues(x.values()), 'of', h.variableNames(x.values()))
28+
print('Make', h.variableValue(var), n, ': Reduced cost', h.variableDual(var))
29+
30+
print()
31+
print('Make', h.vals(x))
3332
print('Make', h.allVariableValues(), 'of', h.allVariableNames())
33+
print()
3434

3535
for c in cons:
3636
print('Constraint', c.name, 'has value', h.constrValue(c), 'and dual', h.constrDual(c))
3737

3838
print('Constraints have values', h.constrValues(cons), 'and duals', h.constrDuals(cons))
3939
print('Constraints have values', h.allConstrValues(), 'and duals', h.allConstrDuals())
40-
40+
print()
4141
print('Optimal objective value is', h.getObjectiveValue())
4242

4343

examples/distillation.py

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,13 @@
77
# TypeA >= 0; TypeB >= 0
88

99
import highspy
10-
11-
# For printing
12-
width = 4
13-
precision = 3
10+
width, precision = (4, 3) # for printing
1411

1512
h = highspy.Highs()
1613
h.silent()
1714

1815
variableNames = ['TypeA', 'TypeB']
19-
x = h.addVariables(variableNames, obj = [8, 10], name = variableNames[0])
16+
x = h.addVariables(variableNames, obj = [8, 10], name = variableNames)
2017

2118
constrNames = ['Product1', 'Product2', 'Product3']
2219
cons = h.addConstrs(2*x['TypeA'] + 2*x['TypeB'] >= 7,
@@ -26,53 +23,53 @@
2623
h.setMinimize()
2724

2825
status = h.writeModel('Distillation.lp')
29-
print('writeModel(\'Distillation.lp\') status =', status)
26+
print(f"writeModel('Distillation.lp') status = {status}")
27+
3028
status = h.writeModel('Distillation.mps')
31-
print('writeModel(\'Distillation.mps\') status =', status)
29+
print(f"writeModel('Distillation.mps') status = {status}")
30+
3231

3332
print()
34-
print('Solve as LP')
33+
print('# Solve as LP')
3534

3635
h.solve()
3736

3837
for name, var in x.items():
39-
print('Use {0:.1f} of {1:s}: reduced cost {2:.6f}'.format(h.variableValue(var), h.variableName(var), h.variableDual(var)))
38+
print(f'Use {h.variableValue(var):.1f} of {name}: reduced cost {h.variableDual(var):.6f}')
4039

41-
print('Use', h.variableValues(x.values()), 'of', h.variableNames(x.values()))
40+
print()
41+
print('Use', h.vals(x))
4242
print('Use', h.allVariableValues(), 'of', h.allVariableNames())
43+
print()
4344

4445
for c in cons:
4546
print(f"Constraint {c.name} has value {h.constrValue(c):{width}.{precision}} and dual {h.constrDual(c):{width}.{precision}}")
4647

4748
print('Constraints have values', h.constrValues(cons), 'and duals', h.constrDuals(cons))
4849
print('Constraints have values', h.allConstrValues(), 'and duals', h.allConstrDuals())
49-
50-
for var in x.values():
51-
print(f"Use {h.variableValue(var):{width}.{precision}} of {h.variableName(var)}")
5250
print(f"Optimal objective value is {h.getObjectiveValue():{width}.{precision}}")
5351

54-
print()
55-
print('Solve as MIP')
5652

57-
for var in x.values():
58-
h.setInteger(var)
53+
print()
54+
print()
55+
print('# Solve as MIP')
5956

57+
h.setInteger(x.values())
6058
h.solve()
6159

6260
for var in x.values():
6361
print(f"Use {h.variableValue(var):{width}.{precision}} of {h.variableName(var)}")
6462
print(f"Optimal objective value is {h.getObjectiveValue():{width}.{precision}}")
6563

6664
print()
67-
print('Solve as LP with Gomory cut')
65+
print()
66+
print('# Solve as LP with Gomory cut')
6867

6968
# Make the variables continuous
70-
for var in x.values():
71-
h.setContinuous(var)
69+
h.setContinuous(x.values())
7270

7371
# Add Gomory cut
7472
h.addConstr(x['TypeA'] + x['TypeB'] >= 4, name = "Gomory")
75-
7673
h.solve()
7774

7875
for var in x.values():

examples/knapsack.py

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,43 @@
1+
# Simple knapsack example to illustrate setSolution
2+
# max 8x1 + 5x2 + 3x3 + 11x4 + 7x5
3+
# 4x1 + 3x2 + 1x3 + 5x4 + 4x5 <= 11
4+
15
import numpy as np
26
import highspy
37

8+
def print_solution(h, x):
9+
print(f"Solution: ({', '.join(map(str, abs(h.val(x))))})")
10+
411
h = highspy.Highs()
5-
h.setOptionValue("output_flag", False);
6-
h.setOptionValue("presolve", "off");
7-
inf = highspy.kHighsInf
8-
lp = highspy.HighsLp()
9-
lp.sense_ = highspy.ObjSense.kMaximize
10-
lp.num_col_ = 5
11-
lp.num_row_ = 1
12-
lp.col_cost_ = np.array([8, 5, 3, 11, 7], dtype=np.double)
13-
lp.col_lower_ = np.array([0, 0, 0, 0, 0], dtype=np.double)
14-
lp.col_upper_ = np.array([1, 1, 1, 1, 1], dtype=np.double)
15-
lp.row_lower_ = np.array([-inf], dtype=np.double)
16-
lp.row_upper_ = np.array([11], dtype=np.double)
17-
lp.a_matrix_.format_ = highspy.MatrixFormat.kRowwise
18-
lp.a_matrix_.start_ = np.array([0, 5])
19-
lp.a_matrix_.index_ = np.array([0, 1, 2, 3, 4])
20-
lp.a_matrix_.value_ = np.array([4, 3, 1, 5, 4], dtype=np.double)
21-
lp.integrality_ = np.array([highspy.HighsVarType.kInteger, highspy.HighsVarType.kInteger, highspy.HighsVarType.kInteger, highspy.HighsVarType.kInteger, highspy.HighsVarType.kInteger])
22-
h.passModel(lp)
12+
h.silent()
13+
h.setOptionValue("presolve", "off")
2314

24-
h.run()
25-
solution = h.getSolution()
26-
print(f"Solution: ({solution.col_value[0]}, {solution.col_value[1]}, {solution.col_value[2]}, {solution.col_value[3]}, {solution.col_value[4]})")
15+
x = h.addBinaries(5)
16+
h.addConstr((x * [4, 3, 1, 5, 4]).sum() <= 11)
17+
h.maximize((x * [8, 5, 3, 11, 7]).sum())
18+
19+
# solution is [1, 0, 1, 1, 0]
20+
print_solution(h, x)
2721

28-
# Solution is [1, 0, 1, 1, 0]
2922

3023
# Illustrate setSolution
31-
3224
# First by passing back the optimal solution
33-
3425
h.clearSolver()
35-
h.setSolution(solution)
26+
h.setSolution(h.getSolution())
3627
h.run()
37-
solution = h.getSolution()
38-
print(f"Solution: ({solution.col_value[0]}, {solution.col_value[1]}, {solution.col_value[2]}, {solution.col_value[3]}, {solution.col_value[4]})")
28+
print_solution(h, x)
3929

4030
# Now passing back the optimal values of two variables as a sparse solution
4131
h.clearSolver()
4232
index = np.array([0, 3])
43-
value = np.array([1, 1], dtype=np.double)
33+
value = np.array([1, 1], dtype=np.float64)
4434
h.setSolution(2, index, value)
4535
h.run()
46-
solution = h.getSolution()
47-
print(f"Solution: ({solution.col_value[0]}, {solution.col_value[1]}, {solution.col_value[2]}, {solution.col_value[3]}, {solution.col_value[4]})")
36+
print_solution(h, x)
4837

4938
# Test passing back the optimal value of one variable, and a non-optimal value of another, as a sparse solution in untyped array
5039
h.clearSolver()
5140
h.setSolution(2, np.array([0, 4]), np.array([1, 1]))
5241
h.run()
53-
solution = h.getSolution()
54-
print(f"Solution: ({solution.col_value[0]}, {solution.col_value[1]}, {solution.col_value[2]}, {solution.col_value[3]}, {solution.col_value[4]})")
42+
print_solution(h, x)
5543

examples/multi_objective.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Multi-objective binary knapsack example
2+
from highspy import Highs, HighsLinearObjective
3+
import numpy as np
4+
5+
# Parameters
6+
capacity = 13
7+
8+
# chosen such that applying each objective lexicographically gives a different solution
9+
profit = np.asarray([
10+
[0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1],
11+
[1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0],
12+
[1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,0,0],
13+
[1,1,1,1,0,1,0,0,1,1,0,0,0,1,1,1,0,0,0,0],
14+
15+
], dtype=np.float64).reshape(4, 20)
16+
17+
OBJECTIVES = profit.shape[0]
18+
ITEMS = profit.shape[1]
19+
20+
def pretty_print(objective_values):
21+
return '[ ' + ', '.join(map(lambda x: "{:2.0f}".format(x), objective_values)) + ' ]'
22+
23+
#
24+
# individual optimization of each objective
25+
def individual_objectives(h, X):
26+
print('## Individual:\n')
27+
objective_values = []
28+
29+
for k in range(OBJECTIVES):
30+
h.maximize(np.dot(X, profit[k,:]))
31+
print(f' Obj {k+1}: [{"".join(map(lambda x: "{:1.0f}".format(x), profit[k,:]))}]')
32+
print(f' Sol {k+1}: [{"".join(map(lambda x: "{:1.0f}".format(x), abs(h.vals(X))))}]\n')
33+
objective_values.append(h.getObjectiveValue())
34+
35+
print(f' OBJ: {pretty_print(objective_values)}\n\n')
36+
37+
#
38+
# manual implementation of lexicographic multi-objective optimization
39+
def manual_lexicographic(h, X):
40+
print('## Manual lexicographic:\n')
41+
cons = []
42+
objs = np.dot(profit, X)
43+
44+
for k in range(OBJECTIVES):
45+
h.maximize(objs[k])
46+
print(f' Obj {k+1}: [{"".join(map(lambda x: "{:1.0f}".format(x), profit[k,:]))}]')
47+
print(f' Sol {k+1}: [{"".join(map(lambda x: "{:1.0f}".format(x), abs(h.vals(X))))}]\n')
48+
49+
# add constraint to ensure next solution are at least as good in this objective
50+
cons.append(h.addConstr(np.dot(profit[k,:], X) >= h.getObjectiveValue()))
51+
52+
objective_values = h.vals(objs)
53+
h.deleteRows(len(cons), cons) # clean up constraints
54+
print(f' SOL: [{"".join(map(lambda x: "{:1.0f}".format(x), abs(h.vals(X))))}]')
55+
print(f' OBJ: {pretty_print(objective_values)}\n\n')
56+
57+
58+
#
59+
# built-in lexicographic multi-objective optimization
60+
def highs_lexicographic(h, X):
61+
print('## Built-in lexicographic:\n')
62+
h.setOptionValue('blend_multi_objectives', False) # use lexicographic
63+
64+
for k in range(OBJECTIVES):
65+
obj = HighsLinearObjective()
66+
obj.coefficients = profit[k,:].tolist()
67+
obj.weight = -1 # maximize
68+
obj.priority = -k # higher priority for lower k
69+
obj.abs_tolerance = 0.01
70+
obj.rel_tolerance = 0.001
71+
72+
h.addLinearObjective(obj)
73+
74+
h.solve()
75+
print(f' SOL: [{"".join(map(lambda x: "{:1.0f}".format(x), abs(h.vals(X))))}]')
76+
print(f' OBJ: {pretty_print(np.dot(profit, h.vals(X)))}\n\n')
77+
78+
print(f' Number of objectives: {h.getNumLinearObjectives()}')
79+
for k in range(h.getNumLinearObjectives()):
80+
obj = h.getLinearObjective(k)
81+
print(f' Obj {k+1}: weight={obj.weight}, priority={obj.priority}, abs_tol={obj.abs_tolerance}, rel_tol={obj.rel_tolerance}')
82+
print('\n')
83+
84+
h.clearLinearObjectives()
85+
86+
87+
#
88+
# manual implementation of weighted multi-objective optimization
89+
def manual_weighted(h, X):
90+
print('## Manual Weighted:\n')
91+
weights = np.asarray([1.0/(k+1) for k in range(OBJECTIVES)], dtype=np.float64)
92+
h.maximize(np.dot(weights[:,None] * profit, X).sum())
93+
94+
print(f' SOL: [{"".join(map(lambda x: "{:1.0f}".format(x), abs(h.vals(X))))}]')
95+
print(f' OBJ: {pretty_print(np.dot(profit, h.vals(X)))}\n')
96+
97+
print(f' Obj: {h.getObjective()[0]}\n\n')
98+
99+
100+
#
101+
# built-in weighted multi-objective optimization
102+
def highs_weighted(h, X):
103+
print('## Built-in Weighted:\n')
104+
h.setOptionValue('blend_multi_objectives', True) # use weighted
105+
106+
for k in range(OBJECTIVES):
107+
obj = HighsLinearObjective()
108+
obj.coefficients = profit[k,:].tolist()
109+
obj.weight = -1.0/(k+1)
110+
111+
h.addLinearObjective(obj)
112+
113+
h.solve()
114+
print(f' SOL: [{"".join(map(lambda x: "{:1.0f}".format(x), abs(h.vals(X))))}]')
115+
print(f' OBJ: {pretty_print(np.dot(profit, h.vals(X)))}\n')
116+
117+
print(f' Number of objectives: {h.getNumLinearObjectives()}')
118+
for k in range(h.getNumLinearObjectives()):
119+
obj = h.getLinearObjective(k)
120+
print(f' Obj {k+1}: weight={obj.weight}, priority={obj.priority}, abs_tol={obj.abs_tolerance}, rel_tol={obj.rel_tolerance}')
121+
print('\n')
122+
123+
h.clearLinearObjectives()
124+
125+
126+
if __name__ == "__main__":
127+
h = Highs()
128+
h.silent(True)
129+
130+
X = h.addBinaries(ITEMS)
131+
h.addConstr(X.sum() <= capacity)
132+
133+
individual_objectives(h, X)
134+
135+
manual_lexicographic(h, X)
136+
manual_weighted(h, X)
137+
138+
highs_lexicographic(h, X)
139+
highs_weighted(h, X)

highs/Highs.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,21 @@ class Highs {
159159
HighsStatus addLinearObjective(const HighsLinearObjective& linear_objective,
160160
const HighsInt iObj = -1);
161161

162+
/**
163+
* @brief Get number of linear objectives from the incumbent model
164+
*/
165+
HighsInt getNumLinearObjectives() const {
166+
return multi_linear_objective_.size();
167+
}
168+
169+
/**
170+
* @brief Get a linear objective from the incumbent model
171+
*/
172+
const HighsLinearObjective& getLinearObjective(const HighsInt idx) const {
173+
assert(idx >= 0 && idx < int(multi_linear_objective_.size()));
174+
return multi_linear_objective_[idx];
175+
}
176+
162177
/**
163178
* @brief Clear the multiple linear objective data
164179
*/

0 commit comments

Comments
 (0)