Skip to content

model

Classes:

Name Description
Model

The object that holds all the variables, constraints, and the objective.

Model(name=None, solver=None, solver_env=None, use_var_names=False, sense=None)

The object that holds all the variables, constraints, and the objective.

Parameters:

Name Type Description Default
name Optional[str]

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

None
solver SUPPORTED_SOLVER_TYPES | Solver | None

The solver to use. If None, Config.default_solver will be used. If Config.default_solver has not been set (None), Pyoframe will try to detect whichever solver is already installed.

None
solver_env Optional[Dict[str, str]]

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

None
use_var_names bool

Whether to pass variable names to the solver. Set to True if you'd like outputs from e.g. Model.write() to be legible. Does not work with HiGHS (see here).

False
sense Union[ObjSense, ObjSenseValue, None]

Either "min" or "max". Indicates whether it's a minmization 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

Examples:

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

Try setting the Gurobi license:

>>> m = pf.Model(
...     solver="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
__del__
__repr__
__setattr__
compute_IIS

Computes the Irreducible Infeasible Set (IIS) of the model.

convert_to_fixed

Turns a mixed integer program into a continuous one by fixing

create_poi_model
dispose

Disposes of the model and cleans up the solver environment.

optimize

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

write

Output the model to a file.

Attributes:

Name Type Description
attr

An object that allows reading and writing model attributes.

binary_variables Iterable[Variable]

Examples:

constraints
integer_variables Iterable[Variable]

Examples:

maximize
minimize
name
objective
params Container

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

sense
solver_name
use_var_names
var_map
variables List[Variable]
Source code in pyoframe/model.py
def __init__(
    self,
    name: Optional[str] = None,
    solver: SUPPORTED_SOLVER_TYPES | Solver | None = None,
    solver_env: Optional[Dict[str, str]] = None,
    use_var_names: bool = False,
    sense: Union[ObjSense, ObjSenseValue, None] = None,
):
    self.poi, self.solver = Model.create_poi_model(solver, solver_env)
    self.solver_name = self.solver.name
    self._variables: List[Variable] = []
    self._constraints: List[Constraint] = []
    self.sense = ObjSense(sense) if sense is not None else None
    self._objective: Optional[Objective] = None
    self.var_map = (
        NamedVariableMapper(Variable) if Config.print_uses_variable_names else None
    )
    self.name = name

    self._params = Container(self._set_param, self._get_param)
    self._attr = Container(self._set_attr, self._get_attr)
    self._use_var_names = use_var_names

attr property

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, and Ipopt).

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(solver="gurobi")
>>> m.attr.NumConstrs
0
>>> m = pf.Model(solver="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 property

Examples:

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

constraints property

integer_variables property

Examples:

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

maximize property writable

minimize property writable

name = name instance-attribute

objective property writable

params property

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

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

Examples:

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

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

sense = ObjSense(sense) if sense is not None else None instance-attribute

solver_name = self.solver.name instance-attribute

use_var_names property

var_map = NamedVariableMapper(Variable) if Config.print_uses_variable_names else None instance-attribute

variables property

__del__()

Source code in pyoframe/model.py
def __del__(self):
    # This ensures that the model is closed *before* the environment is. This avoids the Gurobi warning:
    #   Warning: environment still referenced so free is deferred (Continue to use WLS)
    # I include the hasattr check to avoid errors in case __init__ failed and poi was never set.
    if hasattr(self, "poi"):
        self.poi.close()

__repr__()

Source code in pyoframe/model.py
def __repr__(self) -> str:
    return get_obj_repr(
        self,
        name=self.name,
        vars=len(self.variables),
        constrs=len(self.constraints),
        objective=bool(self.objective),
    )

__setattr__(__name, __value)

Source code in pyoframe/model.py
def __setattr__(self, __name: str, __value: Any) -> None:
    if __name not in Model._reserved_attributes and not isinstance(
        __value, (ModelElement, pl.DataFrame, pd.DataFrame)
    ):
        raise PyoframeError(
            f"Cannot set attribute '{__name}' on the model because it isn't of type ModelElement (e.g. Variable, Constraint, ...)"
        )

    if (
        isinstance(__value, ModelElement)
        and __name not in Model._reserved_attributes
    ):
        if isinstance(__value, ModelElementWithId):
            assert not hasattr(self, __name), (
                f"Cannot create {__name} since it was already created."
            )

        __value.on_add_to_model(self, __name)

        if isinstance(__value, Variable):
            self._variables.append(__value)
            if self.var_map is not None:
                self.var_map.add(__value)
        elif isinstance(__value, Constraint):
            self._constraints.append(__value)
    return super().__setattr__(__name, __value)

compute_IIS()

Computes the Irreducible Infeasible Set (IIS) of the model.

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(solver="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):
    """
    Computes the Irreducible Infeasible Set (IIS) of the model.

    !!! 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(solver="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()

convert_to_fixed()

Turns a mixed integer program into a continuous one by fixing all the integer and binary 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(solver="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("max", solver="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:
    """
    Turns a mixed integer program into a continuous one by fixing
    all the integer and binary 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(solver="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("max", solver="highs")
        >>> m.convert_to_fixed()
        Traceback (most recent call last):
        ...
        NotImplementedError: Method 'convert_to_fixed' is not implemented for solver 'highs'.
    """
    self.poi._converttofixed()

create_poi_model(solver, solver_env) classmethod

Source code in pyoframe/model.py
@classmethod
def create_poi_model(
    cls, solver: Optional[str | Solver], solver_env: Optional[Dict[str, str]]
):
    if solver is None:
        if Config.default_solver is None:
            for solver_option in SUPPORTED_SOLVERS:
                try:
                    return cls.create_poi_model(solver_option, solver_env)
                except RuntimeError:
                    pass
            raise ValueError(
                'Could not automatically find a solver. Is one installed? If so, specify which one: e.g. Model(solver="gurobi")'
            )
        else:
            solver = Config.default_solver

    if isinstance(solver, str):
        solver = solver.lower()
        for s in SUPPORTED_SOLVERS:
            if s.name == solver:
                solver = s
                break
        else:
            raise ValueError(
                f"Unsupported solver: '{solver}'. Supported solvers are: {', '.join(s.name for s in SUPPORTED_SOLVERS)}."
            )

    if solver.name == "gurobi":
        from pyoptinterface import gurobi

        if solver_env is None:
            env = gurobi.Env()
        else:
            env = gurobi.Env(empty=True)
            for key, value in solver_env.items():
                env.set_raw_parameter(key, value)
            env.start()
        model = gurobi.Model(env)
    elif solver.name == "highs":
        from pyoptinterface import highs

        model = highs.Model()
    elif solver.name == "ipopt":
        try:
            from pyoptinterface import ipopt
        except ModuleNotFoundError as e:  # pragma: no cover
            raise ModuleNotFoundError(
                "Failed to import the Ipopt solver. Did you run `pip install pyoptinterface[ipopt]`?"
            ) from e

        try:
            model = ipopt.Model()
        except RuntimeError as e:  # pragma: no cover
            if "IPOPT library is not loaded" in str(e):
                raise RuntimeError(
                    "Could not find the Ipopt solver. Are you sure you've properly installed it and added it to your PATH?"
                ) from e
            raise e
    else:
        raise ValueError(
            f"Solver {solver} not recognized or supported."
        )  # pragma: no cover

    constant_var = model.add_variable(lb=1, ub=1, name="ONE")
    if constant_var.index != CONST_TERM:
        raise ValueError(
            "The first variable should have index 0."
        )  # pragma: no cover
    return model, solver

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()

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

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

write(file_path, pretty=False)

Output the model to a file.

Typical usage includes writing the solution to a .sol file as well as writing the problem to a .lp or .mps file. Set use_var_names in your model constructor to True if you'd like the output to contain human-readable names (useful for debugging).

Parameters:

Name Type Description Default
file_path Union[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: Union[Path, str], pretty: bool = False):
    """
    Output the model to a file.

    Typical usage includes writing the solution to a `.sol` file as well as writing the problem to a `.lp` or `.mps` file.
    Set `use_var_names` in your model constructor to `True` if you'd like the output to contain human-readable names (useful for debugging).

    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.
    """
    self.solver.check_supports_write()

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

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