import numpy as np
import pandas as pd
import os
import pickle
from warnings import catch_warnings
from tabulate import tabulate
from .utils._kwarg_validator import _process_kwargs, _RM_vkwargs, _C_vkwargs
from .utils._design_explorer import _DE_params
from .utils.exceptions import RockGradingError, ArmourUnitsError, InputError, user_warning, NotSupportedError
from .utils._progress import ProgressBar
from .utils._excel_utils import _convert_string
from .utils.cost import _process_cost, cost_influence
from .caisson import Caisson
from .conditions import LimitState
from .material import read_grading, read_units
from .rubble import RockRubbleMound, ConcreteRubbleMound
[docs]def read_configurations(
filepath, structure, kd=None, name=None, LS=None, Grading=None,
ArmourUnits=None, BermMaterial=None):
""" Conceptual design for multiple breakwaters from an Excel file
Make a conceptual design for multiple (types) of breakwaters from
an Excel input file. The Excel input file can be generated with
:obj:`bw.generate_excel <breakwater.utils.input_generator.generate_excel>`
.. warning:
When reading Xbloc or XblocPlus armour units from an Excel file,
the correction factor applied to the nominal diameter is not
computed. Specify :py:class:`Xbloc` or :py:class:`XblocPlus`
for ArmourUnits so that the most optimal design is made.
Parameters
----------
structure : {'RRM', 'CRM', 'RC', 'CC'}
structure for which conceptual designs must be generated. RRM
for a rubble mound with rock as armour layer, CRM for a rubble
mound with concrete armour units as armour layer, RC for a
vertical (composite) breakwater with rock as armour layer for
the foundation and CC for a vertical (composite) breakwater with
concrete armour units as armour layer for the foundation.
kd : int, optional, default: None
stability coefficient [-]
name : str, optional, default: None
name of the ArmourUnit
LS : py:class:`LimitState`, optional, default: None
ULS, SLS or another limit state defined with
:py:class:`LimitState`, by default the LimitState is read from
the Excel input file
Grading : :py:class:`RockGrading`, optional, default: None
standard rock grading defined in the NEN-EN 13383-1 or a user
defined rock grading. By default the Grading is read from
the Excel input file
ArmourUnit : obj, optional, default: None
armour unit class which inherits from :py:class:`ConcreteArmour`,
for instance :py:class:`Xbloc` or :py:class:`XblocPlus`. By
default the ArmourUnit is read from the Excel input file. This
argument is used for RRM.
BermMaterial : obj, optional, default: None
armour unit class which inherits from :py:class:`ConcreteArmour`,
for instance :py:class:`Xbloc` or :py:class:`XblocPlus`. By
default the BermMaterial is read from the Excel input file.
This argument is used for CC.
Returns
-------
bw.Configurations
a Configurations object with breakwater concepts
"""
# convert the input of structure to a list
if isinstance(structure, list):
# must be a list so no change
structure = structure
elif isinstance(structure, str):
# convert single input to list
structure = [structure]
# import the excel as a DataFrame
df = pd.read_excel(filepath, skiprows=1, sheet_name='Parameters')
# generate input dict
params = {}
# load LimitState
if LS is None:
# load LimitState data from excel file
df_ls = pd.read_excel(filepath, index_col=0, sheet_name='LimitState')
# convert to dict
input_ls = df_ls.to_dict()['Value']
# generate LimitState object
params['LimitState'] = LimitState(**input_ls)
else:
params['LimitState'] = LS
# load RockGrading
if Grading is None:
# load Grading
params['Grading'] = read_grading(filepath, sheet_name='RockGrading')
else:
params['Grading'] = Grading
# load ArmourUnits
if ArmourUnits is None and 'CRM' in structure:
# check if kd and name are given
if kd is None:
raise InputError(
('Missing argument: kd, when reading ArmourUnits from Excel '
'the kd value must be given as input'))
if name is None:
raise InputError(
('Missing argument: name, when reading ArmourUnits from Excel '
'the name of the ArmourUnit must be given as input'))
# load ArmourUnits
params['ArmourUnit'] = read_units(
filepath, kd=kd, name=name, sheet_name='ArmourUnit')
elif ArmourUnits is not None and 'CRM' in structure:
params['ArmourUnit'] = ArmourUnit
# load BermMaterial
if BermMaterial is None and 'CC' in structure:
# check if kd and name are given
if kd is None:
raise InputError(
('Missing argument: kd, when reading ArmourUnits from Excel '
'the kd value must be given as input'))
if name is None:
raise InputError(
('Missing argument: name, when reading ArmourUnits from Excel '
'the name of the ArmourUnit must be given as input'))
# load ArmourUnits
params['BermMaterial'] = read_units(
filepath, kd=kd, name=name, sheet_name='BermMaterial')
elif BermMaterial is not None and 'CC' in structure:
params['BermMaterial'] = BermMaterial
# set column names and drop validation
df.columns = ['parameter', 'value', 'min', 'max', 'num', 'validation']
df.drop(labels=['validation'],axis=1, inplace=True)
parameters = df['parameter'].values
values = df['value'].values
for i, parameter in enumerate(parameters):
if 'slope' in parameter:
# check if value is nan
if not isinstance(values[i], str) and np.isnan(values[i]):
# parameter is varying
# convert min and max to tuple
min = _convert_string(df['min'].values[i], 'slope', parameter)
max = _convert_string(df['max'].values[i], 'slope', parameter)
# get num
num = df['num'].values[i]
# set value
value = (min, max, int(num))
else:
# parameter is set as constant parameter
value = _convert_string(values[i], 'slope', parameter)
elif 'lambda' in parameter:
# convert lambda to list
value = _convert_string(values[i], 'lambda', parameter)
else:
# get the value
value = values[i]
# check if filter rule
if isinstance(value, str):
params[parameter] = value
continue
# check if value is nan
if np.isnan(value):
# get varying input
min = df['min'].values[i]
max = df['max'].values[i]
num = df['num'].values[i]
# check if one of the values is nan
if np.isnan([min, max, num]).all():
# if all are nan set value to None
value = None
continue
elif np.isnan([min, max, num]).any():
raise TypeError(
(f'Varying parameter {parameter} is incorrectly '
'formatted'))
# convert to tuple
value = (min, max, int(num))
# add to params dict
params[parameter] = value
return Configurations(structure=structure, **params)
[docs]def read_breakwaters(filepath):
""" Read a breakwaters file into Configurations
Parameters
----------
filepath : path
any valid string path is acceptable, filepath must end with
.breakwaters
Returns
-------
bw.Configurations
a Configurations object with breakwater concepts
"""
# get the extension of the file
extension = os.path.splitext(filepath)[1]
# check if a extension has been included in the filepath
if not extension:
# if not add .breakwaters to the filepath
filepath = f'{filepath}.breakwaters '
else:
# check if extension is .breakwaters
if extension == '.breakwaters':
pass
else:
raise InputError(
(f'Can\'t load Configurations from {extension}, must be '
'.breakwaters'))
# load breakwaters file, and return Configurations object
file = open(filepath, 'rb')
config = pickle.load(file)
return config
[docs]class Configurations:
""" Make a conceptual design for multiple breakwaters
With this class a parametric design of one or more types of
breakwaters. The types of structures included in the tool are: a
rubble mound breakwater with rock (RRM) or concrete armour units
(CRM) as armour layer and a vertical (composite) breakwater with
rock (RC) or concrete armour units (CC) as armour layer of the
foundation layer. Each structure has its own set of required
(table 1) and optional (table 2) parameters.
For the parametric design some parameters are allowed to vary,
these parameters have a superscript in table 1 and 2. The input of
a varying parameter must be a tuple with a length of 3, i.e.
(min, max, num), where min and max are the minimum and maximum
value and num is the number of samples to generate.
.. note::
Alternatively, a design can also be made from an Excel input file
by using :py:func:`read_configurations`. The required Excel input
file can be generated by using
:obj:`bw.generate_excel <breakwater.utils.input_generator.generate_excel>`
Table 1: required parameters
+--------------------+-----+-----+-----+-----+
| Parameter | RRM | CRM | RC | CC |
+====================+=====+=====+=====+=====+
| LimitState | x | x | x | x |
+--------------------+-----+-----+-----+-----+
| rho_w | x | x | x | x |
+--------------------+-----+-----+-----+-----+
| slope_foreshore | x | x | x | x |
+--------------------+-----+-----+-----+-----+
| Grading | x | x | x | x |
+--------------------+-----+-----+-----+-----+
| slope :sup:`1` | x | x | | |
+--------------------+-----+-----+-----+-----+
| B :sup:`1` | x | x | | |
+--------------------+-----+-----+-----+-----+
| Dn50_core :sup:`1` | x | x | | |
+--------------------+-----+-----+-----+-----+
| N | x | | | |
+--------------------+-----+-----+-----+-----+
| ArmourUnit | | x | | |
+--------------------+-----+-----+-----+-----+
| Pc :sup:`1` | | | x | x |
+--------------------+-----+-----+-----+-----+
| rho_c | | | x | x |
+--------------------+-----+-----+-----+-----+
| rho_fill | | | x | x |
+--------------------+-----+-----+-----+-----+
| Bm :sup:`1` | | | x | x |
+--------------------+-----+-----+-----+-----+
| hb :sup:`1` | | | x | x |
+--------------------+-----+-----+-----+-----+
| mu | | | x | x |
+--------------------+-----+-----+-----+-----+
| BermMaterial | | | | x |
+--------------------+-----+-----+-----+-----+
:sup:`1` Parameter is allowed to vary, enter as a tuple with
(min, max, num), where min and max are the minimum and maximum
value and num is the number of samples to generate.
Table 2: Optional parameters with default values
+---------------------------+---------+-----+-----+-----+-----+
| Parameter | Default | RRM | CRM | RC | CC |
+===========================+=========+=====+=====+=====+=====+
| safety | 1 | o | o | o | o |
+---------------------------+---------+-----+-----+-----+-----+
| beta | 0 | o | o | o | o |
+---------------------------+---------+-----+-----+-----+-----+
| Soil | None | o | o | o | o |
+---------------------------+---------+-----+-----+-----+-----+
| slope_toe :sup:`1` | (2,3) | o | o | | |
+---------------------------+---------+-----+-----+-----+-----+
| phi | 40 | o | o | | |
+---------------------------+---------+-----+-----+-----+-----+
| B_toe :sup:`1` | None | o | o | | |
+---------------------------+---------+-----+-----+-----+-----+
| vdm | max | o | | | |
+---------------------------+---------+-----+-----+-----+-----+
| layers_rock | 2 | o | | o | |
+---------------------------+---------+-----+-----+-----+-----+
| layers_units | 1 | | o | | o |
+---------------------------+---------+-----+-----+-----+-----+
| layers_underlayer | 2 | o | o | | |
+---------------------------+---------+-----+-----+-----+-----+
| filter_rule | None | | o | | o |
+---------------------------+---------+-----+-----+-----+-----+
| pe_max | 500 | | | o | o |
+---------------------------+---------+-----+-----+-----+-----+
| SF_sliding | 1.2 | | | o | o |
+---------------------------+---------+-----+-----+-----+-----+
| SF_turning | 1.2 | | | o | o |
+---------------------------+---------+-----+-----+-----+-----+
| slope_foundation :sup:`1` | (2,3) | | | o | o |
+---------------------------+---------+-----+-----+-----+-----+
| \lambda_ | [1,1,1] | | | o | o |
+---------------------------+---------+-----+-----+-----+-----+
:sup:`1` Parameter is allowed to vary, enter as a tuple with
(min, max, num), where min and max are the minimum and maximum
value and num is the number of samples to generate.
Parameters
----------
structure : {'RRM', 'CRM', 'RC', 'CC'}
structure for which conceptual designs must be generated. RRM
for a rubble mound with rock as armour layer, CRM for a rubble
mound with concrete armour units as armour layer, RC for a
vertical (composite) breakwater with rock as armour layer for
the foundation and CC for a vertical (composite) breakwater with
concrete armour units as armour layer for the foundation.
LimitState : :py:class:`LimitState` or list of :py:class:`LimitState`
ULS, SLS or another limit state defined with
:py:class:`LimitState`
rho_w : float
density of water [kg/m³]
slope_foreshore : tuple
slope of the foreshore (V, H). For example a slope of 1:100 is
defined as (1, 100)
Grading : :py:class:`RockGrading`
standard rock grading defined in the NEN-EN 13383-1 or a user
defined rock grading.
safety : float, optional, default: 1
safety factor of design (number of standard deviations from the
mean)
Keyword arguments
-----------------
slope : tuple
Slope of the armour layer (V,H). For example a slope of 3V:4H
is defined as (3,4). Required for RRM and CRM.
B : float
Crest width [m], required for RRM and CRM
Dn50_core : float
nominal diameter for the stones in the core of the breakwater [m],
required for RRM and CRM
N : int
Number of incident waves at the toe of the structure [-],
required for RRM
ArmourUnit : obj
armour unit class which inherits from :py:class:`ConcreteArmour`,
for instance :py:class:`Xbloc` or :py:class:`XblocPlus`. Required
argument for CRM.
Pc : float
contribution of concrete to the total mass of the caisson.
value between 0 and 1. Required argument for RC and CC.
rho_c : float
density of concrete [kg/m³], required argument for RC and CC.
rho_fill : float
density of the fill material [kg/m³], required argument for RC
and CC.
Bm : float
width of the berm [m], required argument for RC and CC.
hb : float
height of the foundation layer [m], required argument for RC
and CC.
mu : float
friction factor between the caisson and the foundation [-],
required argument for RC and CC.
BermMaterial : obj
should be a :py:class:`RockGrading` or armour unit class which
inherits from :py:class:`ConcreteArmour`, for instance
:py:class:`Xbloc` or :py:class:`XblocPlus`. Required argument
for CC
beta : float, optional, default: 0
angle between direction of wave approach and a line normal to
the breakwater (degrees). Optional argument for RC and CC,
default value is 0.
slope_toe : tuple, optional, default: (2,3)
Slope of the armour layer (V,H). For example a slope of 2V:3H
is defined as (2,3). Optional argument for RRM and CRM, default
value is (2,3)
B_toe : float, optional, default: None
width of the top of the toe in meters. By default the width of
toe is taken as 3 * Dn50_toe. Optional argument for RRM and
CRM.
vdm : {min, max, avg}, optional, default: max
value to return in case both the deep and shallow water formula
are valid. min for the lowest value, max for the highest value
and avg for the average value, default is max. Optional argument
for RRM.
layers_rock : int, optional, default: 2
number of layer in the armour layer for a rubble mound breakwater
with rock as armour layer. Optional argument for RRM and RC,
default value is 2
layers_units : int, optional, default: 1
number of layer in the armour layer for a rubble mound
breakwater with concrete armour units as armour layer. Optional
argument for RRM and CC, default value is 1
layers_underlayer : int, optional, default: 2
number of layers in the underlayer. Optional argument for RRM
and CRM
filter_rule : {'Rock', 'Xbloc', 'XblocPlus'}, optional, default: None
filter rule to use for the substructure of the breakwater, for
Rock, Xbloc and XblocPlus the correct filter rule is
automatically selected. In case another type of armour layer is
used one of these filter rules must be chosen. Optional argument
for CRM, required if armour unit is not Xbloc or XblocPlus,
default value is None
pe_max : float, optional, default: 500
maximum value of the bearing pressure at the heel of the caisson.
Default value is set to 500 kPa, Goda (2000) advises a value
between 400 and 500 kPa. Optional parameter for RC and CC
SF_sliding : float, optional, default: 1.2
safety factor against sliding. Default value according to
Goda (2000). Optional argument for RC and CC, default value is 1.2
SF_turning : float, optional, default: 1.2
safety factor against sliding. Default value according to
Goda (2000). Optional argument for RC and CC, default value is 1.2
slope_foundation : tuple, optional, default: (2,3)
Slope of the armour layer (V,H). For example a slope of 2V:3H
is defined as (2,3). Optional argument for RC and CC, default
is (2,3)
lambda_ : list, optional, default: [1,1,1]
modification factors of Takahasi (2002) for alternative
monolithic breakwater. Input must be
\\lambda_= [:math:`\\lambda_1, \\lambda_2, \\lambda_3`].
Optional argument for RC and CC, default value is [1,1,1]
Soil : :py:class:`Soil`, optional, default: None
by default Soil is None, which means that the geotechnical checks
are not performed. By specifying a Soil object, the geotechnical
checks are automatically performed. Optional argument for all
structures
phi : float, optional, default: 40
internal friction angle of rock [degrees]. Optional argument for
RRM and CRM.
Attributes
----------
df : pd.DataFrame
DataFrame with all generated concepts
"""
def __init__(
self, structure, LimitState, rho_w, slope_foreshore, Grading,
Soil=None, safety=1, **kwargs):
""" See help(Configurations) for more info """
# add LimitState, rho_w and slope_foreshore to kwargs input
# so that input can be validated
kwargs['LimitState'] = LimitState
kwargs['rho_w'] = rho_w
kwargs['slope_foreshore'] = slope_foreshore
kwargs['safety'] = safety
kwargs['Grading'] = Grading
kwargs['Soil'] = Soil
# set grading as attribute for adding cost
self._Grading = Grading
# convert the input of structure to a list
if isinstance(structure, list):
# must be a list so no change
structure = structure
elif isinstance(structure, str):
# convert single input to list
structure = [structure]
# set empty configs and vkwargs
RM_config = {}
RM_vkwargs = {}
C_config = {}
C_vkwargs = {}
# check if the input is correct for the type of structure
# and set default values if a default value must be set
if 'RRM' in structure and 'CRM' in structure:
RRM_compute = True
CRM_compute = True
RM_vkwargs = _RM_vkwargs(type='both')
RM_config = _process_kwargs(kwargs=kwargs, vkwargs=RM_vkwargs)
# unpack constant arguments for both
beta = RM_config['beta']
phi = RM_config['phi']
# unpack constant arguments for RRM
N = RM_config['N']
layers_rock = RM_config['layers_rock']
vdm = RM_config['vdm']
# unpack constant arguments for CRM
ArmourUnit = RM_config['ArmourUnit']
layers_units = RM_config['layers_units']
filter_rule = RM_config['filter_rule']
elif 'RRM' in structure:
RRM_compute = True
CRM_compute = False
RM_vkwargs = _RM_vkwargs(type='Rock')
RM_config = _process_kwargs(kwargs=kwargs, vkwargs=RM_vkwargs)
# unpack constant arguments for RRM
beta = RM_config['beta']
N = RM_config['N']
layers_rock = RM_config['layers_rock']
vdm = RM_config['vdm']
phi = RM_config['phi']
elif 'CRM' in structure:
RRM_compute = False
CRM_compute = True
RM_vkwargs = _RM_vkwargs(type='ArmourUnit')
RM_config = _process_kwargs(kwargs=kwargs, vkwargs=RM_vkwargs)
# unpack constant arguments for CRM
beta = RM_config['beta']
ArmourUnit = RM_config['ArmourUnit']
layers_units = RM_config['layers_units']
filter_rule = RM_config['filter_rule']
phi = RM_config['phi']
elif 'RRM' not in structure and 'CRM' not in structure:
RRM_compute = False
CRM_compute = False
# input for caisson in structure
if 'RC' in structure and 'CC' in structure:
RC_compute = True
CC_compute = True
C_vkwargs = _C_vkwargs(type='both')
C_config = _process_kwargs(kwargs=kwargs, vkwargs=C_vkwargs)
# unpack constant arguments for both
rho_c = C_config['rho_c']
rho_fill = C_config['rho_fill']
mu = C_config['mu']
SF_sliding = C_config['SF_sliding']
SF_turning = C_config['SF_turning']
beta = C_config['beta']
lambda_ = C_config['lambda_']
filter_rule = C_config['filter_rule']
pe_max = C_config['pe_max']
# unpack constant arguments for RC
layers_rock = C_config['layers_rock']
# unpack constant arguments for CC
layers_units = C_config['layers_units']
BermMaterial = C_config['BermMaterial']
elif 'RC' in structure:
RC_compute = True
CC_compute = False
C_vkwargs = _C_vkwargs(type='Rock')
C_config = _process_kwargs(kwargs=kwargs, vkwargs=C_vkwargs)
# unpack constant arguments for both
rho_c = C_config['rho_c']
rho_fill = C_config['rho_fill']
mu = C_config['mu']
SF_sliding = C_config['SF_sliding']
SF_turning = C_config['SF_turning']
beta = C_config['beta']
lambda_ = C_config['lambda_']
filter_rule = C_config['filter_rule']
pe_max = C_config['pe_max']
# unpack constant arguments for RC
layers_rock = C_config['layers_rock']
elif 'CC' in structure:
RC_compute = False
CC_compute = True
C_vkwargs = _C_vkwargs(type='ArmourUnit')
C_config = _process_kwargs(kwargs=kwargs, vkwargs=C_vkwargs)
# unpack constant arguments for both
rho_c = C_config['rho_c']
rho_fill = C_config['rho_fill']
mu = C_config['mu']
SF_sliding = C_config['SF_sliding']
SF_turning = C_config['SF_turning']
beta = C_config['beta']
lambda_ = C_config['lambda_']
filter_rule = C_config['filter_rule']
pe_max = C_config['pe_max']
# unpack constant arguments for CC
layers_units = C_config['layers_units']
BermMaterial = C_config['BermMaterial']
elif 'RC' not in structure and 'CC' not in structure:
RC_compute = False
CC_compute = False
# check if a computation must be made
if (not RRM_compute and not CRM_compute
and not RC_compute and not CC_compute):
# noting to compute, raise error
raise NotSupportedError(
(f'{structure} has not been implemented, supported structures'
' are \'RRM\', \'CRM\', \'RC\' and \'CC\''))
# initialize a df for to store the _get_concept_set
self.df = pd.DataFrame()
# design all concepts for a rubble mound breakwater
# get all possible combinations of the varying arguments
RM_varying, RM_num_combinations = self._get_combinations(
vkwargs=RM_vkwargs, config=RM_config)
for i in range(RM_num_combinations):
# in first iteration check if computation is needed
# and unpack arguments if needed
if i == 0:
if not RRM_compute and not CRM_compute:
# break loop if computation is not needed
break
if RRM_compute and CRM_compute:
# both thus double the number of computations
num = 2*RM_num_combinations
else:
# only design 1 structure
num = RM_num_combinations
# initialize progress bar for rubble mound computations
RM_bar = ProgressBar(
number=num, task='Computing Rubble Mound')
# set id of bw
id = i + 1
# get the current concept
concept = self._get_concept_set(configs=RM_varying, id=id)
# unpack varying arguments same for RRM and CRM
B = concept['B']
Dn50_core = concept['Dn50_core']
B_toe = concept['B_toe']
slope = concept['slope']
slope_toe = concept['slope_toe']
if RRM_compute:
try:
with catch_warnings(record=True) as w:
RM_rock = RockRubbleMound(
slope=slope, slope_foreshore=slope_foreshore,
rho_w=rho_w, B=B, N=N, LimitState=LimitState,
Grading=Grading, Dn50_core=Dn50_core,
safety=safety, slope_toe=slope_toe, B_toe=B_toe,
layers=layers_rock, vdm=vdm, Soil=Soil, phi=phi,
id=id)
except RockGradingError:
# if there is no rock class in the grading that
# can satisfy the computed Dn50 of the armour layer
RM_rock = None
# save the concept to a temporary df and append to df
temp_df = pd.DataFrame(data={'type': ['RRM'],
'id': [id],
'concept': [RM_rock],
'B': [B],
'Dn50_core': [Dn50_core],
'B_toe': [B_toe],
'slope': [slope],
'slope_toe': [slope_toe],
'warnings': [w]})
self.df = self.df.append(temp_df, ignore_index=True, sort=True)
RM_bar.next()
if CRM_compute:
try:
with catch_warnings(record=True) as w:
RM_units = ConcreteRubbleMound(
slope=slope, slope_foreshore=slope_foreshore, B=B,
rho_w=rho_w, LimitState=LimitState, safety=safety,
Grading=Grading, ArmourUnit=ArmourUnit, phi=phi,
Dn50_core=Dn50_core, slope_toe=slope_toe,
B_toe=B_toe, layers=layers_units, Soil=Soil, id=id,
filter_rule=filter_rule)
except ArmourUnitsError:
# if there is no class of armour unit that
# can satisfy the computed Dn50 of the armour layer
units_bw = None
# save the concept to a temporary df and append to df
temp_df = pd.DataFrame(data={'type': ['CRM'],
'id': [id],
'concept': [RM_units],
'B': [B],
'Dn50_core': [Dn50_core],
'B_toe': [B_toe],
'slope': [slope],
'slope_toe': [slope_toe],
'warnings': [w]})
self.df = self.df.append(temp_df, ignore_index=True, sort=True)
RM_bar.next()
if id == RM_num_combinations:
RM_bar.finish()
# design all concepts for a caisson breakwater
# get all possible combinations of the varying arguments
C_varying, C_num_combinations = self._get_combinations(
vkwargs=C_vkwargs, config=C_config)
for i in range(C_num_combinations):
# in first iteration check if computation is needed
# and unpack arguments if needed
if i == 0:
if not RC_compute and not CC_compute:
# break loop if computation is not needed
break
if RC_compute and CC_compute:
# both thus double the number of computations
num = 2*C_num_combinations
else:
# only design 1 structure
num = C_num_combinations
if RRM_compute or CRM_compute:
# set task name for nice allignment with RM
task_name = 'Computing Caisson '
else:
# normal task name
task_name = 'Computing Caisson'
# initialize progress bar for caisson computations
C_bar = ProgressBar(number=num, task=task_name)
# set id of bw
id = i + 1
# get the current concept
concept = self._get_concept_set(configs=C_varying, id=id)
# unpack varying arguments
Pc = concept['Pc']
Bm = concept['Bm']
hb = concept['hb']
slope_foundation = concept['slope_foundation']
if RC_compute:
# design with rock as armour layer for the foundation
try:
with catch_warnings(record=True) as w:
C_rock = Caisson(
Pc=Pc, rho_c=rho_c, rho_fill=rho_fill, beta=beta,
rho_w=rho_w, Bm=Bm, hb=hb, layers=layers_rock,
BermMaterial=Grading, mu=mu, LimitState=LimitState,
safety=safety, slope_foreshore=slope_foreshore,
SF_sliding=SF_sliding, SF_turning=SF_turning,
slope_foundation=slope_foundation, lambda_=lambda_,
filter_rule=filter_rule, Grading=Grading, id=id,
pe_max=pe_max, Soil=Soil)
except RockGradingError:
# if there is no rock class in the grading that
# can satisfy the computed Dn50 of the armour layer
C_rock = None
# save the concept to a temporary df and append to df
temp_df = pd.DataFrame(data={'type': ['RC'],
'id': [id],
'concept': [C_rock],
'Pc': [Pc],
'Bm': [Bm],
'hb': [hb],
'slope_foundation': [slope_foundation],
'warnings': [w]})
self.df = self.df.append(temp_df, ignore_index=True, sort=True)
C_bar.next()
if CC_compute:
try:
with catch_warnings(record=True) as w:
C_units = Caisson(
Pc=Pc, rho_c=rho_c, rho_fill=rho_fill, rho_w=rho_w,
Bm=Bm, hb=hb, layers=layers_units, mu=mu, beta=beta,
BermMaterial=BermMaterial, LimitState=LimitState,
slope_foreshore=slope_foreshore, safety=safety,
SF_sliding=SF_sliding, SF_turning=SF_turning,
slope_foundation=slope_foundation, lambda_=lambda_,
filter_rule=filter_rule, Grading=Grading, id=id,
pe_max=pe_max, Soil=Soil)
except ArmourUnitsError:
# if there is no class of armour unit that
# can satisfy the computed Dn50 of the armour layer
C_units = None
# save the concept to a temporary df and append to df
temp_df = pd.DataFrame(data={'type': ['CC'],
'id': [id],
'concept': [C_units],
'Pc': [Pc],
'Bm': [Bm],
'hb': [hb],
'warnings': [w]})
self.df = self.df.append(temp_df, ignore_index=True, sort=True)
C_bar.next()
if id == C_num_combinations:
C_bar.finish()
@staticmethod
def _get_concept_set(configs, id):
""" get unique set of parameters and values """
index = id - 1
unique_set = {}
for param, val in configs.items():
unique_set[param] = val[index]
return unique_set
@staticmethod
def _get_combinations(vkwargs, config):
# get the varying and constant parameters
varying = {}
fixed = {}
for param, val in config.items():
if vkwargs[param]['Constant']:
# constant parameter, already in RM_config
continue
else:
# varying parameter
if isinstance(val, np.ndarray):
# also given as varying parameter
varying[param] = val
else:
# not set as varying thus constant
fixed[param] = val
# create all possible combinations of the varying parameters
# get the parameters and number of parameters
parameters = list(varying.keys())
num_parameters = len(parameters)
# empty dict to store the parameter set of each concept
configs = {}
# create value_index with the index of the value in the list of the dict
# value_index with 0 is the first concept
value_index = [0] * num_parameters
first_concept = True
current_combination = 0
while True:
# check for exit condition
# first compute maximum number of combinations
if first_concept:
max_combinations = 1
for param, values in varying.items():
combinations = len(values)
max_combinations = max_combinations * combinations
if current_combination == max_combinations:
break
# saving the concept in configs
for i in range(num_parameters):
current_parameter = parameters[i]
if first_concept:
# first time a list for each key must be made
configs[current_parameter] = []
# save the value of the current key
value = varying[current_parameter][value_index[i]]
configs[current_parameter].append(value)
# first concept has been generated so set to False
first_concept = False
# set loop variables, current_column_index starts from the right
change = True
current_column_index = num_parameters - 1
while change and current_column_index >= 0:
# get the length of the current parameter (how many values)
current_parameter = parameters[current_column_index]
max_values = len(varying[current_parameter])
# check if all variants for this level have been made
if (value_index[current_column_index] + 1) > max_values-1:
# all variants for the level have been added
# so set the last index to zero
value_index[current_column_index] = 0
# Change the upper variable by one
# We need to increment the immediate upper level loop by one
change = True
else:
# add one to the index for the next combination
value_index[current_column_index] += 1
# set the change to False so that the loop stops for the
# current level
change = False
# move one column to the left
current_column_index -= 1
current_combination += 1
# check if there are fixed values
if fixed:
# add the fixed parameters to the configs
for param, val in fixed.items():
values = [val for i in range(max_combinations)]
configs[param] = values
# return the combinations and number of combinations
return configs, max_combinations
[docs] def add_cost(
self, core_price=None, unit_price=None, concrete_price=None,
fill_price=None, transport_cost=None, investment=None,
length=None):
""" Compute the cost of each concept
Compute the cost of each concept and add the cost to the
:py:attr:`df`. The cost of the rocks must be specified in the
RockGrading. If transport cost are not included in the price of
rocks or core_price it can be given with the argument
transport_cost. For a Caisson breakwater it is possible to
specify the investment for renting a dry dock, the investment
is divided through the length of the breakwater to get the
investment cost per meter.
.. note::
The transport_cost are not added to the price of the armour
layer. The assumption has been made that the cost of
producing and transporting the armour units is included in
the unit_price.
Parameters
----------
core_price : float, optional, default: None
cost of the core material per m³, required for RRM and CRM
unit_price : float, optional, default: None
the cost of an armour unit per m³, required for CRM and CC
concrete_price : float, optional, default: None
price of concrete per m³, required for RC and CC
fill_price : float, optional, default: None
price of the fill material per m³, required for RC and CC
transport_cost : float, optional, default: None
the cost to transport a m³ of rock from the quarry to the
project location
investment : float
the investment required to rent a dry dock
length : float
length of the breakwater [m]
"""
# make dict of the cost for validation
cost = {
'core_price': core_price,
'unit_price': unit_price,
'concrete_price': concrete_price,
'fill_price': fill_price}
# check if all required cost have been given
for type in self.df.type.unique():
# validate cost
_process_cost(type, cost, self._Grading)
# set list to store cost in
computed_cost = []
# iterate over the generated concepts
for i, row in self.df.iterrows():
# check if concept is not None
if row.concept is None:
# not a valid concept
computed_cost.append(None)
else:
# valid concept
# check types and compute price
if row.type == 'RRM':
price = row.concept.cost(
*row.concept.variantIDs, core_price=core_price,
transport_cost=transport_cost, output='variant')
elif row.type == 'CRM':
price = row.concept.cost(
*row.concept.variantIDs, core_price=core_price,
unit_price=unit_price, transport_cost=transport_cost,
output='variant')
elif row.type == 'RC' or row.type == 'CC':
# check if investment cost must be added
if investment is not None and length is not None:
# add investment cost
row.concept.dry_dock(investment, length)
price = row.concept.cost(
*row.concept.variantIDs, concrete_price=concrete_price,
fill_price=fill_price, unit_price=unit_price)
else:
raise NotSupportedError(f'{row.type} is not supported')
# add cost to list
computed_cost.append(price)
# add column to the df
self.df['cost'] = computed_cost
[docs] def to_design_explorer(
self, params, mkdir='DesignExplorer', slopes='angles',
merge_Bm=True, merge_slope_toe=True):
""" Export concepts to Design Explorer 2
Creates a folder that can be used in Design Explorer 2, the
folder consists of the cross sections of all concepts and a csv
file with the data of the concepts. Parameters supported for
export can be seen in table 3. To use the folder in Design
Explorer 2, follow these steps:
- Upload the folder to your Google Drive
- Share the folder and get the shareable link
- Go to http://tt-acm.github.io/DesignExplorer/
- Click on *Get Data* and paste the shareable link below: *From
the cloud*
- Click on *Load Data*
- Enjoy exploring!
Table 3: possible parameter to export
+--------------------------+------------+------------+
| Parameter | RRM + CRM | RC + CC |
+==========================+============+============+
| cost | o | o |
+--------------------------+------------+------------+
| B | o | o |
+--------------------------+------------+------------+
| Rc | o | o |
+--------------------------+------------+------------+
| computed Dn50 armour | o | o |
+--------------------------+------------+------------+
| class armour | o | o |
+--------------------------+------------+------------+
| class Dn50 armour | o | o |
+--------------------------+------------+------------+
| computed Dn50 underlayer | o | o |
+--------------------------+------------+------------+
| class underlayer | o | o |
+--------------------------+------------+------------+
| class Dn50 underlayer | o | o |
+--------------------------+------------+------------+
| slope | o | |
+--------------------------+------------+------------+
| slope_toe | o | o :sup:`1` |
+--------------------------+------------+------------+
| Dn50_core | o | |
+--------------------------+------------+------------+
| B_toe | o | |
+--------------------------+------------+------------+
| computed Dn50 filter | o | |
+--------------------------+------------+------------+
| class filter | o | |
+--------------------------+------------+------------+
| class Dn50 filter | o | |
+--------------------------+------------+------------+
| Pc | | o |
+--------------------------+------------+------------+
| hb | | o |
+--------------------------+------------+------------+
| h_acc | | o |
+--------------------------+------------+------------+
| Bm | o :sup:`2` | o |
+--------------------------+------------+------------+
| slope_foundation | | o |
+--------------------------+------------+------------+
| UC :sup:`3` | | o |
+--------------------------+------------+------------+
| :sup:`1` slope_foundation is interpreted as slope_toe if
merge_slope_toe is set to True
| :sup:`2` B_toe is interpreted as Bm if merge_Bm is set to True
| :sup:`3` UC is the unity check for the bearing capacity of the
subsoil
Parameters
----------
params : list
list of parameters for the design explorer, parameter must
be str. See table 3 for the parameters that can be exported/
mkdir : str, optional, default: concepts
creates a folder in which all cross sections are saved,
upload the created folder to your Google Drive to use
Design Explorer 2
slopes : {angles, tuples}, optional, default: angles
how the slopes must be exported. tuples will export the
slope as (V,H) and angles as an angle in degrees
merge_Bm : bool, optional, default: True
True if Bm must be merged with B_toe when a Rubble Mound and
Vertical breakwater have been designed, False if you do not
want to merge the columns.
merge_slope_toe : bool, optional, default: True
True if slope_foundation must be merged with slope_toe when
a Rubble Mound and Vertical breakwater have been designed,
False if you do not want to merge the columns.
Raises
------
KeyError
if the cost are asked to export but not yet specified
"""
# set empty df for export
to_export = pd.DataFrame()
# check if RRM and CRM are in structure
designed_structures = self.df.type.unique()
if 'CRM' and 'RRM' in designed_structures:
format_class_as_string = True
else:
format_class_as_string = False
# set dir int variable for generating a new dir
dir_int = 0
# while loop to create dir with unique name
while True:
# set name of the new_dir
if dir_int == 0:
# first iteration, so use specified name
new_dir = mkdir
else:
# specified name already exists so add int to name of dir
new_dir = f'{mkdir} {dir_int}'
# check if dir already exists
if os.path.exists(new_dir):
# dir already exists, check if it has files
if len(os.listdir(new_dir)) == 0:
# no files thus data can be added, break loop
break
else:
# increase dir_int with 1 to make new dir
dir_int += 1
else:
# dir does not exists, so make one and break
os.mkdir(new_dir)
break
# check if the name of dir is different from the specified
if mkdir is not new_dir:
# name different, print user warning to notify user
user_warning(
(f'directory {mkdir} already exists, therefore a new directory'
f' has been made. The files can be found in {new_dir}'))
# set up progress bar, and CaseNo
num_concepts = self.df.shape[0]
bar = ProgressBar(number=num_concepts, task='Saving')
CaseNo = 1
# add list to store all CaseNo in
all_CaseNo = []
# start the export
for index, row in self.df.iterrows():
# check if concept exists
if row.concept is None:
# go to next concept
bar.next()
all_CaseNo.append([CaseNo])
continue
# compute number of variants for this concept
num_concepts = len(row.concept.variantIDs)
# store CaseNo of current concept in a list
temp_CaseNo = []
for id in row.concept.variantIDs:
# save name of the cross section
file_name = f'{row.type}.{row.id}'
if num_concepts > 1:
# add id if more than 1 concept
file_name = f'{file_name}{id}'
save_name = f'{new_dir}/{file_name}'
# save cross section of the current concept
row.concept.plot(id, save_name=save_name)
data = {'CaseNo': [CaseNo], 'type': [row.type]}
# get the values from the variant and add to data
variant = row.concept.get_variant(variantID=id)
to_explorer = _DE_params(
args=params, variant=variant, row=row, concept=row.concept,
structure=row.type, slopes=slopes,
change_CRM_class=format_class_as_string)
data.update(to_explorer)
# check if cost must be included
if 'cost' in params:
# check if cost have been added
if 'cost' in self.df.columns:
# add cost to data
data['cost'] = row.cost[id]
else:
raise KeyError(
'Cost have not been added, use add_cost to add '
'cost to the df')
# add image to data
data['img'] = [f'{file_name}.png']
# create a df of data and add to the df to_export
export_row = pd.DataFrame(data=data)
to_export = to_export.append(
export_row, ignore_index=True, sort=True)
# add CaseNo to temp CaseNo and increase with 1
temp_CaseNo.append(CaseNo)
CaseNo += 1
# add all stored CaseNo of the current concept in a list
all_CaseNo.append(temp_CaseNo)
bar.next()
bar.finish()
# add all CaseNo to the df so that concept can be selected by CaseNo
self.df['CaseNo'] = all_CaseNo
# post process for to_export
# if CRM and RRM for class armour values must be sorted
if format_class_as_string:
to_export.sort_values(by=['type'], inplace=True)
# check if all given params are in the columns
columns = list(to_export)
not_used_params = np.setdiff1d(
params, columns, assume_unique=True).tolist()
if any(not_used_params):
# remove parameters not used from params
column_names = [x for x in params if x not in not_used_params]
# raise UserWarning that not all given params have been exported
skipped = ', '.join(not_used_params)
user_warning(
f'Not all params have been exported, skipped: {skipped}')
else:
# all given params have been used
column_names = params
# add CaseNo and type as the left most column_names
# and make img the right most column_names
column_names[0:0] = ['CaseNo', 'type']
column_names.insert(len(column_names), 'img')
# restructure df with order of column_names
to_export = to_export.loc[:, column_names]
# merge B_toe with Bm if set to True
if 'B_toe' in column_names and 'Bm' in column_names and merge_Bm:
to_export.Bm.fillna(to_export.B_toe, inplace=True)
del to_export['B_toe']
# merge slope_foundation with slope_toe if set to True
if ('slope_toe' in column_names and 'slope_foundation' in column_names
and merge_slope_toe):
to_export.slope_toe.fillna(
to_export.slope_foundation, inplace=True)
del to_export['slope_foundation']
# save to_export df to a excel file
excel_save_name = f'{new_dir}/data.csv'
to_export.to_csv(excel_save_name, index=False)
print(f'folder {new_dir} is ready for Design Explorer 2')
[docs] def get_concept(self, id=None, CaseNo=None):
""" Get the specified concept
Parameters
----------
id : str, optional, default: None
id of the concept, the id of a concept for a rubble mound
breakwater out of rock is for instance: RRM.1
CaseNo : int, optional, default: None
CaseNo if the concept, only available if an export for the
Design Explorer has been made with
:py:meth:`to_design_explorer`
Returns
-------
concept : obj
a breakwater concept, for instance :py:obj:`RockRubbleMound`
Raises
------
InputError
if a concept is not selected with CaseNo or id, or if there
is no concept with the specified CaseNo or id
KeyError
if a concept can't be selected by CaseNo because the method
:py:meth:`to_design_explorer` has not yet been used, and the
CaseNo have therefore not yet been added to :py:attr:`df`
"""
if id and CaseNo is not None:
# cannot select concept by both id and CaseNo
raise InputError(
'Concept must be selected with id or CaseNo, not both')
elif id is not None:
# select concept by id
type = id.split('.')[0]
id = id.split('.')[1]
row_ids = self.df[self.df['id'] == int(id)]
row = row_ids[row_ids['type'] == type]
msg = f'id {type}.{id}'
elif CaseNo is not None:
# check if CaseNo is a column in df,
# is only added if the method to_design_explorer is used
if 'CaseNo' not in self.df.columns:
raise KeyError(
('CaseNo will only be added to the df if an export for '
'the design explorer has been made, run '
'to_design_explorer or select by id'))
# get the row of the df where the given CaseNo is in column CaseNo
row = self.df[self.df.apply(
lambda x: CaseNo in x['CaseNo'], axis=1)]
msg = f'CaseNo {CaseNo}'
else:
# CaseNo and id is not given thus raise error
raise InputError(
('No concept could be selected as neither an id nor CaseNo '
' has been given'))
# check if row has values
if row.empty:
# df is empty, thus CaseNo was invalid
raise InputError(f'There is no concept with {msg}')
else:
# return the concept
return row['concept'].values[0]
[docs] def to_breakwaters(self, save_name):
""" Save the df with designs in a pickle object
Method will save the df with all breakwater concepts in a pickle
object with extension .breakwaters.
Parameters
----------
save_name : str
name of the file
"""
# save to pickle
file_name = f'{save_name}.breakwaters'
file = open(file_name, 'wb')
pickle.dump(self, file)
print(f'saved to {file_name}')
[docs] def show_warnings(self):
""" Print all warnings encountered during the design
Method prints a table containing the unique warning messages,
with the time the warning was encountered during the design.
"""
all_warnings = self.df.warnings
unique_warnings = {}
# iterate over de column in the df
for warnings in all_warnings:
# iterate over the list of warnings from each concept
for warning in warnings:
msg = str(warning.message)
# check if warning is already encountered
if msg in unique_warnings:
# not unique, thus add one to the count
unique_warnings[msg] += 1
else:
# warning is unique, thus add to unique-wa
unique_warnings[msg] = 1
# print the table
table = [[key, count] for key, count in unique_warnings.items()]
print(tabulate(table, headers=['Warning', 'Count'], tablefmt='github'))
[docs] def cost_influence(self):
""" Plot the influence of the varying parameters on the cost
Method to see the influence of the varying parameters on the
cost of a concept. If more than 1 parameter has been specified
as a varying parameter the x-axis is normalised (from 0 to 1)
so that all parameters can be plotted on the same axis.
.. note::
When more than one varying parameter is specified several
lines must be plotted. To show the influence of one varying
parameter on the cost the other varying parameters are set
to their lower bound value.
"""
# headers to exclude
exclude = ['concept', 'id', 'type', 'warnings']
# make dict to the line data for plotting
lines = {}
for structure in self.df.type.unique():
# make df where the type equals the current structure
df = self.df[self.df.type == structure]
df = df.drop(columns=exclude)
# iterate over the columns of the df
for parameter in df.columns:
# check if parameter is slope
if 'slope' in parameter:
# check if not all nan
if not df[parameter].isnull().all():
# convert tuples to floats
df[parameter] = df[parameter].apply(
lambda x: np.round(np.arctan(x[0]/x[1])*180/np.pi, 2))
# check if not a cost parameter or in exclude
if parameter != 'cost' and parameter not in exclude:
# get the unique values
unique = df[parameter].unique()
# check if only 1 unique value in the column
if len(unique) > 1:
# varying parameter, add to lines dict
lines[parameter] = {'values': [], 'cost': []}
# get list of the values
values = list(df[parameter].values)
# iterate over the unique values
for unique_val in unique:
# get index of value in the df, first occurance
index = values.index(unique_val)
# get the row in the df with the data
row = df.iloc[index]
# add info to dict
# note that the unique value is retrieved from
# the df and not from the list with unique
# values, this is done so the result can be
# checked, i.e. if all values are indeed unique
lines[parameter]['values'].append(
row[parameter])
# compute average cost and add to dict
lines[parameter]['cost'].append(
np.mean(list(row.cost.values())))
# generate the plot
cost_influence(lines)