### ===================================================================================================================
### Definition of the step load
### ===================================================================================================================
# This calculation tool is based on the Dutch SBR guideline **Trillingen van vloeren door lopen**
# Copyright ©2025 Haskoning Nederland B.V.
### ===================================================================================================================
### 1. Import modules
### ===================================================================================================================
# General imports
from warnings import warn
from pathlib import Path
from typing import Optional, List, Tuple, Union
# References for functions and classes in the haskoning_atr_tools package
from haskoning_atr_tools.config import Config
# Import third-party libraries
import matplotlib
matplotlib.use(Config.MPL_NONINTERACTIVE(notify=False))
import matplotlib.pyplot as plt
### ===================================================================================================================
### 2. StepLoad class
### ===================================================================================================================
[docs]
class StepLoad:
""" The StepLoad class represents the load generated by a single walking step."""
[docs]
def __init__(self, step_frequency: float, body_mass: float):
"""
Input:
- step_frequency (float): The frequency of the steps, in [Hz]. The frequency should be between 1.64 and 3Hz.
According SBR art 4.1.1 when calculating or simulating floor vibrations due to walking, only walking
frequencies between 1.64 Hz and 3.00 Hz are considered relevant for the dynamic response of the floor.
- body_mass (float): The mass of the body, in [kg]. The body mass should be between 40 and 125kg. SBR art
4.1.2 only provides for values for the mass of the walking person in range from 30 kg to 125 kg, in steps
of 5 kg.
"""
self.step_frequency = step_frequency
self.body_mass = body_mass
@property
def step_frequency(self):
return self.__step_frequency
@step_frequency.setter
def step_frequency(self, new_step_frequency: float):
if not isinstance(new_step_frequency, (float, int)):
raise TypeError(
f"ERROR: The step-frequency for the step load should be provided as float. Provided was "
f"{new_step_frequency} of type {type(new_step_frequency)}. Please correct your input.")
if not (1.64 <= new_step_frequency <= 3.00):
raise ValueError(
f"ERROR: The value of the step-frequency for the step load should be larger than 1.64 Hz and smaller "
f"than 3.00 Hz. Provided was {new_step_frequency} Hz.")
self.__step_frequency = new_step_frequency
@property
def body_mass(self):
return self.__body_mass
@body_mass.setter
def body_mass(self, new_body_mass: float):
if not isinstance(new_body_mass, (float, int)):
raise TypeError(
f"ERROR: The body mass for the step load should be provided as float. Provided was "
f"{type(new_body_mass)}. Please correct your input.")
if not (40 <= new_body_mass <= 125):
raise ValueError(
f"ERROR: The value of the body mass for the step load should be larger than 40 kg and smaller than 125 "
f"kg. Provided was {new_body_mass} kg.")
self.__body_mass = new_body_mass
@property
def method(self) -> str:
""" Returns the method used for the calculation, currently only SBR is available, as string."""
return 'SBR'
@property
def step_load_duration(self) -> float:
""" Returns the step load duration based on the step frequency, as float, in [s]."""
return 2.6606 - 1.757 * self.step_frequency + 0.3844 * self.step_frequency ** 2
@property
def k_coefficients(self) -> List[float]:
""" Returns the calculated k-coefficients based on the step frequency, as list of floats, in [-]."""
if self.step_frequency <= 1.75:
return [
-8 * self.step_frequency + 38,
376 * self.step_frequency - 844,
-2804 * self.step_frequency + 6025,
6308 * self.step_frequency - 16573,
1732 * self.step_frequency + 13619,
-24648 * self.step_frequency + 16045,
31836 * self.step_frequency - 33614,
-12948 * self.step_frequency + 15532]
elif 1.75 < self.step_frequency < 2:
return [
24 * self.step_frequency - 18,
-404 * self.step_frequency + 521,
4224 * self.step_frequency - 6274,
-29144 * self.step_frequency + 45468,
109976 * self.step_frequency - 175808,
-217424 * self.step_frequency + 353403,
212776 * self.step_frequency - 350259,
-81572 * self.step_frequency + 135624]
return [
75 * self.step_frequency - 120.4,
-1720 * self.step_frequency + 3153,
17055 * self.step_frequency - 31936,
-94265 * self.step_frequency + 175710,
298940 * self.step_frequency - 553736,
-529390 * self.step_frequency + 977335,
481665 * self.step_frequency - 888037,
-174265 * self.step_frequency + 321008]
@property
def step_load_diagram(self) -> Tuple[List[float], List[float]]:
""" Returns the step load diagram based on default step size. The tuple specifies list of the time-steps as
float, in [s] and the loads as float, in [N]."""
return self.calculate_step_load()
[docs]
def calculate_step_load_at_time(self, defined_time: float = 0) -> float:
"""
Calculate the step load at a specific time according to SBR section 4.4.
Input:
- defined_time (float): The specific time step at which to calculate the load, in [s]. Default value is 0.
Output:
- Returns the value of the step load at a specified time step, in [N].
"""
# Check defined_time input
if defined_time > self.step_load_duration:
defined_time = self.step_load_duration
warn(f"WARNING: Time step selected is larger than step_load_duration. The time step is set to "
f"step_load_duration = {defined_time}s.")
elif defined_time < 0:
defined_time = 0
warn(f"WARNING: Time step selected is lower than 0. The time step is set to {defined_time}s.")
# Step load at specific time step
equation = \
sum(k * defined_time ** i for i, k in enumerate(self.k_coefficients, start=1)) * self.body_mass
if equation < 0:
step_load_defined_time = 0
else:
step_load_defined_time = equation
return step_load_defined_time
[docs]
def calculate_step_load(self, step_size: Optional[float] = None) -> Tuple[List[float], List[float]]:
"""
Calculate the step load of a single step along the specified time duration according to SBR 4.1.
Input:
- step_size (float): Optional input for the step size within the defined time domain of the load, in [s].
Default value None, defines a step size equal to 1/1000 of the step load duration.
Output:
- Returns the step load as list of all the loads along the specified time duration as float, in [N].
"""
# Generate time values for step_load
if step_size is None:
step_size = self.step_load_duration * (1 / 1000)
elif step_size <= 0:
step_size = self.step_load_duration * (1 / 1000)
warn(f"WARNING: The step size selected is smaller or equal to 0. Step_size is set to "
f"{self.step_load_duration * (1 / 1000)}.")
elif step_size > self.step_load_duration:
step_size = self.step_load_duration * (1 / 1000)
warn(f"WARNING: The step size selected is larger than step_interval. Step_size is set to "
f"{self.step_load_duration * (1 / 1000)}.")
time_values = []
counter = 0
while counter <= self.step_load_duration:
time_values.append(counter)
counter += step_size
# Ensure the endpoint is included
if time_values[-1] < self.step_load_duration:
time_values.append(self.step_load_duration)
# Calculate step load value for each step time
load_values = []
for t in time_values:
step_load_time_value = self.calculate_step_load_at_time(defined_time=t)
load_values.append(step_load_time_value)
# Return the values as tuple
return time_values, load_values
[docs]
def plot(
self, show: bool = True, save_file: Optional[Union[str, Path]] = None, step_size: Optional[float] = None,
normalised: bool = False):
"""
Generate a plot of the step load over time.
Input:
- show (bool): Switch to indicate if the pop-up screen of the graph should appear. Default value is True.
- save_file (str or Path): The path of the image if the plot should be saved. If None the image will not be
saved. Default is None.
- step_size (float): Optional input for the step size within the defined domain, in [s]. Default value None,
defines a step size equal to 1/1000 of the step domain.
- normalised (bool): Select to create the plot for the normalised load over time. Default value is False.
Output:
- Returns the matplotlib figure when save_file is None.
- Returns the path to the created image if save_file has been provided.
"""
# Calculate the values for the plot
time_values, load_values = self.calculate_step_load(step_size=step_size)
# Check if normalised plot is requested
if normalised:
load_values = [v / self.body_mass for v in load_values]
# Initialise matplotlib for viewing
if show:
matplotlib.use(Config.MPL_GUI())
# Create the plot
plt.close()
plt.style.use(Config.PLOT_STYLE_FILE.as_posix())
plt.plot(time_values, load_values)
plt.xlabel('Time [s]')
y_min, y_max = plt.ylim()
plt.ylim(y_min, y_max * 1.02)
if normalised:
plt.ylabel('Normalised load [-]')
plt.title('Normalised load vs time')
else:
plt.ylabel('Load [kg]')
plt.title('Load vs time')
plt.grid(True)
# Show the plot if requested
if show:
plt.show()
# Set matplotlib backend back to non-interactive if changed to GUI backend
matplotlib.use(Config.MPL_NONINTERACTIVE())
# Return the file if there is a file generated
plt.close()
if save_file:
return save_file
return plt
### ===================================================================================================================
### 3. End of script
### ===================================================================================================================