Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions examples/optlang/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# optlang Examples

Each sub-directory contains a self-contained example. The order in
which the examples are to appear is specified in `order.json` (an
array of directory names in the expected order).

In each example directory you'll find:

* `config.toml` - must conform to the specification outlined here:
https://docs.pyscript.net/latest/user-guide/configuration/ This is
parsed and ultimately turned into a JSON representation as part of
the package's API object.
* `setup.py` - Python code for contextual and environmental setup,
NOT SEEN BY THE END USER, but is run before the `code.py` code is
evaluated. Allows us to create useful (IPython) shims, avoid
repeating boilerplate and whatnot.
* `code.py` - the actual code added to the editor which forms the
practical example of using the package.
76 changes: 76 additions & 0 deletions examples/optlang/integer_transport_problem/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# ---------------------------------------------------------------------
# A mixed-integer transport problem: shipping cases between cities.
# ---------------------------------------------------------------------
#
# Two warehouses (Seattle, San Diego) ship to three stores (New York,
# Chicago, Topeka). We minimize total freight cost while respecting
# warehouse supply and store demand. Forcing variables to integer type
# turns this into a MILP, solved automatically by GLPK.

heading("Shipping cases from warehouses to stores")

supply = {"Seattle": 350, "San_Diego": 600}
demand = {"New_York": 325, "Chicago": 300, "Topeka": 275}

# Distances in thousands of miles, freight cost is $9 per case-kmile.
distances = {
"Seattle": {"New_York": 2.5, "Chicago": 1.7, "Topeka": 1.8},
"San_Diego": {"New_York": 2.5, "Chicago": 1.8, "Topeka": 1.4},
}
freight_cost = 9

# One integer variable per (origin, destination) lane.
shipments = {}
for origin in supply:
shipments[origin] = {}
for destination in demand:
shipments[origin][destination] = Variable(
name=f"{origin}_to_{destination}", lb=0, type="integer",
)

# Supply constraints: each warehouse ships at most its stock.
constraints = []
for origin in supply:
constraints.append(Constraint(
sum(shipments[origin].values()),
ub=supply[origin],
name=f"{origin}_supply",
))

# Demand constraints: each store receives at least what it needs.
for destination in demand:
constraints.append(Constraint(
sum(row[destination] for row in shipments.values()),
lb=demand[destination],
name=f"{destination}_demand",
))

# Objective: minimize total freight cost across all lanes.
objective = Objective(
sum(
freight_cost * distances[o][d] * shipments[o][d]
for o in supply for d in demand
),
direction="min",
)

model = Model(name="transport")
model.add(constraints)
model.objective = objective

status = model.optimize()
note(f"Solver status: <strong>{status}</strong>")
note(f"Minimum freight cost: <strong>${model.objective.value:.2f}</strong>")

# Lay out the optimal shipping plan as an origin-by-destination table.
header = "<tr><th>From \\ To</th>" + "".join(
f"<th>{d}</th>" for d in demand
) + "</tr>"
body_rows = []
for o in supply:
cells = "".join(
f"<td>{int(shipments[o][d].primal)}</td>" for d in demand
)
body_rows.append(f"<tr><th>{o}</th>{cells}</tr>")
display(HTML("<table>" + header + "".join(body_rows) + "</table>"),
append=True)
1 change: 1 addition & 0 deletions examples/optlang/integer_transport_problem/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["optlang"]
20 changes: 20 additions & 0 deletions examples/optlang/integer_transport_problem/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Lighter setup that mirrors the names established in cell 1."""
import js
from pyscript import window, HTML, display as _display

js.alert = window.alert


def display(*args, **kwargs):
return _display(*args, **kwargs, target=__pyscript_display_target__)


def heading(text, level=2):
display(HTML(f"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)


from optlang import Model, Variable, Constraint, Objective
64 changes: 64 additions & 0 deletions examples/optlang/linear_program_basics/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""
A first look at optlang: formulate and solve a small linear program.

Imagine a tiny workshop that builds three furniture kits (x1, x2, x3).
Each kit consumes different amounts of wood, labor, and finishing time,
and yields different profit. We want to maximize profit, subject to
limited supplies of each resource.

maximize 10*x1 + 6*x2 + 4*x3
subject to x1 + x2 + x3 <= 100 (units of wood)
10*x1 + 4*x2 + 5*x3 <= 600 (labor hours)
2*x1 + 2*x2 + 6*x3 <= 300 (finishing hours)
x1, x2, x3 >= 0

This is the classic GLPK example, recast as a workshop story.
Docs: https://optlang.readthedocs.io
"""
from IPython.core.display import display, HTML
from optlang import Model, Variable, Constraint, Objective


heading("A small workshop's production plan")
note(
"We declare three non-negative variables, three resource "
"constraints, and a profit objective, then solve the model."
)

# Variables: each is non-negative (lb=0). Names are arbitrary labels.
x1 = Variable("x1", lb=0)
x2 = Variable("x2", lb=0)
x3 = Variable("x3", lb=0)

# Constraints are built from symbolic expressions plus bounds.
wood = Constraint(x1 + x2 + x3, ub=100, name="wood")
labor = Constraint(10 * x1 + 4 * x2 + 5 * x3, ub=600, name="labor")
finishing = Constraint(2 * x1 + 2 * x2 + 6 * x3, ub=300, name="finishing")

# Objective: maximize profit.
profit = Objective(10 * x1 + 6 * x2 + 4 * x3, direction="max")

# Assemble the model. Variables get added implicitly via the
# constraints and objective that reference them.
model = Model(name="workshop")
model.objective = profit
model.add([wood, labor, finishing])

status = model.optimize()
note(f"Solver status: <strong>{status}</strong>")
note(f"Maximum profit: <strong>{model.objective.value:.2f}</strong>")

