Turbine-based ambient flow data

In some cases the inflow data is directly known at the turbine locations, in terms of (state, turbine)-type data. Such data can be simulated with foxes by using the TurbinePointCloud states class, as demonstrated here.

We start by importing the necessary packages and setting up the engine:

%matplotlib inline
import numpy as np
from xarray import Dataset, date_range
import matplotlib.pyplot as plt

import foxes
import foxes.variables as FV
/home/runner/work/foxes/foxes/foxes/core/engine.py:6: TqdmExperimentalWarning: Using `tqdm.autonotebook.tqdm` in notebook mode. Use `tqdm.tqdm` instead to force console mode (e.g. in jupyter console)
  from tqdm.autonotebook import tqdm
engine = foxes.Engine.new("default", verbosity=0)

The first step is the creation of a regular grid of wind turbines:

n_turbines_x = 2
n_turbines_y = 3
n_turbines = n_turbines_x * n_turbines_y

farm = foxes.WindFarm()
foxes.input.farm_layout.add_grid(
    farm,
    xy_base=[1000, 1000],
    step_vectors=[[500, 0], [0, 500]],
    steps=[n_turbines_x, n_turbines_y],
    turbine_models=["DTU10MW"],
    verbosity=0,
)
o = foxes.output.FarmLayoutOutput(farm)
o.get_figure()
plt.show()
../_images/5ed3d18c567d16f8f8ede52271477aa46fcd732b9afc162822e90225ebcb838a.png

Next, let’s create an artificial Dataset object that represents data with (time, turbine) dimensions, representing a timeseries in January 2026 measured at he turbines of the above wind farm:

n_times = 10
times = date_range("2026-01-01", periods=n_times, freq="10min", unit="s")
times
DatetimeIndex(['2026-01-01 00:00:00', '2026-01-01 00:10:00',
               '2026-01-01 00:20:00', '2026-01-01 00:30:00',
               '2026-01-01 00:40:00', '2026-01-01 00:50:00',
               '2026-01-01 01:00:00', '2026-01-01 01:10:00',
               '2026-01-01 01:20:00', '2026-01-01 01:30:00'],
              dtype='datetime64[s]', freq='10min')
np.random.seed(42)

# Turbine i has wind speed (10 + i.x) m/s, where x grows linear with time
ws = np.zeros((n_times, n_turbines))
ws[:] = 10 + np.arange(n_turbines)[None, :]
ws += np.linspace(0, 1, n_times, endpoint=False)[:, None]

# the wind directions are randomly selected from a southern sector:
wd = np.random.uniform(0, 190, (n_times, n_turbines))

sdata = Dataset(
    coords={"time": times},
    data_vars={
        "ws": (("time", "turbine"), ws),
        "wd": (("time", "turbine"), wd),
        "ti": ("time", 0.1 + np.arange(n_times) / 100),
        "rho": ("turbine", 1.2 + np.arange(n_turbines) / 100),
    },
)
sdata
<xarray.Dataset> Size: 1kB
Dimensions:  (time: 10, turbine: 6)
Coordinates:
  * time     (time) datetime64[s] 80B 2026-01-01 ... 2026-01-01T01:30:00
Dimensions without coordinates: turbine
Data variables:
    ws       (time, turbine) float64 480B 10.0 11.0 12.0 13.0 ... 13.9 14.9 15.9
    wd       (time, turbine) float64 480B 71.16 180.6 139.1 ... 8.593 61.81
    ti       (time) float64 80B 0.1 0.11 0.12 0.13 0.14 0.15 0.16 0.17 0.18 0.19
    rho      (turbine) float64 48B 1.2 1.21 1.22 1.23 1.24 1.25

This is what the wind speed per turbine looks like, increasing linearly with time as described above:

for t in range(0, n_times):
    plt.scatter(sdata["turbine"], sdata["ws"][t])
plt.xticks(sdata["turbine"])
plt.xlabel("turbine")
plt.ylabel("ws [m/s]")
plt.show()
../_images/cdcde931bcb49c4a5b8be8aa36658abbd5dc7549bf26cf9480cefa0ecb826a90.png

The data frame sdata is the base for our ambient states object:

states = foxes.input.states.TurbinePointCloud(
    data_source=sdata,
    states_coord="time",
    turbine_coord="turbine",
    output_vars=[FV.WS, FV.WD, FV.TI, FV.RHO],
    var2ncvar={
        FV.WS: "ws",
        FV.WD: "wd",
        FV.TI: "ti",
        FV.RHO: "rho",
    },
)

Next, let’s create the algorithm object:

