Introduction
Most functions need an instance of the struct AtmosphericModel as first parameter, which can be created using the following code:
using AtmosphericModels, KiteUtils
set_data_path("data")
set = load_settings("system.yaml"; relax=true)
am::AtmosphericModel = AtmosphericModel(set)This requires that the files system.yaml and settings.yaml exist in the folder data. See also Settings. The parameter relax=true allows loading a yaml file that does not contain all sections needed to run a kite power system simulation. This is useful if you want to use this package for other purposes than simulating kite power systems.
Types
Exported types
AtmosphericModels.ProfileLaw — Type
@enum ProfileLaw CONSTANT=0 EXP=1 LOG=2 EXPLOG=3 CUSTOM_LOG=4 CUSTOM_EXP=5 CUSTOM_JET=6Enumeration to describe the wind profile law that is used.
AtmosphericModels.AtmosphericModel — Type
mutable struct AtmosphericModelStruct that is storing the settings and the state of the atmosphere.
Fields
- set::Settings: The Settings struct
rho_zero_temp- wf::Union{WindField, Nothing}: The 3D
WindFieldornothing jet_cache: cached(heights, speeds, coeffs)of the lastcustom_jetfit, ornothing
AtmosphericModels.AtmosphericModel — Method
AtmosphericModel(set::Settings; nowindfield::Bool=false)Constructs an AtmosphericModel using the provided Settings.
Arguments
set::Settings: The settings object containing configuration parameters for the atmospheric model.nowindfield::Bool=false: Optional keyword argument. Iftrue, the wind field will not be loaded.
Returns
- An instance of
AtmosphericModelconfigured according to the provided settings.
Private types
AtmosphericModels.WindField — Type
struct WindFieldStruct that is storing a 3D model of wind vectors of the atmosphere. The fields u, v and w store the wind turbulence vectors; x, y and z are the grid axes, rebuilt from the settings by grid_axes rather than read from the .npz file.
Fields
- x_max::Float64 = NaN
- x_min::Float64 = NaN
- y_max::Float64 = NaN
- y_min::Float64 = NaN
- z_max::Float64 = NaN
- z_min::Float64 = NaN
- last_speed::Float64 = 0.0
- valid::Bool = false
- x::Union{SRL, Array{Float64, 3}}
- y::Union{SRL, Array{Float64, 3}}
- z::Union{SRL, Array{Float64, 3}}
- u::Array{Float64, 3}
- v::Array{Float64, 3}
- w::Array{Float64, 3}
- param::Vector{Float64} = [0, 0] # [alpha,
v_wind_gnd] v_wind_gnd::Float64: theset.v_wind_gndsentry this field was generated for, which selects the matchingset.rel_turbscorrection inget_wind
Functions
Wind shear and air density calculation
AtmosphericModels.calc_rho — Function
calc_rho(s::AM, height)Calculates the air density at a given height above ground level.
Arguments
s::AM: An instance of theAM(Atmospheric Model) struct containing atmospheric parameters.height: The height above ground level (in meters) at which to calculate the air density.
Returns
- The air density at the specified height (in kg/m³).
Notes
- The calculation assumes an exponential decrease of air density with altitude.
s.rho_zero_tempis the reference air density at ground level.s.set.height_gndis the ground height offset.- The scale height used is 8550.0 meters.
AtmosphericModels.calc_wind_factor — Function
calc_wind_factor(am::AM, height; profile_law::Int64=am.set.profile_law)Calculates the wind factor at a given height using the specified wind profile law.
Arguments
am::AM: An instance of theAMtype containing atmospheric model parameters.height: The height (in meters) at which to calculate the wind factor.profile_law::Int64: (Optional) The wind profile law to use for the calculation. Defaults toam.set.profile_law.
Returns
- The wind factor at the specified height as determined by the chosen profile law.
Custom profile laws
AtmosphericModels.custom_log — Function
custom_log(heights, speeds, height)Evaluate a logarithmic wind profile u(z) = a * log(z) + b at height, with a and b fitted to heights/speeds by ordinary least squares.
AtmosphericModels.custom_exp — Function
custom_exp(heights, speeds, height)Evaluate a power-law wind profile u(z) = c * z^a at height, with a and c fitted to heights/speeds by ordinary least squares in log-log space.
AtmosphericModels.custom_jet — Function
custom_jet(heights, speeds, height)Evaluate a power-law background profile with a superimposed Gaussian jet, u(z) = c * z^a + U_J * exp(-(z-z_c)^2/(2*sigma^2)), at height. All five coefficients are fitted jointly to heights/speeds by nonlinear least squares (Levenberg-Marquardt).
Wind turbulence calculation
AtmosphericModels.get_wind — Function
get_wind(am::AtmosphericModel, x, y, z, t; upwind_dir=-π/4, interpolate=false)Returns the wind vector at the specified position (x, y, z) and time t using the given AtmosphericModel (am).
Uses Taylor's frozen-turbulence hypothesis: the field is advected along the mean wind direction. The position is first rotated into the wind-aligned frame so that:
- the along-wind component (+ time advection) maps to whichever of the field's first two dimensions is long (the larger of
am.set.grid[1],am.set.grid[2]), avoiding short-period repetition during long simulations. - the cross-wind component maps to the short dimension, so the kite stays within the spatial range of the field.
The long/short axis is detected from the actual array size at each call, since which of x/y is longer depends on am.set.grid and differs between configurations (e.g. [4050, 100, ...] vs. the [100, 4050, ...] default).
The stored field is scaled at lookup by am.set.use_turbulence * rel_turbo(am, wf.v_wind_gnd), so one file per ground wind speed serves every turbulence intensity and changing use_turbulence needs no regeneration. The rel_turbs correction is taken for the speed the loaded field was generated for, not for am.set.v_wind, so a field loaded at any other speed keeps its own intensity.
Arguments
am::AtmosphericModel: The atmospheric model providing environmental parameters.x,y,z: Position in the simulation (ENU) frame where the wind is evaluated. [m]t: Current simulation time. [s]upwind_dir(optional, default =-π/4): Direction the wind is coming FROM [rad]. Zero is north, clockwise positive (same convention as incalc_turbulent_wind).interpolate(optional, default =false): Iftrue, interpolate the turbulence trilinearly between the eight surrounding grid points; otherwise, use nearest-grid-point values. Interpolation removes the steps a kite flying through the field sees, at about 1.8x the cost of the lookup itself (29 ns → 51 ns per position for the vector method).
Returns
- A tuple
(v_x, v_y, v_z)representing the wind velocity in the wind-aligned frame [m/s], wherev_xis the along-wind component (includes mean wind),v_yis cross-wind,v_zis vertical.
get_wind(am::AtmosphericModel, positions::AbstractVector, t; upwind_dir=-π/4, interpolate=false)Return the wind vectors at all positions at time t as a Vector{SVec3}.
Faster than calling the scalar get_wind in a loop: the turbulence scaling (rel_turbo, which allocates), the sine/cosine of the wind direction and the settings lookups are computed once per call instead of once per position.
Arguments
am::AtmosphericModel: The atmospheric model providing environmental parameters.positions: Vector of 3D positions in the simulation (ENU) frame, e.g.Vector{SVec3}; any vector of indexable, 3-element positions works. The heightpositions[i][3]must be >= 5 m.t: Current simulation time. [s]upwind_dir(optional, default =-π/4): Direction the wind is coming FROM [rad].interpolate(optional, default =false): Interpolate between grid points, see the scalar method ofget_wind.
Returns
- A
Vector{SVec3}of wind velocities in the wind-aligned frame [m/s], one per position; the components are(v_x, v_y, v_z)as returned by the scalar method.
See also get_wind!, which writes into a pre-allocated result vector.
AtmosphericModels.get_wind! — Function
get_wind!(res::AbstractVector{SVec3}, am::AtmosphericModel, positions::AbstractVector, t;
upwind_dir=-π/4, interpolate=false)In-place version of the vector method of get_wind: write the wind vectors at positions into res and return res. res must have the same length as positions.
AtmosphericModels.calc_turbulent_wind — Function
calc_turbulent_wind(am::AtmosphericModel, pos, t; upwind_dir=-π/4, interpolate=false)Calculate the wind velocity vectors at the kite and at the mid-tether point, in the ENU simulation frame.
When am.set.use_turbulence == 0, the mean wind for the configured am.set.profile_law is returned. Otherwise the turbulent wind vectors are looked up from the pre-computed wind field via get_wind and rotated from the wind-aligned frame into the simulation frame.
Arguments
am::AtmosphericModel: atmospheric model; the settings are read fromam.set.pos: 3D position of the kite [m];pos[3]is the height, clamped to 6.0 m minimum.t: current simulation time [s].upwind_dir(optional, default =-π/4): direction the wind is coming FROM [rad]. Zero is north, clockwise positive (same convention as inget_wind).interpolate(optional, default =false): interpolate between grid points, seeget_wind.
Returns
A tuple (v_wind, v_wind_tether) of SVec3 in the ENU frame [m/s]:
v_wind: wind velocity at the kite position.v_wind_tether: wind velocity at half the kite position(0.5x, 0.5y, 0.5z), with the height clamped to 5.0 m minimum.
AtmosphericModels.rel_turbo — Function
rel_turbo(am::AtmosphericModel, v_wind = am.set.v_wind)Find the closest relative turbulence value for a given ground wind speed.
Arguments
am::AtmosphericModel: The atmospheric model instance containing relevant parameters.v_wind: (Optional) The wind velocity to use for the calculation. Defaults toam.set.v_wind.
Returns
- The computed relative turbulence value.
AtmosphericModels.new_windfield — Function
new_windfield(am::AtmosphericModel, v_wind_gnd; prn=true)Create a new wind field file using the given, scalar ground wind velocity v_wind_gnd.
The field is stored at the reference intensity (sigma1 = calc_sigma1(am, v_wind_gnd)); am.set.use_turbulence and rel_turbo(am) are applied when it is read by get_wind.
Parameters
am::AtmosphericModel: The atmospheric model for which the wind field is created.v_wind_gnd: A scalar representing the wind velocity at ground level.prn: Optional boolean flag to control printing of progress messages (default istrue).
Returns
- nothing
AtmosphericModels.new_windfields — Function
new_windfields(am::AtmosphericModel; prn=true)Create and initialize new wind fields for all ground wind speeds, defined in am.set.v_wind_gnds and save them for the given AtmosphericModel instance am.
Arguments
am::AtmosphericModel: The atmospheric model for which wind fields are to be generated.prn: Optional boolean flag to control printing of progress messages (default istrue).
Returns
- nothing
AtmosphericModels.windfield_path — Function
windfield_path()Directory the generated .npz wind fields are read from and written to.
By default a Scratch.jl scratchspace, created on first use: the files are derived artifacts of ~1.2 GB apiece, so they belong in a cache shared by every package using this one, not in the data/ directory of each of them. Redirect it with set_windfield_path!.
AtmosphericModels.set_windfield_path! — Function
set_windfield_path!(path)Store the wind fields in path instead of the scratchspace, e.g. on a disk with room for them. Pass "" to go back to the default. The directory is created if it does not exist.
Private functions
AtmosphericModels.WindField — Method
WindField(am, speed; prn=true)Load (or generate) the wind field for the ground wind speed and wrap it in a WindField.
Throws if the field cannot be built: the settings are checked by check_windfield_settings first, and any later failure propagates. Returning nothing here, as versions before v0.3.8 did, moved the failure to the wf !== nothing assertion in get_wind, a stack trace away from its cause.
AtmosphericModels.wind_context — Function
wind_context(am::AtmosphericModel, wf::WindField, upwind_dir)Compute everything wind_at needs that does not depend on the position: the sine/cosine of the wind direction, the turbulence scaling and the settings read out of am.set.
Hoisted out of the lookup so that the vector method of get_wind pays for it once per call instead of once per position — rel_turbo in particular allocates.
AtmosphericModels.wind_at — Function
wind_at(am, wf, x, y, z, t, cos_dir, sin_dir, rel_turb, profile_law, v_wind, grid_step,
height_step; interpolate=false)Look up the wind vector at one position; the trailing arguments come from wind_context.
With interpolate=false the turbulence is read at the nearest grid point, with interpolate=true it is interpolated trilinearly between the eight surrounding ones. The mean wind is a smooth function of the height either way, so only the turbulence is affected.
Returns the tuple (v_x, v_y, v_z) in the wind-aligned frame, see get_wind.
AtmosphericModels.check_windfield_settings — Function
check_windfield_settings(set::Settings)Throw an ArgumentError naming the offending key if set cannot describe a wind field.
Runs before a field is loaded or generated, so a missing or inconsistent setting is reported against the settings file rather than surfacing later and elsewhere. set.grid defaulting to Int64[] when the YAML does not declare it is the case that motivated this.
AtmosphericModels.load — Function
load(am::AtmosphericModel; v_wind_gnd=8.0)Read the stored wind field for the ground wind speed v_wind_gnd and return (u, v, w, param).
The file is located with find_windfield, which also accepts the two names used before v0.3.8. Which one was found is logged, because a name without the param_digest cannot be checked against am.set. If there is no file at all, new_windfield generates one under the current name, which takes ~30 s or more.
The x, y and z meshgrids are not read back even when an old file still stores them (622 MB of 1.24 GB for the default grid); the axes are rebuilt from the settings by grid_axes.
Callers normally want load_windfield, which picks v_wind_gnd from am.set.v_wind_gnds and reports back which entry it used.
AtmosphericModels.load_windfield — Function
load_windfield(am::AtmosphericModel, speed)Load the wind field generated for the am.set.v_wind_gnds entry closest to speed.
Returns (u, v, w, param, v_wind_gnd); the trailing v_wind_gnd is the grid speed that was actually chosen, which is what get_wind needs to pick the matching rel_turbs.
AtmosphericModels.find_windfield — Function
find_windfield(set::Settings, v_wind_gnd)Path of the stored wind field for v_wind_gnd, without the .npz suffix, or nothing.
Prefers the current name — the one carrying the param_digest, which proves the file matches set — over the two older ones, and windfield_path over get_data_path(), where versions before v0.3.8 kept the files. A file found under an older name cannot be checked against set; load says so.
AtmosphericModels.grid_basename — Function
grid_basename(set::Settings)File name prefix of a wind field, from set.grid alone. The name a version before v0.3.8 gave the file; today calc_basename appends the param_digest to it.
AtmosphericModels.param_digest — Function
param_digest(set::Settings)Eight hex digits of a SHA-256 over the settings that change the generated field but are not in its file name: grid_step and height_step, which resolve the grid, and i_ref, alpha, avg_height and h_ref, which enter calc_sigma1. grid and the ground wind speed are in the name already.
profile_law and z0 are deliberately not in it: neither reaches the generator, since calc_sigma1 evaluates the wind profile as EXP whatever the setting says. use_turbulence is not either — it scales the field at lookup, see get_wind.
AtmosphericModels.calc_basename — Function
calc_basename(set::Settings)File name prefix of a wind field: grid_basename plus the param_digest, so that changing any setting the field depends on names a different file instead of silently loading the old one.
AtmosphericModels.grid_axes — Function
grid_axes(am::AtmosphericModel)The (x, y, z) coordinate axes of the wind field grid as ranges [m], from am.set.grid, am.set.grid_step and am.set.height_step.
x runs downwind from zero, y is centered on zero and z starts at am.set.grid[4]. The stored .npz holds only u, v, w, so these axes are rebuilt here instead of being read back.
AtmosphericModels.create_grid — Function
create_grid(am::AtmosphericModel)Creates a 3D grid for the wind field model.
Parameters
am: An instance ofAtmosphericModelcontaining the settings.
Returns Y, X and Z
Three arrays representing the generated 3D grid.