diff --git a/archetypal/cli.py b/archetypal/cli.py index b3ab0330..0a01eff5 100644 --- a/archetypal/cli.py +++ b/archetypal/cli.py @@ -292,10 +292,7 @@ def transition(idf, to_version, cores, yes): log( f"executing {len(file_paths)} file(s):\n{file_list}", ) - if not yes: - overwrite = click.confirm("Would you like to overwrite the file(s)?") - else: - overwrite = False + overwrite = click.confirm("Would you like to overwrite the file(s)?") if not yes else False start_time = time.time() to_version = to_version.dash diff --git a/archetypal/eplus_interface/basement.py b/archetypal/eplus_interface/basement.py index b5405de1..052cbfc7 100644 --- a/archetypal/eplus_interface/basement.py +++ b/archetypal/eplus_interface/basement.py @@ -114,12 +114,13 @@ def success_callback(self): """Parse surface temperature and append to IDF file.""" for ep_objects in self.run_dir.glob("EPObjects*"): if ep_objects.exists(): - basement_models = self.idf.__class__( - StringIO(open(ep_objects).read()), - file_version=self.idf.file_version, - as_version=self.idf.as_version, - prep_outputs=False, - ) + with open(ep_objects) as f: + basement_models = self.idf.__class__( + StringIO(f.read()), + file_version=self.idf.file_version, + as_version=self.idf.as_version, + prep_outputs=False, + ) # Loop on all objects and using self.newidfobject added_objects = [] for sequence in basement_models.idfobjects.values(): diff --git a/archetypal/eplus_interface/energy_plus.py b/archetypal/eplus_interface/energy_plus.py index 13e2a213..c1714daa 100644 --- a/archetypal/eplus_interface/energy_plus.py +++ b/archetypal/eplus_interface/energy_plus.py @@ -196,48 +196,47 @@ def run(self): if self.p: self.p.terminate() # terminate process to be sure return - with logging_redirect_tqdm(loggers=[lg.getLogger("archetypal")]): + with logging_redirect_tqdm(loggers=[lg.getLogger("archetypal")]), tqdm( + unit_scale=False, + total=self.idf.energyplus_its if self.idf.energyplus_its > 0 else None, + miniters=1, + desc=f"{eplus_exe.eplus_exe_path} #{self.idf.position}-{self.idf.name}" + if self.idf.position + else f"{eplus_exe.eplus_exe_path} {self.idf.name}", + position=self.idf.position, + ) as progress: # Start process with tqdm bar - with tqdm( - unit_scale=False, - total=self.idf.energyplus_its if self.idf.energyplus_its > 0 else None, - miniters=1, - desc=f"{eplus_exe.eplus_exe_path} #{self.idf.position}-{self.idf.name}" - if self.idf.position - else f"{eplus_exe.eplus_exe_path} {self.idf.name}", - position=self.idf.position, - ) as progress: - self.p = subprocess.Popen( - self.cmd, - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - start_time = time.time() - self.msg_callback("Simulation started") - self.idf._energyplus_its = 0 # reset counter - for line in self.p.stdout: - self.msg_callback(line.decode("utf-8").strip("\n")) - self.idf._energyplus_its += 1 - progress.update() + self.p = subprocess.Popen( + self.cmd, + shell=False, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + start_time = time.time() + self.msg_callback("Simulation started") + self.idf._energyplus_its = 0 # reset counter + for line in self.p.stdout: + self.msg_callback(line.decode("utf-8").strip("\n")) + self.idf._energyplus_its += 1 + progress.update() - # We explicitly close stdout - self.p.stdout.close() + # We explicitly close stdout + self.p.stdout.close() - # Wait for process to complete - self.p.wait() + # Wait for process to complete + self.p.wait() - # Communicate callbacks - if self.cancelled: - self.msg_callback("Simulation cancelled") - self.cancelled_callback(self.std_out, self.std_err) + # Communicate callbacks + if self.cancelled: + self.msg_callback("Simulation cancelled") + self.cancelled_callback(self.std_out, self.std_err) + else: + if self.p.returncode == 0: + self.msg_callback(f"EnergyPlus Completed in {time.time() - start_time:,.2f} seconds") + self.success_callback() else: - if self.p.returncode == 0: - self.msg_callback(f"EnergyPlus Completed in {time.time() - start_time:,.2f} seconds") - self.success_callback() - else: - self.msg_callback("Simulation failed") - self.failure_callback() + self.msg_callback("Simulation failed") + self.failure_callback() def msg_callback(self, *args, **kwargs): msg, *_ = args diff --git a/archetypal/eplus_interface/slab.py b/archetypal/eplus_interface/slab.py index e5bb998b..1e793628 100644 --- a/archetypal/eplus_interface/slab.py +++ b/archetypal/eplus_interface/slab.py @@ -114,12 +114,13 @@ def success_callback(self): """Parse surface temperature and append to IDF file.""" for temp_schedule in self.run_dir.glob("SLABSurfaceTemps*"): if temp_schedule.exists(): - slab_models = self.idf.__class__( - StringIO(open(temp_schedule).read()), - file_version=self.idf.file_version, - as_version=self.idf.as_version, - prep_outputs=False, - ) + with open(temp_schedule) as f: + slab_models = self.idf.__class__( + StringIO(f.read()), + file_version=self.idf.file_version, + as_version=self.idf.as_version, + prep_outputs=False, + ) # Loop on all objects and using self.newidfobject added_objects = [] for sequence in slab_models.idfobjects.values(): diff --git a/archetypal/eplus_interface/version.py b/archetypal/eplus_interface/version.py index 36b6a76f..5df5d20b 100644 --- a/archetypal/eplus_interface/version.py +++ b/archetypal/eplus_interface/version.py @@ -120,14 +120,7 @@ def tuple(self) -> tuple: @property def valid_versions(self) -> set: """List the idd file version found on this machine.""" - if not self.valid_idd_paths: - # Little hack in case E+ is not installed - _choices = { - settings.ep_version, - } - else: - _choices = set(self.valid_idd_paths.keys()) - + _choices = {settings.ep_version} if not self.valid_idd_paths else set(self.valid_idd_paths.keys()) return _choices @property diff --git a/archetypal/idfclass/end_use_balance.py b/archetypal/idfclass/end_use_balance.py index 7024ffd9..180397af 100644 --- a/archetypal/idfclass/end_use_balance.py +++ b/archetypal/idfclass/end_use_balance.py @@ -419,7 +419,7 @@ def separate_gains_and_losses(self, component, level="Key_Name") -> EnergyDataFr Returns: """ - assert component in self.__dict__.keys(), f"{component} is not a valid attribute of EndUseBalance." + assert component in self.__dict__, f"{component} is not a valid attribute of EndUseBalance." component_df = getattr(self, component) assert not component_df.empty, "Expected a component that is not empty." print(component) diff --git a/archetypal/idfclass/extensions.py b/archetypal/idfclass/extensions.py index 11346f27..169c2596 100644 --- a/archetypal/idfclass/extensions.py +++ b/archetypal/idfclass/extensions.py @@ -1,5 +1,6 @@ """Eppy extensions module.""" +import contextlib import copy from eppy.bunch_subclass import BadEPFieldError @@ -31,7 +32,7 @@ def nameexists(self: EpBunch): @extend_class(EpBunch) def get_default(self: EpBunch, name): """Return the default value of a field""" - if "default" in self.getfieldidd(name).keys(): + if "default" in self.getfieldidd(name): _type = _parse_idd_type(self, name) default_ = next(iter(self.getfieldidd_item(name, "default")), None) return _type(default_) @@ -85,10 +86,8 @@ def makedict(self: Eplusdata, dictfile, fnamefobject): dt, dtls = self.initdict(dictfile) fnamefobject.seek(0) # make sure to read from the beginning astr = fnamefobject.read() - try: + with contextlib.suppress(AttributeError): astr = astr.decode("ISO-8859-2") - except AttributeError: - pass nocom = removecomment(astr, "!") idfst = nocom # alist = string.split(idfst, ';') @@ -131,14 +130,8 @@ def _parse_idd_type(epbunch, name): - node -> str (name used in connecting HVAC components) """ _type = next(iter(epbunch.getfieldidd_item(name, "type")), "").lower() - if _type == "real": - return float - elif _type == "alpha": - return str - elif _type == "integer": - return int - else: - return str + type_mapping = {"real": float, "alpha": str, "integer": int} + return type_mapping.get(_type, str) # relationship between epbunch output frequency and db. diff --git a/archetypal/idfclass/idf.py b/archetypal/idfclass/idf.py index bece756b..736b26f7 100644 --- a/archetypal/idfclass/idf.py +++ b/archetypal/idfclass/idf.py @@ -6,6 +6,7 @@ from __future__ import annotations +import contextlib import io import itertools import logging @@ -327,9 +328,8 @@ def __init__( if not self.idd_info: raise ValueError("IDD info is not loaded") self._original_cache = hash_model(self) - if self.as_version is not None: - if self.file_version < self.as_version: - self.upgrade(to_version=self.as_version, overwrite=False) + if self.as_version is not None and self.file_version < self.as_version: + self.upgrade(to_version=self.as_version, overwrite=False) # Set model outputs self._outputs = Outputs( @@ -531,12 +531,11 @@ def idd_version(self) -> tuple: def iddname(self) -> Path: """Get or set the iddname path used to parse the idf model.""" if self._iddname is None: - if self.as_version is not None: - if self.file_version > self.as_version: - raise EnergyPlusVersionError( - f"{self.as_version} cannot be lower then " - f"the version number set in the file: {self.file_version}" - ) + if self.as_version is not None and self.file_version > self.as_version: + raise EnergyPlusVersionError( + f"{self.as_version} cannot be lower then " + f"the version number set in the file: {self.file_version}" + ) self._iddname = self.file_version.current_idd_path return self._iddname @@ -1028,12 +1027,11 @@ def net_conditioned_building_area(self) -> float: zone: EpBunch for zone in zones: for surface in zone.zonesurfaces: - if hasattr(surface, "tilt"): - if surface.tilt == 180.0: - part_of = int(zone.Part_of_Total_Floor_Area.upper() != "NO") - multiplier = float(zone.Multiplier if zone.Multiplier != "" else 1) + if hasattr(surface, "tilt") and surface.tilt == 180.0: + part_of = int(zone.Part_of_Total_Floor_Area.upper() != "NO") + multiplier = float(zone.Multiplier if zone.Multiplier != "" else 1) - area += surface.area * multiplier * part_of + area += surface.area * multiplier * part_of self._area_conditioned = area return self._area_conditioned @@ -1058,12 +1056,11 @@ def unconditioned_building_area(self) -> float: zone: EpBunch for zone in zones: for surface in zone.zonesurfaces: - if hasattr(surface, "tilt"): - if surface.tilt == 180.0: - part_of = int(zone.Part_of_Total_Floor_Area.upper() == "NO") - multiplier = float(zone.Multiplier if zone.Multiplier != "" else 1) + if hasattr(surface, "tilt") and surface.tilt == 180.0: + part_of = int(zone.Part_of_Total_Floor_Area.upper() == "NO") + multiplier = float(zone.Multiplier if zone.Multiplier != "" else 1) - area += surface.area * multiplier * part_of + area += surface.area * multiplier * part_of self._area_unconditioned = area return self._area_unconditioned @@ -1087,11 +1084,10 @@ def total_building_area(self) -> float: zone: EpBunch for zone in zones: for surface in zone.zonesurfaces: - if hasattr(surface, "tilt"): - if surface.tilt == 180.0: - multiplier = float(zone.Multiplier if zone.Multiplier != "" else 1) + if hasattr(surface, "tilt") and surface.tilt == 180.0: + multiplier = float(zone.Multiplier if zone.Multiplier != "" else 1) - area += surface.area * multiplier + area += surface.area * multiplier self._area_total = area return self._area_total @@ -1147,10 +1143,13 @@ def partition_ratio(self) -> float: for surf in zone.zonesurfaces if surf.key.upper() not in ["INTERNALMASS", "WINDOWSHADINGCONTROL"] ]: - if hasattr(surface, "tilt"): - if surface.tilt == 90.0 and surface.Outside_Boundary_Condition != "Outdoors": - multiplier = float(zone.Multiplier if zone.Multiplier != "" else 1) - partition_lineal += surface.width * multiplier + if ( + hasattr(surface, "tilt") + and surface.tilt == 90.0 + and surface.Outside_Boundary_Condition != "Outdoors" + ): + multiplier = float(zone.Multiplier if zone.Multiplier != "" else 1) + partition_lineal += surface.width * multiplier self._partition_ratio = partition_lineal / max( self.net_conditioned_building_area, self.unconditioned_building_area ) @@ -1363,7 +1362,7 @@ def simulate(self, force=False, **kwargs): """ # First, update keys with new values for key, value in kwargs.items(): - if f"_{key}" in self.__dict__.keys(): + if f"_{key}" in self.__dict__: setattr(self, key, value) else: log( @@ -1374,14 +1373,13 @@ def simulate(self, force=False, **kwargs): if self.simulation_dir.exists() and not force: # don't simulate if results exists return self - if self.as_version is not None: - if self.as_version != EnergyPlusVersion(self.idd_version): - raise EnergyPlusVersionError( - None, - self.idfname, - EnergyPlusVersion(self.idd_version), - self.as_version, - ) + if self.as_version is not None and self.as_version != EnergyPlusVersion(self.idd_version): + raise EnergyPlusVersionError( + None, + self.idfname, + EnergyPlusVersion(self.idd_version), + self.as_version, + ) include = self.include if isinstance(include, str): @@ -1560,12 +1558,10 @@ def saveas(self, filename, lineendings="default", encoding="latin-1", inplace=Fa name = Path(name).basename() else: name = file.basename() - try: - file.copy(as_idf.simulation_dir / name) - except shutil.SameFileError: + with contextlib.suppress(shutil.SameFileError): # A copy of self would have the same files in the simdir and # throw an error. - pass + file.copy(as_idf.simulation_dir / name) if inplace: # If inplace, replace content of self with content of as_idf. self.__dict__.update(as_idf.__dict__) @@ -1736,16 +1732,14 @@ def roundto(x, to=10.0): for surface in [ surf for surf in zone.zonesurfaces if surf.key.upper() not in ["INTERNALMASS", "WINDOWSHADINGCONTROL"] ]: - if isclose(surface.tilt, 90, abs_tol=10): - if surface.Outside_Boundary_Condition.lower() == "outdoors": - surf_azim = roundto(surface.azimuth, to=azimuth_threshold) - total_surface_area[surf_azim] += surface.area * multiplier + if isclose(surface.tilt, 90, abs_tol=10) and surface.Outside_Boundary_Condition.lower() == "outdoors": + surf_azim = roundto(surface.azimuth, to=azimuth_threshold) + total_surface_area[surf_azim] += surface.area * multiplier for subsurface in surface.subsurfaces: if hasattr(subsurface, "tilt"): - if isclose(subsurface.tilt, 90, abs_tol=10): - if subsurface.Surface_Type.lower() == "window": - surf_azim = roundto(subsurface.azimuth, to=azimuth_threshold) - total_window_area[surf_azim] += subsurface.area * multiplier + if isclose(subsurface.tilt, 90, abs_tol=10) and subsurface.Surface_Type.lower() == "window": + surf_azim = roundto(subsurface.azimuth, to=azimuth_threshold) + total_window_area[surf_azim] += subsurface.area * multiplier if isclose(subsurface.tilt, 180, abs_tol=80): total_window_area["sky"] += subsurface.area * multiplier # Fix azimuth = 360 which is the same as azimuth 0 @@ -2160,7 +2154,7 @@ def _get_used_schedules(self, yearly_only=False): if obj.key.upper() not in schedule_types: for fieldvalue in obj.fieldvalues: try: - if fieldvalue.upper() in all_schedules.keys() and fieldvalue not in used_schedules: + if fieldvalue.upper() in all_schedules and fieldvalue not in used_schedules: used_schedules.append(fieldvalue) except (KeyError, AttributeError): pass @@ -2589,11 +2583,10 @@ def total_envelope_area(self): zone: EpBunch for zone in zones: for surface in zone.zonesurfaces: - if hasattr(surface, "tilt"): - if surface.tilt == 180.0: - multiplier = float(zone.Multiplier if zone.Multiplier != "" else 1) + if hasattr(surface, "tilt") and surface.tilt == 180.0: + multiplier = float(zone.Multiplier if zone.Multiplier != "" else 1) - area += surface.area * multiplier + area += surface.area * multiplier self._area_total = area for surface in self.getsurfaces(): if surface.Outside_Boundary_Condition.lower() in ["adiabatic", "surface"]: diff --git a/archetypal/idfclass/meters.py b/archetypal/idfclass/meters.py index 9ef70b03..54067a6e 100644 --- a/archetypal/idfclass/meters.py +++ b/archetypal/idfclass/meters.py @@ -98,10 +98,7 @@ def values( # the environment_type is specified by the simulationcontrol. try: for ctrl in self._idf.idfobjects["SIMULATIONCONTROL"]: - if ctrl.Run_Simulation_for_Weather_File_Run_Periods.lower() == "yes": - environment_type = 3 - else: - environment_type = 1 + environment_type = 3 if ctrl.Run_Simulation_for_Weather_File_Run_Periods.lower() == "yes" else 1 except (KeyError, IndexError, AttributeError): reporting_frequency = 3 report = ReportData.from_sqlite( @@ -153,11 +150,10 @@ def __repr__(self): for i in inspect.getmembers(self): # to remove private and protected # functions - if not i[0].startswith("_"): + if not i[0].startswith("_") and not inspect.ismethod(i[1]): # To remove other methods that # do not start with an underscore - if not inspect.ismethod(i[1]): - members.append(i) + members.append(i) return f"{len(members)} available meters" @@ -212,9 +208,6 @@ def __repr__(self): for i in inspect.getmembers(self): # to remove private and protected # functions - if not i[0].startswith("_"): - # To remove other methods that - # do not start with an underscore - if not inspect.ismethod(i[1]): - members.append(i) + if not i[0].startswith("_") and not inspect.ismethod(i[1]): + members.append(i) return tabulate(members, headers=("Available subgroups", "Preview")) diff --git a/archetypal/idfclass/variables.py b/archetypal/idfclass/variables.py index 1ba01bc3..d8aa9094 100644 --- a/archetypal/idfclass/variables.py +++ b/archetypal/idfclass/variables.py @@ -77,10 +77,7 @@ def values( # the environment_type is specified by the simulationcontrol. try: for ctrl in self._idf.idfobjects["SIMULATIONCONTROL"]: - if ctrl.Run_Simulation_for_Weather_File_Run_Periods.lower() == "yes": - environment_type = 3 - else: - environment_type = 1 + environment_type = 3 if ctrl.Run_Simulation_for_Weather_File_Run_Periods.lower() == "yes" else 1 except (KeyError, IndexError, AttributeError): reporting_frequency = 3 report = ReportData.from_sqlite( diff --git a/archetypal/schedule.py b/archetypal/schedule.py index e0295cf2..a9c3fee8 100644 --- a/archetypal/schedule.py +++ b/archetypal/schedule.py @@ -2,6 +2,7 @@ from __future__ import annotations +import contextlib import functools import io import logging as lg @@ -817,12 +818,7 @@ def _field_set(schedule_epbunch, field, start_date, slicer_=None, strict=False): elif field.lower() == "saturday": # return only Saturdays return lambda x: x.index.dayofweek == 5 - elif field.lower() == "summerdesignday": - # return _ScheduleParser.design_day( - # schedule_epbunch, field, slicer_, start_date, strict - # ) - return None - elif field.lower() == "winterdesignday": + elif field.lower() == "summerdesignday" or field.lower() == "winterdesignday": # return _ScheduleParser.design_day( # schedule_epbunch, field, slicer_, start_date, strict # ) @@ -1033,10 +1029,9 @@ def __init__( Values (ndarray): A 24 or 8760 list of schedule values. **kwargs: """ - try: + with contextlib.suppress(Exception): + # todo: make this more robust super().__init__(Name, **kwargs) - except Exception: - pass # todo: make this more robust self.Name = Name self.strict = strict self.startDayOfTheWeek = start_day_of_the_week @@ -1180,10 +1175,7 @@ def mean(self): def series(self): """Return an :class:`EnergySeries`.""" index = pd.date_range(start=self.startDate, periods=self.all_values.size, freq="1h") - if self.Type is not None: - units = self.Type.UnitType - else: - units = None + units = self.Type.UnitType if self.Type is not None else None return EnergySeries(self.all_values, index=index, name=self.Name, units=units) @staticmethod @@ -1488,16 +1480,8 @@ def _conjunction(*conditions, logical=np.logical_and): def _separator(sep): - if sep == "Comma": - return "," - elif sep == "Tab": - return "\t" - elif sep == "Fixed": - return None - elif sep == "Semicolon": - return ";" - else: - return "," + separators = {"Comma": ",", "Tab": "\t", "Fixed": None, "Semicolon": ";"} + return separators.get(sep, ",") def _how(how): diff --git a/archetypal/simple_glazing.py b/archetypal/simple_glazing.py index 33695189..fdcdbd23 100644 --- a/archetypal/simple_glazing.py +++ b/archetypal/simple_glazing.py @@ -96,10 +96,7 @@ def calc_simple_glazing(shgc, u_factor, visible_transmittance=None): # as one of the simple performance indices. # If the user does not enter a value, then the visible properties are the # same as the solar properties. - if visible_transmittance: - T_vis = visible_transmittance - else: - T_vis = T_sol + T_vis = visible_transmittance if visible_transmittance else T_sol R_vis_b = r_vis_b(T_vis) R_vis_f = r_vis_f(T_vis) diff --git a/archetypal/template/conditioning.py b/archetypal/template/conditioning.py index dd797363..a35c9ba8 100644 --- a/archetypal/template/conditioning.py +++ b/archetypal/template/conditioning.py @@ -1,6 +1,7 @@ """archetypal ZoneConditioning.""" import collections +import contextlib import logging as lg import math import sqlite3 @@ -741,13 +742,15 @@ def _set_economizer(self, zone: "ZoneDefinition", zone_ep: EpBunch): self.EconomizerType = EconomizerTypes.NoEconomizer elif object.Economizer_Control_Type == "DifferentialEnthalphy": self.EconomizerType = EconomizerTypes.DifferentialEnthalphy - elif object.Economizer_Control_Type == "DifferentialDryBulb": + elif ( + object.Economizer_Control_Type == "DifferentialDryBulb" + or object.Economizer_Control_Type == "FixedDryBulb" + ): self.EconomizerType = EconomizerTypes.DifferentialDryBulb - elif object.Economizer_Control_Type == "FixedDryBulb": - self.EconomizerType = EconomizerTypes.DifferentialDryBulb - elif object.Economizer_Control_Type == "FixedEnthalpy": - self.EconomizerType = EconomizerTypes.DifferentialEnthalphy - elif object.Economizer_Control_Type == "ElectronicEnthalpy": + elif ( + object.Economizer_Control_Type == "FixedEnthalpy" + or object.Economizer_Control_Type == "ElectronicEnthalpy" + ): self.EconomizerType = EconomizerTypes.DifferentialEnthalphy elif object.Economizer_Control_Type == "FixedDewPointAndDryBulb": self.EconomizerType = EconomizerTypes.DifferentialDryBulb @@ -857,10 +860,7 @@ def fresh_air_from_zone_sizes(self, zone: "ZoneDefinition", zone_ep: EpBunch): oa_design = oa["Minimum Outdoor Air Flow Rate"] # m3/s isoa = oa["Calculated Design Air Flow"] > 0 # True if ach > 0 oa_area = oa_design / zone.area - if zone.occupants > 0: - oa_person = oa_design / zone.occupants - else: - oa_person = np.nan + oa_person = oa_design / zone.occupants if zone.occupants > 0 else np.nan designobjs = zone_ep.getreferingobjs(iddgroups=["HVAC Design Objects"], fields=["Zone_or_ZoneList_Name"]) obj: EpBunch = next(iter(eq for eq in designobjs if eq.key.lower() == "sizing:zone")) @@ -926,10 +926,9 @@ def _set_zone_cops(self, zone: "ZoneDefinition", zone_ep: EpBunch, nolimit: bool ) total_input_heating_energy = 0 for meter in heating_meters: - try: + with contextlib.suppress(KeyError): + # pass if meter does not exist for model total_input_heating_energy += zone_ep.theidf.meters.OutputMeter[meter].values("kWh").sum() - except KeyError: - pass # pass if meter does not exist for model heating_energy_transfer_meters = ( "HeatingCoils__EnergyTransfer", @@ -937,17 +936,14 @@ def _set_zone_cops(self, zone: "ZoneDefinition", zone_ep: EpBunch, nolimit: bool ) total_output_heating_energy = 0 for meter in heating_energy_transfer_meters: - try: + with contextlib.suppress(KeyError): + # pass if meter does not exist for model total_output_heating_energy += zone_ep.theidf.meters.OutputMeter[meter].values("kWh").sum() - except KeyError: - pass # pass if meter does not exist for model if total_output_heating_energy == 0: # IdealLoadsAirSystem - try: + with contextlib.suppress(KeyError): total_output_heating_energy += ( zone_ep.theidf.meters.OutputMeter["Heating__EnergyTransfer"].values("kWh").sum() ) - except KeyError: - pass cooling_meters = ( "Cooling__Electricity", @@ -958,10 +954,9 @@ def _set_zone_cops(self, zone: "ZoneDefinition", zone_ep: EpBunch, nolimit: bool ) total_input_cooling_energy = 0 for meter in cooling_meters: - try: + with contextlib.suppress(KeyError): + # pass if meter does not exist for model total_input_cooling_energy += zone_ep.theidf.meters.OutputMeter[meter].values("kWh").sum() - except KeyError: - pass # pass if meter does not exist for model cooling_energy_transfer_meters = ( "CoolingCoils__EnergyTransfer", @@ -969,17 +964,14 @@ def _set_zone_cops(self, zone: "ZoneDefinition", zone_ep: EpBunch, nolimit: bool ) total_output_cooling_energy = 0 for meter in cooling_energy_transfer_meters: - try: + with contextlib.suppress(KeyError): + # pass if meter does not exist for model total_output_cooling_energy += zone_ep.theidf.meters.OutputMeter[meter].values("kWh").sum() - except KeyError: - pass # pass if meter does not exist for model if total_output_cooling_energy == 0: # IdealLoadsAirSystem - try: + with contextlib.suppress(KeyError): total_output_cooling_energy += ( zone_ep.theidf.meters.OutputMeter["Cooling__EnergyTransfer"].values("kWh").sum() ) - except KeyError: - pass ratio_cooling = total_output_cooling_energy / (total_output_cooling_energy + total_output_heating_energy) ratio_heating = total_output_heating_energy / (total_output_cooling_energy + total_output_heating_energy) diff --git a/archetypal/template/materials/glazing_material.py b/archetypal/template/materials/glazing_material.py index 053bd636..d3521a2c 100644 --- a/archetypal/template/materials/glazing_material.py +++ b/archetypal/template/materials/glazing_material.py @@ -264,7 +264,7 @@ def combine(self, other, weights=None, allow_duplicates=False): pass else: raise NotImplementedError - [new_attr.pop(key, None) for key in meta.keys()] # meta handles these + [new_attr.pop(key, None) for key in meta] # meta handles these # keywords. # create a new object from combined attributes new_obj = self.__class__(**meta, **new_attr) diff --git a/archetypal/template/window_setting.py b/archetypal/template/window_setting.py index 003360d2..02c7d36d 100644 --- a/archetypal/template/window_setting.py +++ b/archetypal/template/window_setting.py @@ -578,25 +578,12 @@ def from_surface(cls, surface, **kwargs): f'defaults for object "{cls.mro()[0].__name__}"', lg.WARNING, ) - elif leak.key.upper() == "AIRFLOWNETWORK:MULTIZONE:SURFACE:CRACK": - log( - f'"{leak.key}" is not fully supported. Rerverting to ' - f'defaults for object "{cls.mro()[0].__name__}"', - lg.WARNING, - ) - elif leak.key.upper() == "AIRFLOWNETWORK:MULTIZONE:COMPONENT:DETAILEDOPENING": - log( - f'"{leak.key}" is not fully supported. Rerverting to ' - f'defaults for object "{cls.mro()[0].__name__}"', - lg.WARNING, - ) - elif leak.key.upper() == "AIRFLOWNETWORK:MULTIZONE:COMPONENT:ZONEEXHAUSTFAN": - log( - f'"{leak.key}" is not fully supported. Rerverting to ' - f'defaults for object "{cls.mro()[0].__name__}"', - lg.WARNING, - ) - elif leak.key.upper() == "AIRFLOWNETWORK:MULTIZONE:COMPONENT:SIMPLEOPENING": + elif ( + leak.key.upper() == "AIRFLOWNETWORK:MULTIZONE:SURFACE:CRACK" + or leak.key.upper() == "AIRFLOWNETWORK:MULTIZONE:COMPONENT:DETAILEDOPENING" + or leak.key.upper() == "AIRFLOWNETWORK:MULTIZONE:COMPONENT:ZONEEXHAUSTFAN" + or leak.key.upper() == "AIRFLOWNETWORK:MULTIZONE:COMPONENT:SIMPLEOPENING" + ): log( f'"{leak.key}" is not fully supported. Rerverting to ' f'defaults for object "{cls.mro()[0].__name__}"', diff --git a/archetypal/template/zone_construction_set.py b/archetypal/template/zone_construction_set.py index 12652c57..100f5f50 100644 --- a/archetypal/template/zone_construction_set.py +++ b/archetypal/template/zone_construction_set.py @@ -265,30 +265,15 @@ def from_zone(cls, zone: "ZoneDefinition", **kwargs): # Returning a set() for each groups of Constructions. facades = set(facade) - if facades: - facade = reduce(OpaqueConstruction.combine, facades) - else: - facade = None + facade = reduce(OpaqueConstruction.combine, facades) if facades else None grounds = set(ground) - if grounds: - ground = reduce(OpaqueConstruction.combine, grounds) - else: - ground = None + ground = reduce(OpaqueConstruction.combine, grounds) if grounds else None partitions = set(partition) - if partitions: - partition = reduce(OpaqueConstruction.combine, partitions) - else: - partition = None + partition = reduce(OpaqueConstruction.combine, partitions) if partitions else None roofs = set(roof) - if roofs: - roof = reduce(OpaqueConstruction.combine, roofs) - else: - roof = None + roof = reduce(OpaqueConstruction.combine, roofs) if roofs else None slabs = set(slab) - if slabs: - slab = reduce(OpaqueConstruction.combine, slabs) - else: - slab = None + slab = reduce(OpaqueConstruction.combine, slabs) if slabs else None z_set = cls( Facade=facade, diff --git a/archetypal/umi_template.py b/archetypal/umi_template.py index ba4dc069..19ef44a8 100644 --- a/archetypal/umi_template.py +++ b/archetypal/umi_template.py @@ -4,9 +4,9 @@ import json import logging as lg -from collections import OrderedDict +from collections import OrderedDict, defaultdict from concurrent.futures.thread import ThreadPoolExecutor -from typing import ClassVar +from typing import ClassVar, Union import networkx as nx from pandas.io.common import get_handle @@ -662,9 +662,8 @@ def unique_components(self, *args: str, exceptions: list[str] | None = None, kee for component in group: # travers each object using generator for parent, key, obj in parent_key_child_traversal(component): - if obj.__class__.__name__ + "s" in inclusion: - if key: - setattr(parent, key, obj.get_unique()) # set unique object on key + if obj.__class__.__name__ + "s" in inclusion and key: + setattr(parent, key, obj.get_unique()) # set unique object on key self.update_components_list(exceptions=exceptions) # Update the components list if keep_orphaned: @@ -739,14 +738,15 @@ def to_graph(self, include_orphans=False): return G -def no_duplicates(file, attribute="Name"): - """Assert whether or not dict has duplicated Names.""" - import json - from collections import defaultdict +def no_duplicates(file: Union[str, dict], attribute="Name"): + """Assert whether dict has duplicated Names.""" + if isinstance(file, str): + with open(file) as f: + data = json.loads(f.read()) + else: + data = file - data = json.loads(open(file).read()) if isinstance(file, str) else file ids = defaultdict(lambda: defaultdict(int)) - for key, value in data.items(): for component in value: _id = component.get(attribute) diff --git a/archetypal/utils.py b/archetypal/utils.py index 3940cec6..2da7584c 100644 --- a/archetypal/utils.py +++ b/archetypal/utils.py @@ -460,10 +460,7 @@ def lcm(x, y): """This function takes two integers and returns the least common multiple.""" # choose the greater number - if x > y: - greater = x - else: - greater = y + greater = x if x > y else y while True: if (greater % x == 0) and (greater % y == 0): diff --git a/archetypal/zone_graph.py b/archetypal/zone_graph.py index c6f12568..fa000436 100644 --- a/archetypal/zone_graph.py +++ b/archetypal/zone_graph.py @@ -460,10 +460,7 @@ def plot_graph2d( # choose nodes and color for each iteration nlist = [nt] label = getattr(nt, "Name", nt) - if color_nodes: - node_color = [colors[nt]] - else: - node_color = "#1f78b4" + node_color = [colors[nt]] if color_nodes else "#1f78b4" # draw the graph sc = networkx.draw_networkx_nodes( tree, diff --git a/poetry.lock b/poetry.lock index 2f2cfbbe..92548a34 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2317,6 +2317,23 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "pytest-xdist" version = "3.6.1" @@ -3489,4 +3506,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "6a3a18c1017759fa5efe6ef8fb8336113a26c84a2799371503f161ad5554164e" +content-hash = "f93c1e2f9c17b4e8ec114a7af9f22b4f20054da8cae75c1c6f6b5430ee89be66" diff --git a/pyproject.toml b/pyproject.toml index 8828db08..7767f5a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,6 +64,7 @@ transforms3d = "^0.4.1" pytest = "^8.2.2" pytest-cov = "^5.0.0" pytest-xdist = "^3.6.1" +pytest-mock = "^3.14.0" tox = "^4.19.0" pre-commit = "^3.8.0" @@ -103,7 +104,7 @@ select = [ # flake8-debugger "T10", # flake8-simplify - # "SIM", + "SIM", # isort "I", # mccabe diff --git a/tests/test_idfclass.py b/tests/test_idfclass.py index 21fcd62e..a76567f6 100644 --- a/tests/test_idfclass.py +++ b/tests/test_idfclass.py @@ -110,8 +110,10 @@ def test_specific_version(self, config, natvent_v9_1_0): assert natvent_v9_1_0.idd_version == (9, 1, 0) assert natvent_v9_1_0.file_version == EnergyPlusVersion("9-1-0") - def test_specific_version_error_simulate(self, natvent_v9_1_0): - with pytest.raises(EnergyPlusVersionError): + def test_specific_version_error_simulate(self, natvent_v9_1_0, mocker): + with mocker.patch( + "archetypal.eplus_interface.energy_plus.EnergyPlusExe.get_exe_path", side_effect=EnergyPlusVersionError() + ), pytest.raises(EnergyPlusVersionError): natvent_v9_1_0.simulate() def test_version(self, natvent_v9_1_0): diff --git a/tests/test_schedules.py b/tests/test_schedules.py index a88813c0..0e7dddd1 100644 --- a/tests/test_schedules.py +++ b/tests/test_schedules.py @@ -111,7 +111,7 @@ def test_replace(self): def schedules_idf(): - config(cache_folder=os.getenv("ARCHETYPAL_CACHE") or data_dir / "../.temp/cache") + config(cache_folder=os.getenv("ARCHETYPAL_CACHE") or (data_dir / "../.temp/cache").resolve()) idf = IDF( idf_file, epw=data_dir / "CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw", @@ -124,7 +124,7 @@ def schedules_idf(): idf = schedules_idf() schedules_dict = idf._get_all_schedules(yearly_only=True) schedules = list(schedules_dict.values()) -ids = [key.replace(" ", "_") for key in schedules_dict.keys()] +ids = [key.replace(" ", "_") for key in schedules_dict] schedules = [ pytest.param(schedule, marks=pytest.mark.xfail(reason="Can't quite capture all possibilities with special days")) @@ -188,7 +188,6 @@ def test_ep_versus_schedule(schedule_parametrized): new.series[index_slice].plot(ax=ax, legend=True, drawstyle="steps-post", linestyle="dotted") expected.loc[index_slice].plot(label="E+", legend=True, ax=ax, drawstyle="steps-post", linestyle="dashdot") ax.set_title(orig.Name.capitalize()) - plt.show() print(pd.DataFrame({"actual": orig.series[mask], "expected": expected[mask]})) np.testing.assert_array_almost_equal(orig.all_values, expected, verbose=True) diff --git a/tests/test_template.py b/tests/test_template.py index fb68e81f..6daefce6 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -1504,13 +1504,13 @@ def test_shgc(self, b_glass_clear_3): temperature, r_values = triple.temperature_profile( outside_temperature=-18, inside_temperature=21, wind_speed=5.5 ) - assert [-18, -16.3, -16.1, 13.6, 13.8, 21.0] == pytest.approx(temperature, 1e-1) + assert pytest.approx(temperature, 1e-1) == [-18, -16.3, -16.1, 13.6, 13.8, 21.0] print(temperature, r_values) shgc = triple.shgc("summer") _, temperature = triple.heat_balance("summer") print("shgc:", shgc) - assert [32, 32.9, 32.9, 31.9, 31.8, 24.0] == pytest.approx(temperature, 1e-1) + assert pytest.approx(temperature, 1e-1) == [32, 32.9, 32.9, 31.9, 31.8, 24.0] print(temperature, r_values) # m2-K/W diff --git a/tests/test_umi.py b/tests/test_umi.py index 121989be..1305c920 100644 --- a/tests/test_umi.py +++ b/tests/test_umi.py @@ -4,7 +4,6 @@ from typing import ClassVar import pytest -from path import Path from archetypal import IDF, settings from archetypal.eplus_interface import EnergyPlusVersion @@ -630,24 +629,6 @@ def test_necb_parallel(self, config): data_dir / "necb/NECB 2011-LargeOffice-NECB HDD Method-CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw.idf", ] - @pytest.mark.skipif( - os.environ.get("CI", "False").lower() == "true", - reason="Skipping this test on CI environment", - ) - @pytest.mark.parametrize("file", Path(data_dir / "problematic").files("*CZ5A*.idf")) - def test_cz5a_serial(self, file, config): - settings.log_console = True - w = data_dir / "CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw" - template = UmiTemplateLibrary.from_idf_files( - name=file.stem, - idf_files=[file], - as_version="9-2-0", - weather=w, - processors=1, - ) - assert no_duplicates(template.to_dict(), attribute="Name") - assert no_duplicates(template.to_dict(), attribute="$id") - @pytest.fixture(scope="session") def climatestudio(config):