TM-PAM Connector Documentation¶
Purpose¶
The TM_PAM_connector bridges the Trade Module (TM) and Plant Agent Model (PAM) by translating trade optimization results into operational parameters for individual furnace groups. It propagates costs through supply chains and updates furnace group attributes (utilization rates, bill of materials, emissions) based on actual trade flows.
Location: src/steelo/domain/trade_modelling/TM_PAM_connector.py
Role in Simulation¶
The Trade Module solves an LP optimization to determine:
Which furnace groups should produce how much
Where products should be shipped
Optimal allocation of feedstocks (scrap, DRI, iron ore, etc.)
The TM-PAM Connector then:
Converts optimization results → NetworkX graph representing the supply chain
Propagates costs from raw materials through processing stages to final products
Updates furnace groups with their allocated production volumes, material costs, and emissions
When it runs: After every Trade Module optimization, before PAM makes strategic decisions.
Key Concepts¶
1. Supply Chain as a Directed Graph¶
The connector models the entire steel production supply chain as a NetworkX MultiDiGraph:
[Iron Ore] ──→ [BF Furnace] ──→ [BOF Furnace] ──→ [Demand]
[Scrap] ──────────→ [EAF Furnace] ──→ [Demand]
Nodes: Process centers (furnace groups, supply sources, demand sinks)
Edges: Material flows with volumes, costs, and efficiencies
2. Cost Propagation¶
Costs accumulate as materials move through the supply chain:
Raw Material Cost
+ Transport Cost (from supplier to furnace)
+ Processing Energy Cost (electricity, gas, etc.)
↓
Intermediate Product Cost
+ Transport Cost (from intermediate to final furnace)
+ Processing Energy Cost
↓
Final Product Cost
Algorithm: Breadth-first traversal starting from raw material sources (iron ore, scrap), accumulating costs at each processing stage.
3. Allocations vs Volumes¶
Volume: Amount of material shipped/produced (output quantity)
Allocation: Amount of material required as input (accounts for process inefficiency)
Formula:
Allocation = Volume / Process Efficiency
Example: If EAF has 95% yield, producing 100t steel requires 100/0.95 = 105.3t of scrap input.
Main Workflow¶
Stage 1: Graph Construction¶
Method: create_graph(solved_trade_allocations)
Extracts all non-zero allocations from LP solution:
(from_pc, to_pc, commodity) → volumeCreates nodes for each process center (furnace group)
Creates edges for each material flow, storing:
volume: Shipped quantitytransport_cost: Cost to move material between locationsprocessing_energy_cost: Energy cost at source (electricity, gas, etc.)process: Process identifier (e.g., “eaf_scrap_electricity”)process_efficiency: Yield/conversion rateoutput: Primary product of this process
Result: self.G populated with nodes and edges representing the trade network.
Stage 2: Input Allocation Calculation¶
Method: calculate_allocations_for_graph()
Converts output volumes to input requirements using process efficiencies:
allocation = edge['volume'] / edge['process_efficiency']
For each edge, updates the allocations attribute with the computed input requirement.
Result: Each edge now has both volume (output) and allocations (input) attributes.
Stage 3: Cost Propagation¶
Method: propage_cost_forward_by_layers_and_normalize()
Propagates costs forward through the graph using breadth-first search:
Identify roots: Nodes with no incoming edges (raw material sources)
BFS traversal: Process nodes layer by layer
For each edge (u → v):
Get base cost from source node
u(raw material cost or accumulated upstream cost)Add the producer’s own per-unit carbon (when
uis a producing furnace, see below)Add processing energy cost at destination
vAdd transport cost for this shipment
Add tariff cost for this shipment (looked up via
get_tariff_cost(from_iso, to_iso, commodity))Multiply by volume shipped
Accumulate total cost at destination node
v
Normalize: Divide total cost by total outgoing volume to get per-unit cost
Result: Each node has:
product_cost: Total accumulated cost by commodityunit_cost: Cost per tonne by commodityallocations: Volume and cost breakdown by commodity
Producer carbon propagation¶
Each producing furnace’s carbon — ProcessCenter.production_cost, equal to the furnace group’s carbon_cost_per_unit — is stamped onto the graph node as own_unit_cost during create_graph(). During BFS, when an edge leaves a producing node, own_unit_cost is added to per_unit_base so it embeds onto outgoing flows. The addition is guarded by G.in_degree(u) > 0 so root supplier nodes are skipped — their cost already enters via base_cost and would otherwise double-count.
Invariant: own carbon flows out, never inward. The producer’s BOM is built from its incoming allocations only, so it never picks up its own self-carbon. Carbon enters the producer’s own economics later via unit_production_cost = unit_total_opex + carbon_cost_per_unit; embedding it on incoming edges as well would double-count.
Tariff propagation¶
Tariff taxes from Allocations.tariff_taxes (carried over from the LP solution) are stored on the connector and looked up per edge via get_tariff_cost(from_iso, to_iso, commodity). The lookup checks the exact (from, to, commodity) key first, then the three wildcard variants (*, to, comm) / (from, *, comm) / (from, to, *), summing every match — mirroring the LP’s return_potential_tariff_keys logic. The resulting per-tonne tariff is added to each edge’s material_tariff_transportation_cost and propagates downstream alongside material and transport costs.
Stage 4: Update Furnace Groups¶
After cost propagation, the connector updates furnace group attributes:
4a. Update Utilization Rates¶
Method: update_furnace_group_utilisation(furnace_groups)
allocated_volumes = sum(outgoing_edge_volumes)
utilization_rate = allocated_volumes / capacity
Sets fg.utilization_rate based on actual production assigned by Trade Module.
4b. Update Bill of Materials¶
Method: update_bill_of_materials(furnace_groups)
For each furnace group, extracts from the graph:
Materials (from node allocations):
{
"scrap": {
"demand": 105.3, # input volume (tonnes)
"total_cost": 31590, # USD — includes current step's processing energy
"unit_cost": 316, # USD per tonne of OUTPUT (total_cost / product_volume)
"total_material_cost": 29500, # USD — excludes current step's processing energy
"unit_material_cost": 295, # USD per tonne of output
"product_volume": 100.0, # output volume used for normalisation (tonnes)
},
"dri": { ... }
}
Two cost pairs are stored per commodity because downstream consumers need different slices:
total_cost/unit_costinclude the processing energy consumed at this furnace group (i.e. everything needed to produce the FG’s output, including its own conversion step).total_material_cost/unit_material_costinclude upstream material costs, transport, and tariffs, but exclude this FG’s own processing energy.
calculate_variable_opex consumes total_material_cost together with energy (which carries this FG’s processing energy separately) to avoid double-counting. The distinction originates inside the cost-propagation step: MaterialCost on graph nodes tracks the cost of inputs to the FG without its own energy, while Cost adds that energy on top.
Energy (from edge processing costs):
{
"electricity": {
"demand": 100.0, # production volume
"total_cost": 6000, # USD
"unit_cost": 60 # USD/t
}
}
Sets fg.bill_of_materials = {"materials": {...}, "energy": {...}}
4c. Update Emissions¶
Method: update_furnace_group_emissions(furnace_groups)
Calls fg.set_emissions_based_on_allocated_volumes() for each furnace group if it has a valid BOM. This calculates emissions from material consumption and technology emission factors.
Sets fg.emissions with structure:
{
"plant_boundary": {"scope_1": 500, "scope_2": 200},
"supply_chain": {"scope_3": 1000}
}
Key Methods Reference¶
Initialization¶
__init__(dynamic_feedstocks_classes, plants, transport_kpis)
Creates connector and populates lookup tables:
flat_feedstocks_dict: O(1) feedstock lookup by namefeedstock_energy_requirements: Energy requirements per feedstockprocessing_energy_cost: Energy costs by furnace group and commoditychosen_reductant: Reductant choice for each furnacetransport_costs: Transport cost lookup(from_iso, to_iso, commodity) → costiron_furnaces,steel_furnaces: Lists of furnace group IDs by product type
Graph Construction & Cost Propagation¶
set_up_network_and_propagate_costs(solved_trade_allocations)
High-level orchestration method that calls in sequence:
create_graph()- Build NetworkX graph from trade resultscalculate_allocations_for_graph()- Convert volumes to input requirementsvalidate_edge_attributes()- Check graph structurepropage_cost_forward_by_layers_and_normalize()- Propagate costs through network
Furnace Group Updates¶
update_furnace_group_utilisation(furnace_groups)
Sets fg.utilization_rate = allocated_volumes / capacity
update_bill_of_materials(furnace_groups)
Sets fg.bill_of_materials with material and energy costs from graph
update_furnace_group_emissions(furnace_groups)
Calculates and sets fg.emissions based on BOM and emission factors
Utility Methods¶
get_transport_cost(from_iso, to_iso, commodity)
Returns transport cost between two countries for a commodity (USD/t)
extract_transportation_costs(furnace_groups)
Returns detailed transport cost breakdown for each furnace group’s incoming shipments
Integration Points¶
Called By¶
update_furnace_utilization_rates handler (in handlers.py:241):
tmpc = TM_PAM_connector(
dynamic_feedstocks_classes=env.dynamic_feedstocks,
plants=uow.plants,
transport_kpis=env.transport_kpis,
)
tmpc.set_up_network_and_propagate_costs(solved_trade_allocations=trade_allocations)
tmpc.update_furnace_group_utilisation(fgs)
tmpc.update_bill_of_materials(fgs)
tmpc.update_furnace_group_emissions(fgs)
env.allocation_and_transportation_costs = tmpc.extract_transportation_costs(fgs)
Dependencies¶
Inputs:
Allocationsobject from Trade Module LP solverPlantInMemoryRepositoryfor accessing all plants and furnace groupsdynamic_feedstocksdict mapping technologies to feedstock optionsTransportKPIlist with transportation costs
Updates:
FurnaceGroup.utilization_rateFurnaceGroup.allocated_volumesFurnaceGroup.bill_of_materialsFurnaceGroup.emissions
Used By:
PAM decision-making (uses updated costs and utilization)
Balance sheet calculations (uses updated BOM costs)
Emissions reporting (uses updated emissions)
Data Flow Through Connector¶
Trade Module LP Solver
↓
Allocations: (from_pc, to_pc, commodity) → volume
↓
TM_PAM_connector.set_up_network_and_propagate_costs()
↓
1. create_graph()
→ NetworkX MultiDiGraph with nodes (furnaces) and edges (flows)
↓
2. calculate_allocations_for_graph()
→ Add input allocations (volume / efficiency) to edges
↓
3. propage_cost_forward_by_layers_and_normalize()
→ BFS cost accumulation from raw materials to final products
→ Each node gets: product_cost, unit_cost, allocations
↓
4. update_furnace_group_utilisation()
→ fg.utilization_rate = sum(outgoing_volumes) / capacity
↓
5. update_bill_of_materials()
→ fg.bill_of_materials = {materials: {...}, energy: {...}}
↓
6. update_furnace_group_emissions()
→ fg.emissions = {boundary: {scope: value}}
↓
PAM uses updated FurnaceGroup attributes for decision-making
Known Limitations¶
Assumes DAG Structure: Cost propagation assumes no cycles in the supply chain graph. This is determined by the structure of the dynamic bill of materials and legal process connectors given to the Trade Module.
No Transport Mode Differentiation: All transport costs treated equally - no distinction between rail, sea, truck, etc.
Zero-Volume Edge Handling: Edges with volume < LP_TOLERANCE are skipped, which may lose some small flows.
Summary¶
The TM-PAM Connector serves as the critical bridge between optimization and simulation:
Input: Trade optimization results (allocations)
Process: Graph-based cost propagation through supply chains
Output: Updated furnace group parameters (utilization, BOM, emissions)
Purpose: Ensures PAM makes decisions based on actual trade-optimized costs and production levels
This two-way integration enables:
Trade → PAM: Operational parameters reflect optimized production schedules
PAM → Trade: Strategic decisions (technology switches, expansions) feed back into future trade optimizations