Operations Research-style modeling for CVXPY
Project description
cvxpy-or
Operations Research-style modeling for CVXPY.
This package provides AMPL/Pyomo-style set-based indexing for CVXPY, enabling natural modeling of transportation, scheduling, and other OR problems.
Installation
pip install cvxpy-or
# Optional: xarray support for matrix-style data
pip install cvxpy-or[xarray]
With uv:
uv add cvxpy-or
pandas and rich are included by default for DataFrame I/O and pretty printing.
Quick Start
import pandas as pd
from cvxpy_or import (
Model, sum_by,
set_from_dataframe, parameter_from_dataframe, parameter_from_series,
)
# Define data as DataFrames
cost_df = pd.DataFrame([
{"warehouse": "Seattle", "customer": "NYC", "cost": 2.5},
{"warehouse": "Seattle", "customer": "LA", "cost": 1.0},
{"warehouse": "Seattle", "customer": "Houston", "cost": 1.8},
{"warehouse": "Denver", "customer": "NYC", "cost": 2.0},
{"warehouse": "Denver", "customer": "LA", "cost": 1.5},
{"warehouse": "Denver", "customer": "Houston", "cost": 1.2},
{"warehouse": "Chicago", "customer": "NYC", "cost": 1.0},
{"warehouse": "Chicago", "customer": "LA", "cost": 2.5},
{"warehouse": "Chicago", "customer": "Houston", "cost": 1.5},
])
supply = pd.Series({"Seattle": 100, "Denver": 80, "Chicago": 120}, name="supply")
demand = pd.Series({"NYC": 80, "LA": 70, "Houston": 50}, name="demand")
# Build model from DataFrames
routes = set_from_dataframe(cost_df, ["warehouse", "customer"])
cost = parameter_from_dataframe(cost_df, ["warehouse", "customer"], "cost", name="cost")
supply_param = parameter_from_series(supply, name="supply")
demand_param = parameter_from_series(demand, name="demand")
m = Model(name="transportation")
ship = m.add_variable(routes, nonneg=True, name="ship")
# Constraints
m.add_constraint("supply", sum_by(ship, "warehouse") <= supply_param)
m.add_constraint("demand", sum_by(ship, "customer") >= demand_param)
# Solve
m.minimize(cost @ ship)
m.solve()
m.print_summary()
m.print_solution(show_zero=False)
# Export results to DataFrame
result_df = m.to_dataframe("ship")
print(result_df[result_df["value"] > 0])
Key Features
- Native CVXPY:
VariableandParameterinherit from CVXPY - all operations work - Model wrapper: Clean interface for building problems
- Set operations: Union, intersection, difference, filtering
- Named aggregation:
sum_by,mean_by,min_by,max_by - Constraint helpers:
at_most_k,exactly_k,implies,mutex,one_of - Validation: Helpful error messages with typo suggestions
- pandas I/O: Load data from DataFrames, export solutions
- Pretty printing: Rich tables for variables and solutions
API Reference
Core Classes
Set(elements, name=None, names=None)
An ordered set of elements for indexing.
warehouses = Set(['W1', 'W2', 'W3'], name='warehouses')
customers = Set(['C1', 'C2', 'C3'], name='customers')
# Cross product
routes = Set.cross(warehouses, customers)
# Set operations
A | B # Union
A & B # Intersection
A - B # Difference
A ^ B # Symmetric difference
A <= B # Subset
# Filtering and transformation
evens = numbers.filter(lambda x: x % 2 == 0)
doubled = numbers.map(lambda x: x * 2)
sorted_set = numbers.sorted()
# Access
s.first(), s.last()
len(s), 'W1' in s
Variable(index, nonneg=False, name=None, **kwargs)
A CVXPY Variable indexed by a Set.
ship = Variable(routes, nonneg=True, name='ship')
ship[('W1', 'C1')] # Access by key
ship.get_value(('W1', 'C1')) # Get solved value
Parameter(index, data=None, name=None, **kwargs)
A CVXPY Parameter indexed by a Set.
cost = Parameter(routes, data={('W1', 'C1'): 10, ...})
cost.set_data(new_data) # Update values
cost.expand(larger_index, positions) # Broadcast
Model(name=None)
Wrapper for building optimization problems.
m = Model(name='my_problem')
# Create components
x = m.add_variable(index, name='x', nonneg=True)
p = m.add_parameter(index, data={...}, name='p')
# Add constraints
m.add_constraint('bounds', x <= 100)
# Set objective
m.minimize(cost @ x) # or m.maximize(...)
# Solve
status = m.solve()
m.print_summary()
m.print_solution()
# Access results
m.status, m.value
m.get_variable('x'), m.get_parameter('p')
df = m.to_dataframe()
Aggregation Functions
from cvxpy_or import sum_by, mean_by, min_by, max_by, count_by, group_keys
# Sum by position (most common)
sum_by(ship, 'warehouses') # Sum over customers for each warehouse
sum_by(ship, ['origin', 'period']) # Keep multiple dimensions
# Mean by position
mean_by(cost, 'warehouses') # Average cost per warehouse
# Min/max by position (returns variable + constraints)
max_ship, constraints = max_by(ship, 'warehouses')
min_ship, constraints = min_by(ship, 'customers')
# Utilities
counts = count_by(routes, 'warehouses') # Elements per group
keys = group_keys(routes, 'warehouses') # Unique group keys
Filtering
from cvxpy_or import where
# Filter expression elements
where(ship, lambda r: r[0] == 'W1') # Callable
where(ship, origin='W1') # Keyword
where(ship, origin=['W1', 'W2']) # Multiple values
Constraint Helpers
from cvxpy_or import at_most_k, at_least_k, exactly_k, implies, mutex, one_of, bounds
# Cardinality constraints (returns list of constraints)
constraints = at_most_k(x, k=3) # At most 3 nonzero
constraints = exactly_k(x, k=3) # Exactly 3 nonzero
constraints = at_least_k(x, k=2) # At least 2 nonzero
# Logical constraints for binary variables
implies(a, b) # a=1 => b=1
mutex(a, b, c) # At most one is 1
one_of(a, b, c) # Exactly one is 1
# Bounds from parameters
constraints = bounds(ship, lower=0, upper=capacity)
Validation
from cvxpy_or import validate_keys, validate_numeric, validate_bounds, ValidationError
# Validate data keys match index (with helpful error messages)
validate_keys(data, index) # Raises ValidationError with suggestions
# Validate data types and bounds
validate_numeric(data)
validate_bounds(data, lower=0, upper=100)
pandas I/O
from cvxpy_or import (
set_from_series, set_from_dataframe, set_from_index,
parameter_from_dataframe, parameter_from_series,
variable_to_dataframe, parameter_to_dataframe
)
# Create Set from DataFrame
customers = set_from_series(df['customer_id'])
routes = set_from_dataframe(df, columns=['origin', 'dest'])
# Create Parameter from DataFrame
cost = parameter_from_dataframe(df, index_cols=['origin', 'dest'], value_col='cost')
supply = parameter_from_series(df.set_index('warehouse')['supply'])
# Export solutions to DataFrame
df = variable_to_dataframe(ship)
df = m.to_dataframe('ship') # From Model
Display
from cvxpy_or import print_variable, print_solution, variable_table
# Print variable values (after solving)
print_variable(ship, show_zero=False)
# Print solution summary
print_solution([ship, inventory], objective_value=m.value, status=m.status)
# Get table as string
table_str = variable_table(ship, precision=2)
Examples
See the examples/ directory for complete examples:
assignment_problem.py- Worker-task assignmentblending_problem.py- Blend optimizationdiet_problem.py- Classic diet problemfacility_location.py- Facility location (UFLP)multi_period_transportation.py- Multi-period with inventory
Comparison: cvxpy-or vs Raw CVXPY
Raw CVXPY:
import cvxpy as cp
import numpy as np
n_warehouses, n_customers = 3, 4
cost = np.array([[1, 2, 3, 4], [2, 1, 2, 3], [3, 2, 1, 2]])
supply = np.array([100, 80, 120])
demand = np.array([60, 70, 50, 40])
ship = cp.Variable((n_warehouses, n_customers), nonneg=True)
prob = cp.Problem(
cp.Minimize(cp.sum(cp.multiply(cost, ship))),
[
cp.sum(ship, axis=1) <= supply,
cp.sum(ship, axis=0) >= demand,
]
)
prob.solve()
# Accessing results requires remembering indices
print(f"Ship from W1 to C2: {ship.value[0, 1]}")
cvxpy-or:
from cvxpy_or import Model, Set, sum_by
m = Model()
warehouses = Set(['Seattle', 'Denver', 'Chicago'], name='warehouses')
customers = Set(['NYC', 'LA', 'Houston', 'Miami'], name='customers')
routes = Set.cross(warehouses, customers)
cost = m.add_parameter(routes, data={('Seattle', 'NYC'): 1, ...})
supply = m.add_parameter(warehouses, data={'Seattle': 100, ...})
demand = m.add_parameter(customers, data={'NYC': 60, ...})
ship = m.add_variable(routes, nonneg=True, name='ship')
m.add_constraint('supply', sum_by(ship, 'warehouses') <= supply)
m.add_constraint('demand', sum_by(ship, 'customers') >= demand)
m.minimize(cost @ ship)
m.solve()
# Named access to results
print(f"Ship from Seattle to LA: {ship.get_value(('Seattle', 'LA'))}")
m.print_solution()
License
Apache-2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cvxpy_or-0.1.0.tar.gz.
File metadata
- Download URL: cvxpy_or-0.1.0.tar.gz
- Upload date:
- Size: 75.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c47ab569c47c13974ae637273b4058ec7058df09acd11586552656c0e62811d
|
|
| MD5 |
3716c9ac857dbb30453c918579e5beff
|
|
| BLAKE2b-256 |
28233fa2ee363c28523041afd99d1ef47a238f9e506037fb04e3c2d89b973552
|
File details
Details for the file cvxpy_or-0.1.0-py3-none-any.whl.
File metadata
- Download URL: cvxpy_or-0.1.0-py3-none-any.whl
- Upload date:
- Size: 34.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fddf56a765d2a579f8f10cebc6a00f65f07f3d4f6be4c68a5a03391acc91c574
|
|
| MD5 |
f3d2a742197e6580ae265bddee3a7a66
|
|
| BLAKE2b-256 |
9a126477ff5550bd937ebafe7fa27fa2f66980b4c30a923bedc7cca99cf5e0c7
|