Source code for breakwater.material

import numpy as np
import matplotlib.pyplot as plt
from tabulate import tabulate
from pandas import read_excel, read_csv

from .utils.exceptions import InputError, RockGradingError, ArmourUnitsError, user_warning


[docs]def read_grading(filepath, rho=2650, sheet_name=0): """ Load a rock grading from an excel or csv file Excel input file can be generated by using :obj:`bw.generate_excel <breakwater.utils.input_generator.generate_excel>` with input argument 'Grading'. Alternatively, when using your own Excel file it must, at least, have the following headers. +------------+-----------+-----------+-----+-----+ | Rock Class | M50 Lower | M50 Upper | NLL | NUL | +------------+-----------+-----------+-----+-----+ Parameters ---------- filepath : str a valid filepath rho : float, optional, default: 2650 density of the armourstone [kg/m³] sheet_name : str, optional, default: 0 name of the sheet with the RockGrading to read, default value of 0 will result in reading the first sheet Returns ------- bw.RockGrading a rock grading object """ extension = filepath.split('.')[-1] if extension == 'xlsx': df = read_excel(filepath, sheet_name=sheet_name) elif extension == 'csv': df = read_csv(filepath, delimiter=";|,", engine='python') else: raise IOError(f'specified file has an invalid extension') # create custom_grading custom_grading = {} # lowercase all columns df.columns = map(str.lower, df.columns) # iterate over the rows in the df for i, row in df.iterrows(): rock_class = row['Rock Class'.lower()] custom_grading[rock_class] = { 'M50': [row['M50 Lower'.lower()], row['M50 Upper'.lower()]], 'NLL': row['NLL'.lower()], 'NUL': row['NUL'.lower()] } return RockGrading(grading=custom_grading, rho=rho)
[docs]def read_units(filepath, kd, name, rho=2400, sheet_name=0): """ Load ArmourUnits from an excel or csv file Excel input file can be generated by using :obj:`bw.generate_excel <breakwater.utils.input_generator.generate_excel>` with input argument 'ArmourUnits'. Alternatively, when using your own Excel file it must, at least, have the following headers. +-----------+-------+-------+--------+ | Volume | D | h | Vc | +-----------+-------+-------+--------+ .. note:: Do not use this function to read Xbloc or XblocPlus armour units from an Excel file. Use the predefined, :obj:`bw.Xbloc <breakwater.material.Xbloc>` or :obj:`bw.XblocPlus <breakwater.material.XblocPlus>` instead. Parameters ---------- filepath : str a valid filepath kd : int Stability coefficient [-] name : str name of the ArmourUnit rho : float, optional, default: 2400 density of the concrete [kg/m³] sheet_name : str name of the sheet with the armour units to read, default value of 0 will result in reading the first sheet Returns ------- bw.ConcreteArmour an armour units object """ extension = filepath.split('.')[-1] if extension == 'xlsx': df = read_excel(filepath, sheet_name=sheet_name) elif extension == 'csv': df = read_csv(filepath, delimiter=";|,", engine='python') else: raise IOError(f'specified file has an invalid extension') if name in ['Xbloc', 'XblocPlus']: user_warning( (f'The correction factor for {name} is not computed when ' f'loading {name} from an Excel file.')) # create custom_grading units = {} # lowercase all columns df.columns = map(str.lower, df.columns) for i, row in df.iterrows(): class_ = row['Volume'.lower()] units[class_] = { 'D': row['D'.lower()], 'h': row['h'.lower()], 'Vc': row['Vc'.lower()] } return ConcreteArmour(kd=kd, units=units, name=name, rho=rho)
[docs]class RockGrading: """ Standard rock grading of the standard NEN-EN 13383-1 (2002) Create a Rock Grading to determine the rock class in :py:class:`RockRubbleMound`, :py:class:`ConcreteRubbleMound`, :py:class:`RubbleMound` or :py:class:`Caisson`. By default the standard rock grading of the NEN-EN 13383-1 (2002) is used. However, it is possible to change the grading to a user defined custom grading by giving one in the arguments. Parameters ---------- grading : dict, optional, default: None If specified the custom grading will replace the default rock grading of the NEN-EN 13383-1. The custom grading must be formatted as {str: {'M50': [float, float], 'NLL': float, 'NUL': float}}, with str the rock class and a lower and upper bound for M50. Furthermore, the NLL and NUL mass must be specified. rho : float, optional, default: 2650 density of the armourstone [kg/m³] y_NLL : float, optional, default: 0.06 the fraction passing at the nominal lower limit mass (NLL) of a grading y_NUL : float, optional, default: 0.9 the fraction passing at the nominal upper limit mass (NUL) of a grading Attributes ---------- grading : dict dictionary of the chosen rock grading rho : float density of the armourstone y_NLL : float the fraction passing at the nominal lower limit mass (NLL) y_NUL : float the fraction passing at the nominal upper limit mass (NUL) """ def __init__(self, grading=None, rho=2650, y_NLL=0.06, y_NUL=0.9): """ See help(RockGrading) for more info """ self.name = 'Rock' self.rho = rho self.y_NLL = y_NLL self.y_NUL = y_NUL if grading is not None: # check if format of rock grading is a valid format msg = ('Invalid custom rock grading. Rock grading must be ' 'formatted as {str: {\'M50\': [float, float], \'NLL\': ' 'float, \'NUL\': float}}') if isinstance(grading, dict): # grading is a dict for class_, values in grading.items(): if isinstance(values, dict): # grading is a nested dict for i, (param, value) in enumerate(values.items()): if isinstance(value, list) and len(value) == 2: # mass is list with an upper and lower bound if param == 'M50': # key is M50 pass else: # invalid key for M50 raise RockGradingError(msg) elif param == 'NLL': pass elif param == 'NUL': pass else: raise RockGradingError(msg) else: # grading is not a nested dict raise RockGradingError(msg) else: # grading is not a dict raise RockGradingError(msg) else: # no custom grading was specified, use NEN-EN 13383-1 grading = {'LMA_5/40': {'M50': [10, 20], 'NLL': 5, 'NUL': 40}, 'LMA_10/60': {'M50': [20, 35], 'NLL': 10, 'NUL': 60}, 'LMA_40/200': {'M50': [80, 120], 'NLL': 40,'NUL': 200}, 'LMA_15/300': {'M50': [45, 135], 'NLL': 15, 'NUL': 300}, 'LMA_60/300': {'M50': [120, 190], 'NLL': 60, 'NUL': 300}, 'HMA_300/1000': {'M50': [540, 690], 'NLL': 300, 'NUL': 1000}, 'HMA_1000/3000': {'M50': [1700, 2100], 'NLL': 1000, 'NUL': 3000}, 'HMA_3000/6000': {'M50': [4200, 4800], 'NLL': 3000, 'NUL': 6000}, 'HMA_6000/10000': {'M50': [7500, 8500], 'NLL': 6000, 'NUL': 10000}, 'HMA_10000/15000': {'M50': [12000, 13000], 'NLL': 10000, 'NUL': 15000}} # set the grading as an attribute self.grading = grading def __str__(self): table = [] for i, (class_, values) in enumerate(self.grading.items()): if i == 0: # set table headers in first iteration headers = ['Rock Class'] # get the keys of the nested dict and add to headers headers.extend(list(values.keys())) # add the rock class to a new row row = [class_] # add the values to the row row.extend(list(values.values())) # add row to the table table.append(row) # return the table as a string for printing return str(tabulate(table, headers, tablefmt='github')) def __setitem__(self, key, value): self.grading[key] = value def __getitem__(self, key): return self.grading[key]
[docs] def add_cost(self, cost): """ Add cost per m³ of each Rock Class Add the cost per m³ of each Rock Class to the :py:attr:`grading` Parameters ---------- cost : dict cost of each Rock Class, keys must be identical to the name of the rock class Raises ------ KeyError if a rock class of the given cost is not in the grading InputError if a rock class of the :py:attr:`grading` has no price """ rock_classes = list(self.grading.keys()) # iterate over the prices in the given dict for class_, price in cost.items(): # check if the rock class is in the grading if class_ in self.grading.keys(): # class is in the grading so add price to the nested dict self.grading[class_]['price'] = price # delete from rock_classes to validate if all have a price rock_classes.remove(class_) else: # class not in the grading, raise KeyError valid_classes = ', '.join(self.grading.keys()) raise KeyError( (f'{class_} is not a rock class of the RockGrading, ' f'valid classes are {valid_classes}')) # check if pricing has been added for all rock_classes if any(rock_classes): # not all have been added, raise error no_pricing = ', '.join(rock_classes) raise InputError(f'No pricing has been given for {no_pricing}')
[docs] def get_class(self, Dn50): """ Get the rock class for a given Dn50 Parameters ---------- Dn50 : float nominal diameter of the armourstone [m] Returns ------- str Rock class Raises ------ RockGradingError If the computed Dn50 of the armour layer is out of range for the specified rock grading """ rock_class = None M_required = self.rho * Dn50**3 for class_, characteristics in self.grading.items(): if characteristics['M50'][1] >= M_required: rock_class = class_ break if rock_class == None: max_class = list(self.grading.keys())[-1] max_mass = self.grading[max_class]['M50'][1] max_dn = np.round((max_mass/2650)**(1/3), 3) raise RockGradingError( f'Dn50 = {np.round(Dn50, 3)} is out of range for the specified' f' rock grading, {max_dn} m is the maximum possible Dn50.') else: return rock_class
[docs] def get_class_dn50(self, class_): """ Get the average Dn50 of a rock class Parameters ---------- class_ : str Rock class Returns ------- Dn50 : float nominal diameter of the armourstone [m] Raises ------ KeyError If the specified rock class is not in the rock grading """ class_M = self.grading[class_]['M50'] average_Dn50 = ((class_M[0] + class_M[1])/(2*self.rho))**(1/3) return average_Dn50
[docs] def rosin_rammler(self, class_, y): """ Compute the Rosin-Rammler curve for an idealised gradings The Rosin-Rammler curve can be used to interpolate between the limits of standard gradings (CIRIA, CUR, CETMEF, 2007). Given that two fixed point on the curve are known, for instance the NLL and NUL limits, M50 and the uniformity coefficient, n_RRM, can be computed. These values are subsequently used to compute My. .. math:: M_{y} = M_{50} \\left(\\frac{\\ln{(1 - y)}}{\\ln{(0.5)}} \\right)^{1/n_{RRM}} Parameters ---------- class_ : str Rock class y : float the fraction passing value Returns ------- My : float the mass corresponding to that value using a percentage subscript to express that fraction [kg] Raises ------ KeyError If the specified rock class is not in the rock grading """ # set NLL and NUL for the given rock class NLL = self.grading[class_]['NLL'] NUL = self.grading[class_]['NUL'] # compute the uniformity index a = np.log(1 - self.y_NUL)/np.log(1 - self.y_NLL) n_RRM = np.log10(a)/np.log10(NUL/NLL) # compute M50 with NLL and the computed n_RRM, in accordance # with the advice from the Rock Manual (p113, bottom) M50 = NLL * (np.log(1 - self.y_NLL)/np.log(0.5))**(-1/n_RRM) # compute the mass passing My = M50 * (np.log(1- y)/np.log(0.5))**(1/n_RRM) return My
[docs] def plot_rosin_rammler(self, class_): """ plot the Rosin-Rammler curve for an idealised gradings Parameters ---------- class_ : str Rock class Raises ------ KeyError If the specified rock class is not in the rock grading """ y = np.linspace(0, 0.999999, 1000) My = self.rosin_rammler(class_=class_, y=y) # plot grading curve and NLL + NUL points plt.plot(My, y) plt.plot(self.grading[class_]['NLL'], self.y_NLL, 'ko') plt.plot(self.grading[class_]['NUL'], self.y_NUL, 'ko') # style plot xmax = self.rosin_rammler(class_=class_, y=0.9999) xmin = self.rosin_rammler(class_=class_, y=0.0001) plt.xlim(xmin=xmin, xmax=xmax) plt.ylim(ymin=0, ymax=1) plt.xlabel('My, Mass of armourstone [kg]') plt.ylabel('y, fraction lighter') plt.title(f'Rosin-Rammler curve for {class_}') plt.grid('minor') plt.show()
[docs]class ConcreteArmour: """ Define concrete armour units Define a type of concrete armour unit, :py:class:`Xbloc` and :py:class:`XblocPlus` have been predefined. Parameters ---------- kd : int Stability coefficient [-] units : dict Dictionary of the defined concrete armour units. Format must be {V: dict}, where V is the volume of the armour unit (float) and dict = {'D': float, 'h': float, 'Vc': float} in which D is the diameter, h is the thickness of the armour layers and Vc is the volume of concrete. name : str, optional, default: None name of the ArmourUnit, by default the name of the armour unit is derived from the name of the class. rho : float, optional, default: 2400 density of the concrete used to make the armour units [kg/m³] Attributes ---------- kd : int Stability coefficient [-] units : dict Dictionary of the defined concrete armour units. Format must be {V: dict}, where V is the volume of the armour unit (float) and dict = {'D': float, 'h': float, 'Vc': float} in which D is the diameter, h is the thickness of the armour layers and Vc is the volume of concrete. rho : float, optional, default: 2400 density of the concrete used to make the armour units [kg/m³] """ def __init__(self, kd, units, name=None, rho=2400): """ See help(ConcreteArmour) for more info """ self.kd = kd self.units = units self.rho = rho # set name of the armour unit if name is None: self.name = self.__class__.__name__ else: self.name = name def __str__(self): table = [] for i, (volume, dimensions) in enumerate(self.units.items()): if i == 0: # set headers of the table headers = list(dimensions.keys()) headers.insert(0, 'V') row = [] row.append(volume) for parameter, dimension in dimensions.items(): row.append(dimension) table.append(row) table = tabulate(table, headers, tablefmt='github') return str(table) def __setitem__(self, key, value): self.units[key] = value def __getitem__(self, key): return self.units[key]
[docs] def get_class(self, d): """ Get the volume of a standard concrete armour unit Parameters ---------- d : float minimal required diameter [m] Returns ------- float Volume of a standard armour unit Raises ------ ArmourUnitsError If the computed Dn of the armour layer is out of range for the specified armour units """ computed_volume = d**3 class_volume = None for volume in self.units: if volume >= computed_volume: class_volume = volume break if class_volume == None: max_class = list(self.units.keys())[-1] max_dn = np.round(max_class**(1/3), 3) msg = ('given dn is out of range for the specified armour units' f', {max_dn} m (V = {max_class} m^3) is the maximum ' 'possible dn.') raise ArmourUnitsError(msg) return class_volume
[docs]class Xbloc(ConcreteArmour): """ Xbloc concrete armour units .. figure:: _figures/Xbloc.png :align: center :alt: Xbloc armour unit Xbloc is an armour unit developed by Delta Marine Consultants (DMC). All units have been predefined in :py:attr:`units`, with the values from table 1 from Delta Marine Consultants (2018). Parameters ---------- rho : float, optional, default: 2400 density of the concrete used to make the armour units Attributes ---------- kd : int Stability coefficient [-] units : dict Dictionary of the defined concrete armour units. Format is {V: dict}, where V is the volume of the Xbloc (float) and dict = {'D': float, 'h': float, 'Vc': float} in which D is the diameter, h is the thickness of the armour layers and Vc is the volume of concrete. rho : float, optional, default: 2400 density of the concrete used to make the armour units [kg/m³] """ def __init__(self, rho=2400): """ See help(Xbloc) for more info """ units = {0.75: {'D': 1.31, 'h': 1.3, 'Vc': 0.53}, 1: {'D': 1.44, 'h': 1.4, 'Vc': 0.58}, 1.5: {'D': 1.65, 'h': 1.6, 'Vc': 0.66}, 2: {'D': 1.82, 'h': 1.8, 'Vc': 0.73}, 2.5: {'D': 1.96, 'h': 1.9, 'Vc': 0.78}, 3: {'D': 2.08, 'h': 2, 'Vc': 0.83}, 4: {'D': 2.29, 'h': 2.2, 'Vc': 0.92}, 5: {'D': 2.47, 'h': 2.4, 'Vc': 0.99}, 6: {'D': 2.62, 'h': 2.5, 'Vc': 1.05}, 7: {'D': 2.76, 'h': 2.7, 'Vc': 1.11}, 8: {'D': 2.88, 'h': 2.8, 'Vc': 1.16}, 9: {'D': 3, 'h': 2.9, 'Vc': 1.2}, 10: {'D': 3.11, 'h': 3, 'Vc': 1.25}, 12: {'D': 3.3, 'h': 3.2, 'Vc': 1.32}, 14: {'D': 3.48, 'h': 3.4, 'Vc': 1.39}, 16: {'D': 3.63, 'h': 3.5, 'Vc': 1.46}, 18: {'D': 3.78, 'h': 3.7, 'Vc': 1.52}, 20: {'D': 3.91, 'h': 3.8, 'Vc': 1.57}} super().__init__(kd=16, units=units, rho=rho)
[docs] def correction_factor( self, Hs, h, Rc, occurrence_hs, slope, slope_foreshore, permeability, logger=None, **kwargs): """ Determine correction factor for Xbloc Correction factors for phenomena which require an increase in the Xbloc unit sizes. For the conceptual design of structures the correction factor must be multiplied with the volume (Delta Marine Consultants, 2018). Parameters ---------- Hs : float significant wave height [m] h : float water depth [m] Rc : float Crest freeboard [m] occurrence_hs : bool True if frequent occurrence of the near-design wave height during the lifetime of the structure. False if no frequent occurrence slope : float slope of the armour layer [rad] slope_foreshore : float slope of the foreshore [rad] permeability : {'permeable', 'low', 'impermeable'} Permeability of the core logger : dict, optional, default: None dict to log messages, must have keys 'INFO' and 'WARNINGS' Returns ------- float The correction factor to be applied on the volume. Returns 1 if no correction factor is needed. """ correction = [1] log = [None] # frequent occurrence of near-design wave height if occurrence_hs: correction.append(1.25) log.append('Xbloc: frequent occurrence of Hs') # steep foreshore if np.arctan(1/30) <= slope_foreshore: if np.arctan(1/20) <= slope_foreshore: if np.arctan(1/15) <= slope_foreshore: if np.arctan(1/10) <= slope_foreshore: correction.append(2) # steeper than 1:10 log.append('Xbloc: steepness of foreshore is greater ' 'than 1:10') else: correction.append(1.5) # between 1:15-1:10 log.append('Xbloc: steepness of foreshore is between ' '1:15 and 1:10') else: correction.append(1.25) # between 1:20-1:15 log.append('Xbloc: steepness of foreshore is between ' '1:20 and 1:15') else: correction.append(1.1) # between 1:30-1:20 log.append('Xbloc: steepness of foreshore is between 1:30 and' ' 1:20') # Check if structure is low crested if Rc/Hs < 1: if Rc/Hs < 0.5: correction.append(2) log.append('Xbloc: low crested structure, Rc/Hs < 0.5') else: correction.append(1.5) log.append('Xbloc: low crested structure, Rc/Hs < 1.0') # Check if water depth is large if h > 2.5*Hs: if h > 3.5*Hs: correction.append(2) log.append('Xbloc: large water depth, h > 3.5 x Hs') else: correction.append(1.5) log.append('Xbloc: large water depth, h > 2.5 x Hs') # low core permeability if permeability == 'low': correction.append(1.5) log.append('Xbloc: low core permeability') elif permeability == 'impermeable': correction.append(2) log.append('Xbloc: impermeable core') # Check if armour slope is mild if slope < np.arctan(2/3): if slope < np.arctan(1/2): correction.append(1.5) log.append('Xbloc: armour slope is milder than 1:2') else: correction.append(1.25) log.append('Xbloc: armour slope is milder than 2:3') max_correction = max(correction) max_index = correction.index(max_correction) msg = (f'Xbloc: used correction factor: {max_correction}, because ' f'{log[max_index]} was the maximum value') if logger is not None: if len(log) > 1: correction.pop(0) log.pop(0) logger['INFO'].extend(log) logger['INFO'].append(msg) return max_correction
[docs]class XblocPlus(ConcreteArmour): """ XblocPlus concrete armour units .. figure:: _figures/XblocPlus.png :align: center :alt: XblocPlus armour unit XblocPlus is an armour unit developed by Delta Marine Consultants (DMC). All units have been predefined in :py:attr:`units`, with the values from table 2 from Delta Marine Consultants (2018). Parameters ---------- rho : float, optional, default: 2400 density of the concrete used to make the armour units Attributes ---------- kd : int Stability coefficient [-] units : dict Dictionary of the defined concrete armour units. Format is {V: dict}, where V is the volume of the XblocPlus unit (float) and dict = {'L1': float, 'L2': float, 'L3': float, 'h': float 'Vc': float} in which L1, L2 and L3 are the dimensions, h is the thickness of the armour layers and Vc is the volume of concrete. rho : float, optional, default: 2400 density of the concrete used to make the armour units [kg/m³] """ def __init__(self, rho=2400): """ See help(XblocPlus) for more info """ units = {0.75: {'L1': 0.75, 'L2': 1.51, 'L3': 1.91, 'h': 1.2, 'Vc': 0.48}, 1: {'L1': 0.83, 'L2': 1.66, 'L3': 2.1, 'h': 1.3, 'Vc': 0.53}, 1.5: {'L1': 0.95, 'L2': 1.9, 'L3': 2.41, 'h': 1.5, 'Vc': 0.6}, 2: {'L1': 1.04, 'L2': 2.09, 'L3': 2.65, 'h': 1.7, 'Vc': 0.66}, 2.5: {'L1': 1.12, 'L2': 2.25, 'L3': 2.85, 'h': 1.8, 'Vc': 0.71}, 3: {'L1': 1.19, 'L2': 2.39, 'L3': 3.03, 'h': 1.9, 'Vc': 0.76}, 4: {'L1': 1.31, 'L2': 2.63, 'L3': 3.34, 'h': 2.1, 'Vc': 0.83}, 5: {'L1': 1.42, 'L2': 2.83, 'L3': 3.59, 'h': 2.3, 'Vc': 0.9}, 6: {'L1': 1.5, 'L2': 3.01, 'L3': 3.82, 'h': 2.4, 'Vc': 0.96}, 7: {'L1': 1.58, 'L2': 3.17, 'L3': 4.02, 'h': 2.5, 'Vc': 1.01}, 8: {'L1': 1.66, 'L2': 3.31, 'L3': 4.2, 'h': 2.7, 'Vc': 1.05}, 9: {'L1': 1.72, 'L2': 3.45, 'L3': 4.37, 'h': 2.8, 'Vc': 1.09}, 10: {'L1': 1.78, 'L2': 3.57, 'L3': 4.53, 'h': 2.9, 'Vc': 1.13}, 12: {'L1': 1.89, 'L2': 3.79, 'L3': 4.81, 'h': 3.0, 'Vc': 1.2}, 14: {'L1': 1.99, 'L2': 3.99, 'L3': 5.06, 'h': 3.2, 'Vc': 1.27}, 16: {'L1': 2.09, 'L2': 4.17, 'L3': 5.29, 'h': 3.3, 'Vc': 1.33}, 18: {'L1': 2.17, 'L2': 4.34, 'L3': 5.5, 'h': 3.5, 'Vc': 1.38}, 20: {'L1': 2.65, 'L2': 4.5, 'L3': 5.7, 'h': 3.6, 'Vc': 1.43}} super().__init__(kd=12, units=units, rho=rho)
[docs] def correction_factor( self, Hs, Rc, slope_foreshore, permeability, logger=None, **kwargs): """ Determine correction factor for XblocPlus Correction factors for phenomena which require an increase in the XblocPlus unit sizes. For the conceptual design of structures the correction factor must be multiplied with the volume (Delta Marine Consultants, 2018). Parameters ---------- Hs : float significant wave height [m] Rc : float Crest freeboard [m] slope_foreshore : float slope of the foreshore [rad] permeability : str Permeability of the core {'permeable', 'low', 'impermeable'} logger : dict, optional, default: None dict to log messages, must have keys 'INFO' and 'WARNINGS' Returns ------- float The correction factor to be applied on the volume. Returns 1 if no correction factor is needed. """ correction = [1] log = [None] # steep foreshore if np.arctan(1/30) <= slope_foreshore: if np.arctan(1/20) <= slope_foreshore: if np.arctan(1/15) <= slope_foreshore: if np.arctan(1/10) <= slope_foreshore: correction.append(2) # steeper than 1:10 log.append('XblocPlus: steepness of foreshore is ' 'greater than 1:10') else: correction.append(1.5) # between 1:15-1:10 log.append('XblocPlus: steepness of foreshore is ' 'between 1:15 and 1:10') else: correction.append(1.25) # between 1:20-1:15 log.append('XblocPlus: steepness of foreshore is between ' '1:20 and 1:15') else: correction.append(1.1) # between 1:30-1:20 log.append('XblocPlus: steepness of foreshore is between 1:30' ' and 1:20') # Check if structure is low crested if Rc/Hs < 1: if Rc/Hs < 0.5: correction.append(1.5) log.append('XblocPlus: low crested structure, Rc/Hs < 0.5') else: correction.append(1.25) log.append('XblocPlus: low crested structure, Rc/Hs < 1.0') # low core permeability if permeability == 'low': correction.append(1.25) log.append('XblocPlus: low core permeability') elif permeability == 'impermeable': correction.append(1.5) log.append('XblocPlus: impermeable core') max_correction = max(correction) max_index = correction.index(max_correction) msg = (f'XblocPlus: used correction factor: {max_correction}, because ' f'{log[max_index]} was the maximum value') if logger is not None: if len(log) > 1: correction.pop(0) log.pop(0) logger['INFO'].extend(log) logger['INFO'].append(msg) return max_correction