From 7a1a0d3e23a9090f9530debe984cd283286a40ed Mon Sep 17 00:00:00 2001 From: misi9170 Date: Tue, 20 Aug 2024 12:16:49 -0600 Subject: [PATCH] Instantiate Turbine objects only once for each different type of turbine. --- floris/core/farm.py | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/floris/core/farm.py b/floris/core/farm.py index 58b29637b..c9000378a 100644 --- a/floris/core/farm.py +++ b/floris/core/farm.py @@ -120,6 +120,10 @@ class Farm(BaseClass): internal_turbine_library: Path = field(init=False, default=default_turbine_library_path) + # Private attributes + _turbine_types: List = field(init=False, validator=iter_validator(list, str)) + _turbine_definition_cache: dict = field(init=False) + def __attrs_post_init__(self) -> None: # Turbine definitions can be supplied in three ways: # - A string selecting a turbine in the floris turbine library @@ -134,21 +138,27 @@ def __attrs_post_init__(self) -> None: # This allows to read the yaml input files once rather than every time they're given. # In other words, if the turbine type is already in the cache, skip that iteration of # the for-loop. + + # TODO: How can we give each one a different name, to avoid the issue raised in #864? turbine_definition_cache = {} + # Is this loop slow if turbine_type is long? for t in self.turbine_type: # If a turbine type is a dict, then it was either preprocessed by the yaml # library to resolve the "!include" or it was set in a script as a dict. In either case, # add an entry to the cache if isinstance(t, dict): + # TODO: If this check was more robust, could avoid needing to change the name of + # each turbine. Would then need to update the name somehow to handle the case where + # the "turbine_type" key is the same if t["turbine_type"] in turbine_definition_cache: - continue + continue # Skip t if already loaded turbine_definition_cache[t["turbine_type"]] = t # If a turbine type is a string, then it is expected in the internal or external # turbine library if isinstance(t, str): if t in turbine_definition_cache: - continue + continue # Skip t if already loaded # Check if the file exists in the internal and/or external library internal_fn = (self.internal_turbine_library / t).with_suffix(".yaml") @@ -184,25 +194,28 @@ def __attrs_post_init__(self) -> None: # types must be used. If we modify that directly and change its shape, recreating this # class with a different layout but not a new self.turbine_type could cause the data # to be out of sync. - _turbine_types = [ + self._turbine_types = [ copy.deepcopy(t["turbine_type"]) if isinstance(t, dict) else t for t in self.turbine_type ] # If 1 turbine definition is given, expand to N turbines; this covers a 1-turbine # farm and 1 definition for multiple turbines - if len(_turbine_types) == 1: - _turbine_types *= self.n_turbines + if len(self._turbine_types) == 1: + self._turbine_types *= self.n_turbines # Check that turbine definitions contain any v3 keys - for t in _turbine_types: - check_turbine_definition_for_v3_keys(turbine_definition_cache[t]) + for _, v in turbine_definition_cache.items(): + check_turbine_definition_for_v3_keys(v) # Map each turbine definition to its index in this list self.turbine_definitions = [ - copy.deepcopy(turbine_definition_cache[t]) for t in _turbine_types + copy.deepcopy(turbine_definition_cache[t]) for t in self._turbine_types ] + # Save the turbine_definition_cache for faster operation when generating the turbine_map + self._turbine_definition_cache = turbine_definition_cache + @layout_x.validator def check_x(self, attribute: attrs.Attribute, value: Any) -> None: if len(value) != len(self.layout_y): @@ -285,7 +298,13 @@ def construct_turbine_correct_cp_ct_for_tilt(self): ) def construct_turbine_map(self): - self.turbine_map = [Turbine.from_dict(turb) for turb in self.turbine_definitions] + # The line below is slow and often unnecessary. We should just be loading + # each different turbine type, _not_ each turbine. + #self.turbine_map = [Turbine.from_dict(turb) for turb in self.turbine_definitions] + turbine_map_unique = { + k: Turbine.from_dict(v) for k, v in self._turbine_definition_cache.items() + } + self.turbine_map = [turbine_map_unique[k] for k in self._turbine_types] def construct_turbine_thrust_coefficient_functions(self): self.turbine_thrust_coefficient_functions = {