algo = foxes.algorithms.Downwind(
    farm=farm,
    states=states,
    wake_models=["TurbOPark"],
    rotor_model="centre",
)

We now run the farm calculation:

with engine:
    farm_results = algo.calc_farm()
Initializing model 'TurbinePointCloud'

Initializing algorithm 'Downwind'

------------------------------------------------------------
  Algorithm: Downwind
  Running Downwind: calc_farm
------------------------------------------------------------
  n_states : 10
  n_turbines: 6
------------------------------------------------------------
  states    : TurbinePointCloud()
  rotor     : CentreRotor()
  controller: BasicFarmController()
  deflection: NoDeflection()
  wake frame: RotorWD()
  wake lngth: None
------------------------------------------------------------
  wakes:
    0) TurbOPark: TurbOParkWake(ws_quadratic_amb_target, induction=Betz, k=0.04*AMB_TI)
------------------------------------------------------------
  partial wakes:
    0) TurbOPark: gaussian_lookup, PartialGaussianLookup(lookup_data=None, bounds_policy=clip)
------------------------------------------------------------
  ground models:
    0) TurbOPark: no_ground, NoGround()
------------------------------------------------------------
  turbine models:
    0) DTU10MW: PCtFile(D=178.3, H=119.0, P_nominal=10000.0, P_unit=kW, rho=1.225, var_ws_ct=REWS, var_ws_P=REWS)
------------------------------------------------------------


--------------------------------------------------
  Model oder
--------------------------------------------------
00) basic_ctrl
01) InitFarmData
02) centre
03) basic_ctrl
  03.0) Post-rotor: DTU10MW
04) SetAmbFarmResults
05) FarmWakesCalculation
06) ReorderFarmOutput
--------------------------------------------------


Input data:

 <xarray.Dataset> Size: 2kB
Dimensions:                  (state: 10, turbine: 6,
                              TurbinePointCloud_vars0: 2,
                              TurbinePointCloud_vars1: 1,
                              TurbinePointCloud_vars2: 1, DTU10MW_WS: 22,
                              DTU10MW_vars: 2, tmodels: 1)
Coordinates:
  * state                    (state) datetime64[s] 80B 2026-01-01 ... 2026-01...
  * TurbinePointCloud_vars0  (TurbinePointCloud_vars0) <U2 16B 'WS' 'WD'
  * TurbinePointCloud_vars1  (TurbinePointCloud_vars1) <U2 8B 'TI'
  * TurbinePointCloud_vars2  (TurbinePointCloud_vars2) <U3 12B 'RHO'
  * DTU10MW_WS               (DTU10MW_WS) float64 176B 4.0 5.0 6.0 ... 24.0 25.0
  * DTU10MW_vars             (DTU10MW_vars) <U2 16B 'P' 'CT'
  * tmodels                  (tmodels) <U7 28B 'DTU10MW'
Dimensions without coordinates: turbine
Data variables:
    TurbinePointCloud_data0  (state, turbine, TurbinePointCloud_vars0) float64 960B ...
    TurbinePointCloud_data1  (state, TurbinePointCloud_vars1) float64 80B 0.1...
    TurbinePointCloud_data2  (turbine, TurbinePointCloud_vars2) float64 48B 1...
    DTU10MW_data             (DTU10MW_WS, DTU10MW_vars) float64 352B 280.2 ..... 

Extra data:
  TurbinePointCloud_meta: dict
  PartialGaussianLookup_weights: Dataset

Farm variables: AMB_CT, AMB_P, AMB_REWS, AMB_RHO, AMB_TI, AMB_WD, AMB_YAW, CT, D, H, P, REWS, RHO, TI, WD, X, Y, YAW, order, weight

Output variables: AMB_CT, AMB_P, AMB_REWS, AMB_RHO, AMB_TI, AMB_WD, AMB_YAW, CT, D, H, P, REWS, RHO, TI, WD, X, Y, YAW, order, weight

The results demonstrate that all data was correctly passed from the flow states to ambient variables:

