jijpresolve
Submodules
Classes
The instance for JijPresolve, a wrapper of |
Functions
|
Execute MILP presolve using PaPILO library and return the presolved instance with statistics. |
|
Execute presolve in a preset order. |
Package Contents
- class jijpresolve.Instance
The instance for JijPresolve, a wrapper of
ommx::v1::Instancein OMMX Rust SDK rather than Python SDK.You need to convert OMMX Python SDK’s
ommx.v1.Instanceto this classjijpresolve.Instancefor executing presolve.Example
We here demonstrate the
convert_single_linear_inequality_into_bounds()method to show how to use this class. It converts \(a x + b \leq 0\) formed constraints (we call it ‘single linear’) into the upper (\(x \leq -b/a\)) or lower (\(x \geq -b/a\)) bound of \(x\) according to the sign of \(a\).First, import required modules.
>>> import jijpresolve >>> from ommx.v1 import DecisionVariable, Instance, Function, Bound
Create an OMMX Python SDK’s instance.
>>> x = [DecisionVariable.continuous(i, lower=0.0, upper=3.0) for i in range(3)] >>> ommx_instance = Instance.from_components( ... decision_variables=x, ... objective=sum(x), ... constraints=[ ... (2 * x[0] <= 1).set_id(0), # single linear inequality ... (x[0] + x[1] + x[2] <= 1).set_id(1), ... ], ... sense=Instance.MAXIMIZE, ... )
Convert the OMMX Python SDK’s instance to jijpresolve’s instance.
>>> presolve_instance = jijpresolve.Instance(ommx_instance)
Execute presolve.
>>> presolve_instance.convert_single_linear_inequality_into_bounds()
Restore the OMMX Python SDK’s instance.
>>> instance: Instance = presolve_instance.to_ommx() >>> instance.decision_variables_df.dropna(axis=1, how="all") kind lower upper subscripts id 0 Continuous 0.0 0.5 [] 1 Continuous 0.0 3.0 [] 2 Continuous 0.0 3.0 []
>>> instance.constraints_df.dropna(axis=1, how="all") equality type used_ids subscripts id 1 <=0 Linear {0, 1, 2} []
The single linear inequality \(2x_0 \leq 1\) is converted to the upper bound \(x_0 \in [0, 0.5]\).
>>> print(instance.removed_constraints_df[["removed_reason", "removed_reason.variable_id"]].to_string(max_colwidth=100)) removed_reason removed_reason.variable_id id 0 convert_single_linear_inequality_into_bounds 0
The converted constraint is stored in the
removed_constraintsfield.removed_reason.variable_idcontains the variable id whose bound is applied to.- to_ommx() Any
Convert to OMMX Python SDK’s
ommx.v1.Instance.
- static from_bytes(bytes: Instance.from_bytes.bytes) Instance
Decode from the protobuf-encoded OMMX Message. You should use
__init__()in most cases.
- to_bytes() bytes
Encode to the protobuf-encoded OMMX Message. You should use
to_ommx()in most cases.
- convert_single_linear_equality_into_fixed_variables(atol: float = 1e-06) None
Convert single linear equality \(a x + b = 0\) into fixed variable \(x = -b/a\).
Example
Ready to use the OMMX Python SDK’s instance.
>>> x = [DecisionVariable.continuous(i, lower=0.0, upper=3.0) for i in range(3)] >>> ommx_instance = Instance.from_components( ... decision_variables=x, ... objective=sum(x), ... constraints=[ ... (2 * x[0] == 1).set_id(0), # single linear equality ... (x[0] + x[1] + x[2] <= 1).set_id(1), ... ], ... sense=Instance.MAXIMIZE, ... )
Call this method
>>> presolve_instance = jijpresolve.Instance(ommx_instance) >>> presolve_instance.convert_single_linear_equality_into_fixed_variables() >>> instance: Instance = presolve_instance.to_ommx()
The fixed value is stored in the
substituted_valuefield of the decision variable.>>> instance.decision_variables_df.dropna(axis=1, how="all") kind lower upper subscripts substituted_value id 0 Continuous 0.0 3.0 [] 0.5 1 Continuous 0.0 3.0 [] <NA> 2 Continuous 0.0 3.0 [] <NA>
The fixed variable \(x_0 = 0.5\) is substituted to objective and constraints.
>>> instance.objective Function(x1 + x2 + 0.5)
>>> instance.get_constraint_by_id(1) Constraint(x1 + x2 - 0.5 <= 0)
The removed constraint is stored in the
removed_constraintsfield.>>> print(instance.removed_constraints_df[["removed_reason", "removed_reason.variable_id"]].to_string(max_colwidth=100)) removed_reason removed_reason.variable_id id 0 convert_single_linear_equality_into_fixed_variables 0
The
removed_reason.variable_idcontains the variable id which has been fixed via this equality.
- convert_single_linear_inequality_into_bounds(atol: float = 1e-06) None
Converts single linear inequality \(a x + b \leq 0\) into the upper (\(x \leq -b/a\)) or lower (\(x \geq -b/a\)) bound of \(x\) according to the sign of \(a\).
See the example in this class
Instancefor more details.
- find_fixed_variables_from_bounds(atol: float = 1e-06) None
Find fixed variables from degenerated bounds \([a, a]\).
This also truncates the bounds of integer variables \([a, b]\) to \([\lceil a \rceil, \lfloor b \rfloor]\), and then check degeneration.
Example
Ready to use the OMMX Python SDK’s instance.
>>> x = [ ... DecisionVariable.continuous(0, lower=3.0, upper=3.0), # fixed to 3 ... DecisionVariable.integer(1, lower=0.9, upper=1.1), # Rounded to [1, 1], and fixed to 1 ... DecisionVariable.integer(2, lower=0.1, upper=3.1), # Rounded to [1, 3] ... ] >>> ommx_instance = Instance.from_components( ... decision_variables=x, ... objective=sum(x), ... constraints=[ ... (x[0] + x[1] + x[2] <= 1).set_id(0), ... ], ... sense=Instance.MAXIMIZE, ... )
Call this method
>>> presolve_instance = jijpresolve.Instance(ommx_instance) >>> presolve_instance.find_fixed_variables_from_bounds() >>> instance: Instance = presolve_instance.to_ommx()
The bounds for the integer variable are rounded to integers, and degenerate bounds are fixed.
>>> instance.decision_variables_df.dropna(axis=1, how="all") kind lower upper subscripts substituted_value id 0 Continuous 3.0 3.0 [] 3.0 1 Integer 1.0 1.0 [] 1.0 2 Integer 1.0 3.0 [] <NA>
The fixed variables are substituted to objective and constraints.
>>> instance.objective Function(x2 + 4) >>> instance.get_constraint_by_id(0) Constraint(x2 + 3 <= 0)
This does not remove any constraints.
- evaluate_constraint_bounds(atol: float = 1e-06) None
Evaluate the bound of \(f(x)\) for the constraints \(f(x) \leq 0\) and \(f(x) = 0\) to remove trivial constraints and detect infeasibility.
Example
Ready to use the OMMX Python SDK’s instance.
>>> x = [DecisionVariable.continuous(i, lower=0.0, upper=2.0) for i in range(3)] >>> ommx_instance = Instance.from_components( ... decision_variables=x, ... objective=sum(x), ... constraints=[ ... (x[0] + x[1] >= 0).set_id(0), # Trivial inequality ... (x[0] + x[1] + x[2] <= 6).set_id(1), # Trivial inequality ... (x[0] + x[2] <= 0).set_id(2), # Convert to equality ... ], ... sense=Instance.MAXIMIZE, ... )
Call this method
>>> presolve_instance = jijpresolve.Instance(ommx_instance) >>> presolve_instance.evaluate_constraint_bounds(1e-7) >>> instance: Instance = presolve_instance.to_ommx()
The trivial constraints are removed, and inequality \(f(x) \leq 0\) with evaluated bound \(f(x) \geq 0\) is converted to equality \(f(x) = 0\).
>>> instance.constraints_df.dropna(axis=1, how="all") equality type used_ids subscripts id 2 =0 Linear {0, 2} []
>>> instance.removed_constraints_df.dropna(axis=1, how="all") equality type used_ids subscripts removed_reason id 0 <=0 Linear {0, 1} [] evaluate_constraint_bounds 1 <=0 Linear {0, 1, 2} [] evaluate_constraint_bounds
Infeasible detection
Let’s create an infeasible inequality \(x_0 + x_1 \leq -1\) for \(x_0, x_1 \in [0, 2]\).
>>> x = [DecisionVariable.continuous(i, lower=0.0, upper=2.0) for i in range(3)] >>> ommx_instance = Instance.from_components( ... decision_variables=x, ... objective=sum(x), ... constraints=[(x[0] + x[1] <= -1).set_id(0)], # x[0] + x[1] must be positive ... sense=Instance.MAXIMIZE, ... )
When infeasible, this method raises a
RuntimeError.>>> presolve_instance = jijpresolve.Instance(ommx_instance) >>> presolve_instance.evaluate_constraint_bounds(1e-7) Traceback (most recent call last): ... RuntimeError: The bound of `f(x)` in the inequality `f(x) <= 0` (ID=0) is inconsistent: Bound[1, 5]
Similarly, for equality \(f(x) = 0\) the bound of \(f(x)\) does not contains \(0\), this raises a
RuntimeError.
- milp_presolve(dual_reductions: str = 'strong', threads: int = 1, randomseed: int = 0) dict
Perform MILP presolve using PaPILO library and modify the instance in place.
This method applies a comprehensive set of presolve techniques to the MILP problem, including domain propagation, constraint aggregation, and variable elimination.
For detailed parameter descriptions, examples, and usage patterns, see
milp_presolve().
- jijpresolve.milp_presolve(instance: Any, dual_reductions: str = 'strong', threads: int = 1, randomseed: int = 0) tuple[Any, dict]
Execute MILP presolve using PaPILO library and return the presolved instance with statistics.
This function applies a comprehensive set of presolve techniques to the MILP problem, including domain propagation, constraint aggregation, and variable elimination. Unlike
Instance.milp_presolve()which modifies the instance in place, this function returns a new presolved instance along with statistics.Parameters:
instance(ommx.v1.Instance): The OMMX instance to presolvedual_reductions(str, optional): Level of dual reductions to apply. Must be one of following:disable: Disable all dual reductionsweak: Preserving the set of optimal solutionsstrong: Preserving the optimal objective value (default)
threads(int, optional): Number of threads to use for presolving (default:1)0: Automatic detection1: Single-threaded (default)N > 1: Use N threads for parallel presolving
randomseed(int, optional): Random seed value for reproducibility (default:0)
Returns:
tuple[ommx.v1.Instance, dict]: A tuple containing:
Presolved OMMX instance
Statistics dictionary with keys such as:
presolve_time,rounds,deleted_columns,deleted_rows, and more
Example
Basic usage with a simple instance
>>> from ommx.v1 import DecisionVariable, Instance >>> import jijpresolve >>> x = [DecisionVariable.integer(i, lower=0, upper=10) for i in range(3)] >>> ommx_instance = Instance.from_components( ... decision_variables=x, ... objective=sum(x), ... constraints=[ ... (x[0] + x[1] + x[2] <= 5).set_id(0), ... ], ... sense=Instance.MAXIMIZE, ... ) >>> presolved_instance, stats = jijpresolve.milp_presolve(ommx_instance) >>> stats['rounds'] 2 >>> stats['deleted_columns'] 3
The original instance is not modified:
>>> ommx_instance.stats()['decision_variables']['by_usage']['fixed'] 0 >>> presolved_instance.stats()['decision_variables']['by_usage']['fixed'] 3
Using MIPLIB instance
>>> import jijpresolve >>> from ommx import dataset >>> ommx_instance = dataset.miplib2017("g503inf") >>> presolved_instance, stats = jijpresolve.milp_presolve(ommx_instance) >>> stats['rounds'] 7 >>> stats['deleted_columns'] 10 >>> stats['deleted_rows'] 6
Compare before and after:
>>> ommx_instance.stats()['decision_variables']['total'] 48 >>> ommx_instance.stats()['constraints']['active'] 41
>>> presolved_instance.stats()['constraints']['active'] 35 >>> presolved_instance.stats()['constraints']['removed'] 6 >>> presolved_instance.stats()['decision_variables']['total'] 48 >>> presolved_instance.stats()['decision_variables']['by_usage']['fixed'] 8
This demonstrates the effectiveness of MILP presolve on larger problem instances.
- jijpresolve.presolve(instance: Any, atol: float = 1e-07) Any
Execute presolve in a preset order.
Currently this function executes the following presolve steps:
Important
The algorithm of this function is not guaranteed to be stable. This function is designed as a better choice for users who do not want to handle the details of presolve and are open to algorithm improvements.
Example
Ready the OMMX Python SDK’s instance.
>>> from ommx.v1 import DecisionVariable, Instance >>> x = [DecisionVariable.continuous(i, lower=0.0, upper=3.0) for i in range(3)] >>> ommx_instance = Instance.from_components( ... decision_variables=x, ... objective=sum(x), ... constraints=[ ... (2 * x[0] <= 1).set_id(0), ... (x[0] + x[1] + x[2] <= 1).set_id(1), ... ], ... sense=Instance.MAXIMIZE, ... )
Call this method
>>> import jijpresolve >>> presolved_instance = jijpresolve.presolve(ommx_instance)
The input instance is not modified, and a new presolved instance is returned.
>>> presolved_instance.decision_variables_df.dropna(axis=1, how="all") kind lower upper subscripts id 0 Continuous 0.0 0.5 [] 1 Continuous 0.0 3.0 [] 2 Continuous 0.0 3.0 []
>>> presolved_instance.constraints_df.dropna(axis=1, how="all") equality type used_ids subscripts id 1 <=0 Linear {0, 1, 2} []
The single linear inequality \(2x_0 \leq 1\) is converted to the upper bound \(x_0 \in [0, 0.5]\) by
convert_single_linear_inequality_into_bounds().