4  Choosing Maintenance Months

NoteSource and adaptation

Based on Factory planning 2, Section 12.4 of Williams (2013). Extends the adapted specialty-chemical plant in Chapter 3. Each of two units has one maintenance month. The results below solve the stated instance and are not presented as the numerical answer to an unmodified textbook problem.

4.1 Problem Statement

Use the margins, processing times, sales limits, storage costs, and boundary stocks in Chapter 3. Each month has a base capacity of 120 reactor hours and 100 finishing hours. Reactor maintenance removes 40 h in exactly one month; finishing maintenance removes 20 h in exactly one month. Choose the two maintenance months and the production plan jointly. Maintenance of both units in the same month is permitted. The original Chapter 3 schedule is reactor maintenance in month 2 and finishing maintenance in month 3.

4.2 Model Formulation

Retain the Chapter 3 LP and introduce binary \(y_{rt}\) to indicate maintenance of unit \(r\) in month \(t\). Replace fixed capacity by

\[\sum_i a_{ri}x_{it}\le C_r^0-L_r y_{rt},\qquad \sum_t y_{rt}=1.\]

\(L_r\) is the loss in available operating hours, not the whole monthly capacity. The stock, sales, and profit equations are unchanged. This MILP contains the original maintenance schedule as a feasible choice, so its optimal profit cannot be lower.

All decision variables and units refer to the problem statement above. Continuous variables are nonnegative unless explicitly stated otherwise; binary and integer domains are specified in the equations and code.

4.3 Pyomo Implementation

The following Python implementation uses Pyomo and HiGHS. Run from the workbook root so that the models package and shared helpers are importable. The shared solver and audit functions check optimal termination before loading values, then verify every active constraint, variable bound, and integer domain. Any imported earlier-chapter model supplies the data and balances already explained there.

The full source is problem04.py. To solve and print this problem independently:

~/.venvs/optim/bin/python -m models.common 4
import pyomo.environ as pyo
from models import problem03 as base
from models.common import frame, solve


def build():
    m = base.build()
    m.capacity.deactivate()
    m.maintenance = pyo.Var(["reactor", "finishing"], m.T, domain=pyo.Binary)
    m.once = pyo.Constraint(
        ["reactor", "finishing"],
        rule=lambda m, r: sum(m.maintenance[r, t] for t in m.T) == 1,
    )
    m.adjusted = pyo.Constraint(
        ["reactor", "finishing"],
        m.T,
        rule=lambda m, r, t: (
            sum(base.HOURS[r][j] * m.make[i, t] for j, i in enumerate(base.PRODUCTS))
            <= ({"reactor": 120, "finishing": 100}[r])
            - ({"reactor": 40, "finishing": 20}[r]) * m.maintenance[r, t]
        ),
    )
    return m


def check(m):
    base.check(m)
    fixed = solve(base.build())
    assert pyo.value(m.obj) >= pyo.value(fixed.obj) - 1e-6


def tables(m):
    answer = base.tables(m)
    answer["maintenance"] = frame(
        [
            [r, t + 1]
            for r in ["reactor", "finishing"]
            for t in m.T
            if pyo.value(m.maintenance[r, t]) > 0.5
        ],
        ["Unit", "Maintenance month"],
    )
    fixed = solve(base.build())
    answer["comparison"] = frame(
        [
            ["Fixed schedule", pyo.value(fixed.obj)],
            ["Optimized schedule", pyo.value(m.obj)],
        ],
        ["Policy", "Contribution (thousand USD)"],
    )
    return answer


def plot(m):
    f = solve(base.build())
    return (
        ["Fixed schedule", "Optimized schedule"],
        [pyo.value(f.obj), pyo.value(m.obj)],
        "Contribution (thousand USD)",
    )

# Solve, audit constraints, and run domain-specific checks.
from models.common import solve, audit
model = solve(build())
audit(model)
check(model)
for name, result_table in tables(model).items():
    print(name)
    print(result_table.to_string(index=False))

4.4 Optimal Solution

The solver reports optimal termination. The objective is 950 thousand USD (maximize). The largest violation across active constraints, variable bounds, and integer domains is 0.00e+00 in the corresponding model units. The problem-specific checks also pass. These checks establish numerical consistency with the stated model, not the validity of its assumptions for a real facility.

4.4.1 Plan

Month Grade Production (t) Sales (t) Stock (t)
1 A 16.25 16.25 0
1 B 15 15 0
1 C 0.625 0.625 0
2 A 15 15 0
2 B 20 20 0
2 C 7.5 7.5 0
3 A 15 10 5
3 B 20 15 5
3 C 7.5 2.5 5

4.4.2 Maintenance

Unit Maintenance month
reactor 1
finishing 1

4.4.3 Comparison

Policy Contribution (thousand USD)
Fixed schedule 937.5
Optimized schedule 950
Figure 4.1: Selected quantities from the verified optimal solution. Units are stated on the axis.

Tables round numerical values for reading; feasibility checks use the original solver values. Multiple optimal decisions may exist. Machine-readable result records the solver status and package versions. Figure-generation code is in figures/workbook.py.

4.5 Brief Discussion

Choosing maintenance months increases contribution by 12.50 thousand USD compared with the specified fixed schedule.

Maintenance flexibility has a value only through the production and inventory decisions it enables. It does not create extra total operating hours over the horizon. Experiment: prohibit simultaneous maintenance with a monthly sum of maintenance binaries at most one, then measure the lost flexibility.