Skip to content

Commit

Permalink
Refactor highspy for enhanced usability
Browse files Browse the repository at this point in the history
Refactor highspy for enhanced usability

This commit significantly improves the `Highs` class within `highs.py`, focusing on enhancing usability, efficiency, and robustness. Key changes include:

- Added comprehensive docstrings.
- Improved methods for adding, deleting, and retrieving multiple variables and constraints, for a more flexible and efficient API.
- Standardized some API conventions.  Note, this is a breaking change for the constraint value/dual methods.
- Updated tests and examples.
  • Loading branch information
mathgeekcoder committed Jul 23, 2024
1 parent b73d4e8 commit 3b5c3da
Show file tree
Hide file tree
Showing 7 changed files with 824 additions and 318 deletions.
36 changes: 13 additions & 23 deletions examples/chip.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,12 @@
h = highspy.Highs()
h.silent()

varNames = list()
varNames.append('Tables')
varNames.append('Sets of chairs')
items = ['Tables', 'Sets of chairs']
x = h.addVariables(items, obj = [10, 20], name = items)

x1 = h.addVariable(obj = 10, name = varNames[0])
x2 = h.addVariable(obj = 25, name = varNames[1])

vars = list()
vars.append(x1)
vars.append(x2)

constrNames = list()
constrNames.append('Assembly')
constrNames.append('Finishing')

h.addConstr(x1 + 2*x2 <= 80, name = constrNames[0])
h.addConstr(x1 + 4*x2 <= 120, name = constrNames[1])
constrNames = ['Assembly', 'Finishing']
cons = h.addConstrs(x['Tables'] + 2*x['Sets of chairs'] <= 80,
x['Tables'] + 4*x['Sets of chairs'] <= 120, name = constrNames)

h.setMaximize()

Expand All @@ -36,16 +25,17 @@

h.solve()

for var in vars:

for n, var in x.items():
print('Make', h.variableValue(var), h.variableName(var), ': Reduced cost', h.variableDual(var))
print('Make', h.variableValues(vars), 'of', h.variableNames(vars))

print('Make', h.variableValues(x.values()), 'of', h.variableNames(x.values()))
print('Make', h.allVariableValues(), 'of', h.allVariableNames())

for name in constrNames:
print('Constraint', name, 'has value', h.constrValue(name), 'and dual', h.constrDual(name))

print('Constraints have values', h.constrValues(constrNames), 'and duals', h.constrDuals(constrNames))

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

print('Constraints have values', h.constrValues(cons), 'and duals', h.constrDuals(cons))
print('Constraints have values', h.allConstrValues(), 'and duals', h.allConstrDuals())

print('Optimal objective value is', h.getObjectiveValue())
Expand Down
48 changes: 18 additions & 30 deletions examples/distillation.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,13 @@
h = highspy.Highs()
h.silent()

variableNames = list()
variableNames.append('TypeA')
variableNames.append('TypeB')
variableNames = ['TypeA', 'TypeB']
x = h.addVariables(variableNames, obj = [8, 10], name = variableNames[0])

useTypeA = h.addVariable(obj = 8, name = variableNames[0])
useTypeB = h.addVariable(obj = 10, name = variableNames[1])

vars = list()
vars.append(useTypeA)
vars.append(useTypeB)

constrNames = list()
constrNames.append('Product1')
constrNames.append('Product2')
constrNames.append('Product3')

h.addConstr(2*useTypeA + 2*useTypeB >= 7, name = constrNames[0])
h.addConstr(3*useTypeA + 4*useTypeB >= 12, name = constrNames[1])
h.addConstr(2*useTypeA + useTypeB >= 6, name = constrNames[2])
constrNames = ['Product1', 'Product2', 'Product3']
cons = h.addConstrs(2*x['TypeA'] + 2*x['TypeB'] >= 7,
3*x['TypeA'] + 4*x['TypeB'] >= 12,
2*x['TypeA'] + x['TypeB'] >= 6, name = constrNames)

h.setMinimize()

Expand All @@ -47,46 +35,46 @@

h.solve()

for var in vars:
for name, var in x.items():
print('Use {0:.1f} of {1:s}: reduced cost {2:.6f}'.format(h.variableValue(var), h.variableName(var), h.variableDual(var)))
print('Use', h.variableValues(vars), 'of', h.variableNames(vars))

print('Use', h.variableValues(x.values()), 'of', h.variableNames(x.values()))
print('Use', h.allVariableValues(), 'of', h.allVariableNames())

for name in constrNames:
print(f"Constraint {name} has value {h.constrValue(name):{width}.{precision}} and dual {h.constrDual(name):{width}.{precision}}")

print('Constraints have values', h.constrValues(constrNames), 'and duals', h.constrDuals(constrNames))
for c in cons:
print(f"Constraint {c.name} has value {h.constrValue(c):{width}.{precision}} and dual {h.constrDual(c):{width}.{precision}}")