farm_results.to_dataframe()[
    [FV.AMB_WD, FV.AMB_RHO, FV.AMB_TI, FV.AMB_REWS, FV.REWS, FV.P]
]
AMB_WD AMB_RHO AMB_TI AMB_REWS REWS P
state turbine
2026-01-01 00:00:00 0 71.162623 1.20 0.10 9.931505 9.198688 5704.148546
1 180.635718 1.21 0.10 10.954918 10.921638 9509.307436
2 139.078849 1.22 0.10 11.983651 8.062609 3829.691653
3 113.745112 1.23 0.10 13.017663 13.017663 10648.337500
4 29.643542 1.24 0.10 14.056911 14.056911 10641.826857
5 29.638959 1.25 0.10 15.101354 15.101354 10679.473520
2026-01-01 00:10:00 0 11.035886 1.20 0.11 10.030820 10.030819 7360.829477
1 164.573468 1.21 0.11 11.054508 9.186246 5679.580514
2 114.211852 1.22 0.11 12.083515 11.027502 9724.173417
3 134.533790 1.23 0.11 13.117799 13.117799 10647.416250
4 3.911054 1.24 0.11 14.157318 14.157317 10646.284883
5 184.282872 1.25 0.11 15.202030 15.202030 10675.275344
2026-01-01 00:20:00 0 158.164102 1.20 0.12 10.130135 10.004665 7297.750428
1 40.344431 1.21 0.12 11.154098 11.152460 9841.734104
2 34.546744 1.22 0.12 12.183379 11.514014 10181.884571
3 34.846857 1.23 0.12 13.217935 13.217935 10646.495000
4 57.806026 1.24 0.12 14.257724 14.257724 10650.742955
5 99.703722 1.25 0.12 15.302706 15.302706 10671.077167
2026-01-01 00:30:00 0 82.069554 1.20 0.13 10.229450 9.951642 7191.006662
1 55.333537 1.21 0.13 11.253688 11.253550 9936.839887
2 116.252050 1.22 0.13 12.283242 12.228544 10641.248315
3 26.503834 1.23 0.13 13.318071 13.318071 10645.573750
4 55.507483 1.24 0.13 14.358131 14.358131 10655.201004
5 69.608750 1.25 0.13 15.403382 15.403382 10666.878991
2026-01-01 00:40:00 0 86.653297 1.20 0.14 10.328765 9.128814 5566.169067
1 149.183433 1.21 0.14 11.353278 11.310652 9990.561718
2 37.938019 1.22 0.14 12.383106 12.382248 10642.693132
3 97.704543 1.23 0.14 13.418207 13.276459 10645.956579
4 112.558768 1.24 0.14 14.458537 13.608445 10642.902305
5 8.825578 1.25 0.14 15.504057 15.504057 10662.680814
2026-01-01 00:50:00 0 115.433522 1.20 0.15 10.428080 10.428080 8318.943186
1 32.399584 1.21 0.15 11.452868 11.452868 10124.358519
2 12.359803 1.22 0.15 12.482970 12.482970 10643.639918
3 180.288252 1.23 0.15 13.518342 13.518342 10643.731250
4 183.470086 1.24 0.15 14.558944 12.261899 10641.561848
5 153.595496 1.25 0.15 15.604733 11.531734 10198.555427
2026-01-01 01:00:00 0 57.876616 1.20 0.16 10.527395 9.212636 5731.692778
1 18.557702 1.21 0.16 11.552458 11.552458 10218.052941
2 130.004275 1.22 0.16 12.582834 11.524180 10191.448633
3 83.628974 1.23 0.16 13.618478 13.590241 10643.069780
4 23.187265 1.24 0.16 14.659350 14.659350 10668.575151
5 94.083613 1.25 0.16 15.705409 15.705409 10654.284461
2026-01-01 01:10:00 0 6.533819 1.20 0.17 10.626710 10.562205 8642.426396
1 172.770876 1.21 0.17 11.652049 11.094124 9786.851649
2 49.168197 1.22 0.17 12.682698 11.987798 10627.620660
3 125.879234 1.23 0.17 13.718614 13.718614 10641.888750
4 59.225104 1.24 0.17 14.759757 14.759757 10673.033200
5 98.812924 1.25 0.17 15.806084 15.806084 10650.086285
2026-01-01 01:20:00 0 103.874953 1.20 0.18 10.726025 10.726025 9037.527277
1 35.122347 1.21 0.18 11.751639 11.544489 10210.554808
2 184.221079 1.22 0.18 12.782561 12.284639 10641.775603
3 147.275236 1.23 0.18 13.818750 13.818750 10640.967500
4 178.504799 1.24 0.18 14.860163 14.859555 10677.464224
5 170.017197 1.25 0.18 15.906760 14.156819 10646.262780
2026-01-01 01:30:00 0 113.600996 1.20 0.19 10.825340 10.824641 9275.369088
1 175.156105 1.21 0.19 11.851229 11.520412 10187.903829
2 16.813575 1.22 0.19 12.882425 12.880273 10647.374564
3 37.236744 1.23 0.19 13.918886 12.982224 10648.332903
4 8.593185 1.24 0.19 14.960570 14.960570 10681.949298
5 61.812763 1.25 0.19 16.007436 16.007436 10641.985129

For completeness, the states results at off-turbine evaluation points will be interpolated by point cloud methods.