geolysis 0.3.0__py3-none-any.whl → 0.4.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
geolysis/spt.py ADDED
@@ -0,0 +1,492 @@
1
+ """ Standard penetration test module.
2
+
3
+ Enums
4
+ =====
5
+
6
+ .. autosummary::
7
+ :toctree: _autosummary
8
+ :nosignatures:
9
+
10
+ HammerType
11
+ SamplerType
12
+
13
+ Classes
14
+ =======
15
+
16
+ .. autosummary::
17
+ :toctree: _autosummary
18
+
19
+ SPTNDesign
20
+ EnergyCorrection
21
+ GibbsHoltzOPC
22
+ BazaraaPeckOPC
23
+ PeckOPC
24
+ LiaoWhitmanOPC
25
+ SkemptonOPC
26
+ DilatancyCorrection
27
+ """
28
+ import enum
29
+ from abc import abstractmethod
30
+ from typing import Final, Sequence
31
+
32
+ from geolysis.utils import isclose, log10, mean, round_, sqrt, validators, \
33
+ enum_repr
34
+
35
+ __all__ = ["SPTNDesign",
36
+ "HammerType",
37
+ "SamplerType",
38
+ "EnergyCorrection",
39
+ "GibbsHoltzOPC",
40
+ "BazaraaPeckOPC",
41
+ "PeckOPC",
42
+ "LiaoWhitmanOPC",
43
+ "SkemptonOPC",
44
+ "DilatancyCorrection"]
45
+
46
+
47
+ class SPTNDesign:
48
+ """ SPT Design Calculations.
49
+
50
+ Due to uncertainty in field procedure in standard penetration test and also
51
+ to consider all the N-value in the influence zone of a foundation, a method
52
+ was suggested to calculate the design N-value which should be used in
53
+ calculating the allowable bearing capacity of shallow foundation rather
54
+ than using a particular N-value. All the N-value from the influence zone is
55
+ taken under consideration by giving the highest weightage to the closest
56
+ N-value from the base.
57
+ """
58
+
59
+ def __init__(self, corrected_spt_n_values: Sequence[float]) -> None:
60
+ """
61
+ :param corrected_spt_n_values: Corrected SPT N-values within the
62
+ foundation influence zone.
63
+ :type corrected_spt_n_values: Sequence[float]
64
+ """
65
+ self.corrected_spt_n_values = corrected_spt_n_values
66
+
67
+ @property
68
+ def corrected_spt_n_values(self) -> Sequence[float]:
69
+ return self._corrected_spt_n_values
70
+
71
+ @corrected_spt_n_values.setter
72
+ @validators.min_len(1)
73
+ def corrected_spt_n_values(self, val: Sequence[float]) -> None:
74
+ self._corrected_spt_n_values = val
75
+
76
+ @round_(ndigits=1)
77
+ def average_spt_n_design(self) -> float:
78
+ """Calculates the average of the corrected SPT N-values within the
79
+ foundation influence zone.
80
+ """
81
+ return mean(self.corrected_spt_n_values)
82
+
83
+ @round_(ndigits=1)
84
+ def minimum_spt_n_design(self):
85
+ """The lowest SPT N-value within the influence zone can be taken as the
86
+ :math:`N_{design}` as suggested by ``Terzaghi & Peck (1948)``.
87
+ """
88
+ return min(self.corrected_spt_n_values)
89
+
90
+ @round_(ndigits=1)
91
+ def weighted_spt_n_design(self):
92
+ r"""Calculates the weighted average of the corrected SPT N-values
93
+ within the foundation influence zone.
94
+
95
+ :Equation:
96
+
97
+ .. math::
98
+
99
+ N_{design} = \dfrac{\sum_{i=1}^{n} \frac{N_i}{i^2}}
100
+ {\sum_{i=1}^{n}\frac{1}{i^2}}
101
+ """
102
+
103
+ sum_total = 0.0
104
+ sum_wgts = 0.0
105
+
106
+ for i, corr_spt_n_val in enumerate(self.corrected_spt_n_values,
107
+ start=1):
108
+ wgt = 1 / i ** 2
109
+ sum_total += wgt * corr_spt_n_val
110
+ sum_wgts += wgt
111
+
112
+ return sum_total / sum_wgts
113
+
114
+
115
+ @enum_repr
116
+ class HammerType(enum.StrEnum):
117
+ """Enumeration of hammer types."""
118
+ AUTOMATIC = enum.auto()
119
+ DONUT_1 = enum.auto()
120
+ DONUT_2 = enum.auto()
121
+ SAFETY = enum.auto()
122
+ DROP = PIN = enum.auto()
123
+
124
+
125
+ @enum_repr
126
+ class SamplerType(enum.StrEnum):
127
+ """Enumeration of sampler types."""
128
+ STANDARD = enum.auto()
129
+ NON_STANDARD = enum.auto()
130
+
131
+
132
+ class EnergyCorrection:
133
+ r"""SPT N-value standardized for field procedures.
134
+
135
+ On the basis of field observations, it appears reasonable to standardize
136
+ the field SPT N-value as a function of the input driving energy and its
137
+ dissipation around the sampler around the surrounding soil. The variations
138
+ in testing procedures may be at least partially compensated by converting
139
+ the measured N-value to :math:`N_{60}` assuming 60% hammer energy being
140
+ transferred to the tip of the standard split spoon.
141
+
142
+ :Equation:
143
+
144
+ .. math::
145
+
146
+ N_{ENERGY} = \dfrac{E_H \cdot C_B \cdot C_S \cdot C_R \cdot N}{ENERGY}
147
+
148
+ ``ENERGY``: 0.6, 0.55, etc
149
+ """
150
+
151
+ #: Hammer efficiency factors
152
+ HAMMER_EFFICIENCY_FACTORS = {HammerType.AUTOMATIC: 0.70,
153
+ HammerType.DONUT_1: 0.60,
154
+ HammerType.DONUT_2: 0.50,
155
+ HammerType.SAFETY: 0.55,
156
+ HammerType.DROP: 0.45,
157
+ HammerType.PIN: 0.45}
158
+
159
+ #: Sampler correction factors
160
+ SAMPLER_CORRECTION_FACTORS = {SamplerType.STANDARD: 1.00,
161
+ SamplerType.NON_STANDARD: 1.20}
162
+
163
+ def __init__(self, recorded_spt_n_value: int, *,
164
+ energy_percentage=0.6,
165
+ borehole_diameter=65.0,
166
+ rod_length=3.0,
167
+ hammer_type=HammerType.DONUT_1,
168
+ sampler_type=SamplerType.STANDARD):
169
+ """
170
+ :param recorded_spt_n_value: Recorded SPT N-value from field.
171
+ :type recorded_spt_n_value: int
172
+
173
+ :param energy_percentage: Energy percentage reaching the tip of the
174
+ sampler, defaults to 0.6
175
+ :type energy_percentage: float, optional
176
+
177
+ :param borehole_diameter: Borehole diameter, defaults to 65.0. (mm)
178
+ :type borehole_diameter: float, optional
179
+
180
+ :param rod_length: Rod length, defaults to 3.0. (m)
181
+ :type rod_length: float, optional
182
+
183
+ :param hammer_type: Hammer type, defaults to :attr:`HammerType.DONUT_1`
184
+ :type hammer_type: HammerType, optional
185
+
186
+ :param sampler_type: Sampler type, defaults to :attr:`SamplerType.STANDARD`
187
+ :type sampler_type: SamplerType, optional
188
+ """
189
+ self.recorded_spt_n_value = recorded_spt_n_value
190
+ self.energy_percentage = energy_percentage
191
+ self.borehole_diameter = borehole_diameter
192
+ self.rod_length = rod_length
193
+ self.hammer_type = hammer_type
194
+ self.sampler_type = sampler_type
195
+
196
+ @property
197
+ def recorded_spt_n_value(self) -> int:
198
+ return self._recorded_spt_value
199
+
200
+ @recorded_spt_n_value.setter
201
+ @validators.le(100)
202
+ @validators.gt(0)
203
+ def recorded_spt_n_value(self, val: int) -> None:
204
+ self._recorded_spt_value = val
205
+
206
+ @property
207
+ def energy_percentage(self) -> float:
208
+ return self._energy_percentage
209
+
210
+ @energy_percentage.setter
211
+ @validators.le(1.0)
212
+ @validators.gt(0.0)
213
+ def energy_percentage(self, val: float) -> None:
214
+ self._energy_percentage = val
215
+
216
+ @property
217
+ def borehole_diameter(self) -> float:
218
+ return self._borehole_diameter
219
+
220
+ @borehole_diameter.setter
221
+ @validators.le(200.0)
222
+ @validators.ge(65.0)
223
+ def borehole_diameter(self, val: float) -> None:
224
+ self._borehole_diameter = val
225
+
226
+ @property
227
+ def rod_length(self) -> float:
228
+ return self._rod_length
229
+
230
+ @rod_length.setter
231
+ @validators.gt(0.0)
232
+ def rod_length(self, val: float) -> None:
233
+ self._rod_length = val
234
+
235
+ @property
236
+ def hammer_efficiency(self) -> float:
237
+ """Hammer efficiency correction factor."""
238
+ return self.HAMMER_EFFICIENCY_FACTORS[self.hammer_type]
239
+
240
+ @property
241
+ def borehole_diameter_correction(self) -> float:
242
+ """Borehole diameter correction factor."""
243
+ if 65 <= self.borehole_diameter <= 115:
244
+ corr = 1.00
245
+ elif 115 < self.borehole_diameter <= 150:
246
+ corr = 1.05
247
+ else:
248
+ corr = 1.15
249
+ return corr
250
+
251
+ @property
252
+ def sampler_correction(self) -> float:
253
+ """Sampler correction factor."""
254
+ return self.SAMPLER_CORRECTION_FACTORS[self.sampler_type]
255
+
256
+ @property
257
+ def rod_length_correction(self) -> float:
258
+ """Rod length correction factor."""
259
+ if 3.0 <= self.rod_length <= 4.0:
260
+ corr = 0.75
261
+ elif 4.0 < self.rod_length <= 6.0:
262
+ corr = 0.85
263
+ elif 6.0 < self.rod_length <= 10.0:
264
+ corr = 0.95
265
+ else:
266
+ corr = 1.00
267
+ return corr
268
+
269
+ def correction(self) -> float:
270
+ """Energy correction factor."""
271
+ numerator = (self.hammer_efficiency
272
+ * self.borehole_diameter_correction
273
+ * self.sampler_correction
274
+ * self.rod_length_correction)
275
+ return numerator / self.energy_percentage
276
+
277
+ @round_(ndigits=1)
278
+ def corrected_spt_n_value(self) -> float:
279
+ """Corrected SPT N-value."""
280
+ return self.correction() * self.recorded_spt_n_value
281
+
282
+
283
+ class OPC:
284
+ """Base class for Overburden Pressure Correction (OPC)."""
285
+
286
+ def __init__(self, std_spt_n_value: float, eop: float) -> None:
287
+ """
288
+ :param std_spt_n_value: SPT N-value standardized for field procedures.
289
+ :type std_spt_n_value: float
290
+
291
+ :param eop: Effective overburden pressure (:math:`kPa`).
292
+ :type eop: float
293
+ """
294
+ self.std_spt_n_value = std_spt_n_value
295
+ self.eop = eop
296
+
297
+ @property
298
+ def std_spt_n_value(self) -> float:
299
+ return self._std_spt_n_value
300
+
301
+ @std_spt_n_value.setter
302
+ @validators.gt(0.0)
303
+ def std_spt_n_value(self, val: float) -> None:
304
+ self._std_spt_n_value = val
305
+
306
+ @round_(ndigits=1)
307
+ def corrected_spt_n_value(self) -> float:
308
+ """Corrected SPT N-value."""
309
+ corrected_spt = self.correction() * self.std_spt_n_value
310
+ # Corrected SPT should not be more
311
+ # than 2 times the Standardized SPT
312
+ return min(corrected_spt, 2 * self.std_spt_n_value)
313
+
314
+ @abstractmethod
315
+ def correction(self) -> float:
316
+ raise NotImplementedError
317
+
318
+
319
+ class GibbsHoltzOPC(OPC):
320
+ r"""Overburden Pressure Correction according to ``Gibbs & Holtz (1957)``.
321
+
322
+ :Equation:
323
+
324
+ .. math:: C_N = \dfrac{350}{\sigma_o + 70} \, \sigma_o \le 280kN/m^2
325
+
326
+ :math:`\frac{N_c}{N_{60}}` should lie between 0.45 and 2.0, if
327
+ :math:`\frac{N_c}{N_{60}}` is greater than 2.0, :math:`N_c` should be
328
+ divided by 2.0 to obtain the design value used in finding the bearing
329
+ capacity of the soil.
330
+ """
331
+
332
+ @property
333
+ def eop(self) -> float:
334
+ return self._eop
335
+
336
+ @eop.setter
337
+ @validators.le(280.0)
338
+ @validators.gt(0.0)
339
+ def eop(self, val: float) -> None:
340
+ self._eop = val
341
+
342
+ def correction(self) -> float:
343
+ """SPT Correction."""
344
+ corr = 350.0 / (self.eop + 70.0)
345
+ return corr / 2.0 if corr > 2.0 else corr
346
+
347
+
348
+ class BazaraaPeckOPC(OPC):
349
+ r"""Overburden Pressure Correction according to ``Bazaraa (1967)``, and
350
+ also by ``Peck and Bazaraa (1969)``.
351
+
352
+ :Equation:
353
+
354
+ .. math::
355
+
356
+ C_N &= \dfrac{4}{1 + 0.0418 \cdot \sigma_o}, \, \sigma_o \lt 71.8kN/m^2
357
+
358
+ C_N &= \dfrac{4}{3.25 + 0.0104 \cdot \sigma_o},
359
+ \, \sigma_o \gt 71.8kN/m^2
360
+
361
+ C_N &= 1 \, , \, \sigma_o = 71.8kN/m^2
362
+ """
363
+
364
+ #: Maximum effective overburden pressure (:math:`kPa`).
365
+ STD_PRESSURE: Final = 71.8
366
+
367
+ @property
368
+ def eop(self) -> float:
369
+ return self._eop
370
+
371
+ @eop.setter
372
+ @validators.ge(0.0)
373
+ def eop(self, val: float) -> None:
374
+ self._eop = val
375
+
376
+ def correction(self) -> float:
377
+ """SPT Correction."""
378
+ if isclose(self.eop, self.STD_PRESSURE, rel_tol=0.01):
379
+ corr = 1.0
380
+ elif self.eop < self.STD_PRESSURE:
381
+ corr = 4.0 / (1.0 + 0.0418 * self.eop)
382
+ else:
383
+ corr = 4.0 / (3.25 + 0.0104 * self.eop)
384
+ return corr
385
+
386
+
387
+ class PeckOPC(OPC):
388
+ r"""Overburden Pressure Correction according to ``Peck et al. (1974)``.
389
+
390
+ :Equation:
391
+
392
+ .. math:: C_N = 0.77 \log \left(\dfrac{2000}{\sigma_o} \right)
393
+ """
394
+
395
+ @property
396
+ def eop(self) -> float:
397
+ return self._eop
398
+
399
+ @eop.setter
400
+ @validators.ge(24.0)
401
+ def eop(self, val: float) -> None:
402
+ self._eop = val
403
+
404
+ def correction(self) -> float:
405
+ """SPT Correction."""
406
+ return 0.77 * log10(2000.0 / self.eop)
407
+
408
+
409
+ class LiaoWhitmanOPC(OPC):
410
+ r"""Overburden Pressure Correction according to ``Liao & Whitman (1986)``.
411
+
412
+ :Equation:
413
+
414
+ .. math:: C_N = \sqrt{\dfrac{100}{\sigma_o}}
415
+ """
416
+
417
+ @property
418
+ def eop(self) -> float:
419
+ return self._eop
420
+
421
+ @eop.setter
422
+ @validators.gt(0.0)
423
+ def eop(self, val: float) -> None:
424
+ self._eop = val
425
+
426
+ def correction(self) -> float:
427
+ """SPT Correction."""
428
+ return sqrt(100.0 / self.eop)
429
+
430
+
431
+ class SkemptonOPC(OPC):
432
+ r"""Overburden Pressure Correction according to ``Skempton (1986)``.
433
+
434
+ :Equation:
435
+
436
+ .. math:: C_N = \dfrac{2}{1 + 0.01044 \cdot \sigma_o}
437
+ """
438
+
439
+ @property
440
+ def eop(self) -> float:
441
+ return self._eop
442
+
443
+ @eop.setter
444
+ @validators.ge(0.0)
445
+ def eop(self, val: float) -> None:
446
+ self._eop = val
447
+
448
+ def correction(self) -> float:
449
+ """SPT Correction."""
450
+ return 2.0 / (1.0 + 0.01044 * self.eop)
451
+
452
+
453
+ class DilatancyCorrection:
454
+ r"""Dilatancy SPT Correction according to ``Terzaghi & Peck (1948)``.
455
+
456
+ For coarse sand, this correction is not required. In applying this
457
+ correction, overburden pressure correction is applied first and then
458
+ dilatancy correction is applied.
459
+
460
+ :Equation:
461
+
462
+ .. math::
463
+
464
+ (N_1)_{60} &= 15 + \dfrac{1}{2}((N_1)_{60} - 15) \, , \,
465
+ (N_1)_{60} \gt 15
466
+
467
+ (N_1)_{60} &= (N_1)_{60} \, , \, (N_1)_{60} \le 15
468
+ """
469
+
470
+ def __init__(self, corr_spt_n_value: float) -> None:
471
+ """
472
+ :param corr_spt_n_value: SPT N-value standardized for field procedures
473
+ and/or corrected for overburden pressure.
474
+ :type corr_spt_n_value: float
475
+ """
476
+ self.corr_spt_n_value = corr_spt_n_value
477
+
478
+ @property
479
+ def corr_spt_n_value(self) -> float:
480
+ return self._std_spt_n_value
481
+
482
+ @corr_spt_n_value.setter
483
+ @validators.gt(0.0)
484
+ def corr_spt_n_value(self, val: float) -> None:
485
+ self._std_spt_n_value = val
486
+
487
+ @round_(ndigits=1)
488
+ def corrected_spt_n_value(self) -> float:
489
+ """Corrected SPT N-value."""
490
+ if self.corr_spt_n_value <= 15.0:
491
+ return self.corr_spt_n_value
492
+ return 15.0 + 0.5 * (self.corr_spt_n_value - 15.0)
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 geolysis
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2024 geolysis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.