# Show the optimal production plan and how tight each constraint is.
rows = ["<tr><th>Kit</th><th>Quantity</th></tr>"]
for name, var in model.variables.items():
rows.append(f"<tr><td>{name}</td><td>{var.primal:.2f}</td></tr>")
display(HTML("<table>" + "".join(rows) + "</table>"), append=True)

note("Resource usage at the optimum (primal value vs. upper bound):")
usage_rows = ["<tr><th>Resource</th><th>Used</th><th>Limit</th></tr>"]
for c in model.constraints:
usage_rows.append(
f"<tr><td>{c.name}</td><td>{c.primal:.2f}</td>"
f"<td>{c.ub}</td></tr>"
)
display(HTML("<table>" + "".join(usage_rows) + "</table>"), append=True)
1 change: 1 addition & 0 deletions examples/optlang/linear_program_basics/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["optlang"]
37 changes: 37 additions & 0 deletions examples/optlang/linear_program_basics/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Shim setup for the first example. Includes the full IPython shim."""
import sys
import types
import js
from pyscript import window, HTML, display as _display

js.alert = window.alert


def display(*args, **kwargs):
return _display(
*args, **kwargs, target=__pyscript_display_target__,
)


ipython = types.ModuleType("IPython")
core = types.ModuleType("IPython.core")
core_display = types.ModuleType("IPython.core.display")
core_display.display = display
core_display.HTML = HTML
ipython.core = core
core.display = core_display
ipython.get_ipython = lambda: None
ipython.display = core_display
sys.modules["IPython"] = ipython
sys.modules["IPython.core"] = core
sys.modules["IPython.core.display"] = core_display
sys.modules["IPython.display"] = core_display


def heading(text, level=2):
display(HTML(f"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)

5 changes: 5 additions & 0 deletions examples/optlang/order.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
"linear_program_basics",
"integer_transport_problem",
"quadratic_objective"
]
72 changes: 72 additions & 0 deletions examples/optlang/quadratic_objective/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# ---------------------------------------------------------------------
# Quadratic objective: fitting a point to a feasible region.
# ---------------------------------------------------------------------
#
# Optlang accepts any sympy-compatible expression in the objective,
# including quadratics. Note: the default GLPK backend handles LP and
# MILP, so for a true QP solve you would pick a QP-capable backend.
# Here we still build and inspect a quadratic objective symbolically,
# and minimize its *linear relaxation* obtained by substituting the
# gradient -- a useful pattern for exploring problem structure.
#
# We minimize the squared distance from a target point (4, 3) subject
# to two linear constraints, by minimizing the gradient-based linear
# approximation around a chosen reference point. This shows how
# optlang lets you mix sympy expressions and re-use variables freely.

heading("Closest feasible point to a target")

# Decision variables, bounded to a tidy region for plotting.
x = Variable("x", lb=0, ub=6)
y = Variable("y", lb=0, ub=6)

# A pentagonal feasible region carved out by two linear constraints.
c1 = Constraint(x + 2 * y, ub=8, name="c1")
c2 = Constraint(3 * x + y, ub=9, name="c2")

# Quadratic "distance squared" expression to the target (4, 3).
target_x, target_y = 4.0, 3.0
distance_sq = (x - target_x) ** 2 + (y - target_y) ** 2
note(f"Quadratic objective expression: <code>{distance_sq}</code>")

# Linearize around the origin: gradient of (x-4)^2 + (y-3)^2 at (0,0)
# is (-8, -6), giving the linear surrogate -8*x - 6*y. Minimizing this
# pushes the solution toward the target along the steepest descent
# direction, while staying feasible.
linear_surrogate = -8 * x - 6 * y
model = Model(name="closest_point")
model.add([c1, c2])
model.objective = Objective(linear_surrogate, direction="min")

status = model.optimize()
sol_x, sol_y = x.primal, y.primal
distance = ((sol_x - target_x) ** 2 + (sol_y - target_y) ** 2) ** 0.5

note(f"Solver status: <strong>{status}</strong>")
note(
f"Solution: x = {sol_x:.3f}, y = {sol_y:.3f}, "
f"distance to target = {distance:.3f}"
)

# Visualize the feasible region, the target, and the optimal point.
fig, ax = plt.subplots(figsize=(6, 6))
xs = np.linspace(0, 6, 400)
ax.fill_between(
xs,
0,
np.minimum((8 - xs) / 2, 9 - 3 * xs).clip(0, 6),
color="lightsteelblue", alpha=0.6, label="Feasible region",
)
ax.plot(target_x, target_y, "r*", markersize=18, label="Target (4, 3)")
ax.plot(sol_x, sol_y, "ko", markersize=10, label="Optimal point")
ax.plot([target_x, sol_x], [target_y, sol_y],
"k--", linewidth=1, alpha=0.7)
ax.set_xlim(0, 6)
ax.set_ylim(0, 6)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_title("Closest feasible point to the target")
ax.legend(loc="upper right")
ax.set_aspect("equal")
fig.tight_layout()
display(fig, append=True)
1 change: 1 addition & 0 deletions examples/optlang/quadratic_objective/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["optlang", "numpy", "matplotlib"]
22 changes: 22 additions & 0 deletions examples/optlang/quadratic_objective/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Setup for the quadratic example: imports and helpers, no IPython shim."""
import js
from pyscript import window, HTML, display as _display

js.alert = window.alert


def display(*args, **kwargs):
return _display(*args, **kwargs, target=__pyscript_display_target__)


def heading(text, level=2):
display(HTML(f"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)


import numpy as np
import matplotlib.pyplot as plt
from optlang import Model, Variable, Constraint, Objective