Source code for haskoning_atr_tools.human_induced_vibrations.step_walking_load

### ===================================================================================================================
###   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 ### ===================================================================================================================