Landing Congestion Analysis

Explore the impact of landing shock events on stochastic playback outputs for the synthetic medium scenario.

[1]:
import sys
from pathlib import Path

PROJECT_ROOT = Path.cwd().resolve()
while PROJECT_ROOT != PROJECT_ROOT.parent and not (PROJECT_ROOT / "pyproject.toml").exists():
    PROJECT_ROOT = PROJECT_ROOT.parent
if not (PROJECT_ROOT / "pyproject.toml").exists():
    raise RuntimeError(
        "Notebook must be executed within a FHOPS checkout (pyproject.toml not found)."
    )
if str(PROJECT_ROOT) not in sys.path:
    sys.path.insert(0, str(PROJECT_ROOT))

from docs.examples.analytics import utils
from fhops.scenario.synthetic import SyntheticDatasetConfig, sampling_config_for

SCENARIO = PROJECT_ROOT / "examples/synthetic/medium/scenario.yaml"
ASSIGNMENTS = PROJECT_ROOT / "docs/examples/analytics/data/synthetic_medium_sa_assignments.csv"

config = SyntheticDatasetConfig(
    name="synthetic-medium",
    tier="medium",
    num_blocks=8,
    num_days=12,
    num_machines=4,
)

baseline_sampling = sampling_config_for(config).model_copy()
baseline_sampling.landing.enabled = False
baseline_tables, baseline_sampling = utils.run_stochastic_summary(
    SCENARIO, ASSIGNMENTS, sampling_config=baseline_sampling
)

shock_sampling = baseline_sampling.model_copy()
shock_sampling.landing.enabled = True
shock_sampling.landing.probability = 0.3
shock_sampling.landing.capacity_multiplier_range = (0.35, 0.7)
shock_sampling.landing.duration_days = 3
shock_tables, shock_sampling = utils.run_stochastic_summary(
    SCENARIO, ASSIGNMENTS, sampling_config=shock_sampling
)

Baseline Ensemble Snapshot

[2]:
baseline_tables.shift.head()
[2]:
day shift_id machine_id machine_role sample_id production_units total_hours idle_hours mobilisation_cost sequencing_violations blackout_conflicts available_hours utilisation_ratio downtime_hours downtime_events weather_severity_total
0 2 S1 M4 forwarder 0 5.1558 8.0 0.0 0.0 0 0 8.0 1.0 0.0 0 0.4
1 8 S1 M1 harvester 0 15.0340 8.0 0.0 0.0 0 0 8.0 1.0 0.0 0 0.0
2 2 S1 M1 harvester 1 12.6600 8.0 0.0 0.0 0 0 8.0 1.0 0.0 0 0.0
3 8 S1 M1 harvester 1 9.0204 8.0 0.0 0.0 0 0 8.0 1.0 0.0 0 0.4
4 2 S1 M4 forwarder 2 8.5930 8.0 0.0 0.0 0 0 8.0 1.0 0.0 0 0.0

Landing Shock Ensemble Snapshot

[3]:
shock_tables.shift.head()
[3]:
day shift_id machine_id machine_role sample_id production_units total_hours idle_hours mobilisation_cost sequencing_violations blackout_conflicts available_hours utilisation_ratio downtime_hours downtime_events weather_severity_total
0 2 S1 M4 forwarder 0 5.15580 8.0 0.0 0.0 0 0 8.0 1.0 0.0 0 0.4
1 8 S1 M1 harvester 0 15.03400 8.0 0.0 0.0 0 0 8.0 1.0 0.0 0 0.0
2 2 S1 M1 harvester 1 12.66000 8.0 0.0 0.0 0 0 8.0 1.0 0.0 0 0.0
3 8 S1 M1 harvester 1 9.02040 8.0 0.0 0.0 0 0 8.0 1.0 0.0 0 0.4
4 2 S1 M4 forwarder 2 4.84338 8.0 0.0 0.0 0 0 8.0 1.0 0.0 0 0.0

KPI Comparison

[4]:
import pandas as pd

comparison = pd.DataFrame(
    {
        "scenario": ["baseline", "landing_shock"],
        "mean_production": [
            baseline_tables.shift.groupby("sample_id")["production_units"].sum().mean(),
            shock_tables.shift.groupby("sample_id")["production_units"].sum().mean(),
        ],
        "mean_downtime_hours": [
            baseline_tables.shift["downtime_hours"].mean(),
            shock_tables.shift["downtime_hours"].mean(),
        ],
        "mean_utilisation": [
            baseline_tables.shift["utilisation_ratio"].mean(),
            shock_tables.shift["utilisation_ratio"].mean(),
        ],
    }
)
comparison
[4]:
scenario mean_production mean_downtime_hours mean_utilisation
0 baseline 20.266083 0.0 1.0
1 landing_shock 19.929846 0.0 1.0

Production Distributions

[5]:
baseline_prod = baseline_tables.shift.groupby("sample_id")["production_units"].sum()
baseline_chart = utils.plot_distribution(
    baseline_prod, title="Baseline Production Distribution", xlabel="Production Units"
)
baseline_chart
[5]:
[6]:
shock_prod = shock_tables.shift.groupby("sample_id")["production_units"].sum()
shock_chart = utils.plot_distribution(
    shock_prod, title="Landing Shock Production Distribution", xlabel="Production Units"
)
shock_chart
[6]: