Skip to content

Model

The founding block of any Pyoframe optimization model onto which variables, constraints, and an objective can be added.

Parameters:

Name Type Description Default
solver SUPPORTED_SOLVER_TYPES | _Solver | None

The solver to use. If None, Pyoframe will try to use whichever solver is installed (unless Config.default_solver was changed from its default value of auto).

None
solver_env dict[str, str] | None

Gurobi only: a dictionary of parameters to set when creating the Gurobi environment.

None
name str | None

The name of the model. Currently it is not used for much.

None
solver_uses_variable_names bool

If True, the solver will use your custom variable names in its outputs (e.g. during Model.write()). This can be useful for debugging .lp, .sol, and .ilp files, but may worsen performance.

False
print_uses_variable_names bool

If True, pyoframe will use your custom variables names when printing elements of the model to the console. This is useful for debugging, but may slightly worsen performance.

True
sense ObjSense | ObjSenseValue | None

Either "min" or "max". Indicates whether it's a minimization or maximization problem. Typically, this parameter can be omitted (None) as it will automatically be set when the objective is set using .minimize or .maximize.

None
verbose bool

If True, logging messages will be printed every time a Variable or Constraint is added to the model. This is useful to discover performance bottlenecks. Logging can be further configured via the logging module by modifying the pyoframe logger.

False

Examples:

>>> m = pf.Model()
>>> m.X = pf.Variable()
>>> m.my_constraint = m.X <= 10
>>> m
<Model vars=1 constrs=1 has_objective=False solver=gurobi>

Use solver_env to, for example, connect to a Gurobi Compute Server:

