Security News
Weekly Downloads Now Available in npm Package Search Results
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.
A powerful Python library for operations research. Define, solve, and interact with mathematical models in a standardized manner across different optimization packages.
Documentation: https://dapensoft.github.io/pyorlib
Source Code: https://github.com/dapensoft/pyorlib
PyORlib is a powerful Python library for operations research and optimization. It provides a set of abstractions to easily define, solve, and interact with mathematical models in a standardized manner across different optimization packages. With PyORlib, you can easily implement mathematical models using a user-friendly interface, while seamlessly identifying the ideal solver or optimization package, such as CPLEX, Gurobi, OR-Tools, or PuLP, that perfectly aligns with your specific requirements.
PyORlib offers a powerful yet easy-to-use set of tools for mathematical modeling and optimization:
By default, PyORlib's core functionalities and optimization utilities only require Python 3.10+. However, additional optional dependencies may be needed to work with optimization models and solver integrations based on your use case. For more information on supported integrations, see the Optional Dependencies section.
PyORlib is available as a Python package and can be easily installed using pip
. To install the core
functionalities, open your terminal and execute the following command:
pip install pyorlib
For optimization models and solver integrations, please refer to the Optional Dependencies section to learn more about the supported integrations and the dependencies you may need to install.
Experience the power of PyORlib through a simple example that illustrates the core concepts and basic usage of the package by formulating and solving a mixed integer programming (MIP) problem from the OR-Tools documentation. This example will provide you with a clear and concise introduction to the package's functionalities and its application in real-world optimization challenges.
In this example, we will find the highest integer coordinates (x, y) on the Y-axis within a defined shape. Our objective is to maximize the value of an objective function while satisfying linear constraints, as shown below with a mathematical formulation:
$$ \begin{align} \text{Maximize:} \quad & x + 10y \ \text{Subject to:} \quad & x + 7y = 17.5 \ & 0 \leq x \leq 3.5 \ & 0 \leq y \leq 2.5 \ \end{align} $$
Since the constraints are linear, we can classify this problem as a linear optimization problem in which the solutions are required to be integers. The feasible region and integer points for this problem are shown below:
In order to model and solve this problem, we'll be using the PyORlib package. In this example, we'll utilize the OR-Tools optimization package, which is one of the built-in integrations provided by PyORlib. However, you can also choose to use other integration options described in the Optional Dependencies section, or even implement your own custom integration. Before proceeding, make sure you have installed the OR-Tools integration. Once that is done, let's get started:
from math import inf
from pyorlib import Model
from pyorlib.engines.ortools import ORToolsEngine
from pyorlib.enums import ValueType, OptimizationType
# Create a Model instance using the ORTools engine
model: Model = Model(engine=ORToolsEngine())
# Add two integer variables for coordinates x and y
x = model.add_variable("x", ValueType.INTEGER, 0, inf)
y = model.add_variable("y", ValueType.INTEGER, 0, inf)
# Define problem constraints
model.add_constraint(x + 7 * y <= 17.5)
model.add_constraint(x <= 3.5)
# Set objective to maximize x + 10y
model.set_objective(OptimizationType.MAXIMIZE, x + 10 * y)
# Solve model
model.solve()
# Print solution
model.print_solution()
As we can see from the previous example, PyORlib follows a simple and user-friendly workflow for defining, solving, and interacting with mathematical models. Let's review the key steps:
Model
class, ORToolsEngine
class, and necessary enums (ValueType
and OptimizationType
).
Model
object and specified that we want to use the
OR-Tools engine for solving the optimization problem.
set_objective
method to
maximize the objective function x + 10 * y.
Having gained a clear understanding of the workflow showcased in the Simple Example, you are now well-equipped to explore more intricate optimization scenarios and fully harness the capabilities of PyORlib in your own projects.
Feel free to experiment and build upon this example to explore the full potential of PyORlib in your projects. With PyORlib, you can define and implement complex mathematical models and algorithms, test multiple optimization packages to identify the ideal one that perfectly aligns with your unique requirements, define and organize the vital components of your optimization model, and much more!
To demonstrate PyORlib in a realistic scenario, we will implement a transportation problem from the GAMS Tutorial by Richard E. Rosenthal, which provides a comprehensive case study for testing PyORlib's optimization capabilities and features.
The transportation problem we will address is a classic instance of linear programming's transportation problem, which has historically served as a testing ground for the development of optimization technology. This transportation problem involves determining the optimal flow of a product from multiple sources (plants) to destinations (markets) to minimize costs while satisfying demands.
$$ \begin{align} \text{Minimize:} \quad & \sum_{i=1}^{n} \sum_{j=1}^{m} c_{ij} x_{ij} \ \ \text{Subject to:} \quad & \sum_{j=1}^{m} x_{ij} \leq a_{i} \quad \forall_{i} \ & \sum_{i=1}^{n} x_{ij} \geq b_{j} \quad \forall_{j} \ & x_{ij} \geq 0 \quad \forall_{ij}, \thinspace integer \ & i=1,...,n; \quad j=1,...,m \ \end{align} $$
Before diving into the implementation, let's take a moment to familiarize ourselves with the key components of the model. This brief exploration will provide a better understanding of how these components work together.
Indices:
$i=$ plants; $\quad j=$ markets.
Parameters (Given Data):
$a_{i}=$ supply of commodity of plant $i$ (in cases).
$b_{j}=$ demand for commodity at market $j$ (cases).
$c_{ij}=$ cost per unit shipment between plan $i$ and market $j$ ($/case).
Decision Variables:
$x_{ij}=$ amount of commodity to ship from plant $i$ to market $j$ (cases).
Constraints:
Observe supply limit at plant $i$: $\sum_{j=1}^{m} x_{ij} \leq a_{i} \quad \forall_{i}$
Satisfy demand at market $j$: $\sum_{i=1}^{n} x_{ij} \geq b_{j} \quad \forall_{j}$
The GAMS tutorial describes a scenario with two canning plants and three markets. It provides sample supply, demand and cost data. We will use this same data to define our model.
New York | Chicago | Topeka | Supply | |
---|---|---|---|---|
Seattle | 2.5 | 1.7 | 1.8 | 350 |
San Diego | 2.5 | 1.8 | 1.4 | 600 |
Demand | 325 | 300 | 275 | |
To model and solve the problem in Python, we will use PyORlib and its CPLEX integration. However, it’s important to note that you can choose any of the supported optimization engine integrations described in the Optional Dependencies or even use your own custom implementations.
Before proceeding, ensure that PyORlib is installed, along with its integration for the CPLEX engine. Once everything is set up, let's build our transportation model:
from math import inf
from pyorlib import Model
from pyorlib.engines.cplex import CplexEngine
from pyorlib.enums import ValueType, OptimizationType
# Create a transportation model using the CplexEngine.
model = Model(engine=CplexEngine(), name="A Transportation Model")
# Define the dimensions of the problem
n = 2 # Number of plants
m = 3 # Number of markets
n_range = range(1, n + 1) # Range of plant indices
m_range = range(1, m + 1) # Range of market indices
# Define the parameters of the model
a_i = [350, 600] # Supply limit at each plant
b_j = [325, 300, 275] # Demand at each market
c_i_j = [ # Transportation costs
2.5, 1.7, 1.8,
2.5, 1.8, 1.4,
]
# Define the decision variables
x_i_j = {
(i, j): model.add_variable(
name=f"x_{i}_{j}",
value_type=ValueType.INTEGER,
lower_bound=0,
upper_bound=inf
)
for i in n_range
for j in m_range
}
# Add supply limit at plants constraints
for i in n_range:
model.add_constraint(
expression=sum(
x_i_j[i, j]
for j in range(1, m + 1)
) <= a_i[i - 1]
)
# Add satisfy demand at markets constraints
for j in m_range:
model.add_constraint(
expression=sum(
x_i_j[i, j]
for i in range(1, n + 1)
) >= b_j[j - 1]
)
# Set the objective function to minimize the total transportation cost
model.set_objective(
opt_type=OptimizationType.MINIMIZE,
expression=sum(
c_i_j[(i - 1) * m + (j - 1)] * x_i_j[i, j]
for i in n_range
for j in m_range
)
)
# Solve the model and print the solution
model.solve()
model.print_solution()
As we can see from this practical example, PyORlib enables us to easily build a transportation model, define its necessary components, optimize the model, and obtain the optimal solution. The simple yet powerful syntax of PyORlib allows us to focus on the problem at hand without getting lost in implementation details.
PyORlib goes beyond the optimization process and offers a powerful modeling workflow that emphasizes code organization, readability, and maintainability over time. This workflow is built upon a set of abstractions and classes from the structures module, that allows you to centralize and standardize the definition of your model's components, such as dimensions, parameters, decision variables, and constant properties.
One significant advantage of PyORlib's workflow is the ability to easily rename and modify components throughout your codebase. Instead of manually searching and replacing strings, you can make changes in one place, ensuring consistency and reducing errors.
from abc import ABC
from dataclasses import dataclass
from pyorlib.enums import ParameterType, ValueType
from pyorlib.structures import DimensionDefinition, ParameterDefinition, TermDefinition
class GenericModelDefinition(ABC):
@dataclass(frozen=True)
class Dimensions(ABC):
n = DimensionDefinition(name="n", display_name="Total number of 'i' indices", min=1)
m = DimensionDefinition(name="m", display_name="Total number of 'j' indices", min=1)
@dataclass(frozen=True)
class Parameters(ABC):
c_i_j = ParameterDefinition(
set_name="c_i_j",
name=lambda i, j: f"c_{i}_{j}",
display_name="Cost per unit shipment between 'i' and 'j'",
parameter_types={ParameterType.FIXED, ParameterType.BOUNDED},
value_types={ValueType.CONTINUOUS},
min=0,
)
@dataclass(frozen=True)
class DecisionVariables(ABC):
x_i_j = TermDefinition(
set_name="x_i_j",
name=lambda i, j: f"x_{i}_{j}",
display_name="Amount of commodity to ship from 'i' to 'j'",
)
# Usage within a model
print(GenericModelDefinition.Dimensions.n.min) # Access the minimum value for dimension 'n'
print(GenericModelDefinition.Parameters.c_i_j.name(1, 1)) # Generate the name for parameter 'c_1_1'
print(GenericModelDefinition.DecisionVariables.x_i_j.display_name) # Access the display name for the decision variable 'x_i_j'
By leveraging PyORlib's structured approach, you can improve the maintainability and scalability of your models. The clean and organized codebase makes for easy navigation, understanding, and modification, making it easier to collaborate with other team members and adapt your models to changing requirements.
In addition to PyORlib's workflow capabilities, this package provides a set of abstractions designed to apply validations and ensure the integrity of your model data. These validation features play a crucial role in identifying errors early on and maintaining consistent, error-free model data, resulting in more robust optimization.
descriptors
and dataclasses
to define validation rules for model
schemas. Attributes like DimensionField
and ParameterField
allow specifying requirements
like minimum/maximum values.
from dataclasses import dataclass
from pyorlib.enums import ParameterType, ValueType
from pyorlib.structures import MultiValueParameter
from pyorlib.validators import DimensionField, ParameterField
@dataclass
class ExampleSchema:
n: int = DimensionField(min=1) # Specifies the minimum value allowed for 'n'
a_i: MultiValueParameter = ParameterField(
parameter_types={ParameterType.FIXED}, # Specifies the allowed param types for 'a_i'
value_types={ValueType.INTEGER}, # Specifies the allowed value types for 'a_i'
min=0, # Specifies the minimum value allowed for 'a_i'
)
ExampleSchema
class with the model data. If initialization succeeds
without errors, the data passed all validations and can be used safely for optimization. However, if the data is
invalid, an error will be raised immediately upon initialization, before the invalid data can be optimized.
schema = ExampleSchema(
n=2,
a_i=MultiValueParameter(
value_type=ValueType.INTEGER,
parameter_type=ParameterType.FIXED,
values=(1, 2),
),
) # succeeds
schema = ExampleSchema(
n=0,
a_i=MultiValueParameter(
value_type=ValueType.INTEGER,
parameter_type=ParameterType.FIXED,
values=(1, 2.6),
),
) # raises ValueError
By validating data upon instantiation, any issues are caught immediately before the model is optimized. This helps maintain data integrity and prevents errors downstream in the optimization process.
At its core, PyORlib provides a modular optimization design that allows you to seamlessly switch between different built-in or custom optimization engine implementations on the fly. Whether you opt for official optimization package integrations or decide to create your own custom ones, PyORlib allows you to tailor the behavior and capabilities of the optimization engine to perfectly align with your unique requirements.
By leveraging the principles of dependency inversion and open-closed design, PyORlib decouples the model's optimization from the underlying implementation, allowing you to optimize models across different optimization engines, including custom ones, without modifying the model definition or employing complex logic.
To showcase the flexibility of PyORlib, let's revisit the Simple Example we discussed earlier and use it as our foundation. After copying the example, we will make some modifications to decouple the dependency from a specific optimization engine to its interface, and encapsulate the model definition and resolution within a function to ensure reusability across different optimization engines, as shown below:
from math import inf
from pyorlib import Model, Engine
from pyorlib.engines.gurobi import GurobiEngine
from pyorlib.engines.ortools import ORToolsEngine
from pyorlib.enums import ValueType, OptimizationType
def mip_problem(engine: Engine):
# Create a Model instance
model: Model = Model(engine)
# Add two integer variables for coordinates x and y
x = model.add_variable("x", ValueType.INTEGER, 0, inf)
y = model.add_variable("y", ValueType.INTEGER, 0, inf)
# Define problem constraints
model.add_constraint(x + 7 * y <= 17.5)
model.add_constraint(x <= 3.5)
# Set objective to maximize x + 10y
model.set_objective(OptimizationType.MAXIMIZE, x + 10 * y)
# Solve model
model.solve()
# Print solution
model.print_solution()
# Solving the MIP problem using the ORTools engine
mip_problem(engine=ORToolsEngine())
# Solving the MIP problem using the Gurobi engine
mip_problem(engine=GurobiEngine())
As we can see from the example, by just depending on the engine interface instead of a concrete implementation and applying dependency injection, we were able to solve the same MIP problem from the Simple Example across multiple optimization engines, including custom ones, without modifying the underlying model definition and optimization.
Out of the box, PyORlib provides integrations for popular solvers like CPLEX, Gurobi, OR-Tools and PuLP, leveraging their proven algorithms to optimize models reliably. These integrations give you access to top-tier solvers without additional work. However, the options are not limited only to built-in integrations.
PyORlib also supports custom engine implementations through its extensible and flexible architecture.
You can create your own optimization engines by subclassing the base Engine
class and implementing the
necessary methods, whether using third-party or custom algorithms.
PyORlib continuously adapts to support developers across various technological and programming domains. Its primary goal is to remain a useful tool for learning about operations research, mathematical model optimization, and testing different optimization packages with ease.
While future development may introduce some changes to enhance and expand certain current functionalities, the highest priority remains providing a simple yet powerful platform for students, researchers, and practitioners to explore optimization concepts, test algorithms, and further their own knowledge. Large-scale changes that could introduce significant complexity are less likely in order to maintain accessibility as the core focus.
PyORlib is an open source project that welcomes community involvement. If you wish to contribute additional optimization suites, improvements, or bug fixes, please check the Contributing section for guidelines on collaborating.
PyORlib is distributed as open source software and is released under the MIT License.
You can view the full text of the license in the LICENSE
file located in the PyORlib repository.
FAQs
A powerful Python library for operations research. Define, solve, and interact with mathematical models in a standardized manner across different optimization packages.
We found that pyorlib demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.
Security News
A Stanford study reveals 9.5% of engineers contribute almost nothing, costing tech $90B annually, with remote work fueling the rise of "ghost engineers."
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.