Skip to content
Draft
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
3 changes: 3 additions & 0 deletions examples/demos/fundamentals/003_factory_simulation/.meta.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
tags:
- optimisation
- simulation
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "0",
"metadata": {},
"source": [
"# Factory simulation with maintenance optimisation\n",
"\n",
"[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/plugboard-dev/plugboard/blob/main/examples/demos/fundamentals/003_factory_simulation/factory-simulation.ipynb)\n",
"\n",
"[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/plugboard-dev/plugboard)\n",
"\n",
"This demo simulates a factory containing 5 machines over 1 year (365 days). The simulation runs in steps of 1 day, with each machine exhibiting realistic behaviour:\n",
"\n",
"### Components:\n",
"\n",
"1. **Iterator** (`clock`): Drives the simulation by emitting day numbers from 0 to 364\n",
"2. **Machine** (5 instances, each with different reliability characteristics):\n",
" - Produces $10,000 of value per day when running\n",
" - Has a probability of random breakdown modelled as a sigmoid function of days since last maintenance — near zero soon after maintenance, rising to near 1 over time\n",
" - Stops for 5 days when it breaks down\n",
" - Has a proactive maintenance schedule: stops for 1 day at regular intervals\n",
"3. **Factory**: Aggregates daily output from all machines and tracks the cumulative total value\n",
"\n",
"### Machine reliability profiles:\n",
"\n",
"| Machine | Sigmoid steepness | Sigmoid midpoint | Profile |\n",
"|---------|------------------|-----------------|----------|\n",
"| 1 | 0.10 | 50 days | Reliable |\n",
"| 2 | 0.12 | 45 days | Average |\n",
"| 3 | 0.15 | 40 days | Average |\n",
"| 4 | 0.18 | 35 days | Fragile |\n",
"| 5 | 0.20 | 30 days | Very fragile |\n",
"\n",
"We can then use the model to find optimal proactive maintenance intervals for each machine to maximise total output."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1",
"metadata": {},
"outputs": [],
"source": [
"# Install plugboard and dependencies for Google Colab\n",
"!pip install -q plugboard[ray]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2",
"metadata": {},
"outputs": [],
"source": [
"from plugboard.connector import AsyncioConnector\n",
"from plugboard.process import LocalProcess\n",
"from plugboard.schemas import ConnectorSpec\n",
"\n",
"from factory_simulation import Iterator, Machine, Factory, MACHINE_CONFIGS"
]
},
{
"cell_type": "markdown",
"id": "3",
"metadata": {},
"source": [
"The components are defined in `factory_simulation.py`:\n",
"\n",
"- **`Iterator`** emits a sequence of day numbers and closes once the configured number of days is reached.\n",
"- **`Machine`** tracks its own state (running, broken, or under maintenance). At each step it checks whether it is due for proactive maintenance, whether a random breakdown occurs (using a sigmoid probability curve), and outputs its daily production value.\n",
"- **`Factory`** sums daily values from all machines and maintains a running total.\n",
"\n",
"Each machine has different sigmoid parameters that control its breakdown probability curve, making some machines more reliable than others."
]
},
{
"cell_type": "markdown",
"id": "4",
"metadata": {},
"source": [
"Now assemble the components into a `Process` and connect them together. Each machine receives the day count from the clock, and sends its daily output to the factory aggregator."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5",
"metadata": {},
"outputs": [],
"source": [
"connect = lambda s, t: AsyncioConnector(spec=ConnectorSpec(source=s, target=t))\n",
"\n",
"components = [Iterator(name=\"clock\", num_days=365)]\n",
"connectors = []\n",
"\n",
"for i, cfg in enumerate(MACHINE_CONFIGS, start=1):\n",
" components.append(Machine(name=f\"machine_{i}\", maintenance_interval=30, **cfg))\n",
" connectors.append(connect(\"clock.day\", f\"machine_{i}.day\"))\n",
" connectors.append(connect(f\"machine_{i}.daily_value\", f\"factory.value_{i}\"))\n",
"\n",
"components.append(Factory(name=\"factory\"))\n",
"\n",
"process = LocalProcess(components=components, connectors=connectors)\n",
"\n",
"print(f\"Process has {len(process.components)} components and {len(process.connectors)} connectors\")"
]
},
{
"cell_type": "markdown",
"id": "6",
"metadata": {},
"source": [
"We can create a diagram of the process to make a visual check.\n",
"\n",
"![Process Diagram](https://mermaid.ink/img/pako:eNrNlLEOgjAURX_lpSOBQYGFGOJkYqKLs4kp7SMQW2pqiTGEf1dAHZowUBzY7z3vnuU1hCmOJCG5UA9WUG3gcDpXAEwodt02cC_oDRPQqq44ch8EzVAksDeoqVF6k-nU8_qw50ELQZCCpKwoK7ysxuvHITK0f_mO0N2eBegn5JS9xz3H-7shMPQ_6e_5P6ivJy5f2-pOgEWohxOXh7a6E2AR6tHE5ZGt7gRYhHo8cXlsqzsB5qkTn0jUkpacJA0xBcruEXLMaS0MadsXZtO5rA==)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7",
"metadata": {},
"outputs": [],
"source": [
"# Visualize the process flow\n",
"from plugboard.diagram import MermaidDiagram\n",
"\n",
"diagram_url = MermaidDiagram.from_process(process).url\n",
"print(diagram_url)"
]
},
{
"cell_type": "markdown",
"id": "8",
"metadata": {},
"source": [
"Run the simulation for 365 days with a default maintenance interval of 30 days for all machines."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9",
"metadata": {},
"outputs": [],
"source": [
"# Run the simulation\n",
"async with process:\n",
" await process.run()"
]
},
{
"cell_type": "markdown",
"id": "10",
"metadata": {},
"source": [
"At the end of the simulation, we can read the cumulative total value from the `Factory` component."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "11",
"metadata": {},
"outputs": [],
"source": [
"total_value = process.components[\"factory\"].total_value\n",
"max_possible = 365 * 5 * 10_000\n",
"print(f\"Total factory output over 365 days: ${total_value:,.0f}\")\n",
"print(f\"Maximum possible output: ${max_possible:,.0f}\")\n",
"print(f\"Efficiency: {total_value / max_possible:.1%}\")"
]
},
{
"cell_type": "markdown",
"id": "12",
"metadata": {},
"source": [
"Now suppose we want to find the optimal proactive maintenance interval for each machine. Maintaining too frequently wastes productive days, but maintaining too rarely leads to expensive breakdowns (5 days of downtime vs 1 day for scheduled maintenance).\n",
"\n",
"We can set up an optimisation to maximise `total_value` by varying the `maintenance_interval` argument on each machine. The YAML config in `factory-simulation.yaml` defines both the process and a tuner configuration. The easiest way to launch an optimisation job is via the CLI by running:\n",
"\n",
"```sh\n",
"plugboard process tune factory-simulation.yaml\n",
"```\n",
"\n",
"This will use Optuna to explore maintenance intervals between 5 and 60 days for each machine and report the combination that maximises total factory output.\n",
"\n",
"Since each machine has different reliability characteristics, the optimal maintenance schedule will differ for each — more fragile machines benefit from more frequent maintenance, while reliable machines can run longer between stops."
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbformat_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
plugboard:
process:
args:
components:
- type: factory_simulation.Iterator
args:
name: clock
num_days: 365
- type: factory_simulation.Machine
args:
name: machine_1
maintenance_interval: 30
sigmoid_steepness: 0.10
sigmoid_midpoint: 50.0
seed: 101
- type: factory_simulation.Machine
args:
name: machine_2
maintenance_interval: 30
sigmoid_steepness: 0.12
sigmoid_midpoint: 45.0
seed: 202
- type: factory_simulation.Machine
args:
name: machine_3
maintenance_interval: 30
sigmoid_steepness: 0.15
sigmoid_midpoint: 40.0
seed: 303
- type: factory_simulation.Machine
args:
name: machine_4
maintenance_interval: 30
sigmoid_steepness: 0.18
sigmoid_midpoint: 35.0
seed: 404
- type: factory_simulation.Machine
args:
name: machine_5
maintenance_interval: 30
sigmoid_steepness: 0.20
sigmoid_midpoint: 30.0
seed: 505
- type: factory_simulation.Factory
args:
name: factory
connectors:
- source: clock.day
target: machine_1.day
- source: clock.day
target: machine_2.day
- source: clock.day
target: machine_3.day
- source: clock.day
target: machine_4.day
- source: clock.day
target: machine_5.day
- source: machine_1.daily_value
target: factory.value_1
- source: machine_2.daily_value
target: factory.value_2
- source: machine_3.daily_value
target: factory.value_3
- source: machine_4.daily_value
target: factory.value_4
- source: machine_5.daily_value
target: factory.value_5
tune:
args:
objective:
object_name: factory
field_type: field
field_name: total_value
parameters:
- type: ray.tune.randint
object_type: component
object_name: machine_1
field_type: arg
field_name: maintenance_interval
lower: 5
upper: 60
- type: ray.tune.randint
object_type: component
object_name: machine_2
field_type: arg
field_name: maintenance_interval
lower: 5
upper: 60
- type: ray.tune.randint
object_type: component
object_name: machine_3
field_type: arg
field_name: maintenance_interval
lower: 5
upper: 60
- type: ray.tune.randint
object_type: component
object_name: machine_4
field_type: arg
field_name: maintenance_interval
lower: 5
upper: 60
- type: ray.tune.randint
object_type: component
object_name: machine_5
field_type: arg
field_name: maintenance_interval
lower: 5
upper: 60
num_samples: 40
mode: max
max_concurrent: 4
Loading