@@ -774,7 +774,6 @@ def graph_hourly_curtailment(tools):
774774@graph (
775775 "total_dispatch" ,
776776 title = "Total dispatched electricity" ,
777- is_long = True ,
778777)
779778def graph_total_dispatch (tools ):
780779 # ---------------------------------- #
@@ -829,6 +828,134 @@ def graph_total_dispatch(tools):
829828>> >> >> > b3590fdb (Redesign graphing API )
830829 )
831830
831+ tools .bar_label ()
832+
833+ @graph (
834+ "energy_balance" ,
835+ title = "Energy Balance For Every Month" ,
836+ supports_multi_scenario = True ,
837+ is_long = True
838+ )
839+ def energy_balance (tools ):
840+ # Get dispatch dataframe
841+ cols = ["timestamp" , "gen_tech" , "gen_energy_source" , "DispatchGen_MW" , "scenario_name" , "scenario_index" ,
842+ "Curtailment_MW" ]
843+ df = tools .get_dataframe ("dispatch.csv" , drop_scenario_info = False )[cols ]
844+ df = tools .transform .gen_type (df )
845+
846+ # Rename and add needed columns
847+ df ["Dispatch Limit" ] = df ["DispatchGen_MW" ] + df ["Curtailment_MW" ]
848+ df = df .drop ("Curtailment_MW" , axis = 1 )
849+ df = df .rename ({"DispatchGen_MW" : "Dispatch" }, axis = 1 )
850+ # Sum dispatch across all the projects of the same type and timepoint
851+ key_columns = ["timestamp" , "gen_type" , "scenario_name" , "scenario_index" ]
852+ df = df .groupby (key_columns , as_index = False ).sum ()
853+ df = df .melt (id_vars = key_columns , value_vars = ["Dispatch" , "Dispatch Limit" ], var_name = "Type" )
854+ df = df .rename ({"gen_type" : "Source" }, axis = 1 )
855+
856+ discharge = df [(df ["Source" ] == "Storage" ) & (df ["Type" ] == "Dispatch" )].drop (["Source" , "Type" ], axis = 1 ).rename (
857+ {"value" : "discharge" }, axis = 1 )
858+
859+ # Get load dataframe
860+ load = tools .get_dataframe ("load_balance.csv" , drop_scenario_info = False )
861+ load = load .drop ("normalized_energy_balance_duals_dollar_per_mwh" , axis = 1 )
862+
863+ # Sum load across all the load zones
864+ key_columns = ["timestamp" , "scenario_name" , "scenario_index" ]
865+ load = load .groupby (key_columns , as_index = False ).sum ()
866+
867+ # Subtract storage dispatch from generation and add it to the storage charge to get net flow
868+ load = load .merge (
869+ discharge ,
870+ how = "left" ,
871+ on = key_columns ,
872+ validate = "one_to_one"
873+ )
874+ load ["ZoneTotalCentralDispatch" ] -= load ["discharge" ]
875+ load ["StorageNetCharge" ] += load ["discharge" ]
876+ load = load .drop ("discharge" , axis = 1 )
877+
878+ # Rename and convert from wide to long format
879+ load = load .rename ({
880+ "ZoneTotalCentralDispatch" : "Total Generation (excl. storage discharge)" ,
881+ "TXPowerNet" : "Transmission Losses" ,
882+ "StorageNetCharge" : "Storage Net Flow" ,
883+ "zone_demand_mw" : "Demand" ,
884+ }, axis = 1 ).sort_index (axis = 1 )
885+ load = load .melt (id_vars = key_columns , var_name = "Source" )
886+ load ["Type" ] = "Dispatch"
887+
888+ # Merge dispatch contributions with load contributions
889+ df = pd .concat ([load , df ])
890+
891+ # Add the timestamp information and make period string to ensure it doesn't mess up the graphing
892+ df = tools .transform .timestamp (df ).astype ({"period" : str })
893+
894+ # Convert to TWh (incl. multiply by timepoint duration)
895+ df ["value" ] *= df ["tp_duration" ] / 1e6
896+
897+ FREQUENCY = "1W"
898+
899+ def groupby_time (df ):
900+ return df .groupby ([
901+ "scenario_name" ,
902+ "period" ,
903+ "Source" ,
904+ "Type" ,
905+ tools .pd .Grouper (key = "datetime" , freq = FREQUENCY , origin = "start" )
906+ ])["value" ]
907+
908+ df = groupby_time (df ).sum ().reset_index ()
909+
910+ # Get the state of charge data
911+ soc = tools .get_dataframe ("StateOfCharge.csv" , dtype = {"STORAGE_GEN_TPS_1" : str }, drop_scenario_info = False )
912+ soc = soc .rename ({"STORAGE_GEN_TPS_2" : "timepoint" , "StateOfCharge" : "value" }, axis = 1 )
913+ # Sum over all the projects that are in the same scenario with the same timepoint
914+ soc = soc .groupby (["timepoint" , "scenario_name" ], as_index = False ).sum ()
915+ soc ["Source" ] = "State Of Charge"
916+ soc ["value" ] /= 1e6 # Convert to TWh
917+
918+ # Group by time
919+ soc = tools .transform .timestamp (soc , use_timepoint = True , key_col = "timepoint" ).astype ({"period" : str })
920+ soc ["Type" ] = "Dispatch"
921+ soc = groupby_time (soc ).mean ().reset_index ()
922+
923+ # Add state of charge to dataframe
924+ df = pd .concat ([df , soc ])
925+ # Add column for day since that's what we really care about
926+ df ["day" ] = df ["datetime" ].dt .dayofyear
927+
928+ # Plot
929+ # Get the colors for the lines
930+ colors = tools .get_colors ()
931+ colors .update ({
932+ "Transmission Losses" : "brown" ,
933+ "Storage Net Flow" : "cadetblue" ,
934+ "Demand" : "black" ,
935+ "Total Generation (excl. storage discharge)" : "black" ,
936+ "State Of Charge" : "green"
937+ })
938+
939+ # plot
940+ num_periods = df ["period" ].nunique ()
941+ pn = tools .pn
942+ plot = pn .ggplot (df ) + \
943+ pn .geom_line (pn .aes (x = "day" , y = "value" , color = "Source" , linetype = "Type" )) + \
944+ pn .facet_grid ("period ~ scenario_name" ) + \
945+ pn .labs (y = "Contribution to Energy Balance (TWh)" ) + \
946+ pn .scales .scale_color_manual (values = colors , aesthetics = "color" , na_value = colors ["Other" ]) + \
947+ pn .scales .scale_x_continuous (
948+ name = "Month" ,
949+ labels = ["J" , "F" , "M" , "A" , "M" , "J" , "J" , "A" , "S" , "O" , "N" , "D" ],
950+ breaks = (15 , 46 , 76 , 106 , 137 , 167 , 198 , 228 , 259 , 289 , 319 , 350 ),
951+ limits = (0 , 366 )) + \
952+ pn .scales .scale_linetype_manual (
953+ values = {"Dispatch Limit" : "dotted" , "Dispatch" : "solid" }
954+ ) + \
955+ pn .theme (
956+ figure_size = (pn .options .figure_size [0 ] * tools .num_scenarios , pn .options .figure_size [1 ] * num_periods ))
957+
958+ tools .save_figure (plot .draw ())
832959
833960@graph (
834961 "curtailment_per_period" ,
0 commit comments