Turbine simulation walkthrough
Now that we have run our precursor simulation and saved inflow boundary condition data, we can run a turbine simulation. Here is the input file:
1#¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨#
2# SIMULATION CONTROL #
3#.......................................#
4time.stop_time = 7800.0 # Max (simulated) time to evolve [s]
5time.max_step = -1 # Max number of time steps; -1 means termination set by timestamps
6time.fixed_dt = 0.12 # Use this constant dt if > 0
7time.cfl = 0.95 # CFL factor
8
9time.plot_interval = 1250 # Steps between plot files
10time.checkpoint_interval = 1250 # Steps between checkpoint files
11ABL.bndry_file = ../precursor/bndry_file.native
12ABL.bndry_io_mode = 1 # 0 = write, 1 = read
13ABL.bndry_planes = xlo
14ABL.bndry_output_start_time = 7200.0
15ABL.bndry_var_names = velocity temperature tke
16
17incflo.physics = ABL Actuator
18io.restart_file = ../spinup/chk14400
19io.outputs = "actuator_src_term"
20turbulence.model = OneEqKsgsM84 # For neutral ABL, use "Smagorinsky"
21TKE.source_terms = KsgsM84Src
22incflo.gravity = 0. 0. -9.81 # Gravitational force (3D)
23incflo.density = 1.225 # Reference density; make sure this agrees with OpenFAST values
24transport.viscosity = 1.0e-5 # Dynamic viscosity [N-s/m^2]
25transport.laminar_prandtl = 0.7
26transport.turbulent_prandtl = 0.3333
27
28#¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨#
29# GEOMETRY & BCs #
30#.......................................#
31geometry.prob_lo = 0. 0. 0. # Lo corner coordinates
32geometry.prob_hi = 2560. 2560. 1280. # Hi corner coordinates
33amr.n_cell = 128 128 64 # Grid cells at coarsest AMRlevel
34amr.max_level = 2 # Max AMR level in hierarchy
35geometry.is_periodic = 0 0 0 # Periodicity x y z (0/1)
36
37xlo.type = mass_inflow
38xlo.density = 1.225
39xlo.temperature = 290.0
40xlo.tke = 0.0
41xhi.type = pressure_outflow
42
43ylo.type = slip_wall
44yhi.type = slip_wall
45
46zlo.type = wall_model
47zhi.type = slip_wall
48zhi.temperature_type = fixed_gradient
49zhi.temperature = 0.003
50
51#¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨#
52# PHYSICS #
53#.......................................#
54ICNS.source_terms = BoussinesqBuoyancy CoriolisForcing BodyForce ABLMeanBoussinesq ActuatorForcing
55##--------- Additions by calc_inflow_stats.py ---------#
56ABL.wall_shear_stress_type = "local"
57ABL.inflow_outflow_mode = true
58ABL.wf_velocity = 7.612874161922772 0.05167978610261268
59ABL.wf_vmag = 7.655337919472146
60ABL.wf_theta = 291.0187046202964
61# BodyForce.magnitude = 0.00034839850789284793 0.0009712494385077595 0.0
62BoussinesqBuoyancy.read_temperature_profile = true
63BoussinesqBuoyancy.tprofile_filename = avg_theta.dat
64##-----------------------------------------------------#
65BodyForce.uniform_timetable_file = "../precursor/abl_forces.txt"
66incflo.velocity = 10.0 0.0 0.0
67ABLForcing.abl_forcing_height = 86.5
68CoriolisForcing.latitude = 36.607322 # Southern Great Plains
69CoriolisForcing.north_vector = 0.0 1.0 0.0
70CoriolisForcing.east_vector = 1.0 0.0 0.0
71BoussinesqBuoyancy.reference_temperature = 290.0
72ABL.reference_temperature = 290.0
73ABL.temperature_heights = 0.0 600.0 700.0 1700.0 # Make sure top height >= the domain height
74ABL.temperature_values = 290.0 290.0 298.0 301.0
75ABL.perturb_temperature = true
76ABL.cutoff_height = 50.0
77ABL.perturb_velocity = true
78ABL.perturb_ref_height = 50.0
79ABL.Uperiods = 4.0
80ABL.Vperiods = 4.0
81ABL.deltaU = 1.0
82ABL.deltaV = 1.0
83ABL.kappa = .40
84ABL.surface_roughness_z0 = 0.01 # [m]
85ABL.surface_temp_flux = 0.05 # Surface temperature flux [K-m/s]
86
87#¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨#
88# POST-Processing #
89#.......................................#
90incflo.post_processing = sampling averaging
91
92# --- Sampling parameters ---
93sampling.output_frequency = 100
94sampling.fields = velocity temperature
95
96#---- sample defs ----
97sampling.labels = xy-domain xz-domain
98
99sampling.xy-domain.type = PlaneSampler
100sampling.xy-domain.num_points = 256 256
101sampling.xy-domain.origin = 0.0 0.0 91.0
102sampling.xy-domain.axis1 = 2550.0 0.0 0.0
103sampling.xy-domain.axis2 = 0.0 2550.0 0.0
104sampling.xy-domain.offset_vector = 0.0 0.0 1.0
105sampling.xy-domain.offsets = -63.45 0.0 63.45
106
107sampling.xz-domain.type = PlaneSampler
108sampling.xz-domain.num_points = 256 128
109sampling.xz-domain.origin = 0.0 1280.0 0.0
110sampling.xz-domain.axis1 = 2550.0 0.0 0.0
111sampling.xz-domain.axis2 = 0.0 0.0 1270.0
112
113#¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨#
114# AVERAGING #
115#.......................................#
116averaging.type = TimeAveraging
117averaging.labels = means stress
118
119averaging.averaging_window = 60.0
120averaging.averaging_start_time = 7200.0
121
122averaging.means.fields = velocity
123averaging.means.averaging_type = ReAveraging
124
125averaging.stress.fields = velocity
126averaging.stress.averaging_type = ReynoldsStress
127
128#¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨#
129# MESH REFINEMENT #
130#.......................................#
131tagging.labels = T0_level_0_zone T1_level_0_zone T2_level_0_zone T0_level_1_zone T1_level_1_zone T2_level_1_zone
132
133# 1st refinement level
134tagging.T0_level_0_zone.type = GeometryRefinement
135tagging.T0_level_0_zone.shapes = T0_level_0_zone
136tagging.T0_level_0_zone.level = 0
137tagging.T0_level_0_zone.T0_level_0_zone.type = box
138tagging.T0_level_0_zone.T0_level_0_zone.origin = 520.0 1040.0 0.0 # -1D, -2D
139tagging.T0_level_0_zone.T0_level_0_zone.xaxis = 360.0 0.0 0.0
140tagging.T0_level_0_zone.T0_level_0_zone.yaxis = 0.0 480.0 0.0
141tagging.T0_level_0_zone.T0_level_0_zone.zaxis = 0.0 0.0 360.0
142
143tagging.T1_level_0_zone.type = GeometryRefinement
144tagging.T1_level_0_zone.shapes = T1_level_0_zone
145tagging.T1_level_0_zone.level = 0
146tagging.T1_level_0_zone.T1_level_0_zone.type = box
147tagging.T1_level_0_zone.T1_level_0_zone.origin = 1160.0 1040.0 0.0 # -1D, -2D
148tagging.T1_level_0_zone.T1_level_0_zone.xaxis = 360.0 0.0 0.0
149tagging.T1_level_0_zone.T1_level_0_zone.yaxis = 0.0 480.0 0.0
150tagging.T1_level_0_zone.T1_level_0_zone.zaxis = 0.0 0.0 360.0
151
152tagging.T2_level_0_zone.type = GeometryRefinement
153tagging.T2_level_0_zone.shapes = T2_level_0_zone
154tagging.T2_level_0_zone.level = 0
155tagging.T2_level_0_zone.T2_level_0_zone.type = box
156tagging.T2_level_0_zone.T2_level_0_zone.origin = 1800.0 1040.0 0.0 # -1D, -2D
157tagging.T2_level_0_zone.T2_level_0_zone.xaxis = 360.0 0.0 0.0
158tagging.T2_level_0_zone.T2_level_0_zone.yaxis = 0.0 480.0 0.0
159tagging.T2_level_0_zone.T2_level_0_zone.zaxis = 0.0 0.0 360.0
160
161# 2nd refinement level
162tagging.T0_level_1_zone.type = GeometryRefinement
163tagging.T0_level_1_zone.shapes = T0_level_1_zone
164tagging.T0_level_1_zone.level = 1
165tagging.T0_level_1_zone.T0_level_1_zone.type = box
166tagging.T0_level_1_zone.T0_level_1_zone.origin = 580.0 1100.0 20.0 # -0.5D, -1.5D
167tagging.T0_level_1_zone.T0_level_1_zone.xaxis = 180.0 0.0 0.0
168tagging.T0_level_1_zone.T0_level_1_zone.yaxis = 0.0 360.0 0.0
169tagging.T0_level_1_zone.T0_level_1_zone.zaxis = 0.0 0.0 180.0
170
171tagging.T1_level_1_zone.type = GeometryRefinement
172tagging.T1_level_1_zone.shapes = T1_level_1_zone
173tagging.T1_level_1_zone.level = 1
174tagging.T1_level_1_zone.T1_level_1_zone.type = box
175tagging.T1_level_1_zone.T1_level_1_zone.origin = 1220.0 1100.0 20.0 # -0.5D, -1.5D
176tagging.T1_level_1_zone.T1_level_1_zone.xaxis = 180.0 0.0 0.0
177tagging.T1_level_1_zone.T1_level_1_zone.yaxis = 0.0 360.0 0.0
178tagging.T1_level_1_zone.T1_level_1_zone.zaxis = 0.0 0.0 180.0
179
180tagging.T2_level_1_zone.type = GeometryRefinement
181tagging.T2_level_1_zone.shapes = T2_level_1_zone
182tagging.T2_level_1_zone.level = 1
183tagging.T2_level_1_zone.T2_level_1_zone.type = box
184tagging.T2_level_1_zone.T2_level_1_zone.origin = 1860.0 1100.0 20.0 # -0.5D, -1.5D
185tagging.T2_level_1_zone.T2_level_1_zone.xaxis = 180.0 0.0 0.0
186tagging.T2_level_1_zone.T2_level_1_zone.yaxis = 0.0 360.0 0.0
187tagging.T2_level_1_zone.T2_level_1_zone.zaxis = 0.0 0.0 180.0
188
189#¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨#
190# TURBINES #
191#.......................................#
192Actuator.labels = T0 T1 T2
193
194Actuator.TurbineFastDisk.density = 1.225 # AirDens in OpenFAST models
195
196Actuator.T0.type = TurbineFastDisk
197Actuator.T0.openfast_input_file = T0_OpenFAST/NREL-2p8-127.fst
198Actuator.T0.base_position = 640.0 1280.0 0.0
199Actuator.T0.rotor_diameter = 126.9
200Actuator.T0.hub_height = 86.5
201Actuator.T0.num_points_blade = 64
202Actuator.T0.num_points_tower = 12
203Actuator.T0.epsilon = 5.0 5.0 5.0
204Actuator.T0.epsilon_tower = 5.0 5.0 5.0
205Actuator.T0.openfast_start_time = 0.0
206Actuator.T0.openfast_stop_time = 99999.0
207Actuator.T0.nacelle_drag_coeff = 0.0
208Actuator.T0.nacelle_area = 0.0
209Actuator.T0.yaw = 0.0
210Actuator.T0.output_frequency = 10
211
212Actuator.T1.type = TurbineFastDisk
213Actuator.T1.openfast_input_file = T1_OpenFAST/NREL-2p8-127.fst
214Actuator.T1.base_position = 1280.0 1280.0 0.0
215Actuator.T1.rotor_diameter = 126.9
216Actuator.T1.hub_height = 86.5
217Actuator.T1.num_points_blade = 64
218Actuator.T1.num_points_tower = 12
219Actuator.T1.epsilon = 5.0 5.0 5.0
220Actuator.T1.epsilon_tower = 5.0 5.0 5.0
221Actuator.T1.openfast_start_time = 0.0
222Actuator.T1.openfast_stop_time = 99999.0
223Actuator.T1.nacelle_drag_coeff = 0.0
224Actuator.T1.nacelle_area = 0.0
225Actuator.T1.yaw = 0.0
226Actuator.T1.output_frequency = 10
227
228Actuator.T2.type = TurbineFastDisk
229Actuator.T2.openfast_input_file = T2_OpenFAST/NREL-2p8-127.fst
230Actuator.T2.base_position = 1920.0 1280.0 0.0
231Actuator.T2.rotor_diameter = 126.9
232Actuator.T2.hub_height = 86.5
233Actuator.T2.num_points_blade = 64
234Actuator.T2.num_points_tower = 12
235Actuator.T2.epsilon = 5.0 5.0 5.0
236Actuator.T2.epsilon_tower = 5.0 5.0 5.0
237Actuator.T2.openfast_start_time = 0.0
238Actuator.T2.openfast_stop_time = 99999.0
239Actuator.T2.nacelle_drag_coeff = 0.0
240Actuator.T2.nacelle_area = 0.0
241Actuator.T2.yaw = 0.0
242Actuator.T2.output_frequency = 10
This file looks like the precursor input file, except for the following changes:
We are now reading in boundary condition data, not writing it out. Similarly, the x- and y- boundaries are no longer periodic, requiring us to specify some extra characteristics about the inflow.
We added the line
io.outputs="actuator_src_term"
to output relevant information about the location of the turbines for plotting purposesincflo.physics
now includesActuator
so that turbine-related calculations can take place.Similarly, we remove
ABLForcing
fromICNS.source_terms
, and we replace it with three new forces:BodyForce ABLMeanBoussinesq ActuatorForcing
. We then provide extra information about these forces. More on how to calculate these values down below.We also add details under
ABL
to inform the wall function.We add information about the turbines and surrounding mesh refinements into the input file. Because of the finer mesh, the small dt is required for this simulation.
time.fixed_dt
is changed from 0.125 to 0.12. Although 0.125 is sufficient for the finer mesh in this simulation, there is an additional constraint of compatibility with the OpenFAST turbine model. The AMR-Wind time step size must be an integer multiple of the recommended time step size of the turbine model (written asDT
in the.fst
OpenFAST input). For this turbine model, the recommended DT is 0.01, requiring a change to the AMR-Wind dt. Due to this change in the time step size, we have also adjusted the plotting, checkpoint, and sampling intervals.
Further details about these source terms and wall function (wf) specifications
Now that this simulation is not periodic and will feature turbine wakes, the previous approach to forcing the velocity field (ABLForcing) is not viable. In addition to the inflow information from the precursor simulation, we also want to mimic the forcing terms. This is the purpose of BodyForce.
ABLMeanBoussinesq modifies the BoussinesqBuoyancy term to make it reference a profile of average temperature values instead of a single reference temperature. Instead of modifying the physics, this term is intended to modify the pressure distribution to make it more compatible with the outflow condition.
ActuatorForcing indicates to the momentum equation that terms from the actuator turbine model should be included. Both the Actuator physics instance and the source_term specification need to be present for the simulation to run as intended.
Similar to how ABLForcing is not a viable option in an inflow-outflow simulation with turbines, wall function implementations that rely on planar averages are not viable either. Instead, time averages of planar-averaged precursor data from is passed to the inflow-outflow simulation to inform that wall model.
When running an inflow-outflow simulation, information must be provided for the new source terms and the wall function. These
values are intended to be statistical data calculated from the precursor simulation. During the precursor, ABL Stats
output planar-averaged data at discrete times. To average this data in time and obtain values for the input arguments,
applying a script is the most direct approach. For this step, run the calc_inflowoutflow_stats.py
script, provided in
the tools directory of the AMR-Wind repo, as follows:
python <path to amr-wind repo>/tools/calc_inflowoutflow_stats.py -sf <path to precursor directory>/post_processing/abl_statistics14400.nc -ts 7200 -te 7800
The -ts
and -te
arguments communicate the start time and end time for the time averaging, respectively. Running the
script provides two important things:
Complete input lines to be copy-pasted into the turbine input file, which feature time-averaged information
An
avg_theta.dat
file with an average temperature profile forABLMeanBoussinesq
. It is critical that this file is available where the job will run; otherwise, AMR-Wind will fail by not being able to find the file.
Although the script provides a value for the BodyForce.magnitude
, this has been commented out after being pasted into the
input file. This is because we prefer to use a timetable for the body force, which was output by the precursor simulation.
If the forcing_timetable_output_file
argument is not used in a precursor simulation setup, this timetable file will not be available.
In that case, using a constant, uniform body force vector is the standard approach, and this script provides the
time-averaged value from the precursor to populate BodyForce.magnitude
.
Because we are simulating turbines, OpenFAST files need to be included for each of the turbines. In this walkthrough, we use 2.8 MW turbines from NREL’s open source turbine repository, and the specific turbine model is here.
Instead of cloning the entire repository of OpenFAST turbine models, we can clone only what is needed to run the full turbine simulation and then copy it to the run directory.
First, clone the repository as empty and enter its directory.
git clone -n --depth=1 --filter=tree:0 https://github.com/NREL/openfast-turbine-models.git && cd openfast-turbine-models
Then, check out only the path that we need.
git sparse-checkout set --no-cone IEA-scaled/NREL-2.8-127/20_monolithic_opt2/OpenFAST && git checkout
cd IEA-scaled/NREL-2.8-127/20_monolithic_opt2/OpenFAST/
Note that, when simulating OpenFAST turbines through AMR-Wind instead of directly through OpenFAST, it is important to make the following changes to the OpenFAST files:
NREL-2p8-127_AeroDyn15.dat
: Make sureWakeMod
is0
NREL-2p8-127_ElastoDyn.dat
: Set the initial RPMRotSpeed
and initial yaw angleNacYaw
to reasonable values (in this walkthrough we use13.5
RPM and0
degrees, respectively)NREL-2p8-127.fst
: SetCompInflow
to be2
andOutFileFmt
to be1
NREL-2p8-127_ServoDyn.dat
: Make sureDLL_FileName
points to alibdiscon.so
(on Linux) orlibdiscon.dylib
(on Mac) file from ROSCO. If you compiled using exawind-manager, see the section of the documentation that discusses how to determine the correct path to this ROSCO library file.
After performing these changes, copy the OpenFAST model to the run directory using the names in the AMR-Wind input file.
cd ..
cp -r OpenFAST/ <path to turbine run directory>/T0_OpenFAST
cp -r OpenFAST/ <path to turbine run directory>/T1_OpenFAST
cp -r OpenFAST/ <path to turbine run directory>/T2_OpenFAST
When all of these steps are complete, the job directory for running the turbine simulation includes
avg_theta.dat T0_OpenFAST/ T1_OpenFAST/ T2_OpenFAST/ turbines.inp
and the turbine inflow-outflow simulation is ready to be submitted.
Below we show the magnitude of the velocity at t=7350s (top) and at the final time
(bottom) of the simulation. In both images, the three turbines are displayed as 3D
contours of the actuator_src_termx
field. Note the turbine-induced wakes
and their impact on the incoming flow for the downstream turbines.