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 “POST /cuopt/request” 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": "Optimal",
 5            "solution": {
 6                "problem_category": "LP",
 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": "Optimal",
 6                "solution": {
 7                    "problem_category": "LP",
 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": "Optimal",
35                "solution": {
36                    "problem_category": "LP",
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": "Optimal",
 5            "solution": {
 6                "problem_category": "LP",
 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": "Optimal",
 5            "solution": {
 6                "problem_category": "LP",
 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
 83print("Termination Reason: ")
 84print(solution_status)
 85
 86# Check found objective value
 87print("Objective Value:")
 88print(solution_obj.get_primal_objective())
 89
 90# Check the MPS parse time
 91print(f"Mps Parse time: {parse_time:.3f} sec")
 92
 93# Check network time (client call - solve time)
 94network_time = network_time - (solution_obj.get_solve_time())
 95print(f"Network time: {network_time:.3f} sec")
 96
 97# Check solver time
 98solve_time = solution_obj.get_solve_time()
 99print(f"Engine Solve time: {solve_time:.3f} sec")
100
101# Check the total end to end time (mps parsing + network + solve time)
102end_to_end_time = parse_time + network_time + solve_time
103print(f"Total end to end time: {end_to_end_time:.3f} sec")
104
105# Print the found decision variables
106print("Variables Values:")
107print(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 “get /cuopt/request” and “get /cuopt/solution” API spec.

Aborting a Running Job in Thin Client#

Please refer to the Aborting a Running Job in Thin Client in the MILP Example 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": "Optimal",
 5            "solution": {
 6                "problem_category": "LP",
 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 Aborting a Running Job In CLI in the MILP Example for more details.

Note

Please use solver settings while using .mps files.