@propertydefmodel_id(self):""" Unique id based on the model type. Returns ------- int Unique id of the model object """returnself._id
[docs]defvar(self,v):""" Creates a model specific variable name. Parameters ---------- v: str The variable name Returns ------- str Model specific variable name """returnf"{self.name}_{v}"
@propertydefinitialized(self):""" Initialization flag. Returns ------- bool : True if the model has been initialized. """returnself.__initialized
[docs]defsub_models(self):""" List of all sub-models Returns ------- smdls: list of foxes.core.Model All sub models """return[]
[docs]defload_data(self,algo,verbosity=0):""" Load and/or create all model data that is subject to chunking. Such data should not be stored under self, for memory reasons. The data returned here will automatically be chunked and then provided as part of the mdata object during calculations. Parameters ---------- algo: foxes.core.Algorithm The calculation algorithm verbosity: int The verbosity level, 0 = silent Returns ------- idata: dict The dict has exactly two entries: `data_vars`, a dict with entries `name_str -> (dim_tuple, data_ndarray)`; and `coords`, a dict with entries `dim_name_str -> dim_array` """ifself.initialized:raiseValueError(f"Model '{self.name}': Cannot call load_data after initialization")return{"coords":{},"data_vars":{}}
[docs]definitialize(self,algo,verbosity=0,force=False):""" Initializes the model. Parameters ---------- algo: foxes.core.Algorithm The calculation algorithm verbosity: int The verbosity level, 0 = silent force: bool Overwrite existing data """ifself.running:raiseValueError(f"Model '{self.name}': Cannot initialize while running")ifnotself.initialized:pr=Falseforminself.sub_models():ifnotm.initialized:ifverbosity>1andnotpr:print(f">> {self.name}: Starting sub-model initialization >> ")pr=Truem.initialize(algo,verbosity)ifpr:print(f"<< {self.name}: Finished sub-model initialization << ")ifverbosity>0:print(f"Initializing model '{self.name}'")algo.store_model_data(self,self.load_data(algo,verbosity),force)self.__initialized=True
@propertydefrunning(self):""" Flag for currently running models Returns ------- flag: bool True if currently running """returnself.__running
[docs]defset_running(self,algo,data_stash,sel=None,isel=None,verbosity=0,):""" Sets this model status to running, and moves all large data to stash. The stashed data will be returned by the unset_running() function after running calculations. Parameters ---------- algo: foxes.core.Algorithm The calculation algorithm data_stash: dict Large data stash, this function adds data here. Key: model name. Value: dict, large model data sel: dict, optional The subset selection dictionary isel: dict, optional The index subset selection dictionary verbosity: int The verbosity level, 0 = silent """ifself.running:raiseValueError(f"Model '{self.name}': Cannot call set_running while running")forminself.sub_models():ifnotm.running:m.set_running(algo,data_stash,sel,isel,verbosity=verbosity)ifverbosity>0:print(f"Model '{self.name}': running")ifself.namenotindata_stash:data_stash[self.name]={}self.__running=True
[docs]defunset_running(self,algo,data_stash,sel=None,isel=None,verbosity=0,):""" Sets this model status to not running, recovering large data from stash Parameters ---------- algo: foxes.core.Algorithm The calculation algorithm data_stash: dict Large data stash, this function adds data here. Key: model name. Value: dict, large model data sel: dict, optional The subset selection dictionary isel: dict, optional The index subset selection dictionary verbosity: int The verbosity level, 0 = silent """ifnotself.running:raiseValueError(f"Model '{self.name}': Cannot call unset_running when not running")forminself.sub_models():ifm.running:m.unset_running(algo,data_stash,sel,isel,verbosity=verbosity)ifverbosity>0:print(f"Model '{self.name}': not running")self.__running=False
[docs]deffinalize(self,algo,verbosity=0):""" Finalizes the model. Parameters ---------- algo: foxes.core.Algorithm The calculation algorithm verbosity: int The verbosity level, 0 = silent """ifself.running:raiseValueError(f"Model '{self.name}': Cannot finalize while running")ifself.initialized:pr=Falseforminself.sub_models():ifverbosity>1andnotpr:print(f">> {self.name}: Starting sub-model finalization >> ")pr=Truem.finalize(algo,verbosity)ifpr:print(f"<< {self.name}: Finished sub-model finalization << ")ifverbosity>0:print(f"Finalizing model '{self.name}'")algo.del_model_data(self)self.__initialized=False
[docs]defget_data(self,variable,target,lookup="smfp",mdata=None,fdata=None,tdata=None,downwind_index=None,accept_none=False,accept_nan=True,algo=None,upcast=False,selection=None,):""" Getter for a data entry in the model object or provided data sources Parameters ---------- variable: str The variable, serves as data key target: str, optional The dimensions identifier for the output, FC.STATE_TURBINE, FC.STATE_TARGET or FC.STATE_TARGET_TPOINT lookup: str The order of data sources. Combination of: 's' for self, 'm' for mdata, 'f' for fdata, 't' for tdata, 'w' for wake modelling data mdata: foxes.core.Data, optional The model data fdata: foxes.core.Data, optional The farm data tdata: foxes.core.Data, optional The target point data downwind_index: int, optional The index in the downwind order data_prio: bool First search the data source, then the object accept_none: bool Do not throw an error if data entry is None accept_nan: bool Do not throw an error if data entry is np.nan algo: foxes.core.Algorithm, optional The algorithm, needed for data from previous iteration upcast: bool Flag for ensuring targets dimension, otherwise dimension 1 is entered selection: numpy.ndarray, optional Apply this selection to the result, state-turbine, state-target, or state-target-tpoint """def_geta(a):sources=[sforsin[mdata,fdata,tdata,algo,self]ifsisnotNone]forsinsources:try:ifa=="states_i0":out=s.states_i0(counter=True)ifoutisnotNone:returnoutelse:out=getattr(s,a)ifoutisnotNone:returnoutexceptAttributeError:passraiseKeyError(f"Model '{self.name}': Failed to determine '{a}'. Maybe add to arguments of get_data: mdata, fdata, tdata, algo?")n_states=_geta("n_states")iftarget==FC.STATE_TURBINE:ifdownwind_indexisnotNone:raiseValueError(f"Target '{target}' is incompatible with downwind_index (here {downwind_index})")n_turbines=_geta("n_turbines")dims=(FC.STATE,FC.TURBINE)shp=(n_states,n_turbines)eliftarget==FC.STATE_TARGET:n_targets=_geta("n_targets")dims=(FC.STATE,FC.TARGET)shp=(n_states,n_targets)eliftarget==FC.STATE_TARGET_TPOINT:n_targets=_geta("n_targets")n_tpoints=_geta("n_tpoints")dims=(FC.STATE,FC.TARGET,FC.TPOINT)shp=(n_states,n_targets,n_tpoints)else:raiseKeyError(f"Model '{self.name}': Wrong parameter 'target = {target}'. Choices: {FC.STATE_TURBINE}, {FC.STATE_TARGET}, {FC.STATE_TARGET_TPOINT}")def_match_shape(a):out=np.asarray(a)iflen(out.shape)<len(shp):fori,sinenumerate(shp):ifi>=len(out.shape):out=out[...,None]elifa.shape[i]notin(1,s):raiseValueError(f"Shape mismatch for '{variable}': Got {out.shape}, expecting {shp}")eliflen(out.shape)>len(shp):raiseValueError(f"Shape mismatch for '{variable}': Got {out.shape}, expecting {shp}")returnoutdef_filter_dims(source):a=source[variable]a_dims=tuple(source.dims[variable])ifdownwind_indexisNoneorFC.TURBINEnotina_dims:d=a_dimselse:slc=tuple([downwind_indexifdd==FC.TURBINEelsenp.s_[:]forddina_dims])a=a[slc]d=tuple([ddforddina_dimsifdd!=FC.TURBINE])returna,dout=Noneforsinlookup:# lookup self:ifs=="s"andhasattr(self,variable):a=getattr(self,variable)ifaisnotNone:out=_match_shape(a)# lookup mdata:elifs=="m"andmdataisnotNoneandvariableinmdata:a,d=_filter_dims(mdata)l=len(d)ifl<=len(dims)andd==dims[:l]:out=_match_shape(mdata[variable])# lookup fdata:elif(s=="f"andfdataisnotNoneandvariableinfdataandtuple(fdata.dims[variable])==(FC.STATE,FC.TURBINE)):iftarget==FC.STATE_TURBINE:out=fdata[variable]elifdownwind_indexisnotNone:out=_match_shape(fdata[variable][:,downwind_index])# lookup pdata:elif(s=="t"andtarget!=FC.STATE_TURBINEandtdataisnotNoneandvariableintdata):a,d=_filter_dims(tdata)l=len(d)ifl<=len(dims)andd==dims[:l]:out=_match_shape(tdata[variable])# lookup wake modelling data:elif(s=="w"andfdataisnotNoneandtdataisnotNoneandvariableinfdataandtuple(fdata.dims[variable])==(FC.STATE,FC.TURBINE)anddownwind_indexisnotNoneandalgoisnotNone):out=_match_shape(algo.wake_frame.get_wake_modelling_data(algo,variable,downwind_index,fdata,tdata=tdata,target=target,))ifoutisnotNone:break# check for None:ifoutisNone:ifnotaccept_none:raiseValueError(f"Model '{self.name}': Variable '{variable}' is requested but not found.")returnout# data from other chunks, only with iterations:if(targetin[FC.STATE_TARGET,FC.STATE_TARGET_TPOINT]andfdataisnotNoneandvariableinfdataandtdataisnotNoneandFC.STATES_SELintdata):ifout.shape!=shp:# upcast to dims:tmp=np.zeros(shp,dtype=out.dtype)tmp[:]=outout=tmpdeltmpelse:out=out.copy()ifdownwind_indexisNone:raiseKeyError(f"Model '{self.name}': Require downwind_index for obtaining results from previous iteration")iftdata[FC.STATE_SOURCE_ORDERI]!=downwind_index:raiseValueError(f"Model '{self.name}': Expecting downwind_index {tdata[FC.STATE_SOURCE_ORDERI]}, got {downwind_index}")ifalgoisNone:raiseValueError(f"Model '{self.name}': Iteration data found for variable '{variable}', requiring algo")fromfoxes.algorithms.sequentialimportSequentialifisinstance(algo,Sequential):i0=algo.states.counterelse:i0=_geta("states_i0")sts=tdata[FC.STATES_SEL]iftarget==FC.STATE_TARGETandtdata.n_tpoints!=1:# find the mean index and round it to nearest integer:sts=tdata.tpoint_mean(FC.STATES_SEL)[:,:,None]sts=(sts+0.5).astype(config.dtype_int)sel=sts<i0ifnp.any(sel):ifnothasattr(algo,"farm_results_downwind"):raiseKeyError(f"Model '{self.name}': Iteration data found for variable '{variable}', requiring iterative algorithm")prev_fres=getattr(algo,"farm_results_downwind")ifprev_fresisnotNone:prev_data=prev_fres[variable].to_numpy()[sts[sel],downwind_index]iftarget==FC.STATE_TARGET:out[sel[:,:,0]]=prev_dataelse:out[sel]=prev_datadelprev_datadelprev_fresifnp.any(~sel):sts=sts[~sel]-i0sel_data=fdata[variable][sts,downwind_index]iftarget==FC.STATE_TARGET:out[~sel[:,:,0]]=sel_dataelse:out[~sel]=sel_datadelsel_datadelsel,sts# check for nan:ifnotaccept_nan:try:ifnp.all(np.isnan(np.atleast_1d(out))):raiseValueError(f"Model '{self.name}': Requested variable '{variable}' contains NaN values.")exceptTypeError:pass# apply selection:ifselectionisnotNone:def_upcast_sel(sel_shape):chp=[]fori,sinenumerate(out.shape):ifi<len(sel_shape)andsel_shape[i]>1:ifsel_shape[i]!=shp[i]:raiseValueError(f"Incompatible selection shape {sel_shape} for output shape {shp[i]}")chp.append(shp[i])else:chp.append(s)chp=tuple(chp)eshp=list(shp[len(sel_shape):])ifchp!=out.shape:nout=np.zeros(chp,dtype=out.dtype)nout[:]=outreturnnout,eshpreturnout,eshpifisinstance(selection,np.ndarray)andselection.dtype==bool:iflen(selection.shape)>len(out.shape):raiseValueError(f"Expecting selection of shape {out.shape}, got {selection.shape}")out,eshp=_upcast_sel(selection.shape)elifisinstance(selection,(tuple,list)):iflen(selection)>len(out.shape):raiseValueError(f"Selection is tuple/list of length {len(selection)}, expecting <= {len(out.shape)} ")out,eshp=_upcast_sel(shp[:len(selection)])else:raiseTypeError(f"Expecting selection of type np.ndarray (bool), or tuple, or list. Got {type(selection).__name__}")out=out[selection]shp=tuple([len(out)]+list(eshp))# apply upcast:ifupcastandout.shape!=shp:tmp=np.zeros(shp,dtype=out.dtype)tmp[:]=outout=tmpdeltmpreturnout