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) or
Gurobi (gurobipy) 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/.mpsdepending 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_timeoutis set and the reported status becomeskTimeLimit.
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").
- gurobi_params (dict): optional extra Gurobi parameters passed
through via Env.setParam before model creation.
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.
Raises
ValueError If unsupported solver name, invalid optimization sense or an invalid tolerance (< 1e-9) is supplied.
Source code in flowpaths/utils/solverwrapper.py
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | |
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_indicator_constraint
Add an indicator constraint (Gurobi only).
Parameters
binary_var : variable Binary variable controlling activation. binary_value : int Indicator value in {0, 1} for activation. expr : TempConstr Constraint expression activated by the indicator. name : str, optional Optional identifier.
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
990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 | |
add_variables
Create a set of variables sharing a common name prefix.
Important: Avoid collisions!
This function does not track or enforce unique/non-overlapping prefixes. The caller is responsible for choosing prefixes that do not create ambiguous names when mixed with other variables.
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 | dict | sequence, default (0, 1)
Lower and upper bounds for created variables. Can be:
- scalars (applied to all indexes), or
- dicts mapping each index -> bound, or
- sequences aligned with indexes order (same length).
var_type : {“integer”, “continuous”, “binary”}, 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).
Source code in flowpaths/utils/solverwrapper.py
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 | |
fix_variable
Fix an existing variable to a constant value by tightening its bounds.
This avoids adding an explicit equality constraint (var == value) which can slow down solving compared to changing bounds directly.
Parameters
var : backend variable object
The variable returned previously by add_variables.
value : int | float
The value to which the variable should be fixed.
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_values
Return solution values for variables without name parsing.
Parameters
variables : iterable | mapping Either an iterable of (index, variable) pairs, or a mapping where keys are indices and values are variable objects (e.g., a dict or a Gurobi tupledict). binary_values : bool, default False If True, round values to 0/1 and validate against tolerance.
Returns
dict Dictionary mapping each provided index to its solution value.
Notes
- For HiGHS, values are retrieved using the internal column index of each
variable via a single call to
allVariableValues. - For Gurobi, values are read from
Var.X. - Indices are taken from the first element of each tuple in
variables(when an iterable of pairs is supplied). If a mapping is supplied, its items() are used.
Source code in flowpaths/utils/solverwrapper.py
get_variable_values
Deprecated
Use get_values(...) instead.
Retrieve the values of variables belonging to a given prefix.
This method matches variables using one of these forms for the given
name_prefix:
- Structured names: <prefix>(i, j, ...) or <prefix>[i, j, ...]
- Legacy single numeric suffix: <prefix>k or <prefix>_k
- Exact scalar variable name: <prefix> (when index_types is empty)
Under these rules, overlapping prefixes (e.g., x and x_long)
won’t interfere with each other. Callers must still avoid custom ad-hoc
naming that mimics these patterns for different variables.
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
780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 | |
has_feasible_solution
Return whether the backend currently exposes a feasible incumbent.
This is primarily used to recover a best-known solution on solver time
limit. It is conservative: returns False when feasibility cannot be
established with confidence.
Source code in flowpaths/utils/solverwrapper.py
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 or brackets.
Supported forms:
- ``<prefix>(i,j,...)`` (HiGHS style / tuple repr)
- ``<prefix>[i,j,...]`` (Gurobi addVars style)
- Single-suffix names ``<prefix><i>`` are handled elsewhere.
Returns a list of raw index components as strings (quotes stripped).
Commas inside nested parentheses/brackets are treated as part of the
component, e.g. var((a,b)) -> [“(a,b)”].
Source code in flowpaths/utils/solverwrapper.py
697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 | |
print_variable_names_values
Print name = value lines for every variable (debug helper).
Source code in flowpaths/utils/solverwrapper.py
queue_fix_variable
Queue a variable to be fixed (LB=UB=value) in a later batch update.
queue_set_var_lower_bound
Queue a variable to have its lower bound raised to lb in batch.
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
set_start_values
Assign MIP-start values to variables when supported by the backend.
Parameters
variable_values : dict
Mapping variable -> value.
Notes
- Gurobi: values are assigned to the
Startattribute. - HiGHS: current wrapper exposes no MIP-start support; the call is a no-op and is kept for a uniform API.
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.