@@ -9,17 +9,21 @@ use indexmap::IndexSet;
99use itertools:: { Itertools , iproduct} ;
1010use petgraph:: Directed ;
1111use petgraph:: algo:: toposort;
12+ use petgraph:: dot:: Dot ;
1213use petgraph:: graph:: Graph ;
1314use std:: collections:: HashMap ;
1415use std:: fmt:: Display ;
16+ use std:: fs:: File ;
17+ use std:: io:: Write as IoWrite ;
18+ use std:: path:: Path ;
1519use strum:: IntoEnumIterator ;
1620
1721/// A graph of commodity flows for a given region and year
18- type CommoditiesGraph = Graph < GraphNode , GraphEdge , Directed > ;
22+ pub type CommoditiesGraph = Graph < GraphNode , GraphEdge , Directed > ;
1923
2024#[ derive( Eq , PartialEq , Clone , Hash ) ]
2125/// A node in the commodity graph
22- enum GraphNode {
26+ pub enum GraphNode {
2327 /// A node representing a commodity
2428 Commodity ( CommodityID ) ,
2529 /// A source node for processes that have no inputs
@@ -43,13 +47,22 @@ impl Display for GraphNode {
4347
4448#[ derive( Eq , PartialEq , Clone , Hash ) ]
4549/// An edge in the commodity graph
46- enum GraphEdge {
50+ pub enum GraphEdge {
4751 /// An edge representing a process
4852 Process ( ProcessID ) ,
4953 /// An edge representing a service demand
5054 Demand ,
5155}
5256
57+ impl Display for GraphEdge {
58+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
59+ match self {
60+ GraphEdge :: Process ( id) => write ! ( f, "{id}" ) ,
61+ GraphEdge :: Demand => write ! ( f, "DEMAND" ) ,
62+ }
63+ }
64+ }
65+
5366/// Creates a directed graph of commodity flows for a given region and year.
5467///
5568/// The graph contains nodes for all commodities that may be consumed/produced by processes in the
@@ -118,7 +131,7 @@ fn create_commodities_graph_for_region_year(
118131 graph
119132}
120133
121- /// Prepares a graph for validation with `validate_commodities_graph`.
134+ /// Prepares a graph for validation with [ `validate_commodities_graph`] .
122135///
123136/// It takes a base graph produced by `create_commodities_graph_for_region_year`, and modifies it to
124137/// account for process availabilities and commodity demands within the given time slice selection,
@@ -196,7 +209,7 @@ fn prepare_commodities_graph_for_validation(
196209/// The validation is only performed for commodities with the specified time slice level. For full
197210/// validation of all commodities in the model, we therefore need to run this function for all time
198211/// slice selections at all time slice levels. This is handled by
199- /// `build_and_validate_commodity_graphs_for_model` .
212+ /// [`validate_commodity_graphs_for_model`] .
200213fn validate_commodities_graph (
201214 graph : & CommoditiesGraph ,
202215 commodities : & CommodityMap ,
@@ -307,7 +320,26 @@ fn topo_sort_commodities(
307320 Ok ( order)
308321}
309322
310- /// Builds and validates commodity graphs for the entire model.
323+ /// Builds base commodity graphs for each region and year
324+ ///
325+ /// These do not take into account demand and process availability
326+ pub fn build_commodity_graphs_for_model (
327+ processes : & ProcessMap ,
328+ region_ids : & IndexSet < RegionID > ,
329+ years : & [ u32 ] ,
330+ ) -> Result < HashMap < ( RegionID , u32 ) , CommoditiesGraph > > {
331+ let commodity_graphs: HashMap < ( RegionID , u32 ) , CommoditiesGraph > =
332+ iproduct ! ( region_ids, years. iter( ) )
333+ . map ( |( region_id, year) | {
334+ let graph = create_commodities_graph_for_region_year ( processes, region_id, * year) ;
335+ ( ( region_id. clone ( ) , * year) , graph)
336+ } )
337+ . collect ( ) ;
338+
339+ Ok ( commodity_graphs)
340+ }
341+
342+ /// Validates commodity graphs for the entire model.
311343///
312344/// This function creates commodity flow graphs for each region/year combination in the model,
313345/// validates the graph structure against commodity type rules, and determines the optimal
@@ -337,23 +369,12 @@ fn topo_sort_commodities(
337369/// - Any commodity graph contains cycles
338370/// - Commodity type rules are violated (e.g., SVD commodities being consumed)
339371/// - Demand cannot be satisfied
340- pub fn build_and_validate_commodity_graphs_for_model (
372+ pub fn validate_commodity_graphs_for_model (
373+ commodity_graphs : & HashMap < ( RegionID , u32 ) , CommoditiesGraph > ,
341374 processes : & ProcessMap ,
342375 commodities : & CommodityMap ,
343- region_ids : & IndexSet < RegionID > ,
344- years : & [ u32 ] ,
345376 time_slice_info : & TimeSliceInfo ,
346377) -> Result < HashMap < ( RegionID , u32 ) , Vec < CommodityID > > > {
347- // Build base commodity graphs for each region and year
348- // These do not take into account demand and process availability
349- let commodity_graphs: HashMap < ( RegionID , u32 ) , CommoditiesGraph > =
350- iproduct ! ( region_ids, years. iter( ) )
351- . map ( |( region_id, year) | {
352- let graph = create_commodities_graph_for_region_year ( processes, region_id, * year) ;
353- ( ( region_id. clone ( ) , * year) , graph)
354- } )
355- . collect ( ) ;
356-
357378 // Determine commodity ordering for each region and year
358379 let commodity_order: HashMap < ( RegionID , u32 ) , Vec < CommodityID > > = commodity_graphs
359380 . iter ( )
@@ -366,7 +387,7 @@ pub fn build_and_validate_commodity_graphs_for_model(
366387 . try_collect ( ) ?;
367388
368389 // Validate graphs at all time slice levels (taking into account process availability and demand)
369- for ( ( region_id, year) , base_graph) in & commodity_graphs {
390+ for ( ( region_id, year) , base_graph) in commodity_graphs {
370391 for ts_level in TimeSliceLevel :: iter ( ) {
371392 for ts_selection in time_slice_info. iter_selections_at_level ( ts_level) {
372393 let graph = prepare_commodities_graph_for_validation (
@@ -392,6 +413,21 @@ pub fn build_and_validate_commodity_graphs_for_model(
392413 Ok ( commodity_order)
393414}
394415
416+ /// Saves commodity graphs to file
417+ ///
418+ /// The graphs are saved as DOT files to the specified output path
419+ pub fn save_commodity_graphs_for_model (
420+ commodity_graphs : & HashMap < ( RegionID , u32 ) , CommoditiesGraph > ,
421+ output_path : & Path ,
422+ ) -> Result < ( ) > {
423+ for ( ( region_id, year) , graph) in commodity_graphs {
424+ let dot = Dot :: new ( & graph) ;
425+ let mut file = File :: create ( output_path. join ( format ! ( "{region_id}_{year}.dot" ) ) ) ?;
426+ write ! ( file, "{dot}" ) ?;
427+ }
428+ Ok ( ( ) )
429+ }
430+
395431#[ cfg( test) ]
396432mod tests {
397433 use super :: * ;
0 commit comments