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