fhops.hauling Package
Pydantic contracts for operational log hauling.
The hauling layer is an optional extension to fhops.scenario.contract.Scenario. It adds
the entities that the harvesting and tactical-operational models do not carry: log sorts,
contractors, truck types and trucks, depots, sort yards, haul arcs, loader location bindings, and
the driver policy from Ghotb (2023).
The contract is deliberately a data layer. It validates identifiers, units, ranges, and cross references; it does not build or solve an optimisation model. The Phase I flow model, Phase II scheduling model, and Phase III simulated-annealing solver live in sibling modules.
References
Ghotb, S. (2023). Optimization of Log Logistics at the Operational Level Considering Sorting Decisions and Synchronization Requirements. PhD thesis, University of British Columbia. <https://hdl.handle.net/2429/85563>
- class fhops.hauling.contract.Barge(*, id, contractor_id, barge_type_id, enabled=True, notes=None)[source]
Bases:
BaseModelAn individual barge or vessel owned by a contractor.
- id
Unique barge identifier. Phase II uses it as the vessel label.
- Type:
- contractor_id
Contractor that owns and dispatches the barge.
- Type:
- barge_type_id
Barge type that determines capacity, cost, and load compatibility.
- Type:
- enabled
Set
Falseto keep the barge in the contract but exclude it from a solve.- Type:
- notes
Optional free-form note surfaced in reports.
- Type:
str | None
- barge_type_id: str
- contractor_id: str
- enabled: bool
- id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.hauling.contract.BargeType(*, id, capacity_m3, fixed_cost=0.0, variable_cost_per_minute=0.0, overtime_cost_per_minute=0.0, load_minutes=0.0, unload_minutes=0.0, can_carry_sorted=True, can_carry_unsorted=True)[source]
Bases:
BaseModelA homogeneous barge/vessel type used on a water haul leg.
- Parameters:
- id
Unique barge-type identifier.
- Type:
- capacity_m3
Cargo capacity in cubic metres per barge trip. Must be greater than zero.
- Type:
- fixed_cost
Fixed cost of dispatching a barge for the day, in scenario currency. Non-negative. Phase II uses it; Phase I charges only variable, handling, and trip costs.
- Type:
- variable_cost_per_minute
Variable cost per minute of barge working time. Non-negative.
- Type:
- overtime_cost_per_minute
Overtime cost per barge minute. Non-negative.
- Type:
- load_minutes
Default loading time at the origin port, in minutes. Non-negative.
- Type:
- unload_minutes
Default unloading time at the destination port, in minutes. Non-negative.
- Type:
- can_carry_sorted
Whether the barge may carry roadside-sorted loads. Defaults to
True.- Type:
- can_carry_unsorted
Whether the barge may carry unsorted loads. Defaults to
True.- Type:
- can_carry_sorted: bool
- can_carry_unsorted: bool
- capacity_m3: float
- fixed_cost: float
- id: str
- load_minutes: float
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- overtime_cost_per_minute: float
- unload_minutes: float
- variable_cost_per_minute: float
- class fhops.hauling.contract.ContractCompatibility(*, contractor_id, block_id=None, truck_type_id=None, log_sort_id=None, allowed=True)[source]
Bases:
BaseModelExplicit compatibility override for a contractor, block, truck type, or log sort.
The first release derives most compatibility from ownership and truck-type flags. This model is reserved for later exceptions and is not required. When
allowedisFalse, the matching combination is refused; whenTrue, it is explicitly allowed. At least one target field must be present.- Parameters:
- allowed: bool
- contractor_id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.hauling.contract.Contractor(*, id, name=None, preferred_daily_truckloads=None)[source]
Bases:
BaseModelA log-trucking contractor.
- id
Unique contractor identifier referenced by trucks and block eligibility.
- Type:
- name
Optional human-readable label.
- Type:
str | None
- preferred_daily_truckloads
Desired number of truckloads per day used by the Phase I social objective (Ghotb’s
phi_o).Nonelets Phase I derive the preferred level from the cost-only solve.- Type:
float | None
- id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.hauling.contract.ContractorBlock(*, contractor_id, block_id)[source]
Bases:
BaseModelEligibility of a contractor to serve a block.
When the contract contains no
contractor_blocksentries, every contractor may serve every block. When it contains any entry, the list is treated as an allowlist: only the listed contractor/block pairs are eligible. This mirrors Ghotb’salpha_boparameter.- block_id: str
- contractor_id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.hauling.contract.Depot(*, id, name=None)[source]
Bases:
BaseModelA central depot where trucks start and end their daily routes.
- id
Unique depot identifier. It is also the location key used by
DepotLeg.- Type:
- name
Optional human-readable label.
- Type:
str | None
- id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.hauling.contract.DepotLeg(*, depot_id, location_id, truck_type_id, travel_minutes)[source]
Bases:
BaseModelTravel time between a depot and a pickup or delivery location.
- depot_id
Depot identifier.
- Type:
- location_id
Block or sort-yard identifier. The contract validates it against the scenario’s blocks and the hauling contract’s yards.
- Type:
- truck_type_id
Truck type for which the travel time applies.
- Type:
- travel_minutes
One-way travel time in minutes. Must be greater than zero.
- Type:
- depot_id: str
- location_id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- travel_minutes: float
- truck_type_id: str
- class fhops.hauling.contract.DriverPolicy(*, regular_minutes=510.0, max_overtime_minutes=150.0, overtime_violation_penalty_per_minute=0.0)[source]
Bases:
BaseModelRegular-time, overtime, and overtime-violation policy for truck drivers.
- Parameters:
- regular_minutes
Regular working minutes per truck per day. The thesis case uses 510 (8.5 h).
- Type:
- max_overtime_minutes
Maximum overtime that may be assigned before a violation penalty applies. The thesis case uses 150 (2.5 h).
- Type:
- overtime_violation_penalty_per_minute
Penalty per minute of overtime above
max_overtime_minutes. Phase III may use this as a soft penalty; Phase II treats the limit as a hard constraint.- Type:
- max_overtime_minutes: float
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- overtime_violation_penalty_per_minute: float
- regular_minutes: float
- class fhops.hauling.contract.HaulArc(*, block_id, yard_id, truck_type_id, travel_minutes, distance_km=None, return_travel_minutes=None, mode=TransportMode.ROAD)[source]
Bases:
BaseModelA feasible one-way road haul leg from a block to a sort yard for one truck type.
Water legs are not represented as
HaulArcrecords. Declare aWaterArc,Portpair,BargeType, andMarineRouteinstead. Themodefield is explicit so a report can distinguish road arcs without inferring the mode from identifiers.- Parameters:
- block_id
Origin block identifier.
- Type:
- yard_id
Destination sort-yard identifier.
- Type:
- truck_type_id
Truck type that can traverse the arc.
- Type:
- travel_minutes
Loaded travel time in minutes, including any arc-specific delay but excluding loading and unloading. Must be greater than zero.
- Type:
- distance_km
Optional distance for reporting and cost checks. Non-negative.
- Type:
float | None
- return_travel_minutes
Optional empty return time from the yard to the block. When
None, Phase II usestravel_minutesfor the empty leg as well.- Type:
float | None
- mode
Transport mode. Only
roadis accepted here; water and rail legs use their dedicated models.- Type:
TransportMode
- block_id: str
- empty_travel_minutes()[source]
Return the empty return time from the yard to the block.
- Return type:
- mode: TransportMode
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- travel_minutes: float
- truck_type_id: str
- yard_id: str
- class fhops.hauling.contract.HaulingConfig(*, planning_horizon_days=20, start_day=1, log_sorts, contractors, truck_types, trucks, depots, sort_yards, depot_legs=<factory>, loader_locations=<factory>, supply, demand=<factory>, haul_arcs, barge_types=<factory>, barges=<factory>, ports=<factory>, water_arcs=<factory>, port_legs=<factory>, marine_routes=<factory>, contractor_blocks=<factory>, compatibility=<factory>, driver_policy=<factory>, roadside_inventory_penalty_per_m3=0.0, port_inventory_penalty_per_m3=0.0, loader_travel_cost_per_km=0.0, default_loader_service_minutes=20.0)[source]
Bases:
BaseModelOptional
hauling:section of a FHOPS scenario.The config carries Ghotb’s operational log-hauling entities. It is validated against the parent scenario so every block, machine, and horizon reference is real. It does not require the tactical-operational contract;
product_idandfacility_idlinks are optional.- Parameters:
planning_horizon_days (int)
start_day (int)
log_sorts (list[LogSort])
contractors (list[Contractor])
truck_types (list[TruckType])
trucks (list[Truck])
depots (list[Depot])
sort_yards (list[SortYard])
depot_legs (list[DepotLeg])
loader_locations (list[LoaderLocation])
supply (list[HaulingSupply])
demand (list[HaulingDemand])
haul_arcs (list[HaulArc])
barge_types (list[BargeType])
barges (list[Barge])
ports (list[Port])
water_arcs (list[WaterArc])
port_legs (list[PortLeg])
marine_routes (list[MarineRoute])
contractor_blocks (list[ContractorBlock])
compatibility (list[ContractCompatibility])
driver_policy (DriverPolicy)
roadside_inventory_penalty_per_m3 (float)
port_inventory_penalty_per_m3 (float)
loader_travel_cost_per_km (float)
default_loader_service_minutes (float)
- planning_horizon_days
Number of working days in the Phase I horizon. Defaults to 20 (four weeks).
- Type:
- start_day
One-indexed day of the parent scenario where the hauling horizon starts.
- Type:
- log_sorts / contractors / truck_types / trucks / depots / sort_yards
Core entity lists.
- depot_legs
Optional travel times from each depot to blocks and yards, by truck type.
- Type:
list[DepotLeg]
- loader_locations
Bindings from FHOPS
Machinerecords to pickup or delivery locations.- Type:
list[LoaderLocation]
- supply / demand / haul_arcs
The Phase I flow inputs.
- haul_arcs
The Phase I road flow inputs.
- Type:
list[HaulArc]
- barge_types / barges
Marine equipment. Barges are first-class vessels, never aliases of truck types.
- ports / water_arcs / port_legs / marine_routes
The marine path: terminal nodes, sailing legs, road drayage legs, and complete block-to-yard chains with a water segment.
- contractor_blocks
Optional contractor-to-block allowlist. When empty, all contractors may serve all blocks.
- Type:
list[ContractorBlock]
- compatibility
Optional explicit exceptions. Reserved for later phases.
- Type:
list[ContractCompatibility]
- driver_policy
Regular and overtime rules for Phase II and Phase III.
- Type:
DriverPolicy
- roadside_inventory_penalty_per_m3
Penalty per cubic metre of sorted or unsorted volume left at a block at the end of the Phase I horizon (Ghotb’s
P). Non-negative.- Type:
- port_inventory_penalty_per_m3
Penalty per cubic metre of log volume left at a port at the end of the Phase I horizon. Non-negative. Zero lets the model store volume at a terminal for free.
- Type:
- loader_travel_cost_per_km
Loader travel cost per kilometre (Ghotb’s
DC_l). The thesis case uses stationary loaders, so this defaults to zero. Non-negative.- Type:
- default_loader_service_minutes
Fallback loading/unloading service time when
LoaderLocation.service_minutesis omitted. Must be greater than zero.- Type:
- barge_type(barge_type_id)[source]
Return a barge type by id.
- barge_types: list[BargeType]
- barges: list[Barge]
- barges_for(contractor_id, barge_type_id)[source]
Return enabled barges of one contractor and type.
- compatibility: list[ContractCompatibility]
- contractor_blocks: list[ContractorBlock]
- contractor_can_serve_block(contractor_id, block_id)[source]
Return whether a contractor may serve a block under the allowlist rule.
- contractors: list[Contractor]
- default_loader_service_minutes: float
- demand: list[HaulingDemand]
- depot_legs: list[DepotLeg]
- depots: list[Depot]
- driver_policy: DriverPolicy
- haul_arcs: list[HaulArc]
- loader_locations: list[LoaderLocation]
- loader_travel_cost_per_km: float
- log_sorts: list[LogSort]
- marine_route(route_id)[source]
Return a marine route by id.
- marine_routes: list[MarineRoute]
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- planning_horizon_days: int
- port(port_id)[source]
Return a port by id.
- port_inventory_penalty_per_m3: float
- port_leg_for(route, side)[source]
Return the drayage leg for one side of a marine route, if configured.
- Parameters:
route (MarineRoute)
side (PortSide)
- Return type:
PortLeg | None
- port_legs: list[PortLeg]
- ports: list[Port]
- roadside_inventory_penalty_per_m3: float
- sort_yards: list[SortYard]
- start_day: int
- supply: list[HaulingSupply]
- truck_types: list[TruckType]
- trucks: list[Truck]
- trucks_for(contractor_id, truck_type_id)[source]
Return enabled trucks of one contractor and type.
- validate_against_scenario(*, block_ids, machine_ids, num_days)[source]
Validate references to the parent scenario and its horizon.
- Parameters:
- Raises:
ValueError – When a block, loader, or horizon reference is invalid.
- Return type:
None
- water_arc(water_arc_id)[source]
Return a water arc by id.
- water_arcs: list[WaterArc]
- class fhops.hauling.contract.HaulingDemand(*, yard_id, log_sort_id, day, volume_m3)[source]
Bases:
BaseModelDemand for one log sort at one yard on one day of the hauling horizon.
- yard_id
Sort-yard identifier from the hauling contract.
- Type:
- log_sort_id
Log-sort identifier from the hauling contract.
- Type:
- day
One-indexed day relative to the hauling horizon (not the parent scenario horizon).
- Type:
- volume_m3
Required volume in cubic metres. Non-negative.
- Type:
- day: int
- log_sort_id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- volume_m3: float
- yard_id: str
- class fhops.hauling.contract.HaulingSupply(*, block_id, log_sort_id, sorted_m3=0.0, unsorted_m3=0.0)[source]
Bases:
BaseModelAvailable sorted and unsorted volume of one log sort at one block.
- block_id
Block identifier from
Scenario.blocks.- Type:
- log_sort_id
Log-sort identifier from the hauling contract.
- Type:
- sorted_m3
Roadside-sorted supply in cubic metres (Ghotb’s
SW_bs). Non-negative.- Type:
- unsorted_m3
Unsorted supply in cubic metres (
SC_bs). Non-negative.- Type:
- block_id: str
- log_sort_id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- sorted_m3: float
- unsorted_m3: float
- class fhops.hauling.contract.LoaderLocation(*, loader_id, location_id, location_kind, service_minutes=None)[source]
Bases:
BaseModelBinding between a FHOPS machine (the loader) and a hauling location.
- Parameters:
- loader_id
Machine identifier from
Scenario.machines. The machine is the physical loader.- Type:
- location_id
Block identifier for a pickup loader or sort-yard identifier for a delivery loader.
- Type:
- location_kind
pickupordelivery; must agree withlocation_id.- Type:
LoaderLocationKind
- service_minutes
Optional loading/unloading service time override. When
None, the model uses the value configured by Phase II. Must be greater than zero when supplied.- Type:
float | None
- loader_id: str
- location_id: str
- location_kind: LoaderLocationKind
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class fhops.hauling.contract.LoaderLocationKind(*values)[source]
Bases:
StrEnumWhether a loader serves a pickup, delivery, or port/terminal node.
PORTloaders serve both the road drayage and the barge load/unload activity at a port.- DELIVERY = 'delivery'
- PICKUP = 'pickup'
- PORT = 'port'
- class fhops.hauling.contract.LogSort(*, id, product_id=None, sorted=True, description=None)[source]
Bases:
BaseModelA log assortment handled by the hauling network.
- id
Unique log-sort identifier referenced by supply, demand, and compatibility records.
- Type:
- product_id
Optional link to a tactical-operational
Product.product_id. The hauling layer does not require the tactical contract; when the link is present it must be non-empty.- Type:
str | None
- sorted
Truefor a roadside-sorted sort,Falsefor an unsorted sort that is processed at a yard. Phase I carries both sorted and unsorted supply for every sort (Ghotb’sSWandSCparameters); this flag describes the kind of load, not a restriction on supply.- Type:
- description
Optional human-readable label.
- Type:
str | None
- id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- sorted: bool
- class fhops.hauling.contract.MarineRoute(*, id, block_id, yard_id, origin_port_id, destination_port_id, water_arc_id, contractor_id, truck_type_id)[source]
Bases:
BaseModelA complete block-to-yard chain with a water leg between two ports.
The route is a composite of a road pickup leg, terminal handling at both ports, a barge sailing leg, and a road delivery leg. Phase I treats it as an alternative to a direct road
HaulArc; Phase II schedules the drayage loads and barge trips separately.- Parameters:
- id
Unique marine-route identifier.
- Type:
- block_id
Origin block for the pickup drayage leg.
- Type:
- yard_id
Destination sort yard for the delivery drayage leg.
- Type:
- origin_port_id
Loading port.
- Type:
- destination_port_id
Unloading port.
- Type:
- water_arc_id
Sailing arc. Its barge type provides capacity, cost, and load compatibility.
- Type:
- contractor_id
Contractor responsible for the route. The contractor must own an enabled barge of the water arc’s type and at least one enabled truck of
truck_type_id.- Type:
- truck_type_id
Road truck type used for the pickup and delivery drayage legs.
- Type:
- block_id: str
- contractor_id: str
- destination_port_id: str
- id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- origin_port_id: str
- truck_type_id: str
- water_arc_id: str
- yard_id: str
- class fhops.hauling.contract.Port(*, id, name=None, storage_m3=0.0, initial_inventory_m3=0.0, handling_minutes_per_trip=0.0, handling_cost_per_trip=0.0, daily_handling_m3=None)[source]
Bases:
BaseModelA port or barge terminal at one end of a water leg.
- Parameters:
- id
Unique port identifier. It is the location key used by water arcs, port legs, and port loader locations.
- Type:
- name
Optional human-readable label.
- Type:
str | None
- storage_m3
Total log-storage capacity at the terminal, in cubic metres. Non-negative; zero means no spare storage constraint is modelled.
- Type:
- initial_inventory_m3
Opening inventory for every log sort at the port. Non-negative.
- Type:
- handling_minutes_per_trip
Terminal handling time charged once per barge trip at this port. Non-negative.
- Type:
- handling_cost_per_trip
Terminal handling cost charged once per barge trip at this port. Non-negative.
- Type:
- daily_handling_m3
Optional daily throughput cap in cubic metres.
Nonemeans uncapped.- Type:
float | None
- handling_cost_per_trip: float
- handling_minutes_per_trip: float
- id: str
- initial_inventory_m3: float
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- storage_m3: float
- class fhops.hauling.contract.PortLeg(*, port_id, location_id, side, truck_type_id, travel_minutes, distance_km=None, return_travel_minutes=None)[source]
Bases:
BaseModelA road drayage leg between a block/yard and a port.
- Parameters:
- port_id
Port identifier.
- Type:
- location_id
Block identifier for an origin-side leg or sort-yard identifier for a destination-side leg.
- Type:
- side
originfor the block-to-port leg,destinationfor the port-to-yard leg.- Type:
PortSide
- truck_type_id
Truck type that performs the drayage.
- Type:
- travel_minutes
Loaded one-way travel time in minutes. Must be greater than zero.
- Type:
- distance_km
Optional road distance. Non-negative.
- Type:
float | None
- return_travel_minutes
Optional empty return time. When
None, the loaded time is used.- Type:
float | None
- empty_travel_minutes()[source]
Return the empty return time between the port and the land location.
- Return type:
- location_id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- port_id: str
- side: PortSide
- travel_minutes: float
- truck_type_id: str
- class fhops.hauling.contract.PortSide(*values)[source]
Bases:
StrEnumWhich end of a marine route a road drayage leg serves.
ORIGINis the block-to-port leg on the loading side;DESTINATIONis the port-to-yard leg on the unloading side.- DESTINATION = 'destination'
- ORIGIN = 'origin'
- class fhops.hauling.contract.SortYard(*, id, facility_id=None, unsorted_storage_m3=0.0, sorted_processing_m3_per_day=0.0, unsorted_processing_m3_per_day=0.0, total_processing_m3_per_day=0.0, initial_unsorted_inventory_m3=0.0, initial_water_inventory_m3=0.0)[source]
Bases:
BaseModelA sort yard or mill that receives logs and may process unsorted volume.
- Parameters:
- id
Unique yard identifier. It is also the delivery location key used by loaders, demand, and haul arcs.
- Type:
- facility_id
Optional link to a tactical-operational
Facility.facility_id.- Type:
str | None
- unsorted_storage_m3
Maximum unsorted inventory that can be stored at the yard (Ghotb’s
SC_y).- Type:
- sorted_processing_m3_per_day
Daily capacity for sorted logs (
CW_y).- Type:
- unsorted_processing_m3_per_day
Daily capacity for sorting unsorted logs (
CC_y).- Type:
- total_processing_m3_per_day
Daily total processing capacity for sorted and unsorted logs (
CY_y). Must be at least the larger of the two component capacities.- Type:
- initial_unsorted_inventory_m3
Unsorted inventory at the start of the first day. Non-negative.
- Type:
- initial_water_inventory_m3
Processed/water inventory at the start of the first day. Non-negative.
- Type:
- id: str
- initial_unsorted_inventory_m3: float
- initial_water_inventory_m3: float
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- sorted_processing_m3_per_day: float
- total_processing_m3_per_day: float
- unsorted_processing_m3_per_day: float
- unsorted_storage_m3: float
- class fhops.hauling.contract.TransportMode(*values)[source]
Bases:
StrEnumTransport mode for a haul leg.
Road legs use a
TruckType; water legs use aBargeType. Rail is reserved for a later extension and is not accepted by the road or water models yet.- RAIL = 'rail'
- ROAD = 'road'
- WATER = 'water'
- class fhops.hauling.contract.Truck(*, id, contractor_id, truck_type_id, enabled=True, notes=None)[source]
Bases:
BaseModelAn individual truck owned by a contractor.
- id
Unique truck identifier. Phase II uses this as the vehicle label.
- Type:
- contractor_id
Contractor that owns and dispatches the truck.
- Type:
- truck_type_id
Truck type that determines capacity, cost, and load compatibility.
- Type:
- enabled
Set
Falseto keep a truck in the contract but exclude it from a solve.- Type:
- notes
Optional free-form note surfaced in reports.
- Type:
str | None
- contractor_id: str
- enabled: bool
- id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- truck_type_id: str
- class fhops.hauling.contract.TruckType(*, id, capacity_m3, fixed_cost=0.0, variable_cost_per_minute=0.0, overtime_cost_per_minute=0.0, can_carry_sorted=True, can_carry_unsorted=True)[source]
Bases:
BaseModelA homogeneous truck type with capacity, costs, and load compatibility.
- Parameters:
- id
Unique truck-type identifier.
- Type:
- capacity_m3
Payload capacity in cubic metres (Ghotb’s
Q_r). Must be greater than zero.- Type:
- fixed_cost
Fixed operating cost of dispatching a truck of this type for the day, in scenario currency (Ghotb’s
FC_k). Non-negative.- Type:
- variable_cost_per_minute
Variable operating cost per minute of regular working time (
VC_k). Non-negative.- Type:
- overtime_cost_per_minute
Overtime cost per minute (
OC_k). Non-negative.- Type:
- can_carry_sorted
Whether the type may carry roadside-sorted loads (
gamma_r).- Type:
- can_carry_unsorted
Whether the type may carry unsorted loads (
epsilon_r).- Type:
- can_carry_sorted: bool
- can_carry_unsorted: bool
- capacity_m3: float
- fixed_cost: float
- id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- overtime_cost_per_minute: float
- variable_cost_per_minute: float
- class fhops.hauling.contract.WaterArc(*, id, origin_port_id, destination_port_id, barge_type_id, travel_minutes, distance_km=None, return_travel_minutes=None, load_minutes=None, unload_minutes=None)[source]
Bases:
BaseModelA sailing leg between two ports for one barge type.
- Parameters:
- id
Unique water-arc identifier referenced by
MarineRoute.- Type:
- origin_port_id
Loading port identifier.
- Type:
- destination_port_id
Unloading port identifier.
- Type:
- barge_type_id
Barge type that can sail the arc.
- Type:
- travel_minutes
Loaded sailing time in minutes. Must be greater than zero.
- Type:
- distance_km
Optional sailing distance for reporting and cost checks. Non-negative.
- Type:
float | None
- return_travel_minutes
Optional empty return sailing time. When
None, Phase I/II usetravel_minutes.- Type:
float | None
- load_minutes
Optional loading-time override for this arc. When
None, the barge type default is used.- Type:
float | None
- unload_minutes
Optional unloading-time override for this arc. When
None, the barge type default is used.- Type:
float | None
- barge_type_id: str
- destination_port_id: str
- empty_travel_minutes()[source]
Return the empty return sailing time from the destination to the origin.
- Return type:
- id: str
- model_config = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- origin_port_id: str
- travel_minutes: float
Phase I monthly flow model for operational log hauling.
This module implements the first phase of Ghotb’s decomposition: a bi-objective mixed-integer program for daily sorted and unsorted log flows from blocks to sort yards, with yard processing, inventory, contractor compatibility, fleet capacity, and contractor workload balance. The bi-objective model is solved by goal programming.
The formulation is documented in notes/hauling_formulation_audit.md (equations 3-1 to 3-23).
The implementation deliberately tightens the thesis in three places:
compatibility is enforced by index restriction instead of a big-M;
the absolute workload deviation is linearised with two inequalities;
goal deviations are non-negative continuous variables (the thesis/paper disagree on which one is integer).
The model is built from a validated fhops.scenario.contract.Scenario with a hauling:
section. It does not import or modify the operational or tactical MILPs.
- class fhops.hauling.flow.FlowSolveResult(status, objective, cost_objective, workload_objective, goal_deviations=<factory>, goal1=None, goal2=None, preferred_truckloads=<factory>, flows=<factory>, truckloads=<factory>, yard_inventory=<factory>, block_inventory=<factory>, contractor_workload=<factory>, marine_flows=<factory>, barge_trips=<factory>, port_inventory=<factory>, marine_truckloads=<factory>, solver='highs', termination='', runtime_seconds=0.0, gap=None, solver_error=None)[source]
Bases:
objectResult payload for a Phase I solve.
- Parameters:
status (str)
objective (float | None)
cost_objective (float | None)
workload_objective (float | None)
goal1 (float | None)
goal2 (float | None)
flows (DataFrame)
truckloads (DataFrame)
yard_inventory (DataFrame)
block_inventory (DataFrame)
contractor_workload (DataFrame)
marine_flows (DataFrame)
barge_trips (DataFrame)
port_inventory (DataFrame)
marine_truckloads (DataFrame)
solver (str)
termination (str)
runtime_seconds (float)
gap (float | None)
solver_error (str | None)
- status
optimal,feasible,infeasible,unbounded, orerror.- Type:
- objective
Value of the objective that was actually solved (cost, workload, or the normalized goal objective).
- Type:
float | None
- cost_objective
Value of Ghotb’s
Z1at the returned solution.- Type:
float | None
- workload_objective
Value of Ghotb’s
Z2at the returned solution.- Type:
float | None
- goal_deviations
costandworkloadpositive deviations when a goal-programming solve ran.
- goal1 / goal2
Goals used by the goal-programming solve.
- preferred_truckloads
Preferred daily truckloads used for the workload objective.
- flows / truckloads / yard_inventory / block_inventory / contractor_workload
DataFrames with the documented schemas.
- marine_flows / barge_trips / port_inventory / marine_truckloads
Marine-leg volume, barge trip, terminal inventory, and drayage truckload tables. They are empty for road-only scenarios.
- solver / termination / runtime_seconds / gap / solver_error
Solver provenance and diagnostics.
- barge_trips: DataFrame
- block_inventory: DataFrame
- contractor_workload: DataFrame
- flows: DataFrame
- marine_flows: DataFrame
- marine_truckloads: DataFrame
- port_inventory: DataFrame
- runtime_seconds: float
- solver: str
- status: str
- termination: str
- truckloads: DataFrame
- yard_inventory: DataFrame
- class fhops.hauling.flow.HaulingFlowProblem(scenario_name, start_day, horizon_days, days, blocks, yards, sorts, truck_types, contractors, supply_sorted, supply_unsorted, demand, cycle_minutes, transport_cost, capacity, fleet_count, preferred, contractor_block_allowlist, can_carry_sorted, can_carry_unsorted, yard_storage, yard_sorted_capacity, yard_unsorted_capacity, yard_total_capacity, initial_unsorted, initial_water, regular_minutes, roadside_penalty, default_service_minutes, pickup_service, delivery_service, marine_lanes=<factory>, marine_ports=(), marine_port_storage=<factory>, marine_port_initial=<factory>, marine_port_daily_handling=<factory>, marine_barge_fleet=<factory>, port_inventory_penalty=0.0)[source]
Bases:
objectNormalised Phase I data derived from a scenario’s hauling section.
- Parameters:
- scenario_name
Parent scenario name.
- Type:
- start_day
One-indexed parent-scenario day where the hauling horizon starts.
- Type:
- horizon_days
Number of days in the Phase I horizon.
- Type:
- blocks / yards / sorts / truck_types / contractors
Ordered entity identifiers.
- supply_sorted / supply_unsorted
(block_id, log_sort_id) -> m3. Zero-supply pairs are omitted.
- demand
(yard_id, log_sort_id, relative_day) -> m3. Missing pairs are zero.
- cycle_minutes
(block_id, yard_id, truck_type_id) ->round-trip plus loading/unloading minutes.
- transport_cost
(block_id, yard_id, truck_type_id) ->cost per started truckload.
- fleet_count
(contractor_id, truck_type_id) ->number of enabled trucks.
- preferred
contractor_id ->preferred daily truckloads (Nonewhen not supplied).
- contractor_block_allowlist
Allowed
(contractor_id, block_id)pairs. Empty means every contractor may serve every block.
- can_carry_sorted / can_carry_unsorted
Truck-type flags.
- yard_storage / yard_sorted_capacity / yard_unsorted_capacity / yard_total_capacity
yard_id ->capacity.
- initial_unsorted / initial_water
(yard_id, log_sort_id) ->opening inventory.
- regular_minutes
Regular truck minutes per day.
- Type:
- roadside_penalty
Cost per m3 left at a block at the end of the horizon.
- Type:
- default_service_minutes
Fallback loader service time.
- Type:
- pickup_service / delivery_service
location_id ->service minutes.
- marine_lanes
route_id -> MarineLane. Empty for a road-only problem.
- marine_ports / marine_port_storage / marine_port_initial / marine_port_daily_handling
Port ids and their storage, opening inventory, and optional daily handling caps.
- marine_barge_fleet
(contractor_id, barge_type_id) ->number of enabled barges.
- port_inventory_penalty
Cost per m3 left at a port at the end of the horizon.
- Type:
- contractor_can_serve(contractor_id, block_id)[source]
Return whether a contractor may serve a block under the allowlist rule.
- default_service_minutes: float
- demand_for(yard_id, log_sort_id, day)[source]
Return demand for one yard/sort/day (zero when absent).
- horizon_days: int
- port_inventory_penalty: float
- regular_minutes: float
- roadside_penalty: float
- scenario_name: str
- start_day: int
- class fhops.hauling.flow.MarineLane(route_id, block_id, yard_id, origin_port_id, destination_port_id, contractor_id, truck_type_id, barge_type_id, water_arc_id, pickup_travel_minutes, pickup_return_minutes, delivery_travel_minutes, delivery_return_minutes, water_travel_minutes, water_return_minutes, load_minutes, unload_minutes, origin_handling_minutes, destination_handling_minutes, origin_handling_cost, destination_handling_cost, barge_capacity, truck_capacity, barge_cycle_minutes, arrival_lag_days, occupancy_days, pickup_cycle_minutes, delivery_cycle_minutes, barge_cost_per_trip, pickup_drayage_cost_per_m3, delivery_drayage_cost_per_m3, drayage_cost_per_m3, can_carry_sorted, can_carry_unsorted)[source]
Bases:
objectNormalised Phase I data for one marine route.
A lane bundles the road pickup leg, the water leg, terminal handling at both ports, and the road delivery leg. The Phase I model treats the lane as an alternative supply path from a block to a yard, while still reporting the three physical legs separately.
- Parameters:
route_id (str)
block_id (str)
yard_id (str)
origin_port_id (str)
destination_port_id (str)
contractor_id (str)
truck_type_id (str)
barge_type_id (str)
water_arc_id (str)
pickup_travel_minutes (float)
pickup_return_minutes (float)
delivery_travel_minutes (float)
delivery_return_minutes (float)
water_travel_minutes (float)
water_return_minutes (float)
load_minutes (float)
unload_minutes (float)
origin_handling_minutes (float)
destination_handling_minutes (float)
origin_handling_cost (float)
destination_handling_cost (float)
barge_capacity (float)
truck_capacity (float)
barge_cycle_minutes (float)
arrival_lag_days (int)
occupancy_days (int)
pickup_cycle_minutes (float)
delivery_cycle_minutes (float)
barge_cost_per_trip (float)
pickup_drayage_cost_per_m3 (float)
delivery_drayage_cost_per_m3 (float)
drayage_cost_per_m3 (float)
can_carry_sorted (bool)
can_carry_unsorted (bool)
- route_id
Marine-route identifier.
- Type:
- block_id / yard_id
Pickup block and delivery sort yard.
- origin_port_id / destination_port_id
Loading and unloading ports.
- contractor_id / truck_type_id
Contractor and road truck type for the drayage legs.
- barge_type_id / water_arc_id
Barge equipment and sailing arc.
- pickup_travel_minutes / pickup_return_minutes
Loaded and empty road times between the block and the origin port.
- delivery_travel_minutes / delivery_return_minutes
Loaded and empty road times between the destination port and the yard.
- water_travel_minutes / water_return_minutes
Loaded and empty sailing times.
- load_minutes / unload_minutes
Barge loading and unloading service times.
- origin_handling_minutes / destination_handling_minutes
Terminal handling time at each port.
- origin_handling_cost / destination_handling_cost
Terminal handling cost per barge trip at each port.
- barge_capacity / truck_capacity
Cargo capacities in cubic metres.
- barge_cycle_minutes
Round-trip barge time including loading, unloading, and terminal handling.
- Type:
- arrival_lag_days
Whole-day lag between a departure and cargo arrival at the destination.
0means same-day delivery.- Type:
- occupancy_days
Whole days a barge is occupied by the round trip. Used by the sliding-window fleet constraint.
- Type:
- pickup_cycle_minutes / delivery_cycle_minutes
Round-trip drayage truck time on each side.
- barge_cost_per_trip
Variable barge cost plus terminal handling cost for one trip.
- Type:
- pickup_drayage_cost_per_m3 / delivery_drayage_cost_per_m3
Road truck variable cost per cubic metre on each drayage leg.
- drayage_cost_per_m3
Sum of the pickup and delivery drayage rates, kept for reporting.
- Type:
- can_carry_sorted / can_carry_unsorted
Barge-type compatibility flags.
- arrival_lag_days: int
- barge_capacity: float
- barge_cost_per_trip: float
- barge_cycle_minutes: float
- barge_type_id: str
- block_id: str
- can_carry_sorted: bool
- can_carry_unsorted: bool
- contractor_id: str
- delivery_cycle_minutes: float
- delivery_drayage_cost_per_m3: float
- delivery_return_minutes: float
- delivery_travel_minutes: float
- destination_handling_cost: float
- destination_handling_minutes: float
- destination_port_id: str
- drayage_cost_per_m3: float
- load_minutes: float
- occupancy_days: int
- origin_handling_cost: float
- origin_handling_minutes: float
- origin_port_id: str
- pickup_cycle_minutes: float
- pickup_drayage_cost_per_m3: float
- pickup_return_minutes: float
- pickup_travel_minutes: float
- route_id: str
- truck_capacity: float
- truck_type_id: str
- unload_minutes: float
- water_arc_id: str
- water_return_minutes: float
- water_travel_minutes: float
- yard_id: str
- fhops.hauling.flow.build_flow_model(problem, *, objective='cost', preferred=None, goals=None, weights=(1.0, 1.0))[source]
Build the Phase I Pyomo model.
- Parameters:
problem (HaulingFlowProblem) – Normalised hauling data.
objective (str) –
"cost"(Ghotb’sZ1),"workload"(Z2), or"goal"(goal programming).preferred (dict[str, float] | None) – Preferred daily truckloads per contractor.
Noneuses 0 for the workload constraint;solve_flow()derives values from a cost-only solve when needed.goals (tuple[float, float] | None) –
(Goal1, Goal2)for the goal-programming objective. Required whenobjective="goal"unlesssolve_flow()derives them.weights (tuple[float, float]) –
(w1, w2)goal weights. Must be non-negative and not both zero whenobjective="goal".
- Returns:
Model with
cost_expr,workload_expr, and a singleobjectivecomponent.- Return type:
pyomo.ConcreteModel
- Raises:
ValueError – For an unknown objective or invalid goal weights/goals.
- fhops.hauling.flow.build_flow_problem(scenario)[source]
Build a
HaulingFlowProblemfrom a validated scenario.- Parameters:
scenario (Scenario) – Scenario with a non-
Nonehaulingsection.- Returns:
Normalised data ready for
build_flow_model()andsolve_flow().- Return type:
HaulingFlowProblem
- Raises:
ValueError – When the scenario has no hauling section, or a supply/demand/arc combination cannot be represented by the contract.
- fhops.hauling.flow.solve_flow(problem, *, objective='cost', solver='highs', time_limit=None, gap=None, weights=(1.0, 1.0), goals=None, preferred=None, tee=False, solver_options=None)[source]
Solve the Phase I flow model.
- Parameters:
problem (HaulingFlowProblem) – Normalised hauling data.
objective (str) –
"cost","workload", or"goal".solver (str) – Pyomo solver name.
highsis the default;gurobiis used when installed and licensed.time_limit (float | None) – Optional solver time limit in seconds.
gap (float | None) – Optional relative MIP gap target.
weights (tuple[float, float]) – Goal weights for
objective="goal".goals (tuple[float, float] | None) –
(Goal1, Goal2). When omitted for a goal solve, a cost-only solve suppliesGoal1andGoal2is set to zero; preferred truckloads are derived from the same cost-only plan.preferred (dict[str, float] | None) – Preferred daily truckloads per contractor. When omitted, the contract values are used, or the cost-only plan’s daily averages for a goal solve.
tee (bool) – Stream the solver log.
solver_options (dict[str, Any] | None) – Additional solver options forwarded verbatim.
- Returns:
Result payload with objective values, DataFrames, solver provenance, and diagnostics.
- Return type:
FlowSolveResult
- fhops.hauling.flow.summarize_flow_result(result)[source]
Return a compact human-readable summary of a Phase I solve.
- Parameters:
result (FlowSolveResult) – Result payload from
solve_flow().- Returns:
Multi-line summary suitable for a CLI or notebook cell. It reports status, objective values, goal deviations, truckloads, delivered volume, and the ending yard inventory.
- Return type:
Network and data preparation for Phase II daily truck scheduling.
Phase II takes the integer truckloads from the Phase I flow plan and turns them into individual
loads on a single working day. This module builds the normalised ScheduleProblem that the
continuous-time MIP in fhops.hauling.schedule solves.
- class fhops.hauling.network.ScheduleProblem(scenario_name, day, scenario_day, horizon_minutes, depot_id, loads, trucks, truck_types, contractors, pickup_loaders, delivery_loaders, loader_service, block_to_yard, yard_to_block, depot_to_block, yard_to_depot, regular_minutes, max_overtime_minutes, overtime_violation_penalty_per_minute, marine=None)[source]
Bases:
objectNormalised Phase II scheduling data.
- Parameters:
- scenario_name
Parent scenario name.
- Type:
- day
Relative hauling-horizon day being scheduled.
- Type:
- scenario_day
Parent-scenario day (
start_day + day - 1).- Type:
- horizon_minutes
Daily time horizon,
regular_minutes + max_overtime_minutes.- Type:
- depot_id
The single central depot used for this first scheduling cut.
- Type:
- loads
Individual truckloads.
- Type:
tuple[Truckload, …]
- trucks
Enabled trucks from the contract.
- Type:
tuple[Truck, …]
- truck_types / contractors
Contract records keyed by id.
- pickup_loaders / delivery_loaders
location_id -> tuple of loader ids.
- block_to_yard / yard_to_block
(block, yard, truck_type) -> minutes.
- depot_to_block / yard_to_depot
(location, truck_type) -> minutes.
- regular_minutes / max_overtime_minutes
Driver policy.
- overtime_violation_penalty_per_minute
Soft penalty per minute above
regular_minutes + max_overtime_minutesused by the SA solver. The exact MIP treats the limit as hard.- Type:
- marine
Optional marine scheduling data for barge cycles and port loaders.
Nonefor road-only days.- Type:
MarineScheduleProblem | None
- compatible_trucks(load)[source]
Return enabled trucks that can carry a load.
- Parameters:
load (Truckload)
- Return type:
tuple[Truck, …]
- day: int
- delivery_loaders_for(load)[source]
Return the delivery loaders at a load’s yard.
- delivery_loads()[source]
Return the loads sorted by yard id and load id.
- Return type:
tuple[Truckload, …]
- depot_id: str
- depot_to_load_minutes(load)[source]
Return the depot-to-pickup travel time.
- Parameters:
load (Truckload)
- Return type:
- empty_travel_minutes(previous, following)[source]
Return the empty travel time from
previous’s yard tofollowing’s block.Marine drayage stages are separate physical movements: a truck that delivers to the destination port cannot carry a pickup load from the origin block without a deadhead that the first release does not model. Missing cross-stage pairs receive a synthetic full-day deadhead so the MIP can express the transition as infeasible rather than raising a key error; a schedule that uses one is reported as a validation warning.
- Parameters:
previous (Truckload)
following (Truckload)
- Return type:
- horizon_minutes: float
- load_to_depot_minutes(load)[source]
Return the delivery-to-depot travel time.
- Parameters:
load (Truckload)
- Return type:
- load_travel_minutes(load)[source]
Return the loaded block-to-yard travel time.
- Parameters:
load (Truckload)
- Return type:
- loads: tuple[Truckload, ...]
- marine: MarineScheduleProblem | None
- max_overtime_minutes: float
- overtime_violation_penalty_per_minute: float
- pickup_loaders_for(load)[source]
Return the pickup loaders at a load’s block.
- pickup_loads()[source]
Return the loads sorted by block id and load id.
- Return type:
tuple[Truckload, …]
- regular_minutes: float
- scenario_day: int
- scenario_name: str
- trucks: tuple[Truck, ...]
- class fhops.hauling.network.Truckload(load_id, day, block_id, yard_id, contractor_id, truck_type_id, sorted, volume_m3, leg='road', marine_route_id=None)[source]
Bases:
objectOne individual truckload on the scheduling day.
- Parameters:
- load_id
Unique identifier within the day.
- Type:
- day
Relative hauling-horizon day (one-indexed).
- Type:
- block_id
Pickup block.
- Type:
- yard_id
Delivery sort yard.
- Type:
- contractor_id
Contractor responsible for the load.
- Type:
- truck_type_id
Compatible truck type.
- Type:
- sorted
Truefor a roadside-sorted load,Falsefor an unsorted load.- Type:
- volume_m3
Nominal load volume. Phase II uses it for reporting; the scheduling constraints use the truck type’s capacity and the load count, not the exact partial volume.
- Type:
- leg
roadfor a direct block-to-yard haul,pickupfor the block-to-origin-port drayage leg, ordeliveryfor the destination-port-to-yard drayage leg.- Type:
- marine_route_id
Marine route identifier for drayage loads;
Nonefor road loads.- Type:
str | None
- block_id: str
- contractor_id: str
- day: int
- leg: str
- load_id: str
- sorted: bool
- truck_type_id: str
- volume_m3: float
- yard_id: str
- fhops.hauling.network.build_schedule_problem(scenario, flow_result, day)[source]
Build a scheduling problem from a Phase I result and a selected day.
- Parameters:
scenario (Scenario) – Scenario with a
haulingsection.flow_result (FlowSolveResult) – Result from
fhops.hauling.flow.solve_flow().day (int) – Relative hauling-horizon day to schedule.
- Returns:
Normalised loads, trucks, loaders, and travel times.
- Return type:
ScheduleProblem
- Raises:
ValueError – When the day is outside the horizon, the scenario has no hauling section, the day has no truckloads, or a required loader, depot leg, or haul arc is missing.
- fhops.hauling.network.build_schedule_problem_from_loads(scenario, loads, day)[source]
Build a scheduling problem from an explicit load list.
This is the test and experimentation entry point. The production path is
build_schedule_problem().- Parameters:
scenario (Scenario) – Scenario with a
haulingsection.loads (Sequence[Truckload]) – Individual loads on the selected day.
day (int) – Relative hauling-horizon day.
- Returns:
Normalised scheduling data.
- Return type:
ScheduleProblem
- Raises:
ValueError – When the day is invalid, the scenario has no hauling section, the contract has more than one depot, or a required loader/travel leg is missing.
Phase II continuous-time daily truck routing and scheduling.
This module implements the second phase of Ghotb’s decomposition: a mixed-integer program that assigns Phase I truckloads to compatible trucks and contractors, routes the trucks from a central depot, and synchronises trucks with stationary loaders at pickup and delivery. Time is continuous, so the model returns arrival, waiting, service, and return times rather than slot assignments.
The formulation follows Chapter 4 of Ghotb (2023), with the corrections recorded in
notes/hauling_formulation_audit.md:
each truckload is assigned to exactly one compatible truck;
each truck has a start/end route and a sequence of loads, with MTZ subtour elimination;
each pickup and delivery load is assigned to one loader at its location;
loaders have a total order and no overlapping service;
trucks and loaders share the service start time, so waiting is the gap between arrival and start;
regular time and overtime are separate variables, so overtime is charged exactly.
The exact MIP is intended for small and medium daily instances. The simulated-annealing solver in P11.4 handles the large case.
- class fhops.hauling.schedule.SchedulePlan(routes, pickup_loader, delivery_loader, pickup_order, delivery_order)[source]
Bases:
objectA route and loader-order plan for a scheduling problem.
- Parameters:
- pickup_loader / delivery_loader
load_id -> loader_id.
- pickup_order / delivery_order
loader_id -> ordered tuple of load ids.
- class fhops.hauling.schedule.ScheduleSolveResult(status, objective, fixed_cost, variable_cost, overtime_cost, schedule=<factory>, truck_summary=<factory>, loader_summary=<factory>, barge_schedule=<factory>, barge_summary=<factory>, barge_objective=None, barge_fixed_cost=0.0, plan=None, solver='highs', termination='', runtime_seconds=0.0, solver_error=None)[source]
Bases:
objectResult payload for a Phase II solve.
- Parameters:
status (str)
objective (float | None)
fixed_cost (float | None)
variable_cost (float | None)
overtime_cost (float | None)
schedule (DataFrame)
truck_summary (DataFrame)
loader_summary (DataFrame)
barge_schedule (DataFrame)
barge_summary (DataFrame)
barge_objective (float | None)
barge_fixed_cost (float)
plan (SchedulePlan | None)
solver (str)
termination (str)
runtime_seconds (float)
solver_error (str | None)
- status
optimal,feasible,infeasible,unbounded, orerror.- Type:
- objective / fixed_cost / variable_cost / overtime_cost
Objective components.
- schedule / truck_summary / loader_summary
DataFrames with the documented schemas.
- barge_schedule / barge_summary
Barge-trip and barge-level tables for a marine day; empty for road-only days.
- plan
The route and loader-order plan, when a solution exists.
- Type:
fhops.hauling.schedule.SchedulePlan | None
- solver / termination / runtime_seconds / solver_error
Solver provenance and diagnostics.
- barge_fixed_cost: float
- barge_schedule: DataFrame
- barge_summary: DataFrame
- loader_summary: DataFrame
- plan: SchedulePlan | None
- runtime_seconds: float
- schedule: DataFrame
- solver: str
- status: str
- termination: str
- truck_summary: DataFrame
- fhops.hauling.schedule.build_schedule_model(problem)[source]
Build the Phase II continuous-time scheduling MIP.
- Parameters:
problem (ScheduleProblem) – Normalised scheduling data from
fhops.hauling.network.build_schedule_problem().- Returns:
Minimisation model with a single
objective.- Return type:
pyomo.ConcreteModel
- Raises:
ValueError – When a load has no compatible truck or no loader.
- fhops.hauling.schedule.simulate_plan(problem, plan)[source]
Recompute the earliest schedule for a fixed route and loader order.
The simulator solves the max-plus system defined by the truck routes and loader sequences by fixed-point iteration. It is independent of the MIP’s time variables and is used as a verification oracle for the model.
- Parameters:
problem (ScheduleProblem) – Scheduling data.
plan (SchedulePlan) – Truck routes and loader orders.
- Returns:
One row per load with
arrival_pickup,pickup_start,pickup_finish,arrival_delivery,delivery_start,delivery_finish, andload_order.- Return type:
pandas.DataFrame
- fhops.hauling.schedule.solve_schedule(problem, *, solver='highs', time_limit=None, gap=None, tee=False, solver_options=None)[source]
Solve the Phase II scheduling MIP.
- Parameters:
problem (ScheduleProblem) – Normalised scheduling data.
solver (str) – Pyomo solver name.
highsis the default;gurobiis used when installed and licensed.time_limit (float | None) – Optional solver time limit in seconds.
gap (float | None) – Optional relative MIP gap target.
tee (bool) – Stream the solver log.
solver_options (dict[str, Any] | None) – Additional solver options forwarded verbatim.
- Returns:
Schedule, truck summary, loader summary, plan, objective components, and solver provenance.
- Return type:
ScheduleSolveResult
- fhops.hauling.schedule.validate_schedule(problem, schedule)[source]
Check a schedule for compatibility, synchronisation, and overlap violations.
Marine barge-cycle scheduling and port synchronization.
The road scheduler in fhops.hauling.schedule routes trucks and loaders. A marine route adds
a second equipment class: a barge that loads at an origin port, sails a water arc, unloads at a
destination port, and returns empty. This module turns the Phase I barge trips into a deterministic
single-day barge schedule with:
one row per barge trip;
port-loader assignment and non-overlapping terminal service;
barge load, sail, unload, and empty-return times;
the synchronized road pickup and delivery windows for the drayage legs.
The deterministic scheduler is deliberately small and auditable. It complements the exact truck MIP and the SA solver; it does not replace either. The exact truck MIP schedules the road loads (including the drayage legs when a marine route is present), while this module schedules the water leg. The returned road windows are the handoff between the two schedules and are reported, not hard-linked, in the first release.
- class fhops.hauling.marine.BargeSchedulePlan(barge_trips)[source]
Bases:
objectDeterministic barge and port-loader assignment.
- barge_trips
route_id -> tuple of (barge_id, origin_loader_id, destination_loader_id)in schedule order.
- class fhops.hauling.marine.BargeScheduleResult(status, schedule=<factory>, summary=<factory>, plan=None, objective=0.0, fixed_cost=0.0, runtime_seconds=0.0, error=None)[source]
Bases:
objectBarge schedule payload attached to a Phase II or Phase III result.
- Parameters:
- status
feasiblewhen every request was scheduled,emptywhen there were no requests.- Type:
- schedule / summary
Trip-level and barge-level DataFrames.
- plan
Deterministic assignment plan.
- Type:
fhops.hauling.marine.BargeSchedulePlan | None
- objective
Total barge fixed, variable, and terminal handling cost.
- Type:
- fixed_cost
Fixed dispatch cost of the barges used.
- Type:
- runtime_seconds
Scheduler wall time.
- Type:
- error
Optional diagnostic.
- Type:
str | None
- fixed_cost: float
- objective: float
- plan: BargeSchedulePlan | None
- runtime_seconds: float
- schedule: DataFrame
- status: str
- summary: DataFrame
- class fhops.hauling.marine.MarineScheduleProblem(scenario_name, day, scenario_day, horizon_minutes, requests, barges, barge_types, ports, port_loaders, loader_service)[source]
Bases:
objectNormalised marine scheduling data for one day.
- Parameters:
- scenario_name / day / scenario_day / horizon_minutes
Run context.
- requests
Barge-trip requests from Phase I.
- Type:
tuple[fhops.hauling.marine.MarineTripRequest, …]
- port_loaders
port_id -> tuple of loader ids. A port loader serves both barge handling and drayage.
- day: int
- horizon_minutes: float
- requests: tuple[MarineTripRequest, ...]
- scenario_day: int
- scenario_name: str
- class fhops.hauling.marine.MarineTripRequest(route_id, contractor_id, truck_type_id, barge_type_id, origin_port_id, destination_port_id, water_arc_id, volume_m3, trips, load_minutes, unload_minutes, sail_minutes, return_minutes, origin_handling_minutes, destination_handling_minutes, origin_handling_cost, destination_handling_cost, pickup_travel_minutes, delivery_travel_minutes, truck_capacity, barge_variable_cost_per_trip)[source]
Bases:
objectOne Phase I barge-trip request for a route/day.
- Parameters:
route_id (str)
contractor_id (str)
truck_type_id (str)
barge_type_id (str)
origin_port_id (str)
destination_port_id (str)
water_arc_id (str)
volume_m3 (float)
trips (int)
load_minutes (float)
unload_minutes (float)
sail_minutes (float)
return_minutes (float)
origin_handling_minutes (float)
destination_handling_minutes (float)
origin_handling_cost (float)
destination_handling_cost (float)
pickup_travel_minutes (float)
delivery_travel_minutes (float)
truck_capacity (float)
barge_variable_cost_per_trip (float)
- route_id
Marine route identifier.
- Type:
- contractor_id / truck_type_id / barge_type_id
Responsible contractor, drayage truck type, and barge type.
- origin_port_id / destination_port_id / water_arc_id
Water-leg topology.
- volume_m3
Water-leg volume in cubic metres.
- Type:
- trips
Number of barge trips that carry
volume_m3.- Type:
- load_minutes / unload_minutes / sail_minutes / return_minutes
Barge service and sailing times per trip.
- origin_handling_minutes / destination_handling_minutes
Terminal handling time per trip at each port.
- origin_handling_cost / destination_handling_cost
Terminal handling cost per trip at each port.
- pickup_travel_minutes / delivery_travel_minutes
Loaded road drayage travel times used to place the synchronized road windows.
- truck_capacity
Drayage truck capacity in cubic metres.
- Type:
- barge_variable_cost_per_trip
Variable barge cost per trip.
- Type:
- barge_type_id: str
- barge_variable_cost_per_trip: float
- contractor_id: str
- delivery_travel_minutes: float
- destination_handling_cost: float
- destination_handling_minutes: float
- destination_port_id: str
- load_minutes: float
- origin_handling_cost: float
- origin_handling_minutes: float
- origin_port_id: str
- pickup_travel_minutes: float
- return_minutes: float
- route_id: str
- sail_minutes: float
- trips: int
- truck_capacity: float
- truck_type_id: str
- unload_minutes: float
- volume_m3: float
- water_arc_id: str
- fhops.hauling.marine.build_marine_schedule_problem(scenario, flow_result, day)[source]
Build the marine scheduling input from a Phase I result.
- Parameters:
scenario (Scenario) – Scenario with a
haulingsection.flow_result (FlowSolveResult) – Phase I result. The
barge_tripstable supplies the trip counts and water volumes.day (int) – Relative hauling-horizon day to schedule.
- Returns:
Nonewhen the day has no barge trips.- Return type:
MarineScheduleProblem | None
- Raises:
ValueError – When a barge trip references an unknown route, a port has no loader, or the contract lacks an enabled barge for the route.
- fhops.hauling.marine.schedule_barge_trips(problem)[source]
Schedule barge trips deterministically with port-loader synchronization.
The scheduler assigns each trip to the compatible barge that becomes available first, and to the compatible port loader that becomes available first at each end. It is a deterministic list scheduler, not an optimiser.
- Parameters:
problem (MarineScheduleProblem) – Normalised marine scheduling data.
- Returns:
One row per barge trip plus a barge-level summary.
- Return type:
BargeScheduleResult
- fhops.hauling.marine.validate_barge_schedule(problem, schedule)[source]
Validate a barge schedule against its problem.
Phase III simulated annealing for daily log-truck scheduling.
This module implements the solution approach of Ghotb (2023), Chapter 5: a permutation-based
representation of truck routes, a shortest-processing-time (SPT) schedule decoder, swap/reversion/
insertion neighbourhoods, and a geometric cooling schedule. It is the large-instance path; the
exact MIP in fhops.hauling.schedule remains the oracle for small days.
The decoder is deliberately built on top of fhops.hauling.schedule.simulate_plan(), so the
timing logic is shared with the exact-model verification oracle. It derives loader orders from
projected arrival times (SPT) and then recomputes the earliest schedule for the fixed routes and
loader orders.
- class fhops.hauling.heuristic.HaulingSAResult(status, objective, fixed_cost, variable_cost, overtime_cost, violation_cost, violation_minutes, schedule, truck_summary, loader_summary, plan, iterations, accepted, runtime_seconds, seed, history, barge_schedule=<factory>, barge_summary=<factory>, barge_objective=None, barge_fixed_cost=0.0, solver_error=None)[source]
Bases:
objectResult payload for a simulated-annealing run.
- Parameters:
status (str)
objective (float | None)
fixed_cost (float | None)
variable_cost (float | None)
overtime_cost (float | None)
violation_cost (float | None)
violation_minutes (float | None)
schedule (DataFrame)
truck_summary (DataFrame)
loader_summary (DataFrame)
plan (SchedulePlan | None)
iterations (int)
accepted (int)
runtime_seconds (float)
seed (int)
history (DataFrame)
barge_schedule (DataFrame)
barge_summary (DataFrame)
barge_objective (float | None)
barge_fixed_cost (float)
solver_error (str | None)
- status
feasiblewhen a schedule was evaluated;erroronly for an invalid problem.- Type:
- objective / fixed_cost / variable_cost / overtime_cost / violation_cost
Best-solution cost components.
- violation_minutes
Best-solution overtime violation.
- Type:
float | None
- schedule / truck_summary / loader_summary / plan
Best-solution artefacts.
- barge_schedule / barge_summary / barge_objective
Barge-cycle tables for a marine day; empty for road-only days.
- iterations / accepted / runtime_seconds / seed
Search provenance.
- history
Per-move objective trace.
- Type:
pandas.DataFrame
- accepted: int
- barge_fixed_cost: float
- barge_schedule: DataFrame
- barge_summary: DataFrame
- history: DataFrame
- iterations: int
- loader_summary: DataFrame
- plan: SchedulePlan | None
- runtime_seconds: float
- schedule: DataFrame
- seed: int
- status: str
- truck_summary: DataFrame
- class fhops.hauling.heuristic.SAConfig(initial_temperature=300.0, min_temperature_ratio=0.0005, cooling_rate=0.95, iterations_per_temperature=90, max_iterations=None, time_limit_seconds=None, seed=42, operator_weights=<factory>, overtime_violation_penalty_per_minute=None)[source]
Bases:
objectSimulated-annealing configuration.
- Parameters:
- initial_temperature
Starting temperature. The thesis Taguchi calibration selects 300.
- Type:
- min_temperature_ratio
Stop when
temperature <= initial_temperature * min_temperature_ratio. The thesis uses0.0005.- Type:
- cooling_rate
Geometric cooling factor. The thesis uses
0.95.- Type:
- iterations_per_temperature
Moves evaluated at each temperature. The thesis uses 90.
- Type:
- max_iterations
Optional hard cap on evaluated moves.
Noneuses the temperature schedule.- Type:
int | None
- time_limit_seconds
Optional wall-clock cap.
Nonedisables it.- Type:
float | None
- seed
RNG seed for reproducibility.
- Type:
- operator_weights
Relative weights for
swap,reversion, andinsertion. Must be non-negative and not all zero.
- overtime_violation_penalty_per_minute
Penalty per minute above the daily horizon.
Noneuses the scenario’s driver policy.- Type:
float | None
- cooling_rate: float
- initial_temperature: float
- iterations_per_temperature: int
- min_temperature_ratio: float
- seed: int
- class fhops.hauling.heuristic.SolutionEvaluation(objective, fixed_cost, variable_cost, overtime_cost, violation_cost, violation_minutes, schedule, truck_summary, loader_summary, plan)[source]
Bases:
objectCost and schedule evaluation of one route solution.
- Parameters:
- objective
Total cost including fixed, variable, overtime, and overtime-violation penalty.
- Type:
- fixed_cost / variable_cost / overtime_cost / violation_cost
Cost components.
- violation_minutes
Total minutes above
regular_minutes + max_overtime_minutes.- Type:
- schedule / truck_summary / loader_summary
DataFrames with the Phase II output schemas.
- plan
The decoded route and loader-order plan.
- Type:
fhops.hauling.schedule.SchedulePlan
- fixed_cost: float
- loader_summary: DataFrame
- objective: float
- overtime_cost: float
- plan: SchedulePlan
- schedule: DataFrame
- truck_summary: DataFrame
- variable_cost: float
- violation_cost: float
- violation_minutes: float
- fhops.hauling.heuristic.decode_routes(problem, routes)[source]
Decode a route solution into a loader assignment and SPT loader order.
- Parameters:
- Returns:
Loader assignments and pickup/delivery orders derived from projected arrival times.
- Return type:
SchedulePlan
- Raises:
ValueError – When a route references an unknown load, a load is missing, or a load appears more than once.
- fhops.hauling.heuristic.evaluate_routes(problem, routes, config=None)[source]
Evaluate a route solution with the SPT decoder and cost model.
- Parameters:
- Returns:
Cost components, schedule, truck/loader summaries, and the decoded plan.
- Return type:
SolutionEvaluation
- fhops.hauling.heuristic.initial_solution(problem, rng)[source]
Build a randomized feasible initial route solution.
Loads are visited in a stable order and assigned to the compatible truck with the fewest loads (ties broken randomly). This keeps every load assigned to a compatible truck and spreads work across the fleet before searching.
- fhops.hauling.heuristic.solve_sa(problem, config=None)[source]
Run the simulated-annealing scheduler.
- Parameters:
problem (ScheduleProblem) – Scheduling data.
config (SAConfig | None) – SA configuration.
Noneuses the thesis-derived defaults.
- Returns:
Best schedule, cost components, summaries, search provenance, and history.
- Return type:
HaulingSAResult
- fhops.hauling.heuristic.summarize_sa_result(result)[source]
Return a compact human-readable SA summary.
- Parameters:
result (HaulingSAResult)
- Return type:
Bridges from FHOPS operational/tactical plans to the hauling contract.
The hauling layer needs supply (volume by block and log sort) and demand (volume by yard and log sort). The operational and tactical models provide these in different shapes. This module turns them into a validated hauling scenario and reports any assumption or residual explicitly; it never silently invents a sort split or a missing arc.
- class fhops.hauling.integration.HaulingIntegrationResult(scenario, warnings=<factory>, residuals=<factory>)[source]
Bases:
objectA hauling scenario plus the assumptions and residuals behind it.
- scenario
Scenario with the updated
haulingsection.- Type:
Scenario
- residuals
One row per log sort with total supply, demand, and the residual.
- Type:
pd.DataFrame
- residuals: pd.DataFrame
- scenario: Scenario
- fhops.hauling.integration.residual_report(scenario)[source]
Return total supply, total demand, and the residual per log sort.
- Parameters:
scenario (Scenario) – Scenario with a hauling section.
- Returns:
Columns
log_sort_id,supply_m3,demand_m3, andresidual_m3. A positive residual means demand exceeds supply.- Return type:
pandas.DataFrame
- fhops.hauling.integration.scenario_from_operational_plan(scenario, assignments, *, sort_shares=None, production_column='production')[source]
Build hauling supply from an operational assignment table.
- Parameters:
scenario (Scenario) – Base scenario with a
haulingsection.assignments (pd.DataFrame) – DataFrame with
block_idandproductioncolumns. Ifproductionis absent,work_requiredis used for blocks that appear in the assignments.sort_shares (dict[str, float] | None) – Optional
log_sort_id -> fractionsplit. Fractions must be non-negative and sum to one. When omitted, each log sort receives an equal share of the block volume; this is recorded as a warning because the operational model does not carry product yields.production_column (str) – Name of the production column. Defaults to
production.
- Returns:
Updated scenario, warnings, and supply/demand residuals.
- Return type:
HaulingIntegrationResult
- Raises:
ValueError – When the scenario has no hauling section, the assignment table lacks
block_id, or the sort split is invalid.
- fhops.hauling.integration.scenario_from_tactical_plan(scenario, tactical, *, period_id, production=None)[source]
Build hauling supply and demand from a tactical-operational period.
- Parameters:
scenario (Scenario) – Base scenario with a
haulingsection.tactical (TacticalOperationalScenario) – Tactical-operational scenario.
period_id (str) – Period to translate.
production (pd.DataFrame | None) – Optional tactical production table with
block_id,product_id,period_id, andvolume_m3. When omitted, supply is left unchanged and a warning is recorded; the model does not guess harvest volume from potential yields.
- Returns:
Updated scenario, warnings, and residuals.
- Return type:
HaulingIntegrationResult
- Raises:
ValueError – When the scenario has no hauling section, no sort yard links the tactical facilities, or the period is not known.
Stable output writers and reports for log-hauling runs.
The filenames in this module are the artefact contract used by the CLI and, later, the web UI. The writers do not recompute a result; they serialise the DataFrames and objective payloads produced by the flow, scheduling, and SA solvers.
- fhops.hauling.reporting.build_markdown_report(run_dir)[source]
Build a compact Markdown report from a run directory.
- Parameters:
run_dir (Path) – Directory containing
haul_summary.jsonand the stable CSV/JSON artefacts.- Returns:
Markdown report. Missing artefacts are named rather than treated as zero.
- Return type:
- Raises:
FileNotFoundError – When
haul_summary.jsonis absent.
- fhops.hauling.reporting.write_flow_run(result, out_dir, *, scenario_name, start_day, run_type='plan')[source]
Write a Phase I result to the stable artefact names.
- Parameters:
- Returns:
Logical name -> written path.
- Return type:
- fhops.hauling.reporting.write_schedule_run(result, out_dir, *, scenario_name, day, run_type='schedule')[source]
Write a Phase II/III schedule to the stable artefact names.
- Parameters:
- Returns:
Logical name -> written path.
- Return type:
- fhops.hauling.reporting.write_solve_run(flow_result, schedule_result, out_dir, *, scenario_name, start_day, day)[source]
Write a combined Phase I + II/III run and merge the summaries.
- Parameters:
flow_result (FlowSolveResult) – Phase I result.
schedule_result (ScheduleSolveResult | HaulingSAResult) – Phase II or III result.
out_dir (Path) – Directory to create/write.
scenario_name (str) – Scenario label.
start_day (int) – Parent-scenario start day of the hauling horizon.
day (int) – Relative hauling-horizon day of the schedule.
- Returns:
Logical name -> written path, including both phases.
- Return type: