99
1010
1111class EndUseBalance :
12+ HVAC_MODE = ("Zone Predicted Sensible Load to Setpoint Heat Transfer Rate" ,)
1213 HVAC_INPUT_SENSIBLE = ( # not multiplied by zone or group multipliers
1314 "Zone Air Heat Balance System Air Transfer Rate" ,
1415 "Zone Air Heat Balance System Convective Heat Gain Rate" ,
@@ -34,52 +35,36 @@ class EndUseBalance:
3435 "Zone Hot Water Equipment Convective Heating Energy" ,
3536 "Zone Other Equipment Convective Heating Energy" ,
3637 )
37- PEOPLE_GAIN = ("Zone People Sensible Heating Energy" ,) # checked, Todo: +latent
38+ PEOPLE_GAIN = ("Zone People Total Heating Energy" ,) # checked
3839 SOLAR_GAIN = ("Zone Windows Total Transmitted Solar Radiation Energy" ,) # checked
3940 INFIL_GAIN = (
40- "Zone Infiltration Sensible Heat Gain Energy" , # checked
41- # "Zone Infiltration Latent Heat Gain Energy",
42- "AFN Zone Infiltration Sensible Heat Gain Energy" ,
43- # "AFN Zone Infiltration Latent Heat Gain Energy",
41+ "Zone Infiltration Total Heat Gain Energy" , # checked
42+ "AFN Zone Infiltration Total Heat Gain Energy" ,
4443 )
4544 INFIL_LOSS = (
46- "Zone Infiltration Sensible Heat Loss Energy" , # checked
47- # "Zone Infiltration Latent Heat Loss Energy",
48- "AFN Zone Infiltration Sensible Heat Loss Energy" ,
49- # "AFN Zone Infiltration Latent Heat Loss Energy",
45+ "Zone Infiltration Total Heat Loss Energy" , # checked
46+ "AFN Zone Infiltration Total Heat Loss Energy" ,
5047 )
51- VENTILATION_LOSS = ("Zone Air System Sensible Heating Energy" ,)
52- VENTILATION_GAIN = ("Zone Air System Sensible Cooling Energy" ,)
48+ VENTILATION_LOSS = ("Zone Air System Total Heating Energy" ,)
49+ VENTILATION_GAIN = ("Zone Air System Total Cooling Energy" ,)
5350 NAT_VENT_GAIN = (
54- # "Zone Ventilation Total Heat Gain Energy",
55- "Zone Ventilation Sensible Heat Gain Energy" ,
56- # "Zone Ventilation Latent Heat Gain Energy",
57- "AFN Zone Ventilation Sensible Heat Gain Energy" ,
58- # "AFN Zone Ventilation Latent Heat Gain Energy",
51+ "Zone Ventilation Total Heat Gain Energy" ,
52+ "AFN Zone Ventilation Total Heat Gain Energy" ,
5953 )
6054 NAT_VENT_LOSS = (
61- # "Zone Ventilation Total Heat Loss Energy",
62- "Zone Ventilation Sensible Heat Loss Energy" ,
63- # "Zone Ventilation Latent Heat Loss Energy",
64- "AFN Zone Ventilation Sensible Heat Loss Energy" ,
65- # "AFN Zone Ventilation Latent Heat Loss Energy",
66- )
67- MECHANICAL_VENT_LOSS = (
68- "Zone Mechanical Ventilation No Load Heat Removal Energy" ,
69- "Zone Mechanical Ventilation Heating Load Increase Energy" ,
70- "Zone Mechanical Ventilation Cooling Load Decrease Energy" ,
71- )
72- MECHANICAL_VENT_GAIN = (
73- "Zone Mechanical Ventilation No Load Heat Addition Energy" ,
74- "Zone Mechanical Ventilation Heating Load Decrease Energy" ,
75- "Zone Mechanical Ventilation Cooling Load Increase Energy" ,
55+ "Zone Ventilation Total Heat Loss Energy" ,
56+ "AFN Zone Ventilation Total Heat Loss Energy" ,
7657 )
7758 OPAQUE_ENERGY_FLOW = ("Surface Outside Face Conduction Heat Transfer Energy" ,)
7859 OPAQUE_ENERGY_STORAGE = ("Surface Heat Storage Energy" ,)
7960 WINDOW_LOSS = ("Zone Windows Total Heat Loss Energy" ,) # checked
8061 WINDOW_GAIN = ("Zone Windows Total Heat Gain Energy" ,) # checked
81- HEAT_RECOVERY_LOSS = ("Heat Exchanger Total Cooling Energy" ,)
82- HEAT_RECOVERY_GAIN = ("Heat Exchanger Total Heating Energy" ,)
62+ HRV_LOSS = ("Heat Exchanger Total Cooling Energy" ,)
63+ HRV_GAIN = ("Heat Exchanger Total Heating Energy" ,)
64+ AIR_SYSTEM = (
65+ "Air System Heating Coil Total Heating Energy" ,
66+ "Air System Cooling Coil Total Cooling Energy" ,
67+ )
8368
8469 def __init__ (
8570 self ,
@@ -100,6 +85,7 @@ def __init__(
10085 opaque_storage ,
10186 window_flow ,
10287 heat_recovery ,
88+ air_system ,
10389 is_cooling ,
10490 is_heating ,
10591 units = "J" ,
@@ -122,6 +108,7 @@ def __init__(
122108 self .opaque_storage = opaque_storage
123109 self .window_flow = window_flow
124110 self .heat_recovery = heat_recovery
111+ self .air_system = air_system
125112 self .units = units
126113 self .use_all_solar = use_all_solar
127114 self .is_cooling = is_cooling
@@ -168,15 +155,15 @@ def from_sql_file(
168155 axis = 1 ,
169156 verify_integrity = True ,
170157 )
171-
172- rolling_sign = cls .get_rolling_sign_change (_hvac_input )
158+ mode = sql . timeseries_by_name ( cls . HVAC_MODE ) # positive = Heating
159+ rolling_sign = cls .get_rolling_sign_change (mode ). fillna ( 0 )
173160
174161 # Create both heating and cooling masks
175- is_heating = rolling_sign > 0
176- is_cooling = rolling_sign < 0
162+ is_heating = rolling_sign . droplevel ([ "IndexGroup" , "Name" ], axis = 1 ) == 1
163+ is_cooling = rolling_sign . droplevel ([ "IndexGroup" , "Name" ], axis = 1 ) == - 1
177164
178- heating = _hvac_input [ is_heating ]. fillna ( 0 )
179- cooling = _hvac_input [ is_cooling ]. fillna ( 0 )
165+ heating = _hvac_input . mul ( is_heating , level = "KeyValue" , axis = 1 )
166+ cooling = _hvac_input . mul ( is_cooling , level = "KeyValue" , axis = 1 )
180167
181168 lighting = sql .timeseries_by_name (cls .LIGHTING ).to_units (units )
182169 zone_multipliers = sql .zone_info .set_index ("ZoneName" )["Multiplier" ].rename (
@@ -204,23 +191,10 @@ def from_sql_file(
204191 nat_vent_gain = cls .apply_multipliers (nat_vent_gain , zone_multipliers )
205192 nat_vent_loss = sql .timeseries_by_name (cls .NAT_VENT_LOSS ).to_units (units )
206193 nat_vent_loss = cls .apply_multipliers (nat_vent_loss , zone_multipliers )
207- mech_vent_gain = sql .timeseries_by_name (cls .MECHANICAL_VENT_GAIN ).to_units (
208- units
209- )
210- mech_vent_gain = cls .apply_multipliers (mech_vent_gain , zone_multipliers )
211- mech_vent_loss = sql .timeseries_by_name (cls .MECHANICAL_VENT_LOSS ).to_units (
212- units
213- )
214- mech_vent_loss = cls .apply_multipliers (mech_vent_loss , zone_multipliers )
215- heat_recovery_loss = sql .timeseries_by_name (cls .HEAT_RECOVERY_LOSS ).to_units (
216- units
217- )
218- heat_recovery_gain = sql .timeseries_by_name (cls .HEAT_RECOVERY_GAIN ).to_units (
219- units
220- )
221- heat_recovery = cls .subtract_loss_from_gain (
222- heat_recovery_gain , heat_recovery_loss , level = "KeyValue"
223- )
194+ hrv_loss = sql .timeseries_by_name (cls .HRV_LOSS ).to_units (units )
195+ hrv_gain = sql .timeseries_by_name (cls .HRV_GAIN ).to_units (units )
196+ hrv = cls .subtract_loss_from_gain (hrv_gain , hrv_loss , level = "KeyValue" )
197+ air_system = sql .timeseries_by_name (cls .AIR_SYSTEM ).to_units (units )
224198
225199 # subtract losses from gains
226200 infiltration = None
@@ -230,10 +204,6 @@ def from_sql_file(
230204 infiltration = cls .subtract_loss_from_gain (
231205 infil_gain , infil_loss , level = "Name"
232206 )
233- if not any ((vent_gain .empty , vent_loss .empty , cooling .empty , heating .empty )):
234- mech_vent = cls .subtract_loss_from_gain (
235- mech_vent_gain , mech_vent_loss , level = "Name"
236- )
237207 if nat_vent_gain .shape == nat_vent_loss .shape :
238208 nat_vent = cls .subtract_loss_from_gain (
239209 nat_vent_gain , nat_vent_loss , level = "Name"
@@ -296,7 +266,8 @@ def from_sql_file(
296266 opaque_flow ,
297267 opaque_storage ,
298268 window_flow ,
299- heat_recovery ,
269+ hrv ,
270+ air_system ,
300271 is_cooling ,
301272 is_heating ,
302273 units ,
@@ -492,9 +463,7 @@ def subtract_vent_from_system(cls, system, vent, level="Key_Name"):
492463 index = system .index ,
493464 )
494465
495- def separate_gains_and_losses (
496- self , component , level = "Key_Name" , stack_on_level = None
497- ) -> EnergyDataFrame :
466+ def separate_gains_and_losses (self , component , level = "Key_Name" ) -> EnergyDataFrame :
498467 """Separate gains from losses when cooling and heating occurs for the component.
499468
500469 Args:
@@ -509,38 +478,31 @@ def separate_gains_and_losses(
509478 ), f"{ component } is not a valid attribute of EndUseBalance."
510479 component_df = getattr (self , component )
511480 assert not component_df .empty , "Expected a component that is not empty."
512- if isinstance (level , str ):
513- level = [level ]
514481 print (component )
515482
516- # mask when cooling occurs in zone (negative values)
517- mask = (self .is_cooling .stack ("KeyValue" ) == True ).any (axis = 1 )
518-
519- # get the dataframe using the attribute name, summarize by `level` and stack so that a Series is returned.
520- stacked = getattr (self , component ).sum (level = level , axis = 1 ).stack (level [0 ])
483+ c_df = getattr (self , component )
521484
522- # concatenate the masked values with keys to easily create a MultiIndex when unstacking
485+ # concatenate the Periods
523486 inter = pd .concat (
524487 [
525- stacked [ mask ]. reindex ( stacked . index ),
526- stacked [ ~ mask ]. reindex ( stacked . index ),
488+ c_df . mul ( self . is_cooling . rename_axis ( level , axis = 1 ), level = level ),
489+ c_df . mul ( self . is_heating . rename_axis ( level , axis = 1 ), level = level ),
527490 ],
528491 keys = ["Cooling Periods" , "Heating Periods" ],
529- names = ["Period" ]
530- + stacked .index .names , # prepend the new key name to the existing index names.
492+ names = ["Period" ],
531493 )
532494
533495 # mask when values are positive (gain)
534496 positive_mask = inter >= 0
535497
536- # concatenate the masked values with keys to easily create a MultiIndex when unstacking
498+ # concatenate the Gain/Loss
537499 final = pd .concat (
538500 [
539501 inter [positive_mask ].reindex (inter .index ),
540502 inter [~ positive_mask ].reindex (inter .index ),
541503 ],
542504 keys = ["Heat Gain" , "Heat Loss" ],
543- names = ["Gain/Loss" ] + inter . index . names ,
505+ names = ["Gain/Loss" ],
544506 ).unstack (["Period" , "Gain/Loss" ])
545507 final .sort_index (axis = 1 , inplace = True )
546508 return final
@@ -560,40 +522,24 @@ def to_df(self, separate_gains_and_losses=False, level="KeyValue"):
560522 "infiltration" ,
561523 "window_energy_flow" ,
562524 "nat_vent" ,
563- "mech_vent" ,
564525 ]:
565526 if not getattr (self , component ).empty :
566527 summary_by_component [component ] = (
567528 self .separate_gains_and_losses (
568529 component ,
569530 level = level ,
570531 )
571- .unstack (level )
572- .reorder_levels ([ level , "Period" , "Gain/Loss" ], axis = 1 )
532+ .groupby (level = [ "KeyValue" , "Period" , "Gain/Loss" ], axis = 1 )
533+ .sum ( )
573534 .sort_index (axis = 1 )
574535 )
575- for (surface_type ), data in (
576- self .separate_gains_and_losses (
577- "opaque_flow" , ["Zone_Name" , "Surface_Type" ]
578- )
579- .unstack ("Zone_Name" )
580- .groupby (level = ["Surface_Type" ], axis = 1 )
581- ):
536+ for (surface_type ), data in self .separate_gains_and_losses (
537+ "opaque_flow" , level = "Zone_Name"
538+ ).groupby (level = ["Surface_Type" ], axis = 1 ):
582539 summary_by_component [surface_type ] = data .sum (
583540 level = ["Zone_Name" , "Period" , "Gain/Loss" ], axis = 1
584541 ).sort_index (axis = 1 )
585542
586- # for (surface_type), data in (
587- # self.separate_gains_and_losses(
588- # "opaque_storage", ["Zone_Name", "Surface_Type"]
589- # )
590- # .unstack("Zone_Name")
591- # .groupby(level=["Surface_Type"], axis=1)
592- # ):
593- # summary_by_component[surface_type + " Storage"] = data.sum(
594- # level=["Zone_Name", "Period", "Gain/Loss"], axis=1
595- # ).sort_index(axis=1)
596-
597543 else :
598544 summary_by_component = {}
599545 for component in [
@@ -606,20 +552,40 @@ def to_df(self, separate_gains_and_losses=False, level="KeyValue"):
606552 "infiltration" ,
607553 "window_energy_flow" ,
608554 "nat_vent" ,
609- "mech_vent" ,
610555 ]:
611556 component_df = getattr (self , component )
612557 if not component_df .empty :
613558 summary_by_component [component ] = component_df .sum (
614559 level = level , axis = 1
615560 ).sort_index (axis = 1 )
616- for (zone_name , surface_type ), data in self .face_energy_flow .groupby (
561+ for (zone_name , surface_type ), data in self .opaque_flow .groupby (
617562 level = ["Zone_Name" , "Surface_Type" ], axis = 1
618563 ):
619564 summary_by_component [surface_type ] = data .sum (
620565 level = "Zone_Name" , axis = 1
621566 ).sort_index (axis = 1 )
622567 levels = ["Component" , "Zone_Name" ]
568+
569+ # Add contribution of heating/cooling outside air, if any
570+ if not self .air_system .empty :
571+ summary_by_component ["air_system_heating" ] = (
572+ self .air_system .xs (
573+ "Air System Heating Coil Total Heating Energy" , level = "Name" , axis = 1
574+ )
575+ .assign (** {"Period" : "Heating Periods" , "Gain/Loss" : "Heat Loss" })
576+ .set_index (["Period" , "Gain/Loss" ], append = True )
577+ .unstack (["Period" , "Gain/Loss" ])
578+ .droplevel ("IndexGroup" , axis = 1 )
579+ )
580+ summary_by_component ["air_system_cooling" ] = (
581+ self .air_system .xs (
582+ "Air System Cooling Coil Total Cooling Energy" , level = "Name" , axis = 1
583+ )
584+ .assign (** {"Period" : "Cooling Periods" , "Gain/Loss" : "Heat Gain" })
585+ .set_index (["Period" , "Gain/Loss" ], append = True )
586+ .unstack (["Period" , "Gain/Loss" ])
587+ .droplevel ("IndexGroup" , axis = 1 )
588+ )
623589 return pd .concat (
624590 summary_by_component , axis = 1 , verify_integrity = True , names = levels
625591 )
@@ -693,6 +659,9 @@ def to_sankey(self, path_or_buf):
693659 "interior_equipment" : "Equipment" ,
694660 "window_energy_flow" : "Windows" ,
695661 "Wall" : "Walls" ,
662+ "air_system_heating" : "OA Heating" ,
663+ "air_system_cooling" : "OA Cooling" ,
664+ "cooling" : "Cooling" ,
696665 },
697666 inplace = True ,
698667 )
@@ -752,9 +721,9 @@ def to_sankey(self, path_or_buf):
752721 )
753722
754723 system_input = (
755- system_input
756- # .replace({0: np.NaN} )
757- .dropna (how = "all" ). dropna ( how = "all" , axis = 1 )
724+ system_input . replace ({ 0 : np . NaN })
725+ . dropna ( how = "all" )
726+ .dropna (how = "all" , axis = 1 )
758727 )
759728 system_input .rename_axis ("source" , axis = 1 , inplace = True )
760729 system_input .rename_axis ("target" , axis = 0 , inplace = True )
@@ -789,21 +758,41 @@ def to_sankey(self, path_or_buf):
789758 link_cooling_system_to_gains ,
790759 ) = self ._sankey_cooling (cooling_load , load_type = "cooling" )
791760
792- return (
793- pd .DataFrame (
794- system_input_data
795- + link_heating_system_to_gains
796- + heating_energy_to_heating_system
797- + heating_load_source_data
798- + heating_load_target_data
799- + cooling_energy_to_heating_system
800- + cooling_load_source_data
801- + cooling_load_target_data
802- + link_cooling_system_to_gains
803- )
804- # .div(floor_area)
805- .to_csv (path_or_buf , index = False )
761+ flows = pd .DataFrame (
762+ system_input_data
763+ + heating_energy_to_heating_system
764+ + heating_load_source_data
765+ + heating_load_target_data
766+ + cooling_energy_to_heating_system
767+ + cooling_load_source_data
768+ + cooling_load_target_data
769+ )
770+ # Fix the HVAC difference
771+ diff = (
772+ flows .loc [flows .source == "Heating Load" , "value" ].sum ()
773+ - flows .loc [flows .target == "Heating Load" , "value" ].sum ()
774+ )
775+ flows .loc [flows .source == "Heating System" , "value" ] = (
776+ flows .loc [flows .source == "Heating System" , "value" ] + diff
777+ )
778+
779+ diff = (
780+ flows .loc [flows .source == "Cooling Load" , "value" ].sum ()
781+ - flows .loc [flows .target == "Cooling Load" , "value" ].sum ()
782+ )
783+ flows .loc [flows .source == "Cooling System" , "value" ] = (
784+ flows .loc [flows .source == "Cooling System" , "value" ] + diff
785+ )
786+
787+ # fix names
788+ flows .replace ({"OA Heating Heat Losses" : "OA Heating" }, inplace = True )
789+
790+ # TO EUI
791+ flows ["value" ] = flows ["value" ] / floor_area
792+ links = pd .DataFrame (
793+ link_heating_system_to_gains + link_cooling_system_to_gains
806794 )
795+ return pd .concat ([flows , links ]).to_csv (path_or_buf , index = False )
807796
808797 def _sankey_heating (self , load , load_type = "heating" ):
809798 assert load_type in ["heating" , "cooling" ]
0 commit comments