>>> m = pf.Model(
...     "gurobi",
...     solver_env=dict(ComputeServer="myserver", ServerPassword="mypassword"),
... )
Traceback (most recent call last):
...
RuntimeError: Could not resolve host: myserver (code 6, command POST http://myserver/api/v1/cluster/jobs)

Methods:

Name Description
compute_IIS

Gurobi and COPT only: Computes the Irreducible Infeasible Set (IIS) of the model.

constraints_size_info

Returns a DataFrame with information about the memory usage of each constraint in the model.

convert_to_fixed

Gurobi only: Converts a mixed integer program into a continuous one by fixing all the non-continuous variables to their solution values.

dispose

Disposes of the model and cleans up the solver environment.

optimize

Optimizes the model using your selected solver (e.g. Gurobi, HiGHS).

variables_size_info

Returns a DataFrame with information about the memory usage of each variable in the model.

write

Outputs the model or the solution to a file (e.g. a .lp, .sol, .mps, or .ilp file).

Attributes:

Name Type Description
attr Container

An object that allows reading and writing model attributes.

binary_variables Generator[Variable]

Returns the model's binary variables.

constraints list[Constraint]

Returns the model's constraints.

has_objective bool

Returns whether the model's objective has been defined.

integer_variables Generator[Variable]

Returns the model's integer variables.

maximize Objective

Sets or gets the model's objective for maximization problems.

minimize Objective

Sets or gets the model's objective for minimization problems.

name str | None
objective Objective

Returns the model's objective.

params Container

An object that allows reading and writing solver-specific parameters.

poi

The underlying PyOptInterface model used to interact with the solver.

sense ObjSense | None
solver_name str
solver_uses_variable_names

Whether to pass human-readable variable names to the solver.

variables list[Variable]

Returns a list of the model's variables.

Source code in pyoframe/_model.py
def __init__(
    self,
    solver: SUPPORTED_SOLVER_TYPES | _Solver | None = None,
    solver_env: dict[str, str] | None = None,
    *,
    name: str | None = None,
    solver_uses_variable_names: bool = False,
    print_uses_variable_names: bool = True,
    sense: ObjSense | ObjSenseValue | None = None,
    verbose: bool = False,
):
    self._poi, self.solver = Model._create_poi_model(solver, solver_env)
    self.solver_name: str = self.solver.name
    self._variables: list[Variable] = []
    self._constraints: list[Constraint] = []
    self.sense: ObjSense | None = ObjSense(sense) if sense is not None else None
    self._objective: Objective | None = None
    self._var_map = NamedVariableMapper() if print_uses_variable_names else None
    self.name: str | None = name

    self._params = Container(self._set_param, self._get_param)
    self._attr = Container(self._set_attr, self._get_attr)
    self._solver_uses_variable_names = solver_uses_variable_names

    self._logger = None
    self._last_log = None
    if verbose:
        import logging

        self._logger = logging.getLogger("pyoframe")
        self._logger.addHandler(logging.NullHandler())
        self._logger.setLevel(logging.DEBUG)

        self._last_log = time.time()

attr: Container

An object that allows reading and writing model attributes.

Several model attributes are common across all solvers making it easy to switch between solvers (see supported attributes for Gurobi, HiGHS, Ipopt), and COPT.

We additionally support all of Gurobi's attributes when using Gurobi.

Examples:

>>> m = pf.Model()
>>> m.v = pf.Variable(lb=1, ub=1, vtype="integer")
>>> m.attr.Silent = True  # Prevent solver output from being printed
>>> m.optimize()
>>> m.attr.TerminationStatus
<TerminationStatusCode.OPTIMAL: 2>

Some attributes, like NumVars, are solver-specific.

>>> m = pf.Model("gurobi")
>>> m.attr.NumConstrs
0
>>> m = pf.Model("highs")
>>> m.attr.NumConstrs
Traceback (most recent call last):
...
KeyError: 'NumConstrs'
See Also

Variable.attr for setting variable attributes and Constraint.attr for setting constraint attributes.

binary_variables: Generator[Variable]

Returns the model's binary variables.

Examples:

>>> m = pf.Model()
>>> m.X = pf.Variable(vtype=pf.VType.BINARY)
>>> m.Y = pf.Variable()
>>> len(list(m.binary_variables))
1

constraints: list[Constraint]

Returns the model's constraints.

has_objective: bool

Returns whether the model's objective has been defined.

Examples:

>>> m = pf.Model()
>>> m.has_objective
False
>>> m.X = pf.Variable()
>>> m.maximize = m.X
>>> m.has_objective
True

integer_variables: Generator[Variable]

Returns the model's integer variables.

Examples:

>>> m = pf.Model()
>>> m.X = pf.Variable(vtype=pf.VType.INTEGER)
>>> m.Y = pf.Variable()
>>> len(list(m.integer_variables))
1

maximize: Objective

Sets or gets the model's objective for maximization problems.

minimize: Objective

Sets or gets the model's objective for minimization problems.

name: str | None = name

objective: Objective

Returns the model's objective.

Raises:

Type Description
ValueError

If the objective has not been defined.

See Also

Model.has_objective

params: Container

An object that allows reading and writing solver-specific parameters.

See the list of available parameters for Gurobi, HiGHS, Ipopt, and COPT.

Examples:

For example, if you'd like to use Gurobi's barrier method, you can set the Method parameter:

>>> m = pf.Model("gurobi")
>>> m.params.Method = 2

poi

The underlying PyOptInterface model used to interact with the solver.

Modifying the underlying model directly is not recommended and may lead to unexpected behaviors.

sense: ObjSense | None = ObjSense(sense) if sense is not None else None

solver_name: str = self.solver.name

solver_uses_variable_names

Whether to pass human-readable variable names to the solver.

variables: list[Variable]

Returns a list of the model's variables.

compute_IIS()

Gurobi and COPT only: Computes the Irreducible Infeasible Set (IIS) of the model.

Gurobi and COPT only

This method only works with the Gurobi and COPT solver. Open an issue if you'd like to see support for other solvers.

Examples:

>>> m = pf.Model("gurobi")
>>> m.X = pf.Variable(lb=0, ub=2)
>>> m.Y = pf.Variable(lb=0, ub=2)
>>> m.bad_constraint = m.X >= 3
>>> m.minimize = m.X + m.Y
>>> m.optimize()
>>> m.attr.TerminationStatus
<TerminationStatusCode.INFEASIBLE: 3>
>>> m.bad_constraint.attr.IIS
Traceback (most recent call last):
...
RuntimeError: Unable to retrieve attribute 'IISConstr'
>>> m.compute_IIS()
>>> m.bad_constraint.attr.IIS
True
Source code in pyoframe/_model.py
@for_solvers("gurobi", "copt")
def compute_IIS(self):
    """Gurobi and COPT only: Computes the Irreducible Infeasible Set (IIS) of the model.

    !!! warning "Gurobi and COPT only"
        This method only works with the Gurobi and COPT solver. Open an issue if you'd like to see support for other solvers.

    Examples:
        >>> m = pf.Model("gurobi")
        >>> m.X = pf.Variable(lb=0, ub=2)
        >>> m.Y = pf.Variable(lb=0, ub=2)
        >>> m.bad_constraint = m.X >= 3
        >>> m.minimize = m.X + m.Y
        >>> m.optimize()
        >>> m.attr.TerminationStatus
        <TerminationStatusCode.INFEASIBLE: 3>
        >>> m.bad_constraint.attr.IIS
        Traceback (most recent call last):
        ...
        RuntimeError: Unable to retrieve attribute 'IISConstr'
        >>> m.compute_IIS()
        >>> m.bad_constraint.attr.IIS
        True
    """
    self.poi.computeIIS()

constraints_size_info(memory_unit: pl.SizeUnit = 'b') -> pl.DataFrame

Returns a DataFrame with information about the memory usage of each constraint in the model.

Experimental

This method is experimental and may change or be removed in future versions. We're interested in your feedback.

Parameters:

Name Type Description Default
memory_unit SizeUnit

The size of the memory unit to use for the memory usage information. See polars.DataFrame.estimated_size.

'b'

Examples:

>>> m = pf.Model()
>>> m.X = pf.Variable()
>>> m.Y = pf.Variable(pf.Set(dim_x=range(100)))
>>> m.c1 = m.X.over("dim_x") + m.Y <= 10
>>> m.c2 = m.X + m.Y.sum() <= 20
>>> m.constraints_size_info()
shape: (3, 7)
┌───────┬───────────────┬──────────────┬──────────────┬──────────────┬──────────────┬──────────────┐
│ name  ┆ num_constrain ┆ num_constrai ┆ num_non_zero ┆ num_non_zero ┆ pyoframe_mem ┆ pyoframe_mem │
│ ---   ┆ ts            ┆ nts_perc     ┆ s            ┆ s_perc       ┆ ory_usage    ┆ ory_usage_pe │
│ str   ┆ ---           ┆ ---          ┆ ---          ┆ ---          ┆ ---          ┆ rc           │
│       ┆ i64           ┆ str          ┆ i64          ┆ str          ┆ i64          ┆ ---          │
│       ┆               ┆              ┆              ┆              ┆              ┆ str          │
╞═══════╪═══════════════╪══════════════╪══════════════╪══════════════╪══════════════╪══════════════╡
│ c1    ┆ 100           ┆ 99.0%        ┆ 300          ┆ 74.6%        ┆ 7314         ┆ 85.6%        │
│ c2    ┆ 1             ┆ 1.0%         ┆ 102          ┆ 25.4%        ┆ 1228         ┆ 14.4%        │
│ TOTAL ┆ 101           ┆ 100.0%       ┆ 402          ┆ 100.0%       ┆ 8542         ┆ 100.0%       │
└───────┴───────────────┴──────────────┴──────────────┴──────────────┴──────────────┴──────────────┘
Source code in pyoframe/_model.py
def constraints_size_info(self, memory_unit: pl.SizeUnit = "b") -> pl.DataFrame:
    """Returns a DataFrame with information about the memory usage of each constraint in the model.

    !!! warning "Experimental"
        This method is experimental and may change or be removed in future versions. We're interested in your [feedback](https://github.com/Bravos-Power/pyoframe/issues).

    Parameters:
        memory_unit:
            The size of the memory unit to use for the memory usage information.
            See [`polars.DataFrame.estimated_size`](https://docs.pola.rs/api/python/stable/reference/dataframe/api/polars.DataFrame.estimated_size.html).

    Examples:
        >>> m = pf.Model()
        >>> m.X = pf.Variable()
        >>> m.Y = pf.Variable(pf.Set(dim_x=range(100)))
        >>> m.c1 = m.X.over("dim_x") + m.Y <= 10
        >>> m.c2 = m.X + m.Y.sum() <= 20
        >>> m.constraints_size_info()
        shape: (3, 7)
        ┌───────┬───────────────┬──────────────┬──────────────┬──────────────┬──────────────┬──────────────┐
        │ name  ┆ num_constrain ┆ num_constrai ┆ num_non_zero ┆ num_non_zero ┆ pyoframe_mem ┆ pyoframe_mem │
        │ ---   ┆ ts            ┆ nts_perc     ┆ s            ┆ s_perc       ┆ ory_usage    ┆ ory_usage_pe │
        │ str   ┆ ---           ┆ ---          ┆ ---          ┆ ---          ┆ ---          ┆ rc           │
        │       ┆ i64           ┆ str          ┆ i64          ┆ str          ┆ i64          ┆ ---          │
        │       ┆               ┆              ┆              ┆              ┆              ┆ str          │
        ╞═══════╪═══════════════╪══════════════╪══════════════╪══════════════╪══════════════╪══════════════╡
        │ c1    ┆ 100           ┆ 99.0%        ┆ 300          ┆ 74.6%        ┆ 7314         ┆ 85.6%        │
        │ c2    ┆ 1             ┆ 1.0%         ┆ 102          ┆ 25.4%        ┆ 1228         ┆ 14.4%        │
        │ TOTAL ┆ 101           ┆ 100.0%       ┆ 402          ┆ 100.0%       ┆ 8542         ┆ 100.0%       │
        └───────┴───────────────┴──────────────┴──────────────┴──────────────┴──────────────┴──────────────┘
    """
    data = pl.DataFrame(
        [
            dict(
                name=c.name,
                n=len(c),
                non_zeros=c.lhs.data.height,
                mem=c.estimated_size(memory_unit),
            )
            for c in self.constraints
        ]
    ).sort("n", descending=True)

    total = data.sum().with_columns(name=pl.lit("TOTAL"))
    data = pl.concat([data, total])

    def format(col: pl.Expr) -> pl.Expr:
        return (100 * col).round(1).cast(pl.String) + pl.lit("%")

    data = data.with_columns(
        n_per=format(pl.col("n") / total["n"].item()),
        non_zeros_per=format(pl.col("non_zeros") / total["non_zeros"].item()),
        mem_per=format(pl.col("mem") / total["mem"].item()),
    )

    data = data.select(
        "name", "n", "n_per", "non_zeros", "non_zeros_per", "mem", "mem_per"
    )
    data = data.rename(
        {
            "n": "num_constraints",
            "n_per": "num_constraints_perc",
            "non_zeros": "num_non_zeros",
            "non_zeros_per": "num_non_zeros_perc",
            "mem": "pyoframe_memory_usage",
            "mem_per": "pyoframe_memory_usage_perc",
        }
    )

    return pl.DataFrame(data)

convert_to_fixed() -> None

Gurobi only: Converts a mixed integer program into a continuous one by fixing all the non-continuous variables to their solution values.

Gurobi only

This method only works with the Gurobi solver. Open an issue if you'd like to see support for other solvers.

Examples:

>>> m = pf.Model("gurobi")
>>> m.X = pf.Variable(vtype=pf.VType.BINARY, lb=0)
>>> m.Y = pf.Variable(vtype=pf.VType.INTEGER, lb=0)
>>> m.Z = pf.Variable(lb=0)
>>> m.my_constraint = m.X + m.Y + m.Z <= 10
>>> m.maximize = 3 * m.X + 2 * m.Y + m.Z
>>> m.optimize()
>>> m.X.solution, m.Y.solution, m.Z.solution
(1, 9, 0.0)
>>> m.my_constraint.dual
Traceback (most recent call last):
...
RuntimeError: Unable to retrieve attribute 'Pi'
>>> m.convert_to_fixed()
>>> m.optimize()
>>> m.my_constraint.dual
1.0

Only works for Gurobi:

>>> m = pf.Model("highs")
>>> m.convert_to_fixed()
Traceback (most recent call last):
...
NotImplementedError: Method 'convert_to_fixed' is not implemented for solver 'highs'.
Source code in pyoframe/_model.py
@for_solvers("gurobi")
def convert_to_fixed(self) -> None:
    """Gurobi only: Converts a mixed integer program into a continuous one by fixing all the non-continuous variables to their solution values.

    !!! warning "Gurobi only"
        This method only works with the Gurobi solver. Open an issue if you'd like to see support for other solvers.

    Examples:
        >>> m = pf.Model("gurobi")
        >>> m.X = pf.Variable(vtype=pf.VType.BINARY, lb=0)
        >>> m.Y = pf.Variable(vtype=pf.VType.INTEGER, lb=0)
        >>> m.Z = pf.Variable(lb=0)
        >>> m.my_constraint = m.X + m.Y + m.Z <= 10
        >>> m.maximize = 3 * m.X + 2 * m.Y + m.Z
        >>> m.optimize()
        >>> m.X.solution, m.Y.solution, m.Z.solution
        (1, 9, 0.0)
        >>> m.my_constraint.dual
        Traceback (most recent call last):
        ...
        RuntimeError: Unable to retrieve attribute 'Pi'
        >>> m.convert_to_fixed()
        >>> m.optimize()
        >>> m.my_constraint.dual
        1.0

        Only works for Gurobi:

        >>> m = pf.Model("highs")
        >>> m.convert_to_fixed()
        Traceback (most recent call last):
        ...
        NotImplementedError: Method 'convert_to_fixed' is not implemented for solver 'highs'.
    """
    self.poi._converttofixed()

dispose()

Disposes of the model and cleans up the solver environment.

When using Gurobi compute server, this cleanup will ensure your run is not marked as 'ABORTED'.

Note that once the model is disposed, it cannot be used anymore.

Examples:

>>> m = pf.Model()
>>> m.X = pf.Variable(ub=1)
>>> m.maximize = m.X
>>> m.optimize()
>>> m.X.solution
1.0
>>> m.dispose()
Source code in pyoframe/_model.py
def dispose(self):
    """Disposes of the model and cleans up the solver environment.

    When using Gurobi compute server, this cleanup will
    ensure your run is not marked as 'ABORTED'.

    Note that once the model is disposed, it cannot be used anymore.

    Examples:
        >>> m = pf.Model()
        >>> m.X = pf.Variable(ub=1)
        >>> m.maximize = m.X
        >>> m.optimize()
        >>> m.X.solution
        1.0
        >>> m.dispose()
    """
    env = None
    if hasattr(self.poi, "_env"):
        env = self.poi._env
    self.poi.close()
    if env is not None:
        env.close()

optimize()

Optimizes the model using your selected solver (e.g. Gurobi, HiGHS).

Source code in pyoframe/_model.py
def optimize(self):
    """Optimizes the model using your selected solver (e.g. Gurobi, HiGHS)."""
    self.poi.optimize()

variables_size_info(memory_unit: pl.SizeUnit = 'b') -> pl.DataFrame

Returns a DataFrame with information about the memory usage of each variable in the model.

Experimental

This method is experimental and may change or be removed in future versions. We're interested in your [feedback](feedback.

Parameters:

Name Type Description Default
memory_unit SizeUnit

The size of the memory unit to use for the memory usage information. See polars.DataFrame.estimated_size.

'b'

Examples:

>>> m = pf.Model()
>>> m.X = pf.Variable()
>>> m.Y = pf.Variable(pf.Set(dim_x=range(100)))
>>> m.variables_size_info()
shape: (3, 5)
┌───────┬───────────────┬────────────────────┬───────────────────────┬────────────────────────────┐
│ name  ┆ num_variables ┆ num_variables_perc ┆ pyoframe_memory_usage ┆ pyoframe_memory_usage_perc │
│ ---   ┆ ---           ┆ ---                ┆ ---                   ┆ ---                        │
│ str   ┆ i64           ┆ str                ┆ i64                   ┆ str                        │
╞═══════╪═══════════════╪════════════════════╪═══════════════════════╪════════════════════════════╡
│ Y     ┆ 100           ┆ 99.0%              ┆ 1200                  ┆ 99.7%                      │
│ X     ┆ 1             ┆ 1.0%               ┆ 4                     ┆ 0.3%                       │
│ TOTAL ┆ 101           ┆ 100.0%             ┆ 1204                  ┆ 100.0%                     │
└───────┴───────────────┴────────────────────┴───────────────────────┴────────────────────────────┘
Source code in pyoframe/_model.py
def variables_size_info(self, memory_unit: pl.SizeUnit = "b") -> pl.DataFrame:
    """Returns a DataFrame with information about the memory usage of each variable in the model.

    !!! warning "Experimental"
        This method is experimental and may change or be removed in future versions. We're interested in your [feedback]([feedback](https://github.com/Bravos-Power/pyoframe/issues).

    Parameters:
        memory_unit:
            The size of the memory unit to use for the memory usage information.
            See [`polars.DataFrame.estimated_size`](https://docs.pola.rs/api/python/stable/reference/dataframe/api/polars.DataFrame.estimated_size.html).

    Examples:
        >>> m = pf.Model()
        >>> m.X = pf.Variable()
        >>> m.Y = pf.Variable(pf.Set(dim_x=range(100)))
        >>> m.variables_size_info()
        shape: (3, 5)
        ┌───────┬───────────────┬────────────────────┬───────────────────────┬────────────────────────────┐
        │ name  ┆ num_variables ┆ num_variables_perc ┆ pyoframe_memory_usage ┆ pyoframe_memory_usage_perc │
        │ ---   ┆ ---           ┆ ---                ┆ ---                   ┆ ---                        │
        │ str   ┆ i64           ┆ str                ┆ i64                   ┆ str                        │
        ╞═══════╪═══════════════╪════════════════════╪═══════════════════════╪════════════════════════════╡
        │ Y     ┆ 100           ┆ 99.0%              ┆ 1200                  ┆ 99.7%                      │
        │ X     ┆ 1             ┆ 1.0%               ┆ 4                     ┆ 0.3%                       │
        │ TOTAL ┆ 101           ┆ 100.0%             ┆ 1204                  ┆ 100.0%                     │
        └───────┴───────────────┴────────────────────┴───────────────────────┴────────────────────────────┘
    """
    data = pl.DataFrame(
        [
            dict(name=v.name, n=len(v), mem=v.estimated_size(memory_unit))
            for v in self.variables
        ]
    ).sort("n", descending=True)

    total = data.sum().with_columns(name=pl.lit("TOTAL"))
    data = pl.concat([data, total])

    def format(expr: pl.Expr) -> pl.Expr:
        return (100 * expr).round(1).cast(pl.String) + pl.lit("%")

    data = data.with_columns(
        n_per=format(pl.col("n") / total["n"].item()),
        mem_per=format(pl.col("mem") / total["mem"].item()),
    )

    data = data.select("name", "n", "n_per", "mem", "mem_per")
    data = data.rename(
        {
            "n": "num_variables",
            "n_per": "num_variables_perc",
            "mem": "pyoframe_memory_usage",
            "mem_per": "pyoframe_memory_usage_perc",
        }
    )

    return pl.DataFrame(data)

write(file_path: Path | str, pretty: bool = False)

Outputs the model or the solution to a file (e.g. a .lp, .sol, .mps, or .ilp file).

These files can be useful for manually debugging a model. Consult your solver documentation to learn more.

When creating your model, set solver_uses_variable_names to make the outputed file human-readable.

m = pf.Model(solver_uses_variable_names=True)

For Gurobi, solver_uses_variable_names=True is mandatory when using .write(). This may become mandatory for other solvers too without notice.

Parameters:

Name Type Description Default
file_path Path | str

The path to the file to write to.

required
pretty bool

Only used when writing .sol files in HiGHS. If True, will use HiGH's pretty print columnar style which contains more information.

False
Source code in pyoframe/_model.py
def write(self, file_path: Path | str, pretty: bool = False):
    """Outputs the model or the solution to a file (e.g. a `.lp`, `.sol`, `.mps`, or `.ilp` file).

    These files can be useful for manually debugging a model.
    Consult your solver documentation to learn more.

    When creating your model, set [`solver_uses_variable_names`][pyoframe.Model]
    to make the outputed file human-readable.

    ```python
    m = pf.Model(solver_uses_variable_names=True)
    ```

    For Gurobi, `solver_uses_variable_names=True` is mandatory when using
    .write(). This may become mandatory for other solvers too without notice.

    Parameters:
        file_path:
            The path to the file to write to.
        pretty:
            Only used when writing .sol files in HiGHS. If `True`, will use HiGH's pretty print columnar style which contains more information.
    """
    if not self.solver.supports_write:
        raise NotImplementedError(f"{self.solver.name} does not support .write()")
    if (
        not self.solver_uses_variable_names
        and self.solver.accelerate_with_repeat_names
    ):
        raise ValueError(
            f"{self.solver.name} requires solver_uses_variable_names=True to use .write()"
        )

    file_path = Path(file_path)
    file_path.parent.mkdir(parents=True, exist_ok=True)

    kwargs = {}
    if self.solver.name == "highs":
        if self.solver_uses_variable_names:
            self.params.write_solution_style = 1
        kwargs["pretty"] = pretty
    self.poi.write(str(file_path), **kwargs)