import pathlib
import numpy as np
import pandas as pd
from pydantic import ConfigDict
from cfdmod import utils
from cfdmod.climate.wind_profile import WindProfile
[docs]
class WindProfile_NBR(WindProfile):
"""Data for wind analysis and calculation"""
model_config = ConfigDict(arbitrary_types_allowed=True)
# Pandas with keys: wind_direction, I, II, III, IV, V, Kd
# Kd is optional and defaults to read, it defaults to one
directional_data: pd.DataFrame
V0: float
U_H_overwrite: float | None = None
@classmethod
def build(cls, data_csv: pathlib.Path, V0: float, U_H_overwrite: float | None = None):
df = pd.read_csv(data_csv, index_col=None)
df = df.fillna(0)
req_keys = ["wind_direction", "I", "II", "III", "IV", "V"]
if not utils.validate_keys_df(df, req_keys):
raise KeyError(
"Not all required keys are in wind CSV. "
f"Required ones are: {req_keys}, found {list(df.columns)}"
)
if "Kd" not in df.columns:
df["Kd"] = 1
df = df[req_keys + ["Kd"]]
df.sort_values(by=["wind_direction"], inplace=True)
return WindProfile_NBR(directional_data=df, V0=V0, U_H_overwrite=U_H_overwrite)
def get_opencountry_profile(self):
directional_data_cat2 = self.directional_data.copy()
for cat in ["I", "III", "IV", "V"]:
directional_data_cat2[cat] = 0
directional_data_cat2["II"] = 1
return WindProfile(
U_H_overwrite=self.U_H_overwrite, directional_data=directional_data_cat2, V0=self.V0
)
def p(self, direction: float, time_filter_seconds: int | float):
validate_time_filter(time_filter_seconds)
p_database = {
3: {"I": 0.06, "II": 0.085, "III": 0.1, "IV": 0.12, "V": 0.15},
5: {"I": 0.065, "II": 0.09, "III": 0.105, "IV": 0.125, "V": 0.16},
10: {"I": 0.07, "II": 0.1, "III": 0.115, "IV": 0.135, "V": 0.175},
600: {"I": 0.095, "II": 0.15, "III": 0.185, "IV": 0.23, "V": 0.31},
3600: {"I": 0.1, "II": 0.16, "III": 0.2, "IV": 0.25, "V": 0.35},
}
p = p_database[time_filter_seconds]
df = self.directional_data
row = df.loc[(df["wind_direction"] - direction).abs().idxmin()].squeeze()
return sum(row[k] * p[k] for k in p.keys())
def b(self, direction: float, time_filter_seconds: int | float):
validate_time_filter(time_filter_seconds)
b_database = {
3: {"I": 1.1, "II": 1.00, "III": 0.94, "IV": 0.86, "V": 0.74},
5: {"I": 1.11, "II": 1.00, "III": 0.94, "IV": 0.85, "V": 0.73},
10: {"I": 1.12, "II": 1.00, "III": 0.93, "IV": 0.84, "V": 0.71},
600: {"I": 1.23, "II": 1.00, "III": 0.86, "IV": 0.71, "V": 0.50},
3600: {"I": 1.25, "II": 1.00, "III": 0.85, "IV": 0.68, "V": 0.44},
}
b = b_database[time_filter_seconds]
df = self.directional_data
row = df.loc[(df["wind_direction"] - direction).abs().idxmin()].squeeze()
return sum(row[k] * b[k] for k in b.keys())
def F_r(self, time_filter_seconds: int | float):
validate_time_filter(time_filter_seconds)
Fr = {
3: 1,
5: 0.98,
10: 0.95,
600: 0.69,
2600: 0.65,
}
return Fr[time_filter_seconds]
def S2(self, height: float, direction: float, time_filter_seconds: int | float):
# parameters from NBR 6123, mean speed of 10min
p = self.p(direction, time_filter_seconds)
b = self.b(direction, time_filter_seconds)
Fr = self.F_r(time_filter_seconds)
return Fr * b * (height / 10) ** p
def S3(self, recurrence_period: float):
return 0.54 * (0.994 / recurrence_period) ** -0.157
def get_U_H(
self,
height: float,
direction: float,
recurrence_period: float,
time_filter_seconds: float = 600,
use_kd: bool = False,
) -> float:
if self.U_H_overwrite is not None:
return self.U_H_overwrite
df = self.directional_data
row = df.loc[(df["wind_direction"] - direction).abs().idxmin()].squeeze()
V0 = self.V0
kd = row["Kd"] if use_kd else 1
S2 = self.S2(height, direction, time_filter_seconds)
S3 = self.S3(recurrence_period)
return V0 * kd * S2 * S3
[docs]
class WindProfile_EU(WindProfile):
"""Data for wind analysis and calculation for EU standard EN1991"""
model_config = ConfigDict(arbitrary_types_allowed=True)
# Pandas with keys: wind_direction, I, II, III, IV, V, Kd
# Kd is optional and defaults to read, it defaults to one
directional_data: pd.DataFrame
Vb: float
U_H_overwrite: float | None = None
@classmethod
def build(cls, data_csv: pathlib.Path, Vb: float, U_H_overwrite: float | None = None):
df = pd.read_csv(data_csv, index_col=None)
req_keys = ["wind_direction", "z0"]
if not utils.validate_keys_df(df, req_keys):
raise KeyError(
"Not all required keys are in wind CSV. "
f"Required ones are: {req_keys}, found {list(df.columns)}"
)
if "Kd" not in df.columns:
df["Kd"] = 1
df = df[req_keys + ["Kd"]]
df.sort_values(by=["wind_direction"], inplace=True)
return WindProfile_EU(directional_data=df, Vb=Vb, U_H_overwrite=U_H_overwrite)
def get_opencountry_profile(self):
directional_data_cat2 = self.directional_data.copy()
directional_data_cat2["z0"] = 0.05
return WindProfile(
U_H_overwrite=self.U_H_overwrite, directional_data=directional_data_cat2, Vb=self.Vb
)
def kr(self, direction: float):
direction = float(direction)
df = self.directional_data
row = df.loc[df["wind_direction"] == direction].squeeze()
return 0.19 * (row["z0"] / 0.05) ** 0.07
def c_prob(self, rec_period: float = 50) -> float:
K = 0.2
n = 0.5
p = 1 / rec_period
return ((1 - K * np.log(-np.log(1 - p))) / (1 - K * np.log(-np.log(0.98)))) ** n
def c_r(self, height: float, direction: float) -> float:
direction = float(direction)
df = self.directional_data
row = df.loc[df["wind_direction"] == direction].squeeze()
return self.kr(direction) * np.log(height / row["z0"])
def get_U_H(
self, height: float, direction: float, recurrence_period: float = 50, use_kd: bool = False
) -> float:
if self.U_H_overwrite is not None:
return self.U_H_overwrite
direction = float(direction)
df = self.directional_data
row = df.loc[df["wind_direction"] == direction].squeeze()
Vb = self.Vb
Kd = row["Kd"] if use_kd else 1
c_season = 1 # for future implementations, ...maybe
c_r = self.c_r(height, direction)
c_prob = self.c_prob(recurrence_period)
return (Vb * Kd * c_prob * c_season) * c_r
def validate_time_filter(time_filter_seconds: int | float):
if time_filter_seconds not in [3, 5, 10, 600, 3600]:
raise Exception("S2 is implemented only for 3s, 5s, 10s, 10min and 1h")