Road Maintenance Optimization

Budget allocation for road infrastructure with non-linear deterioration models
Published

February 8, 2026

1 Problem Statement

An infrastructure asset manager needs to allocate a limited maintenance budget across a network of road segments to:

  • Maximize user satisfaction weighted by traffic volume
  • Account for non-linear repair effectiveness (diminishing returns)
  • Model user satisfaction as a sigmoid function of road condition

This example demonstrates Optyx’s ability to handle complex nested non-linearities where manual gradient calculation would be tedious and error-prone.


2 Mathematical Formulation

Given: - \(B_i\) = budget allocated to road \(i\) - \(PCI_i\) = current Pavement Condition Index (0-100) - \(d_i\) = annual decay rate - \(T_i\) = daily traffic volume

Repair Model (Diminishing Returns):

\[ \text{Gain}(B) = 40 \cdot (1 - e^{-0.5 \cdot B}) \]

Satisfaction Model (S-curve):

\[ S(PCI) = \frac{100}{1 + e^{-0.12 \cdot (PCI - 50)}} \]

Objective:

\[ \max \sum_i T_i \cdot S\left(PCI_i - d_i + \text{Gain}(B_i)\right) \]

Subject to:

\[ \sum_i B_i \leq B_{total} \]


3 Why Auto-Diff Matters Here

The derivative of the objective with respect to budget \(B_i\) requires the chain rule through both the sigmoid and exponential:

\[ \frac{\partial U}{\partial B_i} = T_i \cdot S'(\cdot) \cdot \frac{\partial}{\partial B_i}\left[40(1 - e^{-0.5 B_i})\right] \]

Expanding this manually is tedious. Optyx computes it symbolically and exactly.


4 Implementation

import numpy as np
from optyx import Variable, VectorVariable, Problem, exp
from optyx.core.compiler import compile_gradient

# Road network data
roads = ["Hwy 101", "Route 66", "I-95", "Main St", "Broadway"]
traffic = np.array([50000, 12000, 85000, 5000, 15000])  # Daily vehicles
current_pci = np.array([45, 60, 35, 70, 55])           # Current condition
decay_rate = np.array([5, 3, 8, 2, 4])                 # Annual deterioration

total_budget = 10.0  # $10M available
max_per_road = 5.0   # Max $5M per road

print("Road Network Status:")
print("-" * 50)
for i, road in enumerate(roads):
    print(f"{road:<12} Traffic: {traffic[i]:>6,}  PCI: {current_pci[i]:>3}  Decay: {decay_rate[i]}")
Road Network Status:
--------------------------------------------------
Hwy 101      Traffic: 50,000  PCI:  45  Decay: 5
Route 66     Traffic: 12,000  PCI:  60  Decay: 3
I-95         Traffic: 85,000  PCI:  35  Decay: 8
Main St      Traffic:  5,000  PCI:  70  Decay: 2
Broadway     Traffic: 15,000  PCI:  55  Decay: 4

5 Building the Non-Linear Model

# Decision variables
budget = VectorVariable("budget", len(roads), lb=0.0, ub=max_per_road)

# Non-linear models as optyx expressions
# These functions work element-wise on VectorVariables
def repair_model(spend):
    """Diminishing returns: Gain = 40 * (1 - exp(-0.5 * spend))"""
    return 40.0 * (1 - exp(-0.5 * spend))

def satisfaction_model(pci):
    """S-curve: S = 100 / (1 + exp(-0.12 * (pci - 50)))"""
    return 100.0 / (1 + exp(-0.12 * (pci - 50)))

# Build objective: maximize total weighted satisfaction
# Vectorized operations:
future_pci = current_pci - decay_rate + repair_model(budget)
user_sat = satisfaction_model(future_pci)
total_utility = traffic @ user_sat

print("✓ Symbolic model built with vectorized operations")
✓ Symbolic model built with vectorized operations

6 Inspecting the Automatic Gradient

# Compile the gradient function
grad_fn = compile_gradient(total_utility, budget)

# Evaluate at uniform $1M allocation
test_point = np.ones(len(roads))
grad_values = grad_fn(test_point)

print("Marginal Utility at $1M uniform allocation:")
print("-" * 45)
for i, road in enumerate(roads):
    print(f"∂U/∂{road:<10}: {grad_values[i]:>12,.0f}")
print("-" * 45)
print("Higher gradient = more value from additional spending")
Marginal Utility at $1M uniform allocation:
---------------------------------------------
∂U/∂Hwy 101   :    1,619,833
∂U/∂Route 66  :      100,521
∂U/∂I-95      :    2,573,190
∂U/∂Main St   :       12,266
∂U/∂Broadway  :      227,747
---------------------------------------------
Higher gradient = more value from additional spending

The gradient shows I-95 has 25× higher marginal utility than Main St — the optimizer should allocate accordingly.


7 Solving the Problem

# Add penalty for unspent budget (encourages full allocation)
budget_sum = budget.sum()

penalty = 1e6 * (budget_sum - total_budget) ** 2
penalized_utility = total_utility - penalty

# Create and solve problem
prob = Problem().maximize(penalized_utility)
prob.subject_to(budget_sum <= total_budget)

sol = prob.solve()

print(f"Solver status: {sol.status.value}")
print(f"Solve time: {sol.solve_time*1000:.1f} ms")
Solver status: optimal
Solve time: 33.4 ms

8 Results

print("\nOptimal Budget Allocation:")
print("-" * 70)
print(f"{'Road':<12} {'Traffic':>10} {'Old PCI':>10} {'Budget':>10} {'New PCI':>10} {'Satisf%':>10}")
print("-" * 70)

# Get all budget values at once
budget_vals = sol[budget]

total_spent = 0
for i, road in enumerate(roads):
    spend = budget_vals[i]
    total_spent += spend
    
    pci_gain = 40.0 * (1 - np.exp(-0.5 * spend))
    final_pci = current_pci[i] - decay_rate[i] + pci_gain
    sat = 100.0 / (1 + np.exp(-0.12 * (final_pci - 50)))
    
    print(f"{road:<12} {traffic[i]:>10,} {current_pci[i]:>10} ${spend:>9.2f} {final_pci:>10.1f} {sat:>10.1f}")

print("-" * 70)
print(f"Total Spent: ${total_spent:.2f}M / ${total_budget:.2f}M")

Optimal Budget Allocation:
----------------------------------------------------------------------
Road            Traffic    Old PCI     Budget    New PCI    Satisf%
----------------------------------------------------------------------
Hwy 101          50,000         45 $     3.09       71.5       92.9
Route 66         12,000         60 $     0.72       69.1       90.8
I-95             85,000         35 $     5.00       63.7       83.8
Main St           5,000         70 $     0.00       68.0       89.7
Broadway         15,000         55 $     1.19       68.9       90.7
----------------------------------------------------------------------
Total Spent: $10.00M / $10.00M

9 Key Insights

The optimal solution shows:

  1. I-95 gets the maximum ($5M) — highest traffic volume
  2. Hwy 101 gets significant funding — high traffic and improvable condition
  3. Main St gets $0 — already in good condition (diminishing returns on S-curve)
  4. Route 66 and Broadway get modest allocations

This matches the gradient predictions perfectly. The optimizer uses the exact symbolic derivatives computed by Optyx to navigate the complex objective landscape efficiently.


10 Extensions

This model can be extended with:

  • Multi-year planning horizons
  • Different repair action types (patching vs. resurfacing vs. reconstruction)
  • Stochastic deterioration models
  • Network connectivity weights (critical routes)
  • Equity constraints (minimum service levels for all roads)