Module ocean_science_utilities.wavephysics.balance.st6_wind_input
Expand source code
import numpy as np
import numba # type: ignore
from typing import Optional, TypedDict
from ocean_science_utilities.wavephysics.balance.generation import WindGeneration
from ocean_science_utilities.wavephysics.fluidproperties import (
AIR,
WATER,
GRAVITATIONAL_ACCELERATION,
)
from ocean_science_utilities.wavespectra.operations import (
numba_directionally_integrate_spectral_data,
)
from ocean_science_utilities.wavetheory.lineardispersion import (
inverse_intrinsic_dispersion_relation,
intrinsic_group_velocity,
)
class ST6WaveGenerationParameters(TypedDict):
gravitational_acceleration: float
charnock_maximum_roughness: float
charnock_constant: float
air_density: float
water_density: float
vonkarman_constant: float
friction_velocity_scaling: float
elevation: float
class ST6WindInput(WindGeneration):
name = "st6 generation"
def __init__(
self,
parameters: Optional[ST6WaveGenerationParameters] = None,
):
super(ST6WindInput, self).__init__(parameters)
self._wind_source_term_function = _st6_wind_generation_point
@staticmethod
def default_parameters() -> ST6WaveGenerationParameters:
return ST6WaveGenerationParameters(
gravitational_acceleration=GRAVITATIONAL_ACCELERATION,
charnock_maximum_roughness=np.inf,
charnock_constant=0.015,
air_density=AIR.density,
water_density=WATER.density,
vonkarman_constant=AIR.vonkarman_constant,
friction_velocity_scaling=28,
elevation=10,
)
# ----------------------------------------------------------------------------------------------------------------------
# st6 Point wise implementation functions (apply to a single spatial point)
# ----------------------------------------------------------------------------------------------------------------------
@numba.njit(cache=True)
def _st6_wind_generation_point(
variance_density,
wind,
depth,
roughness_length,
spectral_grid,
parameters,
wind_source=None,
):
"""
Implememtation of the st6 wind growth rate term
:param variance_density:
:param wind:
:param depth:
:param roughness_length:
:param spectral_grid:
:param parameters:
:return:
"""
# Get the spectral size
number_of_frequencies, number_of_directions = variance_density.shape
# Allocate the output variable
if wind_source is None:
wind_source = np.empty((number_of_frequencies, number_of_directions))
# Unpack variables passed in dictionaries/tuples for ease of reference and some
# slight speed benifits (avoid dictionary lookup in loops).
vonkarman_constant = parameters["vonkarman_constant"]
friction_velocity_scaling = parameters["friction_velocity_scaling"]
air_density = parameters["air_density"]
water_density = parameters["water_density"]
elevation = parameters["elevation"]
radian_frequency = spectral_grid["radian_frequency"]
radian_direction = spectral_grid["radian_direction"]
wind_forcing, wind_direction_degrees, wind_forcing_type = wind
# Preprocess winds to the correct conventions (U10->friction velocity if need be
# wind direction degrees->radians)
if wind_forcing_type == "u10":
u10 = wind_forcing
friction_velocity = (
u10 * vonkarman_constant / np.log(elevation / roughness_length)
)
elif wind_forcing_type in ["ustar", "friction_velocity"]:
u10 = wind_forcing / vonkarman_constant * np.log(elevation / roughness_length)
friction_velocity = wind_forcing
else:
raise ValueError("unknown wind forcing")
wind_direction_radian = wind_direction_degrees * np.pi / 180
us = friction_velocity * friction_velocity_scaling
frequency_spectrum = numba_directionally_integrate_spectral_data(
variance_density, spectral_grid
)
wavenumber = inverse_intrinsic_dispersion_relation(radian_frequency, depth)
wavespeed = radian_frequency / wavenumber
group_velocity = intrinsic_group_velocity(wavenumber, depth)
saturation_spectrum = (
frequency_spectrum * group_velocity * wavenumber**3 / 2 / np.pi
)
peak_index = np.argmax(frequency_spectrum)
peak_wave_speed = wavespeed[peak_index]
peak_radian_frequency = radian_frequency[peak_index]
# precalculate the constant multiplication.
constant_factor = air_density / water_density
# Loop over all frequencies/directions
for frequency_index in range(number_of_frequencies):
# Since wavenumber and wavespeed only depend on frequency we calculate those
# outside of the direction loop.
constant_frequency_factor = constant_factor * radian_frequency[frequency_index]
st6_directional_spreading_function = 1.12 * (us / peak_wave_speed) ** (-0.5) * (
radian_frequency[frequency_index] / peak_radian_frequency
) ** -(0.95) + 1 / (2 * np.pi)
root_of_saturation = np.sqrt(
saturation_spectrum[frequency_index] * st6_directional_spreading_function
)
for direction_index in range(number_of_directions):
# Calculate the directional factor in the wind input formulation
W = (
u10
* np.cos(radian_direction[direction_index] - wind_direction_radian)
/ wavespeed[frequency_index]
- 1
)
if W > 0:
# If the wave direction has an along wind component we continue.
directional_saturation = W**2 * root_of_saturation
st6_sheltering_coefficient = 2.8 - (
1 + np.tanh(10 * directional_saturation - 11)
)
growth_rate = (
constant_frequency_factor
* st6_sheltering_coefficient
* directional_saturation
)
else:
growth_rate = 0.0
wind_source[frequency_index, direction_index] = (
growth_rate * variance_density[frequency_index, direction_index]
)
return wind_source
Classes
class ST6WaveGenerationParameters (*args, **kwargs)
-
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
Expand source code
class ST6WaveGenerationParameters(TypedDict): gravitational_acceleration: float charnock_maximum_roughness: float charnock_constant: float air_density: float water_density: float vonkarman_constant: float friction_velocity_scaling: float elevation: float
Ancestors
- builtins.dict
Class variables
var air_density : float
var charnock_constant : float
var charnock_maximum_roughness : float
var elevation : float
var friction_velocity_scaling : float
var gravitational_acceleration : float
var vonkarman_constant : float
var water_density : float
class ST6WindInput (parameters: Optional[ST6WaveGenerationParameters] = None)
-
Helper class that provides a standard way to create an ABC using inheritance.
Expand source code
class ST6WindInput(WindGeneration): name = "st6 generation" def __init__( self, parameters: Optional[ST6WaveGenerationParameters] = None, ): super(ST6WindInput, self).__init__(parameters) self._wind_source_term_function = _st6_wind_generation_point @staticmethod def default_parameters() -> ST6WaveGenerationParameters: return ST6WaveGenerationParameters( gravitational_acceleration=GRAVITATIONAL_ACCELERATION, charnock_maximum_roughness=np.inf, charnock_constant=0.015, air_density=AIR.density, water_density=WATER.density, vonkarman_constant=AIR.vonkarman_constant, friction_velocity_scaling=28, elevation=10, )
Ancestors
- WindGeneration
- SourceTerm
- abc.ABC
Class variables
var name
Static methods
def default_parameters() ‑> ST6WaveGenerationParameters
-
Expand source code
@staticmethod def default_parameters() -> ST6WaveGenerationParameters: return ST6WaveGenerationParameters( gravitational_acceleration=GRAVITATIONAL_ACCELERATION, charnock_maximum_roughness=np.inf, charnock_constant=0.015, air_density=AIR.density, water_density=WATER.density, vonkarman_constant=AIR.vonkarman_constant, friction_velocity_scaling=28, elevation=10, )