Solver Wrapper
HighsCustom
Bases: Highs
Thin subclass of highspy.Highs
exposing a no-solve objective setter.
The stock Highs
object couples minimize
/ maximize
with an
immediate solve. For modelling convenience we sometimes want to build a
model incrementally and set/replace an objective multiple times before the
first solve. set_objective_without_solving
mirrors the internal logic of
Highs.minimize
/ Highs.maximize
sans the final call to solve
.
set_objective_without_solving
Set objective coefficients and sense without triggering solve
.
Parameters
obj : linear expression Must be a linear expression (a single variable should be wrapped by the caller). Inequality expressions are rejected. sense : {“minimize”, “min”, “maximize”, “max”}, default “minimize” Optimization direction.
Raises
Exception
If the provided expression encodes an inequality.
ValueError
If sense
is invalid.
Source code in flowpaths/utils/solverwrapper.py
SolverWrapper
Unified MILP/LP modelling convenience layer for HiGHS and Gurobi.
This class provides *one* API that delegates to either the
[HiGHS (``highspy``)](https://highs.dev) or
[Gurobi (``gurobipy``)](https://www.gurobi.com/solutions/gurobi-optimizer/) back-end.
Only a very small, stable subset of features needed by *flowpaths* is
wrapped - it is **not** a general purpose replacement for the native APIs.
Key capabilities
----------------
- Create variables (continuous / integer) in bulk with name prefixing
- Add linear constraints
- Add specialized modelling shortcuts:
- binary * continuous (McCormick) product constraints
- integer * continuous product constraints (bit expansion + binaries)
- piecewise constant constraints (one-hot selection)
- Build linear objectives without triggering a solve (HiGHS) or with native
semantics (Gurobi)
- Optimize with optional *custom* wall clock timeout (in addition to the
solver internal time limit)
- Retrieve objective, variable names, variable values (optionally enforcing
integrality for binaries), status (mapped to HiGHS style codes)
- Persist model to disk (``.lp`` / ``.mps`` depending on backend support)
Design notes
------------
- A minimal set of solver parameters is exposed via keyword arguments in
``__init__`` to keep call sites clean.
- Status codes for Gurobi are mapped onto HiGHS style names where a clear
1-to-1 mapping exists (see ``gurobi_status_to_highs``).
- A *secondary* timeout based on POSIX signals can be enabled to guard
against situations where the native solver time limit is not obeyed
precisely. When this fires ``did_timeout`` is set and the reported status
becomes ``kTimeLimit``.
Parameters
----------
**kwargs :
Flexible configuration. Recognised keys (all optional):
- ``external_solver`` (str): ``"highs"`` (default) or ``"gurobi"``.
- ``threads`` (int): Thread limit for solver (default: ``4``).
- ``time_limit`` (float): Internal solver time limit in seconds
(default: ``inf`` = no limit).
- ``use_also_custom_timeout`` (bool): If ``True`` activate an *extra*
signal based timeout equal to ``time_limit`` (default ``False``).
- ``presolve`` (str): HiGHS presolve strategy (default ``"choose"``).
- ``log_to_console`` (str): ``"true"`` / ``"false"`` (default
``"false"``) - normalized to solver specific flags.
- ``tolerance`` (float): MIP gap, feasibility, integrality tolerance
applied uniformly (default ``1e-9``; must be >= 1e-9).
- ``optimization_sense`` (str): ``"minimize"`` or ``"maximize``
(default ``"minimize"``).
Attributes
----------
external_solver : str
Active backend (``"highs"`` or ``"gurobi"``).
solver : object
Underlying solver object (``highspy.Highs`` or ``gurobipy.Model``).
time_limit : float
Configured internal solver time limit (seconds).
use_also_custom_timeout : bool
Whether the secondary POSIX signal timeout is enabled.
tolerance : float
Unified tolerance applied to various solver parameters.
optimization_sense : str
``"minimize"`` or ``"maximize"`` as last set on the objective.
did_timeout : bool
Flag set to ``True`` only when the *custom* timeout fired.
variable_name_prefixes : list[str]
Record of prefixes used to detect accidental collisions.
Raises
ValueError If unsupported solver name, invalid optimization sense or an invalid tolerance (< 1e-9) is supplied.
Source code in flowpaths/utils/solverwrapper.py
add_binary_continuous_product_constraint
add_binary_continuous_product_constraint(
binary_var,
continuous_var,
product_var,
lb,
ub,
name: str,
)
Description
This function adds constraints to model the equality: binary_var
* continuous_var
= product_var
.
Assumptions
binary_var
\(\in [0,1]\)- lb ≤
continuous_var
≤ ub
Note
This works correctly also if continuous_var
is an integer variable.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
binary_var
|
variable
|
The binary variable. |
required |
continuous_var
|
variable
|
The continuous variable (can also be integer). |
required |
product_var
|
variable
|
The variable that should be equal to the product of the binary and continuous variables. |
required |
lb
|
float
|
The lower bound of the continuous variable. |
required |
ub
|
float
|
The upper bound of the continuous variable. |
required |
name
|
str
|
The name of the constraint. |
required |
Source code in flowpaths/utils/solverwrapper.py
add_constraint
Add a linear (in)equation to the model.
Parameters
expr : linear expression / bool The solver specific constraint expression. name : str, optional Optional identifier for the constraint.
Source code in flowpaths/utils/solverwrapper.py
add_integer_continuous_product_constraint
add_integer_continuous_product_constraint(
integer_var,
continuous_var,
product_var,
lb,
ub,
name: str,
)
This function adds constraints to model the equality
integer_var * continuous_var = product_var
Assumptions
lb <= product_var <= ub
Note
This works correctly also if continuous_var
is an integer variable.
Parameters
binary_var : Variable The binary variable. continuous_var : Variable The continuous variable (can also be integer). product_var : Variable The variable that should be equal to the product of the binary and continuous variables. lb, ub : float The lower and upper bounds of the continuous variable. name : str The name of the constraint
Source code in flowpaths/utils/solverwrapper.py
add_piecewise_constant_constraint
Enforces that variable y
equals a constant from constants
depending on the range that x
falls into.
For each piece i
if x in [ranges[i][0], ranges[i][1]] then y = constants[i].
Assumptions
- The ranges must be non-overlapping. Otherwise, if x belongs to more ranges, the solver will choose one arbitrarily.
- The value of x must be within the union of the ranges. Otherwise the solver will not find a feasible solution.
This is modeled by:
- introducing binary variables z[i] with sum(z) = 1,
- for each piece i:
x >= L_i - M*(1 - z[i])
x <= U_i + M*(1 - z[i])
y <= constant[i] + M*(1 - z[i])
y >= constant[i] - M*(1 - z[i])
Parameters
x: The continuous variable (created earlier) whose value determines the segment. y: The continuous variable whose value equals the corresponding constant. ranges: List of tuples [(L0, U0), (L1, U1), …] constants: List of constants [c0, c1, …] for each segment. name_prefix: A prefix for naming the added variables and constraints.
Returns
y: The created piecewise output variable.
Source code in flowpaths/utils/solverwrapper.py
add_variables
Create a set of variables sharing a common name prefix.
A collision check ensures no prefix is a prefix of an existing one to avoid ambiguous parsing later when values are retrieved.
Parameters
indexes : iterable
Iterable of index labels (numbers or hashables) used only to
suffix the variable names for uniqueness.
name_prefix : str
Prefix for each created variable (e.g. x_
). Must be unique with
respect to existing prefixes (no prefix / super-prefix relations).
lb, ub : float, default (0, 1)
Lower and upper bounds for every created variable.
var_type : {“integer”, “continuous”}, default “integer”
Variable domain type.
Returns
dict | list Mapping from provided index to underlying solver variable objects (HiGHS returns an internal structure; Gurobi returns a dict).
Raises
ValueError
If name_prefix
conflicts with an existing prefix.
Source code in flowpaths/utils/solverwrapper.py
get_all_variable_names
Return names for all variables in solver insertion order.
Source code in flowpaths/utils/solverwrapper.py
get_all_variable_values
Return values for all variables in solver insertion order.
Source code in flowpaths/utils/solverwrapper.py
get_model_status
Return HiGHS style model status string (or raw Gurobi code).
If the custom timeout was triggered the synthetic status kTimeLimit
is returned irrespective of the underlying solver state.
Parameters
raw : bool, default False
When using Gurobi: if True
return the untouched integer status
code; otherwise attempt to map to a HiGHS style enum name.
Returns
str | int
Status name (always a string for HiGHS). Integer code for Gurobi
only when raw=True
.
Source code in flowpaths/utils/solverwrapper.py
get_objective_value
Return objective value of last solve.
Returns
float Objective value according to the configured optimization sense.
Source code in flowpaths/utils/solverwrapper.py
get_variable_values
Retrieve the values of variables whose names start with a given prefix.
This method extracts variable values from the solver, filters them based on a specified prefix, and returns them in a dictionary with appropriate indexing.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
name_prefix
|
str
|
The prefix of the variable names to filter. |
required |
index_types
|
list
|
A list of types corresponding to the indices of the variables. Each type in the list is used to cast the string indices to the appropriate type. If empty, then it is assumed that the variable has no index, and does exact matching with the variable name. |
required |
binary_values
|
bool
|
If True, ensures that the variable values (rounded) are binary (0 or 1). Defaults to False. |
False
|
Returns:
Name | Type | Description |
---|---|---|
values |
dict
|
A dictionary where the keys are the indices of the variables (as tuples or single values) and the values are the corresponding variable values. If index_types is empty, then the unique key is 0 and the value is the variable value. |
Raises:
Type | Description |
---|---|
Exception
|
If the length of |
Exception
|
If |
Source code in flowpaths/utils/solverwrapper.py
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 |
|
optimize
Run the solver.
Behaviour:
- If time_limit
is infinity OR use_also_custom_timeout
is
False
we rely solely on the backend’s time limit handling.
- Otherwise we also arm a POSIX signal based timeout (coarse, whole
seconds) which, when firing, sets did_timeout
. The underlying
solver is not forcibly terminated beyond the signal alarm; we rely on
cooperative interruption.
Source code in flowpaths/utils/solverwrapper.py
parse_var_name
Parse a variable name and extract indices inside parentheses.
Variable names follow one of two patterns:
<prefix>(i,j,...)
- parenthesised, comma separated indices where elements may be quoted strings or integers.<prefix><i>
- single suffix index (handled elsewhere).
Parameters
string : str Full variable name. name_prefix : str Expected prefix.
Returns
list[str] Raw index components (still strings, quotes stripped).
Raises
ValueError If the name does not match the expected pattern.
Source code in flowpaths/utils/solverwrapper.py
print_variable_names_values
Print name = value
lines for every variable (debug helper).
Source code in flowpaths/utils/solverwrapper.py
quicksum
Backend agnostic fast summation of linear terms.
Parameters
expr : iterable Iterable of linear terms.
Returns
linear expression A solver specific linear expression representing the sum.
Source code in flowpaths/utils/solverwrapper.py
set_objective
Set (and replace) the linear objective.
For HiGHS this delegates to HighsCustom.set_objective_without_solving
(i.e. does not trigger a solve). For Gurobi it uses the native
setObjective
method.
Parameters
expr : linear expression Objective linear expression. sense : {“minimize”, “min”, “maximize”, “max”}, default “minimize” Optimization direction.
Raises
ValueError
If sense
is invalid.
Source code in flowpaths/utils/solverwrapper.py
write_model
Persist model to a file supported by the backend.
Parameters
filename : str | os.PathLike
Target path. HiGHS chooses format based on extension; Gurobi uses
native write
method behaviour.