diff --git a/archetypal/__init__.py b/archetypal/__init__.py index ed98e5149..d3929e07f 100644 --- a/archetypal/__init__.py +++ b/archetypal/__init__.py @@ -28,6 +28,5 @@ from .trnsys import * from .template import * from .core import * -from .building import * from .umi_template import * from .cli import * diff --git a/archetypal/building.py b/archetypal/building.py deleted file mode 100644 index 0b9e2271f..000000000 --- a/archetypal/building.py +++ /dev/null @@ -1,102 +0,0 @@ -################################################################################ -# Module: building.py -# Description: Functions related to building -# License: MIT, see full license in LICENSE.txt -# Web: https://github.com/samuelduchesne/archetypal -################################################################################ - -import numpy as np -import pandas -import pyomo.core as pyomo -import pyomo.environ -from pyomo.opt import SolverFactory -from .energyseries import EnergySeries - - -def create_fake_profile( - x=None, - y1={}, - y2={}, - normalize=False, - profile_type="undefined", - sorted=False, - ascending=False, - units="J", -): - """Utility that generates a generic EnergySeries isntance - - Args: - x (np.ndarray): is a linspace. Default is np.linspace(0, 8759, 8760) - y1 (dict): {'A':1, 'f':1/8760, 'phy':1, 's':0.5} - y2 (dict): {'A':1, 'f':1/24, 'phy':1, 's':0.5} - ascending (bool): if True, sorts in ascending order. Implies 'sorted' - is also True - profile_type (str): name to give the series. eg. 'heating load' or - 'cooling load' - sorted (bool): id True, series will be sorted. - - Returns: - EnergySeries: the EnergySeries - """ - if x is None: - x = np.linspace(0, 8759, 8760) - A1 = y1.get("A", 1) - f = y1.get("f", 1 / 8760) - w = 2 * np.pi * f - phy = y1.get("phy", 1) - s = y1.get("s", 0.5) - y1 = A1 * np.sin(w * x + phy) + s - - A = y2.get("A", A1) - f = y2.get("f", 1 / 24) - w = 2 * np.pi * f - phy = y2.get("phy", 1) - s = y2.get("s", 0.5) - y2 = A * np.sin(w * x + phy) + s - - y = y1 + y2 - return EnergySeries( - y, - index=x, - frequency="1H", - units=units, - profile_type=profile_type, - normalize=normalize, - is_sorted=sorted, - ascending=ascending, - ) - - -def discretize(profile, bins=5): - m = pyomo.ConcreteModel() - - m.bins = pyomo.Set(initialize=range(bins)) - m.timesteps = pyomo.Set(initialize=range(8760)) - m.profile = profile.copy() - m.duration = pyomo.Var(m.bins, within=pyomo.NonNegativeIntegers) - m.amplitude = pyomo.Var(m.bins, within=pyomo.NonNegativeReals) - - m.total_duration = pyomo.Constraint( - m.bins, - doc="All duration must be " "smaller or " "equal to 8760", - rule=total_duration_rule, - ) - - m.obj = pyomo.Objective( - sense=pyomo.minimize, doc="Minimize the sum of squared errors", rule=obj_rule - ) - optim = SolverFactory("gurobi") - - result = optim.solve(m, tee=True, load_solutions=False) - - m.solutions.load_from(result) - - return m - - -def total_duration_rule(m): - return sum(m.duration[i] for i in m.bins) == 8760 - - -def obj_rule(m): - return sum(m.duration * m.amplitude) diff --git a/archetypal/idfclass.py b/archetypal/idfclass.py index 2ee873556..3f46cbe2b 100644 --- a/archetypal/idfclass.py +++ b/archetypal/idfclass.py @@ -958,7 +958,7 @@ def run_eplus( version (bool, optional): Display version information (default: False) Returns: - tuple: a 1-tuple or a 2-tuple + 2-tuple: a 1-tuple or a 2-tuple - dict: dict of [(title, table), .....] - IDF: The IDF object. Only provided if return_idf is True. """ diff --git a/archetypal/template/building_template.py b/archetypal/template/building_template.py index 709127d0b..e1596c6d4 100644 --- a/archetypal/template/building_template.py +++ b/archetypal/template/building_template.py @@ -73,7 +73,7 @@ def __init__( self.Windows = Windows def __hash__(self): - return hash((self.Name, self.idf.name)) + return hash((self.__class__.__name__, self.Name)) def __eq__(self, other): if not isinstance(other, BuildingTemplate): diff --git a/archetypal/template/conditioning.py b/archetypal/template/conditioning.py index de098738c..4ca627e1b 100644 --- a/archetypal/template/conditioning.py +++ b/archetypal/template/conditioning.py @@ -178,7 +178,7 @@ def __add__(self, other): return self.combine(other) def __hash__(self): - return hash(self.Name) + return hash((self.__class__.__name__, self.Name)) def __eq__(self, other): if not isinstance(other, ZoneConditioning): diff --git a/archetypal/template/dhw.py b/archetypal/template/dhw.py index b41ee1f87..d1cf21f36 100644 --- a/archetypal/template/dhw.py +++ b/archetypal/template/dhw.py @@ -57,7 +57,7 @@ def __add__(self, other): return self.combine(other) def __hash__(self): - return hash(self.Name) + return hash((self.__class__.__name__, self.Name)) def __eq__(self, other): if not isinstance(other, DomesticHotWaterSetting): diff --git a/archetypal/template/gas_material.py b/archetypal/template/gas_material.py index 6bcf3fe56..7dd24ac2a 100644 --- a/archetypal/template/gas_material.py +++ b/archetypal/template/gas_material.py @@ -29,7 +29,7 @@ def __init__(self, *args, Category="Gases", Type="Gas", **kwargs): self.Type = Type def __hash__(self): - return hash(self.Name) + return hash((self.__class__.__name__, self.Name)) def __eq__(self, other): if not isinstance(other, GasMaterial): diff --git a/archetypal/template/glazing_material.py b/archetypal/template/glazing_material.py index 45be9c3a9..04e8e3700 100644 --- a/archetypal/template/glazing_material.py +++ b/archetypal/template/glazing_material.py @@ -94,7 +94,7 @@ def __add__(self, other): return self.combine(other) def __hash__(self): - return hash(self.Name) + return hash((self.__class__.__name__, self.Name)) def __eq__(self, other): if not isinstance(other, GlazingMaterial): diff --git a/archetypal/template/load.py b/archetypal/template/load.py index 9fc09bed4..509ee7181 100644 --- a/archetypal/template/load.py +++ b/archetypal/template/load.py @@ -99,7 +99,7 @@ def __add__(self, other): return self.combine(other) def __hash__(self): - return hash(self.Name) + return hash((self.__class__.__name__, self.Name)) def __eq__(self, other): if not isinstance(other, ZoneLoad): diff --git a/archetypal/template/opaque_construction.py b/archetypal/template/opaque_construction.py index 239b4b939..b3cbaa1e0 100644 --- a/archetypal/template/opaque_construction.py +++ b/archetypal/template/opaque_construction.py @@ -104,7 +104,7 @@ def __add__(self, other): return self.combine(other) def __hash__(self): - return hash((self.Name)) + return hash((self.__class__.__name__, self.Name)) def __eq__(self, other): if not isinstance(other, OpaqueConstruction): diff --git a/archetypal/template/opaque_material.py b/archetypal/template/opaque_material.py index bad493cd7..519e0d1d2 100644 --- a/archetypal/template/opaque_material.py +++ b/archetypal/template/opaque_material.py @@ -112,7 +112,7 @@ def __add__(self, other): return self.combine(other) def __hash__(self): - return hash((self.Name)) + return hash((self.__class__.__name__, self.Name)) def __eq__(self, other): if not isinstance(other, OpaqueMaterial): diff --git a/archetypal/template/schedule.py b/archetypal/template/schedule.py index 6330198b6..5b6614a95 100644 --- a/archetypal/template/schedule.py +++ b/archetypal/template/schedule.py @@ -82,7 +82,7 @@ def __str__(self): return repr(self) def __hash__(self): - return hash(self.Name) + return hash((self.__class__.__name__, self.Name)) def __eq__(self, other): if not isinstance(other, UmiSchedule): @@ -387,7 +387,7 @@ def get_days(self, epbunch): ] for day in dayname: week_day_schedule_name = epbunch["{}_ScheduleDay_Name".format(day)] - blocks.append(self.all_objects[("DaySchedule", week_day_schedule_name)]) + blocks.append(self.all_objects[hash(("DaySchedule", week_day_schedule_name))]) return blocks @@ -478,7 +478,7 @@ def get_parts(self, epbunch): FromMonth, ToDay, ToMonth, - self.all_objects[("WeekSchedule", week_day_schedule_name)], + self.all_objects[hash(("WeekSchedule", week_day_schedule_name))], ) ) return parts diff --git a/archetypal/template/structure.py b/archetypal/template/structure.py index fa1837933..82f3958e9 100644 --- a/archetypal/template/structure.py +++ b/archetypal/template/structure.py @@ -89,7 +89,7 @@ def __init__( self.MassRatios = MassRatios def __hash__(self): - return hash(self.Name) + return hash((self.__class__.__name__, self.Name)) def __eq__(self, other): if not isinstance(other, StructureDefinition): diff --git a/archetypal/template/umi_base.py b/archetypal/template/umi_base.py index b8f223966..d2ffe76fe 100644 --- a/archetypal/template/umi_base.py +++ b/archetypal/template/umi_base.py @@ -28,7 +28,7 @@ def __call__(cls, *args, **kwargs): *args: **kwargs: """ - key = (cls.mro()[0].__name__, kwargs["Name"]) + key = hash((cls.mro()[0].__name__, kwargs["Name"])) if key not in CREATED_OBJECTS: self = cls.__new__(cls, *args, **kwargs) cls.__init__(self, *args, **kwargs) @@ -149,12 +149,12 @@ def _get_predecessors_meta(self, other): def rename(self, name): """renames self as well as the cached object""" - key = (self.__class__.mro()[0].__name__, self.Name) + key = hash((self.__class__.mro()[0].__name__, self.Name)) self._cache.pop(key) CREATED_OBJECTS.pop(key) self.Name = name - newkey = (self.__class__.mro()[0].__name__, name) + newkey = hash((self.__class__.mro()[0].__name__, name)) self._cache[newkey] = self CREATED_OBJECTS[newkey] = self @@ -185,7 +185,7 @@ def get_random_schedule(self): ) def __hash__(self): - return hash(self.Name) + return hash((self.__class__.mro()[0].__name__, self.Name)) def to_dict(self): return {"$ref": str(self.id)} @@ -275,13 +275,14 @@ def extend(self, other): Returns: UmiBase: self """ + self.all_objects.pop(self.__hash__(), None) id = self.id - new_obj = self + other + new_obj = self.combine(other) new_obj.__dict__.pop("id") new_obj.id = id name = new_obj.__dict__.pop("Name") - self.__dict__.update(**new_obj.__dict__) - self.all_objects.pop((self.__class__.__name__, name)) + self.__dict__.update(Name=name, **new_obj.__dict__) + self.all_objects[self.__hash__()] = self return self @@ -357,7 +358,7 @@ def __init__( self.TransportEnergy = TransportEnergy def __hash__(self): - return hash(self.Name) + return hash((self.__class__.__name__, self.Name)) def __eq__(self, other): if not isinstance(other, MaterialBase): diff --git a/archetypal/template/ventilation.py b/archetypal/template/ventilation.py index f7660925d..17dde4034 100644 --- a/archetypal/template/ventilation.py +++ b/archetypal/template/ventilation.py @@ -131,7 +131,7 @@ def __add__(self, other): return self.combine(other) def __hash__(self): - return hash(self.Name) + return hash((self.__class__.__name__, self.Name)) def __eq__(self, other): if not isinstance(other, VentilationSetting): diff --git a/archetypal/template/window.py b/archetypal/template/window.py index 3c6e479d8..c83f22b37 100644 --- a/archetypal/template/window.py +++ b/archetypal/template/window.py @@ -61,7 +61,7 @@ def __init__( self.Layers = None def __hash__(self): - return hash(self.Name) + return hash((self.__class__.__name__, self.Name)) def __eq__(self, other): if not isinstance(other, WindowConstruction): @@ -310,7 +310,7 @@ def __str__(self): return repr(self) def __hash__(self): - return hash(self.Name) + return hash((self.__class__.__name__, self.Name)) def __eq__(self, other): if not isinstance(other, WindowSetting): diff --git a/archetypal/template/zone.py b/archetypal/template/zone.py index 71a9edb91..9a553b7a9 100644 --- a/archetypal/template/zone.py +++ b/archetypal/template/zone.py @@ -28,16 +28,18 @@ DomesticHotWaterSetting, OpaqueConstruction, WindowSetting, -) + CREATED_OBJECTS) -class Zone(UmiBase, metaclass=Unique): +class Zone(UmiBase): """Class containing HVAC settings: Conditioning, Domestic Hot Water, Loads, Ventilation, adn Consructions .. image:: ../images/template/zoneinfo-zone.png """ + _cache = {} + def __init__( self, Conditioning=None, @@ -92,6 +94,9 @@ def __init__( self._area = kwargs.get("area", None) self._volume = kwargs.get("volume", None) + self._cache[hash(self)] = self + CREATED_OBJECTS[hash(self)] = self + def __add__(self, other): """ Args: @@ -101,7 +106,7 @@ def __add__(self, other): return self.combine(other) def __hash__(self): - return hash((self.Name, self.idf.name)) + return hash((self.Name, id(self.idf))) def __eq__(self, other): if not isinstance(other, Zone): @@ -322,20 +327,19 @@ def from_json(cls, *args, **kwargs): return zone @classmethod - def from_zone_epbunch(cls, zone_ep, **kwargs): + def from_zone_epbunch(cls, zone_ep, sql): """Create a Zone object from an eppy 'ZONE' epbunch. Args: - zone_ep (EpBunch): - **kwargs: + zone_ep (EpBunch): The Zone EpBunch. + sql (dict): The sql dict for this IDF object. """ - cached = cls.get_cached(zone_ep.Name) + cached = cls.get_cached(zone_ep.Name, zone_ep.theidf) if cached: return cached start_time = time.time() log('\nConstructing :class:`Zone` for zone "{}"'.format(zone_ep.Name)) name = zone_ep.Name - sql = kwargs.get("sql") zone = cls( Name=name, idf=zone_ep.theidf, @@ -362,14 +366,16 @@ def from_zone_epbunch(cls, zone_ep, **kwargs): return zone @classmethod - def get_cached(cls, name): - """Retrieve the cached object by Name. If not, returns None. + def get_cached(cls, name, idf): + """Retrieve the cached object by Name and idf name. If not, returns + None. Args: name (str): The name of the object in the cache. + idf (IDF): The :class:`IDF` object. """ try: - cached = cls._cache[(cls.mro()[0].__name__, name)] + cached = cls._cache[hash((name, id(idf)))] except KeyError: return None else: @@ -751,7 +757,7 @@ def __add__(self, other): return self.combine(other) def __hash__(self): - return hash((self.Name, self.idf.name)) + return hash((self.__class__.__name__, self.Name)) def __eq__(self, other): if not isinstance(other, ZoneConstructionSet): @@ -778,6 +784,7 @@ def combine(self, other, weights=None): Args: other (ZoneConstructionSet): + weights: Returns: (ZoneConstructionSet): the combined ZoneConstructionSet object. diff --git a/tests/test_cli.py b/tests/test_cli.py index 4f5915cbb..b2f5682b3 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -15,8 +15,8 @@ def test_reduce(self, config): necb = Path("tests/input_data/necb") test_file = examples / "2ZoneDataCenterHVAC_wEconomizer.idf" test_file_list = [ - "tests/input_data/trnsys/ASHRAE90.1_Warehouse_STD2004_Rochester.idf", - "tests/input_data/trnsys/ASHRAE90.1_RestaurantSitDown_STD2004_Rochester.idf", + "tests/input_data/umi_samples/B_Off_0.idf", + "tests/input_data/umi_samples/B_Res_0_Masonry.idf", ] test_files = necb.glob("*Retail*.idf") result = runner.invoke( diff --git a/tests/test_template.py b/tests/test_template.py index 272cc7685..2e7b4ff28 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -13,7 +13,7 @@ def small_idf(config): file = "tests/input_data/umi_samples/B_Off_0.idf" w = "tests/input_data/CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw" idf = ar.load_idf(file) - sql = ar.run_eplus( + sql: dict = ar.run_eplus( file, weather_file=w, prep_outputs=True, @@ -331,6 +331,7 @@ def test_iadd_glazing_material(self): def test_hash_eq_glaz_mat(self, config): """Test equality and hashing of :class:`OpaqueConstruction`""" from copy import copy + sg_a = ar.calc_simple_glazing(0.763, 2.716, 0.812) mat_a = ar.GlazingMaterial(Name="mat_ia", **sg_a) mat_b = copy(mat_a) @@ -854,9 +855,11 @@ def test_iadd_zoneconstructionset(self, small_idf): zone_core = idf.getobject("ZONE", core_name) zone_perim = idf.getobject("ZONE", perim_name) - z_core = ar.ZoneConstructionSet.from_zone(ar.Zone.from_zone_epbunch(zone_core)) + z_core = ar.ZoneConstructionSet.from_zone( + ar.Zone.from_zone_epbunch(zone_core, sql=sql) + ) z_perim = ar.ZoneConstructionSet.from_zone( - ar.Zone.from_zone_epbunch(zone_perim) + ar.Zone.from_zone_epbunch(zone_perim, sql=sql) ) id_ = z_core.id z_core += z_perim @@ -1589,8 +1592,8 @@ def test_iadd_zone(self, small_idf): zone_core = idf.getobject("ZONE", core_name) zone_perim = idf.getobject("ZONE", perim_name) - z_core = ar.Zone.from_zone_epbunch(zone_core) - z_perim = ar.Zone.from_zone_epbunch(zone_perim) + z_core = ar.Zone.from_zone_epbunch(zone_core, sql=sql) + z_perim = ar.Zone.from_zone_epbunch(zone_perim, sql=sql) volume = z_core.volume + z_perim.volume # save volume before changing area = z_core.area + z_perim.area # save area before changing @@ -1610,7 +1613,7 @@ def test_hash_eq_zone(self, small_idf): from archetypal.template import Zone from copy import copy - idf, sql = small_idf + idf, sql = map(deepcopy, small_idf) clear_cache() zone_ep = idf.idfobjects["ZONE"][0] zone = Zone.from_zone_epbunch(zone_ep, sql=sql) @@ -1664,7 +1667,7 @@ def test_hash_eq_zone(self, small_idf): assert idf is not idf_2 assert zone_ep is not zone_ep_3 assert zone_ep != zone_ep_3 - assert hash(zone) == hash(zone_3) + assert hash(zone) != hash(zone_3) assert id(zone) != id(zone_3) assert zone is not zone_3 assert zone == zone_3