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.