### ===================================================================================================================
### Definition of the step walking 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
import warnings
import numpy as np
from pathlib import Path
from typing import Optional, Union, List
# References for functions and classes in the haskoning_atr_tools package
from haskoning_atr_tools.config import Config
from haskoning_atr_tools.human_induced_vibrations.config import ATRConfigHumanInducedVibrations as ATRConfig
from haskoning_atr_tools.human_induced_vibrations.step_load import StepLoad
# References for functions and classes in the haskoning_structural package
try:
# Only used in method to_fem, which uses the optional package for Haskoning DataFusr structural
from haskoning_structural.general import Timeseries
from haskoning_structural.fem_status import Project as FemProject
fem_use = True
except ImportError:
Timeseries = None
FemProject = None
fem_use = False
# Import third-party libraries
import matplotlib
matplotlib.use(Config.MPL_NONINTERACTIVE(notify=False))
import matplotlib.pyplot as plt
### ===================================================================================================================
### 2. StepWalkingLoad class
### ===================================================================================================================
[docs]
class StepWalkingLoad:
""" The StepWalkingLoad class represents the load generated by a series of walking steps."""
# Setting for the minimum number of steps in the step walking load
_minimum_nr_steps = 15
# Setting for the reduction of the load in the first and last (reversed) number of steps
_load_factor_start_end = [0.2, 0.4, 0.6, 0.8]
[docs]
def __init__(
self, step_load: StepLoad, nr_steps: int = 15, load_reduction: bool = False,
step_size: Optional[float] = None):
"""
Input:
- step_load (float): The definition for the single step in the walking load. Instance of StepLoad class.
- nr_steps (int): The number of steps to apply in the walking load. The total amount of steps should be at
least 15. Default value is 15.
- load_reduction (bool): Toggle to apply a load reduction is applied to the first four and last four
steps (load reduction 80%, 60%, 40% and 20% per step). Default value is False, no load reduction is
applied.
- step_size (float): Optional input to directly set the time resolution, in [s], for evaluating the walking
load. If None, a default value based on step frequency is used. Recommended to be small enough to capture
step dynamics accurately. Maximum allowed value is 1/50 of the interval between steps (factor set in the
config file).
"""
warnings.warn(
"WARNING:The Human Induced Vibrations Tool is currently under active development. Functionality may change "
"in future updates, and formal validation is still pending. Please verify your results carefully before "
"using them in your applications.")
self.step_load = step_load
self.nr_steps = nr_steps
self.load_reduction = load_reduction
self.step_size = step_size
def __repr__(self):
return f"ATR: {self.name}"
@property
def name(self) -> str:
name = f'StepWalkingLoad - {self.step_load.body_mass}kg, {self.step_load.step_frequency}Hz, #{self.nr_steps}'
if self.load_reduction:
return name + ', reduced'
return name
@property
def step_load(self):
return self.__step_load
@step_load.setter
def step_load(self, new_step_load: StepLoad):
if not isinstance(new_step_load, StepLoad):
raise TypeError(
f"ERROR: The step load for the single step should be provided as instance of {StepLoad.__name__} "
f"class. Provided was {type(new_step_load)}. Please correct your input.")
self.__step_load = new_step_load
@property
def nr_steps(self):
return self.__nr_steps
@nr_steps.setter
def nr_steps(self, new_nr_steps: int = 15):
if not isinstance(new_nr_steps, int):
raise TypeError(
f"ERROR: The number of steps for the step walking load should be provided as integer. Provided was "
f"{type(new_nr_steps)}. Please correct your input.")
if new_nr_steps < self._minimum_nr_steps:
raise ValueError(
f"ERROR: The value of the number of steps should be larger than 15. Provided was {new_nr_steps} steps.")
self.__nr_steps = new_nr_steps
@property
def load_reduction(self):
return self.__load_reduction
@load_reduction.setter
def load_reduction(self, new_load_reduction: bool = False):
if not isinstance(new_load_reduction, bool):
raise TypeError(
f"ERROR: The number of steps for the step walking load should be provided as integer. Provided was "
f"{type(new_load_reduction)}. Please correct your input.")
self.__load_reduction = new_load_reduction
@property
def step_size(self) -> float:
return self.__step_size
@step_size.setter
def step_size(self, new_step_size: Optional[float] = None):
if new_step_size is None:
new_step_size = self.interval / ATRConfig.STEP_SIZE_FACTOR
elif new_step_size <= 0:
raise ValueError("ERROR: Input for step_size of StepWalkingLoad instance must be a positive float.")
elif new_step_size > ATRConfig.STEP_SIZE_FACTOR / self.step_load.step_frequency:
raise ValueError(
f"ERROR: Input for step_size of StepWalkingLoad instance is too large to capture step dynamics "
f"accurately. Maximum allowed value is {ATRConfig.STEP_SIZE_FACTOR / self.step_load.step_frequency:.4f}"
f" s, provided was {new_step_size}. Please correct your input.")
self.__step_size = new_step_size
@property
def method(self) -> str:
""" Returns the method used for the calculation, currently only SBR is available, as string."""
return 'SBR'
@property
def total_walking_time(self) -> float:
""" Duration of the complete walking load, in [s]."""
return ((self.nr_steps - 1) / self.step_load.step_frequency) + self.step_load.step_load_duration
@property
def interval(self) -> float:
""" Time between two consecutive steps, according SBR, in [s]."""
return 1 / self.step_load.step_frequency
@property
def time_domain(self) -> List[float]:
""" Returns list of values of the time steps for the step walking load, in [s]."""
time_domain = np.arange(0, self.total_walking_time, self.step_size).tolist()
# Ensuring last value of time_domain equal to total_walking_time
if time_domain[-1] != self.total_walking_time:
time_domain[-1] = self.total_walking_time
return time_domain
@property
def step_walking_loads(self) -> List[float]:
""" Returns list of values of the step walking load for a series of steps, in [N]."""
# The user can specify to apply load reduction for the first and last steps
if self.load_reduction:
# Define reduction factors for the beginning and the end
reductions = \
self._load_factor_start_end + [1.0] * (self.nr_steps - 2 * len(self._load_factor_start_end)) + \
self._load_factor_start_end[::-1]
else:
# No reductions applied
reductions = [1.0] * self.nr_steps
# Calculate the walking load
walking_load_array = np.zeros(len(self.time_domain)).tolist()
for j, t in enumerate(self.time_domain):
walking_load_array[j] = sum([
self.step_load.calculate_step_load_at_time(
defined_time=t - (i / self.step_load.step_frequency)) * reductions[i] for i in range(self.nr_steps)
if 0 < t - (i / self.step_load.step_frequency) < self.step_load.step_load_duration])
# Return the calculated walking load
return walking_load_array
[docs]
def to_fem(self, project: FemProject, name: Optional[str] = None) -> Timeseries:
"""
Method of StepWalkingLoad to convert the instance to a Timeseries object in the haskoning_structural library,
and added to the provided FEM project.
Input:
- project (obj): Object reference of the project in the haskoning_structural module.
- name (str): Optional input for the name of the time-series function. If not provided it will be numbered
based on the function ID in the FEM project.
Output:
- Returns the object created in the 'Timeseries' class of the haskoning_structural library.
- The timeseries is added as function to the FEM project collections.
"""
if not fem_use:
raise ImportError(
"ERROR: This functionality requires the haskoning_structural module to be installed. Please proceed "
"after installation.")
if name is None:
name = self.name
return project.create_timeseries_function(
time_series=self.time_domain, value_series=self.step_walking_loads, name=name)
[docs]
def plot(self, show: bool = True, save_file: Optional[Union[str, Path]] = None, normalised: bool = False):
"""
Generate a plot of the step walking 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.
- 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.
"""
# Check if normalised plot is requested
load_values = self.step_walking_loads
if normalised:
load_values = [v / self.step_load.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(self.time_domain, load_values)
plt.xlabel('Time [s]')
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
### ===================================================================================================================