LP Python Examples#
The following example showcases how to use the CuOptServiceSelfHostClient
to solve a simple LP problem in normal mode and batch mode (where multiple problems are solved at once).
The OpenAPI specification for the server is available in open-api spec. The example data is structured as per the OpenAPI specification for the server, please refer LPData under schema section. LP and MILP share same spec.
If you want to run server locally, please run the following command in a terminal or tmux session so you can test examples in another terminal.
1export ip="localhost"
2export port=5000
3python -m cuopt_server.cuopt_service --ip $ip --port $port
Genric Example With Normal Mode and Batch Mode#
1from cuopt_sh_client import CuOptServiceSelfHostClient
2import json
3import time
4
5# Example data for LP problem
6# The data is structured as per the OpenAPI specification for the server, please refer /cuopt/request -> schema -> LPData
7data = {
8 "csr_constraint_matrix": {
9 "offsets": [0, 2, 4],
10 "indices": [0, 1, 0, 1],
11 "values": [3.0, 4.0, 2.7, 10.1]
12 },
13 "constraint_bounds": {
14 "upper_bounds": [5.4, 4.9],
15 "lower_bounds": ["ninf", "ninf"]
16 },
17 "objective_data": {
18 "coefficients": [-0.2, 0.1],
19 "scalability_factor": 1.0,
20 "offset": 0.0
21 },
22 "variable_bounds": {
23 "upper_bounds": ["inf", "inf"],
24 "lower_bounds": [0.0, 0.0]
25 },
26 "maximize": False,
27 "solver_config": {
28 "tolerances": {
29 "optimality": 0.0001
30 }
31 }
32}
33
34# If cuOpt is not running on localhost:5000, edit ip and port parameters
35cuopt_service_client = CuOptServiceSelfHostClient(
36 ip="localhost",
37 port=5000,
38 polling_timeout=25,
39 timeout_exception=False
40)
41
42# Number of repoll requests to be carried out for a successful response
43repoll_tries = 500
44
45def repoll(solution, repoll_tries):
46 # If solver is still busy solving, the job will be assigned a request id and response is sent back in the
47 # following format {"reqId": <REQUEST-ID>}.
48 # Solver needs to be re-polled for response using this <REQUEST-ID>.
49
50 if "reqId" in solution and "response" not in solution:
51 req_id = solution["reqId"]
52 for i in range(repoll_tries):
53 solution = cuopt_service_client.repoll(req_id, response_type="dict")
54 if "reqId" in solution and "response" in solution:
55 break;
56
57 # Sleep for a second before requesting
58 time.sleep(1)
59
60 return solution
61
62# Logging callback
63def log_callback(log):
64 for i in log:
65 print("server-log: ", log)
66
67solution = cuopt_service_client.get_LP_solve(
68 data, response_type="dict", logging_callback=log_callback
69)
70
71solution = repoll(solution, repoll_tries)
72
73print("---------- Normal mode --------------- \n", json.dumps(solution, indent=4))
74
75# For batch mode send list of mps/dict/DataModel
76
77solution = cuopt_service_client.get_LP_solve(
78 [data, data], response_type="dict", logging_callback=log_callback
79)
80solution = repoll(solution, repoll_tries)
81
82print("---------- Batch mode ----------------- \n", json.dumps(solution, indent=4))
The response would be as follows:
Normal mode response:
1{
2 "response": {
3 "solver_response": {
4 "status": 1,
5 "solution": {
6 "problem_category": 0,
7 "primal_solution": [
8 1.8,
9 0.0
10 ],
11 "dual_solution": [
12 -0.06666666666666668,
13 0.0
14 ],
15 "primal_objective": -0.36000000000000004,
16 "dual_objective": 6.92188481708744e-310,
17 "solver_time": 0.006462812423706055,
18 "vars": {},
19 "lp_statistics": {
20 "primal_residual": 6.92114652678267e-310,
21 "dual_residual": 6.9218848170975e-310,
22 "gap": 6.92114652686054e-310,
23 "nb_iterations": 1
24 },
25 "reduced_cost": [
26 0.0,
27 0.0031070813207920247
28 ],
29 "milp_statistics": {}
30 }
31 },
32 "total_solve_time": 0.013341188430786133
33 },
34 "reqId": "c7f2e5a1-d210-4e2e-9308-4257d0a86c4a"
35}
Batch mode response:
1{
2 "response": {
3 "solver_response": [
4 {
5 "status": 1,
6 "solution": {
7 "problem_category": 0,
8 "primal_solution": [
9 1.8,
10 0.0
11 ],
12 "dual_solution": [
13 -0.06666666666666668,
14 0.0
15 ],
16 "primal_objective": -0.36000000000000004,
17 "dual_objective": 6.92188481708744e-310,
18 "solver_time": 0.005717039108276367,
19 "vars": {},
20 "lp_statistics": {
21 "primal_residual": 6.92114652678267e-310,
22 "dual_residual": 6.9218848170975e-310,
23 "gap": 6.92114652686054e-310,
24 "nb_iterations": 1
25 },
26 "reduced_cost": [
27 0.0,
28 0.0031070813207920247
29 ],
30 "milp_statistics": {}
31 }
32 },
33 {
34 "status": 1,
35 "solution": {
36 "problem_category": 0,
37 "primal_solution": [
38 1.8,
39 0.0
40 ],
41 "dual_solution": [
42 -0.06666666666666668,
43 0.0
44 ],
45 "primal_objective": -0.36000000000000004,
46 "dual_objective": 6.92188481708744e-310,
47 "solver_time": 0.007481813430786133,
48 "vars": {},
49 "lp_statistics": {
50 "primal_residual": 6.921146112128e-310,
51 "dual_residual": 6.9218848170975e-310,
52 "gap": 6.92114611220587e-310,
53 "nb_iterations": 1
54 },
55 "reduced_cost": [
56 0.0,
57 0.0031070813207920247
58 ],
59 "milp_statistics": {}
60 }
61 }
62 ],
63 "total_solve_time": 0.013
64 },
65 "reqId": "69dc8f36-16c3-4e28-8fb9-3977eb92b480"
66}
Note
Warm start is only applicable to LP and not for MILP.
Warm Start#
Previously run solutions can be saved and be used as warm start for new requests using previously run reqIds as follows:
1from cuopt_sh_client import CuOptServiceSelfHostClient
2import json
3
4data = {
5 "csr_constraint_matrix": {
6 "offsets": [0, 2, 4],
7 "indices": [0, 1, 0, 1],
8 "values": [3.0, 4.0, 2.7, 10.1]
9 },
10 "constraint_bounds": {
11 "upper_bounds": [5.4, 4.9],
12 "lower_bounds": ["ninf", "ninf"]
13 },
14 "objective_data": {
15 "coefficients": [-0.2, 0.1],
16 "scalability_factor": 1.0,
17 "offset": 0.0
18 },
19 "variable_bounds": {
20 "upper_bounds": ["inf", "inf"],
21 "lower_bounds": [0.0, 0.0]
22 },
23 "maximize": False,
24 "solver_config": {
25 "tolerances": {
26 "optimality": 0.0001
27 }
28 }
29}
30
31# If cuOpt is not running on localhost:5000, edit ip and port parameters
32cuopt_service_client = CuOptServiceSelfHostClient(
33 ip="localhost",
34 port=5000,
35 timeout_exception=False
36)
37
38# Set delete_solution to false so it can be used in next request
39initial_solution = cuopt_service_client.get_LP_solve(
40 data, delete_solution=False, response_type="dict"
41)
42
43# Use previous solution saved in server as initial solution to this request.
44# That solution is referenced with previous request id.
45solution = cuopt_service_client.get_LP_solve(
46 data, warmstart_id=initial_solution["reqId"], response_type="dict"
47)
48
49print(json.dumps(solution, indent=4))
50
51# Delete saved solution if not required to save space
52cuopt_service_client.delete(initial_solution["reqId"])
The response would be as follows:
1{
2 "response": {
3 "solver_response": {
4 "status": 1,
5 "solution": {
6 "problem_category": 0,
7 "primal_solution": [
8 1.8,
9 0.0
10 ],
11 "dual_solution": [
12 -0.06666666666666668,
13 0.0
14 ],
15 "primal_objective": -0.36000000000000004,
16 "dual_objective": 6.92188481708744e-310,
17 "solver_time": 0.006613016128540039,
18 "vars": {},
19 "lp_statistics": {
20 "primal_residual": 6.921146112128e-310,
21 "dual_residual": 6.9218848170975e-310,
22 "gap": 6.92114611220587e-310,
23 "nb_iterations": 1
24 },
25 "reduced_cost": [
26 0.0,
27 0.0031070813207920247
28 ],
29 "milp_statistics": {}
30 }
31 },
32 "total_solve_time": 0.013310909271240234
33 },
34 "reqId": "6d1e278f-5505-4bcc-8a33-2f7f7d6f8a30"
35}
Using MPS file directly#
An example on using .mps files as input is shown below:
1from cuopt_sh_client import CuOptServiceSelfHostClient, ThinClientSolverSettings
2import json
3
4data = "sample.mps"
5
6mps_data = """* optimize
7* cost = -0.2 * VAR1 + 0.1 * VAR2
8* subject to
9* 3 * VAR1 + 4 * VAR2 <= 5.4
10* 2.7 * VAR1 + 10.1 * VAR2 <= 4.9
11NAME good-1
12ROWS
13 N COST
14 L ROW1
15 L ROW2
16COLUMNS
17 VAR1 COST -0.2
18 VAR1 ROW1 3 ROW2 2.7
19 VAR2 COST 0.1
20 VAR2 ROW1 4 ROW2 10.1
21RHS
22 RHS1 ROW1 5.4 ROW2 4.9
23ENDATA
24"""
25
26with open(data, "w") as file:
27 file.write(mps_data)
28
29# If cuOpt is not running on localhost:5000, edit `ip` and `port` parameters
30cuopt_service_client = CuOptServiceSelfHostClient(
31 ip="localhost",
32 port=5000,
33 timeout_exception=False
34)
35
36ss = ThinClientSolverSettings()
37
38ss.set_parameter("time_limit", 5)
39ss.set_optimality_tolerance(0.00001)
40
41solution = cuopt_service_client.get_LP_solve(data, solver_config=ss, response_type="dict")
42
43print(json.dumps(solution, indent=4))
The response is:
1{
2 "response": {
3 "solver_response": {
4 "status": 1,
5 "solution": {
6 "problem_category": 0,
7 "primal_solution": [
8 1.8,
9 0.0
10 ],
11 "dual_solution": [
12 -0.06666666666666668,
13 0.0
14 ],
15 "primal_objective": -0.36000000000000004,
16 "dual_objective": 6.92188481708744e-310,
17 "solver_time": 0.008397102355957031,
18 "vars": {
19 "VAR1": 1.8,
20 "VAR2": 0.0
21 },
22 "lp_statistics": {
23 "primal_residual": 6.921146112128e-310,
24 "dual_residual": 6.9218848170975e-310,
25 "gap": 6.92114611220587e-310,
26 "nb_iterations": 1
27 },
28 "reduced_cost": [
29 0.0,
30 0.0031070813207920247
31 ],
32 "milp_statistics": {}
33 }
34 },
35 "total_solve_time": 0.014980316162109375
36 },
37 "reqId": "3f36bad7-6135-4ffd-915b-858c449c7cbb"
38}
Generate Datamodel from MPS Parser#
Use a datamodel generated from mps file as input; this yields a solution object in response. For more details please refer to LP/MILP parameters.
1from cuopt_sh_client import (
2 CuOptServiceSelfHostClient,
3 ThinClientSolverSettings,
4 PDLPSolverMode
5)
6import cuopt_mps_parser
7import json
8import time
9
10# -- Parse the MPS file --
11
12data = "sample.mps"
13
14mps_data = """* optimize
15* cost = -0.2 * VAR1 + 0.1 * VAR2
16* subject to
17* 3 * VAR1 + 4 * VAR2 <= 5.4
18* 2.7 * VAR1 + 10.1 * VAR2 <= 4.9
19NAME good-1
20ROWS
21 N COST
22 L ROW1
23 L ROW2
24COLUMNS
25 VAR1 COST -0.2
26 VAR1 ROW1 3 ROW2 2.7
27 VAR2 COST 0.1
28 VAR2 ROW1 4 ROW2 10.1
29RHS
30 RHS1 ROW1 5.4 ROW2 4.9
31ENDATA
32"""
33
34with open(data, "w") as file:
35 file.write(mps_data)
36
37# Parse the MPS file and measure the time spent
38parse_start = time.time()
39data_model = cuopt_mps_parser.ParseMps(data)
40parse_time = time.time() - parse_start
41
42# -- Build the client object --
43
44# If cuOpt is not running on localhost:5000, edit `ip` and `port` parameters
45cuopt_service_client = CuOptServiceSelfHostClient(
46 ip="localhost",
47 port=5000,
48 timeout_exception=False
49)
50
51# -- Set the solver settings --
52
53ss = ThinClientSolverSettings()
54
55# Set the solver mode to the same of the blogpost, Fast1.
56# Stable1 could also be used.
57ss.set_parameter("pdlp_solver_mode", PDLPSolverMode.Fast1)
58
59# Set the general tolerance to 1e-4 which is already the default value.
60# For more detail on optimality checkout `SolverSettings.set_optimality_tolerance()`
61ss.set_optimality_tolerance(1e-4)
62
63# Here you could set an iteration limit to 1000 and time limit to 10 seconds
64# By default there is no iteration limit and the max time limit is 10 minutes
65# Any problem taking more than 10 minutes to solve will stop and the current solution will be returned
66# For this example, no limit is set
67# settings.set_iteration_limit(1000)
68# settings.set_time_limit(10)
69ss.set_parameter("time_limit", 5)
70
71# -- Call solve --
72
73network_time = time.time()
74solution = cuopt_service_client.get_LP_solve(data_model, ss)
75network_time = time.time() - network_time
76
77# -- Retrieve the solution object and print the details --
78
79solution_status = solution["response"]["solver_response"]["status"]
80solution_obj = solution["response"]["solver_response"]["solution"]
81
82# Check Termination Reason
83# For more detail on termination reasons: checkout `Solution.get_termination_reason()`
84print("Termination Reason: (1 is Optimal)")
85print(solution_status)
86
87# Check found objective value
88print("Objective Value:")
89print(solution_obj.get_primal_objective())
90
91# Check the MPS parse time
92print(f"Mps Parse time: {parse_time:.3f} sec")
93
94# Check network time (client call - solve time)
95network_time = network_time - (solution_obj.get_solve_time())
96print(f"Network time: {network_time:.3f} sec")
97
98# Check solver time
99solve_time = solution_obj.get_solve_time()
100print(f"Engine Solve time: {solve_time:.3f} sec")
101
102# Check the total end to end time (mps parsing + network + solve time)
103end_to_end_time = parse_time + network_time + solve_time
104print(f"Total end to end time: {end_to_end_time:.3f} sec")
105
106# Print the found decision variables
107print("Variables Values:")
108print(solution_obj.get_vars())
The response would be as follows:
1 Termination Reason: (1 is Optimal)
2 1
3 Objective Value:
4 -0.36000000000000004
5 Mps Parse time: 0.000 sec
6 Network time: 1.062 sec
7 Engine Solve time: 0.004 sec
8 Total end to end time: 1.066 sec
9 Variables Values:
10 {'VAR1': 1.8, 'VAR2': 0.0}
Example with DataModel is available in the Examples Notebooks Repository.
The data
argument to get_LP_solve
may be a dictionary of the format shown in LP Open-API spec. More details on the response can be found under the responses schema request and solution API spec.
Aborting a Running Job in Thin Client#
Please refer to the MILP Example on Aborting a Running Job in Thin Client for more details.
LP CLI Examples#
Generic Example#
The following examples showcase how to use the cuopt_sh
CLI to solve a simple LP problem.
echo '{
"csr_constraint_matrix": {
"offsets": [0, 2, 4],
"indices": [0, 1, 0, 1],
"values": [3.0, 4.0, 2.7, 10.1]
},
"constraint_bounds": {
"upper_bounds": [5.4, 4.9],
"lower_bounds": ["ninf", "ninf"]
},
"objective_data": {
"coefficients": [0.2, 0.1],
"scalability_factor": 1.0,
"offset": 0.0
},
"variable_bounds": {
"upper_bounds": ["inf", "inf"],
"lower_bounds": [0.0, 0.0]
},
"maximize": "False",
"solver_config": {
"tolerances": {
"optimality": 0.0001
}
}
}' > data.json
Invoke the CLI.
# Please update these values if the server is running on a different IP address or port
export ip="localhost"
export port=5000
cuopt_sh data.json -t LP -i $ip -p $port -sl
Response is as follows:
1{
2 "response": {
3 "solver_response": {
4 "status": 1,
5 "solution": {
6 "problem_category": 0,
7 "primal_solution": [1.8, 0.0],
8 "dual_solution": [-0.06666666666666668, 0.0],
9 "primal_objective": -0.36000000000000004,
10 "dual_objective": 6.92188481708744e-310,
11 "solver_time": 0.007324934005737305,
12 "vars": {},
13 "lp_statistics": {
14 "primal_residual": 6.921146112128e-310,
15 "dual_residual": 6.9218848170975e-310,
16 "gap": 6.92114611220587e-310,
17 "nb_iterations": 1
18 },
19 "reduced_cost": [0.0, 0.0031070813207920247],
20 "milp_statistics": {}
21 }
22 },
23 "total_solve_time": 0.014164209365844727
24 },
25 "reqId": "4665e513-341e-483b-85eb-bced04ba598c"
26}
Warm Start in CLI#
To use a previous solution as the initial/warm start solution for a new request ID, you are required to save the previous solution, which can be accomplished use option -k
. Use the previous reqId in the next request as follows:
Note
Warm start is only applicable to LP and not for MILP.
# Please update these values if the server is running on a different IP address or port
export ip="localhost"
export port=5000
reqId=$(cuopt_sh -t LP data.json -i $ip -p $port -k | sed "s/'/\"/g" | sed 's/False/false/g' | jq -r '.reqId')
cuopt_sh data.json -t LP -i $ip -p $port -wid $reqId
In case the user needs to update solver settings through CLI, the option -ss
can be used as follows:
# Please update these values if the server is running on a different IP address or port
export ip="localhost"
export port=5000
cuopt_sh data.json -t LP -i $ip -p $port -ss '{"tolerances": {"optimality": 0.0001}, "time_limit": 5}'
In the case of batch mode, you can send a bunch of mps
files at once, and acquire results. The batch mode works only for mps
in the case of CLI:
Note
Batch mode is not available for MILP problems.
echo "* optimize
* cost = -0.2 * VAR1 + 0.1 * VAR2
* subject to
* 3 * VAR1 + 4 * VAR2 <= 5.4
* 2.7 * VAR1 + 10.1 * VAR2 <= 4.9
NAME good-1
ROWS
N COST
L ROW1
L ROW2
COLUMNS
VAR1 COST -0.2
VAR1 ROW1 3 ROW2 2.7
VAR2 COST 0.1
VAR2 ROW1 4 ROW2 10.1
RHS
RHS1 ROW1 5.4 ROW2 4.9
ENDATA" > sample.mps
# Please update these values if the server is running on a different IP address or port
export ip="localhost"
export port=5000
cuopt_sh sample.mps sample.mps sample.mps -t LP -i $ip -p $port -ss '{"tolerances": {"optimality": 0.0001}, "time_limit": 5}'
Aborting a Running Job In CLI#
Please refer to the MILP Example for more details.
Note
Please use solver settings while using .mps files.