Skip to content

Commit a807755

Browse files
authored
Add python API (#223)
## Issue Authors: - Ishika Roy (https://github.com/Iroy30) - Trevor McKay (https://github.com/tmckayus) - Ramakrishnap (https://github.com/rgsl888prabhu) Approvers: - Trevor McKay (https://github.com/tmckayus) - Chris Maes (https://github.com/chris-maes) URL: #223
1 parent b965d80 commit a807755

File tree

12 files changed

+1722
-8
lines changed

12 files changed

+1722
-8
lines changed

dependencies.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,6 @@ dependencies:
537537
- output_types: conda
538538
packages:
539539
- cupy>=12.0.0
540-
541540
depends_on_rapids_logger:
542541
common:
543542
- output_types: [conda, requirements, pyproject]

docs/cuopt/source/cuopt-python/index.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,13 @@ This section contains details on the cuOpt Python package.
2121
:name: Routing Optimization
2222
:titlesonly:
2323

24-
Routing Optimization <routing/index.rst>
24+
Routing Optimization <routing/index.rst>
25+
26+
27+
.. toctree::
28+
:maxdepth: 3
29+
:caption: Linear Programming and Mixed Integer Linear Programming
30+
:name: LP and MILP API
31+
:titlesonly:
32+
33+
Linear Programming and Mixed Integer Linear Programming <lp-milp/index.rst>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
=======================================================
2+
Linear Programming and Mixed Integer Linear Programming
3+
=======================================================
4+
5+
This section contains details on the cuOpt linear programming and mixed integer linear programming Python API.
6+
7+
.. toctree::
8+
:maxdepth: 3
9+
:caption: LP and MILP
10+
:name: LP and MILP
11+
:titlesonly:
12+
13+
lp-milp-api.rst
14+
lp-milp-examples.rst
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
=========================
2+
LP and MILP API Reference
3+
=========================
4+
5+
.. autoclass:: cuopt.linear_programming.problem.VType
6+
:members:
7+
:member-order: bysource
8+
:undoc-members:
9+
:exclude-members: capitalize, casefold, center, count, encode, endswith, expandtabs, find, format, format_map, index, isalnum, isalpha, isascii, isdecimal, isdigit, isidentifier, islower, isnumeric, isprintable, isspace, istitle, isupper, join, ljust, lower, lstrip, maketrans, partition, removeprefix, removesuffix, replace, rfind, rindex, rjust, rpartition, rsplit, rstrip, split, splitlines, startswith, strip, swapcase, title, translate, upper, zfill
10+
11+
.. autoclass:: cuopt.linear_programming.problem.CType
12+
:members:
13+
:member-order: bysource
14+
:undoc-members:
15+
:exclude-members: capitalize, casefold, center, count, encode, endswith, expandtabs, find, format, format_map, index, isalnum, isalpha, isascii, isdecimal, isdigit, isidentifier, islower, isnumeric, isprintable, isspace, istitle, isupper, join, ljust, lower, lstrip, maketrans, partition, removeprefix, removesuffix, replace, rfind, rindex, rjust, rpartition, rsplit, rstrip, split, splitlines, startswith, strip, swapcase, title, translate, upper, zfill
16+
17+
.. autoclass:: cuopt.linear_programming.problem.sense
18+
:members:
19+
:member-order: bysource
20+
:exclude-members: __new__, __init__, _generate_next_value_, as_integer_ratio, bit_count, bit_length, conjugate, denominator, from_bytes, imag, is_integer, numerator, real, to_bytes
21+
:no-inherited-members:
22+
23+
.. autoclass:: cuopt.linear_programming.problem.Problem
24+
:members:
25+
:undoc-members:
26+
:show-inheritance:
27+
:exclude-members: reset_solved_values, post_solve, dict_to_object, NumNZs, NumVariables, NumConstraints, IsMIP
28+
29+
.. autoclass:: cuopt.linear_programming.problem.Variable
30+
:members:
31+
:undoc-members:
32+
:show-inheritance:
33+
:exclude-members:
34+
35+
.. autoclass:: cuopt.linear_programming.problem.LinearExpression
36+
:members:
37+
:undoc-members:
38+
:show-inheritance:
39+
40+
.. autoclass:: cuopt.linear_programming.problem.Constraint
41+
:members:
42+
:undoc-members:
43+
:show-inheritance:
44+
:exclude-members: compute_slack
Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
====================
2+
LP and MILP Examples
3+
====================
4+
5+
This section contains examples of how to use the cuOpt linear programming and mixed integer linear programming Python API.
6+
7+
.. note::
8+
9+
The examples in this section are not exhaustive. They are provided to help you get started with the cuOpt linear programming and mixed integer linear programming Python API. For more examples, please refer to the `cuopt-examples GitHub repository <https://github.com/NVIDIA/cuopt-examples>`_.
10+
11+
12+
Simple Linear Programming Example
13+
---------------------------------
14+
15+
.. code-block:: python
16+
17+
from cuopt.linear_programming.problem import Problem, CONTINUOUS, MAXIMIZE
18+
from cuopt.linear_programming.solver_settings import SolverSettings
19+
20+
# Create a new problem
21+
problem = Problem("Simple LP")
22+
23+
# Add variables
24+
x = problem.addVariable(lb=0, vtype=CONTINUOUS, name="x")
25+
y = problem.addVariable(lb=0, vtype=CONTINUOUS, name="y")
26+
27+
# Add constraints
28+
problem.addConstraint(x + y <= 10, name="c1")
29+
problem.addConstraint(x - y >= 0, name="c2")
30+
31+
# Set objective function
32+
problem.setObjective(x + y, sense=MAXIMIZE)
33+
34+
# Configure solver settings
35+
settings = SolverSettings()
36+
settings.set_parameter("time_limit", 60)
37+
38+
# Solve the problem
39+
problem.solve(settings)
40+
41+
# Check solution status
42+
if problem.Status.name == "Optimal":
43+
print(f"Optimal solution found in {problem.SolveTime:.2f} seconds")
44+
print(f"x = {x.getValue()}")
45+
print(f"y = {y.getValue()}")
46+
print(f"Objective value = {problem.ObjValue}")
47+
48+
The response is as follows:
49+
50+
.. code-block:: text
51+
52+
Optimal solution found in 0.01 seconds
53+
x = 10.0
54+
y = 0.0
55+
Objective value = 10.0
56+
57+
Mixed Integer Linear Programming Example
58+
----------------------------------------
59+
60+
.. code-block:: python
61+
62+
from cuopt.linear_programming.problem import Problem, INTEGER, MAXIMIZE
63+
from cuopt.linear_programming.solver_settings import SolverSettings
64+
65+
# Create a new MIP problem
66+
problem = Problem("Simple MIP")
67+
68+
# Add integer variables with bounds
69+
x = problem.addVariable(vtype=INTEGER, name="V_x")
70+
y = problem.addVariable(lb=10, ub=50, vtype=INTEGER, name="V_y")
71+
72+
# Add constraints
73+
problem.addConstraint(2 * x + 4 * y >= 230, name="C1")
74+
problem.addConstraint(3 * x + 2 * y <= 190, name="C2")
75+
76+
# Set objective function
77+
problem.setObjective(5 * x + 3 * y, sense=MAXIMIZE)
78+
79+
# Configure solver settings
80+
settings = SolverSettings()
81+
settings.set_parameter("time_limit", 60)
82+
83+
# Solve the problem
84+
problem.solve(settings)
85+
86+
# Check solution status and results
87+
if problem.Status.name == "Optimal":
88+
print(f"Optimal solution found in {problem.SolveTime:.2f} seconds")
89+
print(f"x = {x.getValue()}")
90+
print(f"y = {y.getValue()}")
91+
print(f"Objective value = {problem.ObjValue}")
92+
else:
93+
print(f"Problem status: {problem.Status.name}")
94+
95+
The response is as follows:
96+
97+
.. code-block:: text
98+
99+
Optimal solution found in 0.00 seconds
100+
x = 36.0
101+
y = 40.99999999999999
102+
Objective value = 303.0
103+
104+
105+
Advanced Example: Production Planning
106+
-------------------------------------
107+
108+
.. code-block:: python
109+
110+
from cuopt.linear_programming.problem import Problem, INTEGER, MAXIMIZE
111+
from cuopt.linear_programming.solver_settings import SolverSettings
112+
113+
# Production planning problem
114+
problem = Problem("Production Planning")
115+
116+
# Decision variables: production quantities
117+
# x1 = units of product A
118+
# x2 = units of product B
119+
x1 = problem.addVariable(lb=10, vtype=INTEGER, name="Product_A")
120+
x2 = problem.addVariable(lb=15, vtype=INTEGER, name="Product_B")
121+
122+
# Resource constraints
123+
# Machine time: 2 hours per unit of A, 1 hour per unit of B, max 100 hours
124+
problem.addConstraint(2 * x1 + x2 <= 100, name="Machine_Time")
125+
126+
# Labor: 1 hour per unit of A, 3 hours per unit of B, max 120 hours
127+
problem.addConstraint(x1 + 3 * x2 <= 120, name="Labor_Hours")
128+
129+
# Material: 4 units per unit of A, 2 units per unit of B, max 200 units
130+
problem.addConstraint(4 * x1 + 2 * x2 <= 200, name="Material")
131+
132+
# Objective: maximize profit
133+
# Profit: $50 per unit of A, $30 per unit of B
134+
problem.setObjective(50 * x1 + 30 * x2, sense=MAXIMIZE)
135+
136+
# Solve with time limit
137+
settings = SolverSettings()
138+
settings.set_parameter("time_limit", 30)
139+
problem.solve(settings)
140+
141+
# Display results
142+
if problem.Status.name == "Optimal":
143+
print("=== Production Planning Solution ===")
144+
print(f"Status: {problem.Status.name}")
145+
print(f"Solve time: {problem.SolveTime:.2f} seconds")
146+
print(f"Product A production: {x1.getValue()} units")
147+
print(f"Product B production: {x2.getValue()} units")
148+
print(f"Total profit: ${problem.ObjValue:.2f}")
149+
150+
else:
151+
print(f"Problem not solved optimally. Status: {problem.Status.name}")
152+
153+
The response is as follows:
154+
155+
.. code-block:: text
156+
157+
=== Production Planning Solution ===
158+
159+
Status: Optimal
160+
Solve time: 0.09 seconds
161+
Product A production: 36.0 units
162+
Product B production: 28.000000000000004 units
163+
Total profit: $2640.00
164+
165+
Working with Expressions and Constraints
166+
----------------------------------------
167+
168+
.. code-block:: python
169+
170+
from cuopt.linear_programming.problem import Problem, MAXIMIZE
171+
from cuopt.linear_programming.solver_settings import SolverSettings
172+
173+
problem = Problem("Expression Example")
174+
175+
# Create variables
176+
x = problem.addVariable(lb=0, name="x")
177+
y = problem.addVariable(lb=0, name="y")
178+
z = problem.addVariable(lb=0, name="z")
179+
180+
# Create complex expressions
181+
expr1 = 2 * x + 3 * y - z
182+
expr2 = x + y + z
183+
184+
# Add constraints using expressions
185+
problem.addConstraint(expr1 <= 100, name="Complex_Constraint_1")
186+
problem.addConstraint(expr2 >= 20, name="Complex_Constraint_2")
187+
188+
# Add constraint with different senses
189+
problem.addConstraint(x + y == 50, name="Equality_Constraint")
190+
problem.addConstraint(1 * x <= 30, name="Upper_Bound_X")
191+
problem.addConstraint(1 * y >= 10, name="Lower_Bound_Y")
192+
problem.addConstraint(1 * z <= 100, name="Upper_Bound_Z")
193+
194+
# Set objective
195+
problem.setObjective(x + 2 * y + 3 * z, sense=MAXIMIZE)
196+
197+
settings = SolverSettings()
198+
settings.set_parameter("time_limit", 20)
199+
200+
problem.solve(settings)
201+
202+
203+
if problem.Status.name == "Optimal":
204+
print("=== Expression Example Results ===")
205+
print(f"x = {x.getValue()}")
206+
print(f"y = {y.getValue()}")
207+
print(f"z = {z.getValue()}")
208+
print(f"Objective value = {problem.ObjValue}")
209+
210+
The response is as follows:
211+
212+
.. code-block:: text
213+
214+
=== Expression Example Results ===
215+
x = 0.0
216+
y = 50.0
217+
z = 99.99999999999999
218+
Objective value = 399.99999999999994
219+
220+
Working with Incumbent Solutions
221+
--------------------------------
222+
223+
Incumbent solutions are intermediate feasible solutions found during the MIP solving process. They represent the best integer-feasible solution discovered so far and can be accessed through callback functions.
224+
225+
.. note::
226+
Incumbent solutions are only available for Mixed Integer Programming (MIP) problems, not for pure Linear Programming (LP) problems.
227+
228+
.. code-block:: python
229+
230+
from cuopt.linear_programming.problem import Problem, INTEGER, MAXIMIZE
231+
from cuopt.linear_programming.solver_settings import SolverSettings
232+
from cuopt.linear_programming.solver.solver_parameters import CUOPT_TIME_LIMIT
233+
from cuopt.linear_programming.internals import GetSolutionCallback, SetSolutionCallback
234+
235+
# Create a callback class to receive incumbent solutions
236+
class IncumbentCallback(GetSolutionCallback):
237+
def __init__(self):
238+
super().__init__()
239+
self.solutions = []
240+
self.n_callbacks = 0
241+
242+
def get_solution(self, solution, solution_cost):
243+
"""
244+
Called whenever the solver finds a new incumbent solution.
245+
246+
Parameters
247+
----------
248+
solution : array-like
249+
The variable values of the incumbent solution
250+
solution_cost : array-like
251+
The objective value of the incumbent solution
252+
"""
253+
self.n_callbacks += 1
254+
255+
# Store the incumbent solution
256+
incumbent = {
257+
"solution": solution.copy_to_host(),
258+
"cost": solution_cost.copy_to_host()[0],
259+
"iteration": self.n_callbacks
260+
}
261+
self.solutions.append(incumbent)
262+
263+
print(f"Incumbent {self.n_callbacks}: {incumbent['solution']}, cost: {incumbent['cost']:.2f}")
264+
265+
# Create a more complex MIP problem that will generate multiple incumbents
266+
problem = Problem("Incumbent Example")
267+
268+
# Add integer variables
269+
x = problem.addVariable(vtype=INTEGER)
270+
y = problem.addVariable(vtype=INTEGER)
271+
272+
# Add constraints to create a problem that will generate multiple incumbents
273+
problem.addConstraint(2 * x + 4 * y >= 230)
274+
problem.addConstraint(3 * x + 2 * y <= 190)
275+
276+
# Set objective to maximize
277+
problem.setObjective(5 * x + 3 * y, sense=MAXIMIZE)
278+
279+
# Configure solver settings with callback
280+
settings = SolverSettings()
281+
# Set the incumbent callback
282+
incumbent_callback = IncumbentCallback()
283+
settings.set_mip_callback(incumbent_callback)
284+
settings.set_parameter(CUOPT_TIME_LIMIT, 30) # Allow enough time to find multiple incumbents
285+
286+
# Solve the problem
287+
problem.solve(settings)
288+
289+
# Display final results
290+
print(f"\n=== Final Results ===")
291+
print(f"Problem status: {problem.Status.name}")
292+
print(f"Solve time: {problem.SolveTime:.2f} seconds")
293+
print(f"Final solution: x={x.getValue()}, y={y.getValue()}")
294+
print(f"Final objective value: {problem.ObjValue:.2f}")
295+
296+
The response is as follows:
297+
298+
.. code-block:: text
299+
300+
Optimal solution found.
301+
Incumbent 1: [ 0. 58.], cost: 174.00
302+
Incumbent 2: [36. 41.], cost: 303.00
303+
Generated fast solution in 0.158467 seconds with objective 303.000000
304+
Consuming B&B solutions, solution queue size 2
305+
Solution objective: 303.000000 , relative_mip_gap 0.000000 solution_bound 303.000000 presolve_time 0.043211 total_solve_time 0.160270 max constraint violation 0.000000 max int violation 0.000000 max var bounds violation 0.000000 nodes 4 simplex_iterations 3
306+
307+
=== Final Results ===
308+
Problem status: Optimal
309+
Solve time: 0.16 seconds
310+
Final solution: x=36.0, y=40.99999999999999
311+
Final objective value: 303.00
312+
313+

0 commit comments

Comments
 (0)