#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = "Bruno Stuyts"
# Native Python packages
# 3rd party packages
import numpy as np
from scipy.stats import linregress
import warnings
from plotly import subplots
import plotly.graph_objs as go
# Project imports
from groundhog.general.validation import Validator
PILETEST_CHINKONDLER = {
'loads': {'type': 'list', 'elementtype': 'float', 'order': 'ascending', 'unique': False, 'empty_allowed': False},
'settlements': {'type': 'list', 'elementtype': 'float', 'order': 'ascending', 'unique': True, 'empty_allowed': False},
'no_discard_points': {'type': 'int', 'min_value': 0, 'max_value': None},
'max_settlement': {"type": "float", "min_value": 0.0, "max_value": 1000.0},
'selected_settlement': {"type": "float", "min_value": 0.0, "max_value": 1000.0},
}
PILETEST_CHINKONDLER_ERRORRETURN = {
'intercept [mm/kN]': np.nan,
'slope [1/kN]': np.nan,
'Correlation coefficient [-]': np.nan,
'Qmax [kN]': np.nan,
'Qdisp [kN]': None,
'Settlements [mm]': None,
'Q [kN]': None,
'construction_fig': None,
'extrapolation_fig': None
}
[docs]
@Validator(PILETEST_CHINKONDLER, PILETEST_CHINKONDLER_ERRORRETURN)
def piletest_chinkondler(loads, settlements, no_discard_points=1, max_settlement=50, selected_settlement=40, show_fig=True, **kwargs):
"""
Extrapotates a pile head load-settlement curve based on the procedure by Chin-Kondler.
The settlements are divided by the corresponding loads. This yields a straight line in a graph of this fraction vs settlement.
The user selects how many points to discard and the fitting happens using ``np.polyfit``.
An extrapolated load-settlement curve is calculated and plotted for the user to check the results.
:param loads: List of loads recorded during the load test. Note that unload-reload loops should be removed before running the algorithm (:math:`Q`) [kN]
:param settlements: List of settlements recorded during the load test. Should be the same length as the list with loads (:math:`s`) [mm]
:param no_discard_points: Number of points at the start of the curve to discard for the fitting of the straight line.
:param max_settlement: Maximum settlement used for plotting the reconstructed pile head load-settlement curve (:math:`s_{max}`) [mm]. Optional, default=50mm.
:param selected_settlement: Settlement as which pile capacity is calculated (e.g. 10% of OD) (:math:`Q_{s=s_{\\text{selected}}}`) [kN]. Optional, default=40mm.
:param show_fig: Boolean determining whether the figures for the Chin-Kondler construction need to be plotted (default behaviour) or returned in the output dictionary.
.. math::
\\frac{s}{Q} = a + b \\cdot s
Q_{\\text{ult}} = 1 / b
:returns: Dictionary with the following keys:
- 'intercept [mm/kN]': Coefficient :math:`a` from the linear regression [mm/kN]
- 'slope [1/kN]': Slope :math:`b` for the linear regression [1/kN]
- 'Correleation coefficient [-]': Pearson correlation coefficient for the points used for the construction.
- 'Qmax [kN]': Ultimate pile resistance (fully mobilised shaft and base) (:math:`Q_{\\text{ult}}`) [kN]
- 'Qdisp [kN]': Pile resistance at the selected displacement level (e.g. 10% of OD) (:math:`Q_{s=s_\\text{selected}}`) [kN]
- 'Settlements [mm]': List with settlements for the load-displacement reconstruction [mm]
- 'Q [kN]': List with loads for the load-displacement reconstruction [kN]
- 'construction_fig': Figure with the linear regression construction (for inspection of the goodness-of-fit)
"""
if loads.__len__() != settlements.__len__():
raise ValueError("Array of loads and settlements need to be the same length.")
else:
pass
_loads = np.array(loads)
_settlements = np.array(settlements)
_s_Q = _settlements / _loads
_result = linregress(_settlements[no_discard_points:], _s_Q[no_discard_points:])
_reconstruction_settlements = np.linspace(0, max_settlement, 250)
_reconstruction_s_Q = _result.intercept + _result.slope * _reconstruction_settlements
_reconstruction_Q = _reconstruction_settlements / _reconstruction_s_Q
construction_fig = subplots.make_subplots(rows=1, cols=1, print_grid=False)
_data = go.Scatter(x=_settlements, y=_s_Q, showlegend=False, mode='markers',name='Test')
construction_fig.add_trace(_data, 1, 1)
_data = go.Scatter(x=_reconstruction_settlements, y=_reconstruction_s_Q, showlegend=True, mode='lines',name='Fit', line=dict(color='red', dash='dot'))
construction_fig.add_trace(_data, 1, 1)
construction_fig['layout']['xaxis1'].update(title=r'$ s \ \text{[mm]}$')
construction_fig['layout']['yaxis1'].update(title=r'$ s/Q \ \text{[mm/kN]}$')
construction_fig['layout'].update(height=500, width=700)
if show_fig:
construction_fig.show()
extrapolation_fig = subplots.make_subplots(rows=1, cols=1, print_grid=False, shared_yaxes=True)
trace1 = go.Scatter(x=_loads, y=_settlements, showlegend=True, mode='markers', name='Test')
extrapolation_fig.append_trace(trace1, 1, 1)
_data = go.Scatter(x=_reconstruction_Q, y=_reconstruction_settlements, showlegend=True, mode='lines',name='Fit', line=dict(color='red', dash='dot'))
extrapolation_fig.add_trace(_data, 1, 1)
_data = go.Scatter(
x=[1 / _result.slope, 1 / _result.slope],
y=[0, max_settlement], showlegend=True, mode='lines',name=r'$Q_{max}$', line=dict(color='grey', dash='dashdot'))
extrapolation_fig.add_trace(_data, 1, 1)
extrapolation_fig['layout']['xaxis1'].update(title=r'$ Q \ \text{[kN]}$', side='top', anchor='y')
extrapolation_fig['layout']['yaxis1'].update(title=r'$ s \ \text{[mm]}$', autorange='reversed')
extrapolation_fig['layout'].update(height=600, width=600)
if show_fig:
extrapolation_fig.show()
return {
'intercept [mm/kN]': _result.intercept,
'slope [1/kN]': _result.slope,
'Correlation coefficient [-]': _result.rvalue,
'Qmax [kN]': 1 / _result.slope,
'Qdisp [kN]': np.interp(selected_settlement, _reconstruction_settlements, _reconstruction_Q),
'Settlements [mm]': _reconstruction_settlements,
'Q [kN]': _reconstruction_Q,
'construction_fig': construction_fig,
'extrapolation_fig': extrapolation_fig
}