Source code for breakwater.caisson

import numpy as np
import matplotlib.pyplot as plt
from tabulate import tabulate

from .utils.exceptions import InputError, user_warning, NotSupportedError, RockGradingError
from .core.goda import Goda
from .core.overtopping import vertical
from .core.toe import toe_berm_stability
from .core.substructure import underlayer, _supported_armour_layers, layer_coefficient
from .core.scour import scour_protection


[docs]class Caisson: """ Design a (composite) vertical breakwater Makes a conceptual design of a vertical or composite vertical breakwater, with a caisson on a rubble mound foundation. The following computations are performed: - The necessary size of the armour layer of the foundation is designed with the modified Tanimoto formula (Takahashi, 2002). - The required stone size for the core of the foundation - The water depth in front of the caisson is computed based on the dimensions of the foundation and water depth - The crest freeboard is computed with the formulae from EurOtop (2018), :py:func:`vertical` is used, which automatically classifies the breakwater so that the correct formula is used. - The required width of the caisson is computed with the extended Goda formula (Takahasi, 2002). - The required width of the scour protection with Sumer and Fredsoe (2000). Note that a scour protection is only added if the width of the foundation is not sufficient. - If a Soil is specified the bearing capacity of the soil will also be checked with Brinch Hansen (1970). Parameters ---------- Pc : float contribution of concrete to the total mass of the caisson. value between 0 and 1 rho_c : float density of concrete [kg/m³] rho_fill : float density of the fill material [kg/m³] rho_w : float density of water [kg/m³] Bm : float width of the berm [m] hb : float height of the foundation layer [m] layers : int number of layers in the armour layer of the toe berm 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` LimitState : :py:class:`LimitState` or list of :py:class:`LimitState` ULS, SLS or another limit state defined with :py:class:`LimitState` slope_foreshore : tuple slope of the foreshore (V, H). For example a slope of 1:100 is defined as (1,100) mu : float friction factor between the caisson and the foundation [-] safety : float, optional, default: 1 safety factor of design (number of standard deviations from the mean) SF_sliding : float, optional, default: 1.2 safety factor against sliding. Default value according to Goda (2000) SF_turning : float, optional, default: 1.2 safety factor against sliding. Default value according to Goda (2000) beta : float, optional, default: 0 angle between direction of wave approach and a line normal to the breakwater [degrees] 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) 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`]. 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. [kPa] 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. Grading : :py:class:`RockGrading`, optional, default: None standard rock grading defined in the NEN-EN 13383-1 or a user defined rock grading. Required if the BermMaterial is not a :py:class:`RockGrading`. 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. id : int, optional, default: None unique id of the breakwater Attributes ---------- logger : dict dict of warnings and messages structure : dict dictionary with the computed Dn50, rock class, and average Dn50 of the rock class for each layer and the toe. This dictionary includes all variants, use :py:meth:`get_variant` to get the parameters of one specific variant. Alternatively, :py:meth:`print_variant` can be used to print the details of one, multiple or all variants. id : int unique id of the breakwater variantIDs : list list with the IDs of the variants generated for this rubble mound breakwater. price : dict cost of each variant, generated when :py:meth:`cost` or :py:meth:`dry_dock` is used. width_scour : float the required length of the scour protection [m]. The distance is measured from the front of the caisson geotechnical : dict dictionary with the computed values from the Brinch Hansen equation, has keys: Fr with the resistance, N with the net downward force and UC, which is the unity check (N/Fr). This dictionary is generated when the :py:obj:`Soil` argument is specified. """ def __init__( self, Pc, rho_c, rho_fill, rho_w, Bm, hb, layers, BermMaterial, LimitState, slope_foreshore, mu, safety=1, SF_sliding=1.2, SF_turning=1.2, beta=0, slope_foundation=(2,3), lambda_=[1,1,1], pe_max=500, filter_rule=None, Grading=None, Soil=None, id=None, **kwargs): """ See help(Caisson) for more info """ # set logger and structure self.logger = {'INFO': [], 'WARNING': []} self.structure = {} # set id and variantIDs self.id = id self.variantIDs = ['a'] # set cost Attribute self.price = None # compute relative buoyant density delta = (BermMaterial.rho - rho_w)/rho_w # convert beta from degrees to rad beta = beta*np.pi/180 # compute the angle of the foreshore slope_foreshore = np.arctan(slope_foreshore[0]/slope_foreshore[1]) # set top layer and filter layer top_layer = BermMaterial.name supported = _supported_armour_layers() if top_layer in supported: # supported armour layer filter_rule = top_layer if Grading is None and top_layer != 'Rock': # no grading for armour unit, thus raise error supported_armour_units = [ layer for layer in supported if layer is not 'Rock'] support_out = ', '.join(supported_armour_units) raise InputError( 'Missing argument: Grading. The top layer is made out of ' f'{support_out}, therefore a RockGrading must be specified') elif top_layer == 'Rock': Grading = BermMaterial if filter_rule is None: supported_rules = ', '.join(supported) raise NotSupportedError( (f'Filter rule for {top_layer} is not implemented, set ' f'filter rule to use with filter_rule to {supported_rules}')) # restructure the input LimitState(s) self._LimitStates = [] if isinstance(LimitState, list): self._LimitStates.extend(LimitState) else: self._LimitStates.append(LimitState) # set private attribute for plotting cross section self._input_arguments = { 'Grading': Grading, 'slope_foundation': slope_foundation, 'armour': top_layer} # design the armour layer of the foundation # set temporary values to check changes Dn50_berm, state = 0, 0 # iterate over the LimitStates for i, LimitState in enumerate(self._LimitStates): # get the wave height and submerged depth of caisson H13 = LimitState.get_Hs(definition='H13') T13 = LimitState['T13'] h = LimitState.h d = h - hb # set values for the while loop Dn50 = 0 compute_berm = True # compute Dn50 of the berm in a while loop, # because d = h - hb - layers*Dn50 while compute_berm: Dn50_temp = toe_berm_stability( Hs=H13, T=T13, d=d, Bm=Bm, Delta=delta, beta=beta) if (Dn50_temp - Dn50) > -0.05 and (Dn50_temp - Dn50) < 0.05: compute_berm = False else: Dn50 = Dn50_temp d = h - hb - layers*Dn50 # check if computed Dn50 of current LimitState is larger # than current normative Dn50 if Dn50 > Dn50_berm: # value is larger than current normative Dn50_berm = Dn50 state = i if d <= 0: raise InputError( ('Encountered negative value for d. The chosen foundation ' 'height (hb) is probably very close to the water depth (h)')) class_berm = BermMaterial.get_class(Dn50_berm) if top_layer == 'Rock': # berm material is made out of rock class_dn = BermMaterial.get_class_dn50(class_berm) else: # berm material is made out of concrete armour units class_dn = class_berm**(1/3) # compute d, the depth above the foundation d = self._LimitStates[state].h - hb - layers*Dn50_berm # add dimensions of armour layer to the structure self.structure['armour'] = { 'computed Dn50': Dn50_berm, 'class': class_berm, 'class Dn50': class_dn, 'state': state, 'layers': layers} # check if additional layer is needed range_Dn50_u = underlayer( Dn_armour=class_dn, armour_layer=filter_rule, rho=BermMaterial.rho, rho_rock=Grading.rho) class_u, class_dn_u = [], [] for dn_u in range_Dn50_u: rock_class = Grading.get_class(dn_u) if rock_class in class_u: # rock class is already in the underlayer so no need to # generate a new variant because design will be the same continue else: # first loop or new variant class_u.append(rock_class) class_dn_u.append(Grading.get_class_dn50(rock_class)) self.structure['foundation'] = {'computed Dn50': range_Dn50_u, 'class': class_u, 'class Dn50': class_dn_u, 'state': 'see armour'} # add message to the log if new variant was generated if len(class_u) == 2: self.logger['INFO'].append( 'two rock classes possible for the underlayer, generated new ' 'variant b') self.variantIDs.append('b') # compute the crest height # set temporary values Rc, state_overtopping, B, m = 0, 0, 0, 0 for i, LimitState in enumerate(self._LimitStates): self.logger['INFO'].append(f'computing with {LimitState.label}:') # get hydraulic parameters Hm0 = LimitState.get_Hs(definition='Hm0') L = LimitState.L(period='T_m_min_1', deep_water=True) s = LimitState.s(number='spectral') h = LimitState.h Rc_temp = vertical( Hm0=Hm0, q=LimitState['q'], h=h, d=d, L_m_min_1=L, s_m_min_1=s, safety=safety, logger=self.logger) # check if computed Rc of current LimitState is larger # than current normative Rc if Rc_temp > Rc: # if larger the normative Rc must be changed Rc = Rc_temp state_overtopping = i # compute/get wave heights for Goda H13 = LimitState.get_Hs(definition='H13') Hmax = LimitState['Hmax'] # compute the height of the caisson h_acc = h - hb goda = Goda( Hs=H13, Hmax=Hmax, h=h, d=d, h_acc=h_acc, hc=Rc_temp, Bm=Bm, T=LimitState['T13'], beta=beta, rho=rho_w, logger=self.logger, slope_foreshore=slope_foreshore, lambda_=lambda_) B_temp = goda.required_width( Pc=Pc, rho_c=rho_c, rho_f=rho_fill, rho_w=rho_w, mu=mu, t=0.5, SF_sliding=SF_sliding, SF_turning=SF_turning, logger=self.logger) # check bearing capacity pe = goda.bearing_pressure(Pc=Pc, rho_c=rho_c, rho_fill=rho_fill) if pe/1000 > pe_max: # bearing capacity is not large enough B_temp = goda.bearing_pressure_width( B1=B_temp, Pc=Pc, rho_c=rho_c, rho_fill=rho_fill, pe_max=pe_max) # replace normative msg from required_width in logger self.logger['INFO'][-1] = ('The bearing pressure is normative' ' for the computation of the width') m_temp = goda.mass(Pc=Pc, rho_c=rho_c, rho_fill=rho_fill) # check if values have changed from the current normative if m_temp > m: # set new normative values B = B_temp m = m_temp self.goda = goda state_goda = i # compute submerged depth of the caisson h_acc = self._LimitStates[state_goda].h - hb self.structure['caisson'] = { 'hb': hb, 'h_acc': h_acc, 'Pc': Pc, 'd': d, 'Rc': Rc, 'state_overtop': state_overtopping, 'B': B, 'state_goda': state_goda, 'Bm': Bm} # Compute required scour protection self.width_scour = 0 for LimitState in self._LimitStates: w = scour_protection(L=LimitState.L(period='Tm')) # check if larger than previous value if w >= self.width_scour: # set w as new width scour self.width_scour = w # make geotechnical design if Soil is not None: # compute the effective width of the foundation B_eff_sub = self._effective_width_foundation(rho_w, hb, B, m) # compute the forces of the foundation on the subsoil # compute horizontal stress per meter t = (self.goda.P()/(h_acc + Rc))/B_eff_sub/1000 # compute vertical stress per meter p = (self.goda._dFv(m) + self._Fsill(rho_w, hb, B))/B_eff_sub/1000 # compute the bearing capacity of the subsoil p_cap = Soil.brinch_hansen( p=p, t=t, B=B_eff_sub, L=None, q=0, rho_w=rho_w) # check if capacity is enough if p >= p_cap: # force is larger user_warning( ('Bearing capacity of the soil is smaller than the ' 'exerted stress')) # make geotechnical attribute and add values self.geotechnical = {'p_cap': p_cap, 'p': p, 'UC': p/p_cap} def _Fsill(self, rho_w, hb, B): """ Compute the downward force of the foundation """ # slope V, H = 1, 1 # get the grading Grading = self._input_arguments['Grading'] # compute downward force of the foundation # only the foundation directly below the caisson return (Grading.rho - rho_w)*9.81*(V/H * hb**2 + B*hb) def _effective_width_foundation(self, rho_w, hb, B, m): """ Compute the effective width of the foundation """ # slope V, H = 1, 1 # compute moment around the middle of the foundation Mb = self.goda.Ma() + self.goda.P() * hb # compute the eccentricity e = Mb/(self.goda._dFv(m) + self._Fsill(rho_w, hb, B)) # compute effective width of the foundation return B + 2*V/H*hb - 2*e def _validate_variant(self, variants): """ Validate the input of the variant Parameters ---------- variants : tuple variantIDs given as args input """ # check if a variant is specified if not variants: # no input is given so get the valid args for variant valid_args = self.variantIDs valid_args.append('all') # raise error valid = ', '.join(valid_args) raise InputError( 'did not specify which variants to use, possible arguments ' f'are {valid}') # check if input all is in variants if 'all' in variants: # set specified variants to all variantIDs variants = tuple(self.variantIDs) # return the variants return variants def _layers(self, variantID): """ compute the coordinates of all layers Parameters ---------- variantID : str identifier of the variant, see :py:attr:`variantIDs` for a list of all generated variants. Returns ------- dict coordinates of all layers """ # get the structure of the current variant structure = self.get_variant(variantID) # set empty dict to store coordinates in coordinates = {} # get the slope V, H = self._input_arguments['slope_foundation'] # conmpute constant to transfrom thickness of layer to x and # y coordinates, switched V and H because orthogonality transform_x = V/np.sqrt(V**2+H**2) transform_y = H/np.sqrt(V**2+H**2) # get geometrical parameters of the caisson B = structure['caisson']['B'] hc = structure['caisson']['h_acc'] + structure['caisson']['Rc'] hb = structure['caisson']['hb'] Bm = structure['caisson']['Bm'] # determine thickness of the armour layer layers_armour = structure['armour']['layers'] kt_armour = layer_coefficient( self._input_arguments['armour'], layers=layers_armour, placement='standard') dn = structure['armour']['class Dn50'] t_armour = kt_armour*layers_armour*dn # check if scour protection is required if Bm >= self.width_scour: # no scour protection required t_scour = 0 req_width = 0 else: # define thickness of the scour t_scour = 0.4 # compute required width of the scour protection req_width = self.width_scour - Bm - H/V * (hb + t_armour - t_scour) if req_width <= 0: # negative or zero width required width of the foundation # is enough, thus set values to 0 t_scour, req_width = 0, 0 # define line of the caisson # clockwise starting at the lower left of the caisson coordinates['caisson'] = { 'x': [-0.5*B, -0.5*B, 0.5*B, 0.5*B, -0.5*B], 'y': [hb, hb + hc, hb + hc, hb, hb] } # define points of the foundation arm_top = hb + t_armour arm_x3 = -0.5*B arm_x2 = arm_x3 - Bm arm_x1 = arm_x2 - H/V * (arm_top - t_scour) found_x0 = arm_x1 - req_width - H*t_scour/V found_x1 = arm_x1 - req_width found_x2 = (arm_x1 + H*(t_armour * transform_y)/V + t_armour*transform_x) found_x3 = found_x2 + H/V * (hb - t_scour) found_x4 = -0.5*B found_x5 = 0.5*B + Bm found_x6 = found_x5 + H/V * hb # define line of the foundation layers # clockwise starting at the left with the upper line coordinates['armour'] = { 'x': [arm_x1, arm_x2, arm_x3, arm_x3, found_x3, found_x2, arm_x1], 'y': [t_scour, arm_top, arm_top, hb, hb, t_scour, t_scour] } # check if scour protection is needed if req_width != 0: # protection needed coordinates['foundation'] = { 'x': [found_x0, found_x1, found_x2, found_x3, found_x4, found_x5, found_x6, found_x0], 'y': [0, t_scour, t_scour, hb, hb, hb, 0, 0] } else: # not needed coordinates['foundation'] = { 'x': [found_x2, found_x3, found_x4, found_x5, found_x6, found_x2], 'y': [0, hb, hb, hb, 0, 0] } return coordinates
[docs] def print_logger(self, level='warnings'): """ Print messages and warnings in the logger Parameters ---------- msg_level : {'info', 'warnings'}, optional, default: 'warnings' specify print level, highest level is warnings and lowest level is info. Note that the info level will also print all warnings """ # check if correct input has been given if level.lower() not in ['warnings', 'info']: raise NotSupportedError( f'{level} not implemented, must be info or warnings') # print logger for type, messages in self.logger.items(): if level.lower() == 'warnings': if type == 'INFO': continue print(f'{type}:') if messages: for message in messages: print(message) else: print(f'no {type.lower()} messages in log') print()
[docs] def get_variant(self, variantID): """ Get the dimensions for the specified variant Parameters ---------- variantID : str identifier of the variant, see :py:attr:`variantIDs` for a list of all generated variants. Returns ------- dict Parameters and values of the caisson (B, Rc) and the foundation layer (Dn50, Rock class) for one variant. Raises ------ KeyError If there is no variant with the given identifier """ variant = {} if variantID in self.variantIDs: key_u = self.variantIDs.index(variantID) else: raise KeyError(f'Variant with ID = {variantID} is not a variant, ' f'generated variants are: {self.variantIDs}') # add caisson variant['caisson'] = self.structure['caisson'] # add foundation armour and underlayer variant['armour'] = self.structure['armour'] if 'foundation' in self.structure: variant['foundation'] = {} for param, val in self.structure['foundation'].items(): if param == 'state': variant['foundation'][param] = val else: variant['foundation'][param] = val[key_u] # return the generated variant return variant
[docs] def print_variant(self, *variants, decimals=3): """ Print the details for the specified variant(s) This method will print the details of the structure for the specified variant(s). It prints the dimensions of the caisson (B, Rc) and the dimensions of the foundation layer (Dn50, rock class). Furthermore, from the table the normative LimitState for the design can be read. Parameters ---------- *variants : str IDs of the variants to plot, see :py:attr:`variantIDs` for a list of all generated variants. If 'all' is in the arguments, all variants will be plotted. decimals : int, optional, default: 3 number of decimal places to round to Raises ------ InputError If no arguments are specified KeyError If there is no variant with the given identifier """ # validate variants variants = self._validate_variant(variants) # print variant for id in variants: variant = self.get_variant(id) # print the name of the table if isinstance(self.id, int): table_name = f'Variant {self.id}{id}:' else: table_name = f'Variant {id}:' # set defaults and generate empty tables headers_caisson = ['parameter', 'value'] table_caisson, table_f = [], [] set_headers = True # fill the empty tables for i, (layer, dimensions) in enumerate(variant.items()): # caisson table if layer == 'caisson': for param, val in dimensions.items(): if param == 'Rc': val = np.round(val, decimals) state = self.structure['caisson']['state_overtop'] label = self._LimitStates[state].label table_val = f'{val} (with {label})' table_caisson.append([param, table_val]) elif param == 'B': val = np.round(val, decimals) state = self.structure['caisson']['state_goda'] label = self._LimitStates[state].label table_val = f'{val} (with {label})' table_caisson.append([param, table_val]) elif param == 'state_overtop' or param == 'state_goda': continue else: table_caisson.append( [param, np.round(val, decimals)]) # table for the foundation layer else: if set_headers: # set headers in first iteration headers_foundation = list(dimensions.keys()) headers_foundation.insert(0, 'layer') set_headers = False # make row with the layer and dimensions row = [layer] row.extend(dimensions.values()) # check if state is in the headers if 'state' in headers_foundation: # get the normative state and index of state state = dimensions['state'] i_state = headers_foundation.index('state') # check if state is an int if isinstance(state, int): # get the label of the normative limitstate label = self._LimitStates[state].label row[i_state] = label # add row to the table table_f.append(row) # print tables print(table_name) print() print(' Caisson dimensions') print(tabulate(table_caisson, headers_caisson, tablefmt="github")) print() print(' Foundation dimensions') print(tabulate( table_f, headers_foundation, tablefmt="github", floatfmt=(f'.{decimals}f'))) print('\n')
[docs] def plot(self, *variants, wlev=None, save_name=None): """ Plot the cross section of the specified breakwater(s) Parameters ---------- *variants : str IDs of the variants to plot, see :py:attr:`variantIDs` for a list of all generated variants. If 'all' is in the arguments, all variants will be plotted. wlev : str, optional, default: None label of the :py:class:`LimitState` from which the water level will be plotted. If no value is specified the water level from the normative limit state is used, which is the normative LimitState from the crest freeboard computation. save_name : str, optional, default: None if given the cross section is not shown but saved with the given name Raises ------ InputError If no variants are specified or if the label of wlev is not a valid label of a :py:class:`LimitState` KeyError If there is no variant with the given identifier """ # validate variants variants = self._validate_variant(variants) if wlev is None: wlev = self.structure['caisson']['state_overtop'] else: for i, LimitState in enumerate(self._LimitStates): if LimitState.label == wlev: wlev = i break # check if wlev has been changed from LimitState label into index if isinstance(wlev, str): # wlev is still a string so not changed, which means that # the specified wlev is not a specified LimitState raise InputError('There is no LimitState with the given label') # set figure plt.figure(figsize=(10, 5)) for i, id in enumerate(variants): if len(variants) == 2: # make subplot if two variants must be plotted plt.subplot(1, 2, i+1) # get the coordinates coordinates = self._layers(id) # set xlim_max and xlim_min variable xlim_max, xlim_min = 0, 0 # plot lines for layer, lines in coordinates.items(): plt.plot(lines['x'], lines['y'], color='k') # check largest value for xlim if np.max(lines['x']) >= xlim_max: # set max as xlim_max xlim_max = np.max(lines['x']) # check smallest value for xlim if np.min(lines['x']) <= xlim_min: # set min as xlim_min xlim_min = np.min(lines['x']) # plot bottom and wlev x_wlev_max = np.max(coordinates['caisson']['x']) plt.axhline(y=0, color='k', linewidth=2) plt.hlines( y=self._LimitStates[wlev].h, xmin=xlim_min*1.2, xmax=-x_wlev_max, color='b') plt.hlines( y=self._LimitStates[wlev].h, xmin=x_wlev_max, xmax=xlim_max*1.2, color='b') # set xlim and ylim ymax = np.max(coordinates['caisson']['y'])*1.2 plt.xlim(xlim_min*1.2, xlim_max*1.2) plt.ylim(-0.5, ymax) # add title to the plot if save_name is None: if isinstance(self.id, int): title = ('Cross section of monolithic breakwater ' f'{self.id}{id}') else: title = f'Cross section of monolithic breakwater {id}' else: name = save_name.split('/')[-1] title = f'Cross section of {name}' # add title, grid and set equal aspect ratio plt.title(title) plt.grid() plt.gca().set_aspect('equal', adjustable='box') plt.tight_layout() # save the figure if save_name is not None: plt.savefig(f'{save_name}.png') plt.close() else: plt.show()
[docs] def plot_pressure(self): """ Plot pressure distribution computed extended Goda formula Plots the pressure distribution computed with the extended Goda formula (Takahasi, 2002) together with the dimensions of the monolithic breakwater. .. warning:: Do not read the dimensions of the monolithic breakwater from the axes of the figure. The correct dimensions of the monolithic breakwater are depicted in the figure, or use :py:meth:`get_variant` or :py:meth:`print_variant`. """ self.goda.plot()
[docs] def area(self, variantID): """ Compute the area of all layers Method computes the area of each layer using Gauss's area formula. Which is given by the following formula: .. math:: \\mathbf{A}=\\frac{1}{2} | \\sum_{i=1}^{n-1} x_{i} y_{i+1} +x_{n} y_{1}-\\sum_{i=1}^{n-1} x_{i+1} y_{i}-x_{1} y_{n} | Parameters ---------- variantID : str identifier of the variant, see :py:attr:`variantIDs` for a list of all generated variants. Returns ------- dict dict with the area of each layer """ # get the coordinates of the layers coordinates = self._layers(variantID) # iterate over the layers to compute the area area = {} for layer, coord in coordinates.items(): # get the x and y coordinates x = coord['x'] y = coord['y'] # use Gauss's area formula A = 0.5*np.abs(np.dot(x,np.roll(y,1))-np.dot(y,np.roll(x,1))) # add to area dict area[layer] = A return area
[docs] def cost( self, *variants, concrete_price, fill_price, unit_price=None, output='variant'): """ Compute the cost per meter for each variant Method to compute the cost of each generated variant, the cost is computed per meter Parameters ---------- *variants : str IDs of the variants to plot, see :py:attr:`variantIDs` for a list of all generated variants. If 'all' is in the arguments, all variants will be plotted. concrete_price : float price of concrete per m³ fill_price : float price of the fill material per m³ unit_price : float, optional, default: None the cost of an armour unit per m³, required if the armour layer of the foundation is made out of armour units output : {variant, layer, average} format of the output dict, variant returns the total cost of each variant, layer the cost of each layer for each variant and average returns the average cost. Returns ------- dict the cost Raises ------ RockGradingError if no pricing is included in the given RockGrading """ # validate variants variants = self._validate_variant(variants) # get the grading, and check if the cost has been added Grading = self._input_arguments['Grading'] if 'price' in Grading[list(Grading.grading.keys())[0]]: # pricing has been added pass else: # pricing has not been added, raise error raise RockGradingError('There is no pricing in the RockGrading') # set empty dict to store the output in cost = {} # iterate over the generated variants for id in variants: # get the areas and structure of the variants areas = self.area(id) structure = self.get_variant(id) # iterate over the layers to price each layer variant_price = {} for layer, area in areas.items(): if layer is 'caisson': # compute price of the caisson Pc = structure['caisson']['Pc'] price = area*Pc*concrete_price + area*(1-Pc)*fill_price elif (self._input_arguments['armour'] is not 'Rock' and layer is 'armour'): # concrete armour units if unit_price is not None: price = area * unit_price else: raise InputError( ('argument unit_price is required when computing ' 'the cost with armour units as BermMaterial')) else: # layer of the breakwater rock_class = structure[layer]['class'] # get the price per meter price = Grading[rock_class]['price'] * area # add to dict variant_price[layer] = np.round(price, 2) # add to cost dict if output is 'variant' or output is 'average': # add total cost of all layers cost[id] = np.round(np.sum(list(variant_price.values())), 2) elif output is 'layer': # add the cost of each layer cost[id] = variant_price else: # invalid input raise NotSupportedError( (f'Cost can\'t be exported as {output}, must be variant, ' 'layer or average')) # check if average must be computed if output is 'average': # compute average cost cost = {'average': np.round(np.average(list(cost.values())), 2)} # check if dry dock has been added if self.price is None: # no dry dock added self.price = cost else: # check output if output == 'variant': # add investment to each variant for id, price in cost.items(): cost[id] = price + self.price elif output == 'layer': # add investment as a key cost['investment'] = self.price elif output == 'average': # add investment cost['average'] += self.price # check if the average cost is computed for only one variant if len(variants) == 1 and output == 'average': # print user_warning and change key into variant cost[variants[0]] = cost.pop('average') user_warning( ('Computing the average for one variantID, changed key ' 'average in dict with the specified variantID')) return cost
[docs] def dry_dock(self, investment, length): """ Add the investment cost of a dry dock to the concept This method adds the investment required to rent a dry dock to the concept. The investment cost is added to the concept by dividing the investment trough the length of the breakwater to get the required investment per running meter. Parameters ---------- investment : float the investment required to rent a dry dock length : float length of the breakwater [m] """ # check if cost have already been added if self.price is not None: # iterate over the price dict for id, price in self.price.items(): # check if mode was average if id == 'average': # mode was average self.price[id] = price + investment/length break else: # check if price is a dict if isinstance(price, dict): # mode was layer # add investment as additional key self.price['investment'] = investment/length break else: # set new price self.price[id] = price + investment/length else: # set investment as attribute self.price = investment/length