print('Constraints have values', h.constrValues(cons), 'and duals', h.constrDuals(cons))
print('Constraints have values', h.allConstrValues(), 'and duals', h.allConstrDuals())

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

print()
print('Solve as MIP')

for var in vars:
for var in x.values():
h.setInteger(var)

h.solve()

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

print()
print('Solve as LP with Gomory cut')

# Make the variables continuous
for var in vars:
for var in x.values():
h.setContinuous(var)

# Add Gomory cut
h.addConstr(useTypeA + useTypeB >= 4, name = "Gomory")
h.addConstr(x['TypeA'] + x['TypeB'] >= 4, name = "Gomory")

h.solve()

for var in vars:
for var in x.values():
print(f"Use {h.variableValue(var):{width}.{precision}} of {h.variableName(var)}")
print(f"Optimal objective value is {h.getObjectiveValue():{width}.{precision}}")
37 changes: 33 additions & 4 deletions examples/minimal.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,40 @@
import highspy
import time
import networkx as nx
from random import randint

h = highspy.Highs()

x1 = h.addVariable(lb = -h.inf)
x2 = h.addVariable(lb = -h.inf)
(x1, x2) = h.addVariables(2, lb = -h.inf)

h.addConstr(x2 - x1 >= 2)
h.addConstr(x1 + x2 >= 0)
h.addConstrs(x2 - x1 >= 2,
x1 + x2 >= 0)

h.minimize(x2)



h = highspy.Highs()


G = nx.circular_ladder_graph(5).to_directed()
nx.set_edge_attributes(G, {e: {'weight': randint(1, 9)} for e in G.edges})

d = h.addBinaries(G.edges, obj=nx.get_edge_attributes(G, 'weight'))

h.addConstrs(sum(d[e] for e in G.in_edges(i)) - sum(d[e] for e in G.out_edges(i)) == 0 for i in G.nodes)

h = highspy.Highs()

ts = time.time()
perf1 = [h.addBinary() for _ in range(1000000)]
t1 = time.time() - ts
print(t1)

h = highspy.Highs()

ts = time.time()
perf2 = h.addVariables(1000000)
t2 = time.time() - ts
print(t2)

37 changes: 37 additions & 0 deletions examples/network_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Example of a shortest path network flow in a graph
# Shows integration of highspy with networkx

import highspy
import networkx as nx

orig, dest = ('A', 'D')

# create directed graph with edge weights (distances)
G = nx.DiGraph()
G.add_weighted_edges_from([('A', 'B', 2.0), ('B', 'C', 3.0), ('A', 'C', 1.5), ('B', 'D', 2.5), ('C', 'D', 1.0)])

h = highspy.Highs()
h.silent()

x = h.addBinaries(G.edges, obj=nx.get_edge_attributes(G, 'weight'))

# add flow conservation constraints
# { 1 if n = orig
# sum(out) - sum(in) = { -1 if n = dest
# { 0 otherwise
rhs = lambda n: 1 if n == orig else -1 if n == dest else 0
flow = lambda E: sum((x[e] for e in E))

h.addConstrs(flow(G.out_edges(n)) - flow(G.in_edges(n)) == rhs(n) for n in G.nodes)
h.minimize()

# Print the solution
print('Shortest path from', orig, 'to', dest, 'is: ', end = '')
sol = h.vals(x)

n = orig
while n != dest:
print(n, end=' ')
n = next(e[1] for e in G.out_edges(n) if sol[e] > 0.5)

print(dest)
29 changes: 29 additions & 0 deletions examples/nqueens.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# This is an example of the N-Queens problem, which is a classic combinatorial problem.
# The problem is to place N queens on an N x N chessboard so that no two queens attack each other.
#
# We show how to model the problem as a MIP and solve it using highspy.
# Using numpy can simplify the construction of the constraints (i.e., diagonal).

import highspy
import numpy as np

N = 8
h = highspy.Highs()
h.silent()

x = np.reshape(h.addBinaries(N*N), (N, N))

h.addConstrs(sum(x[i,:]) == 1 for i in range(N)) # each row has exactly one queen
h.addConstrs(sum(x[:,j]) == 1 for j in range(N)) # each col has exactly one queen

y = np.fliplr(x)
h.addConstrs(x.diagonal(k).sum() <= 1 for k in range(-N + 1, N)) # each diagonal has at most one queen
h.addConstrs(y.diagonal(k).sum() <= 1 for k in range(-N + 1, N)) # each 'reverse' diagonal has at most one queen

h.solve()
sol = np.array(h.vals(x))

print('Queens:')

for i in range(N):
print(''.join('Q' if sol[i, j] > 0.5 else '*' for j in range(N)))
Loading

0 comments on commit 3b5c3da

Please sign in to comment.