geolysis 0.2.0__py3-none-any.whl → 0.3.0__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/core/spt.py ADDED
@@ -0,0 +1,633 @@
1
+ from abc import abstractmethod
2
+ from dataclasses import KW_ONLY, dataclass
3
+ from statistics import StatisticsError
4
+ from typing import Protocol, Sequence
5
+
6
+ from .constants import ERROR_TOL
7
+ from .utils import isclose, log10, mean, round_, sqrt
8
+
9
+ __all__ = [
10
+ "WeightedSPT",
11
+ "AverageSPT",
12
+ "MinSPT",
13
+ "EnergyCorrection",
14
+ "GibbsHoltzOPC",
15
+ "BazaraaPeckOPC",
16
+ "PeckOPC",
17
+ "LiaoWhitmanOPC",
18
+ "SkemptonOPC",
19
+ "DilatancyCorrection",
20
+ ]
21
+
22
+
23
+ class OPCError(ValueError):
24
+ pass
25
+
26
+
27
+ class _SPTNDesign(Protocol):
28
+ def spt_n_design(self) -> float: ...
29
+
30
+
31
+ class _SPTCorrection(Protocol):
32
+ @property
33
+ @abstractmethod
34
+ def corrected_spt_number(self) -> float: ...
35
+
36
+
37
+ class _OPC(Protocol):
38
+ std_spt_number: float
39
+ eop: float
40
+
41
+ @property
42
+ @abstractmethod
43
+ def correction(self) -> float: ...
44
+
45
+ @property
46
+ @round_
47
+ def corrected_spt_number(self) -> float:
48
+ """Corrected SPT N-value."""
49
+ corrected_spt = self.correction * self.std_spt_number
50
+ return min(corrected_spt, 2 * self.std_spt_number)
51
+
52
+
53
+ @dataclass
54
+ class WeightedSPT:
55
+ r"""Calculates the weighted average of the corrected SPT N-values
56
+ within the foundation influence zone.
57
+
58
+ Due to uncertainty in field procedure in standard penetration test
59
+ and also to consider all the N-value in the influence zone of a
60
+ foundation, a method was suggested to calculate the design N-value
61
+ which should be used in calculating the allowable bearing capacity
62
+ of shallow foundation rather than using a particular N-value. All
63
+ the N-value from the influence zone is taken under consideration by
64
+ giving the highest weightage to the closest N-value from the base.
65
+
66
+ Parameters
67
+ ----------
68
+ spt_numbers : Sequence[float]
69
+ SPT N-values within the foundation influence zone. ``spt_numbers``
70
+ can either be **corrected** or **uncorrected** SPT N-values.
71
+
72
+ Notes
73
+ -----
74
+ Weighted average is given by the formula:
75
+
76
+ .. math::
77
+
78
+ N_{design} = \dfrac{\sum_{i=1}^{n} \frac{N_i}{i^2}}{\sum_{i=1}^{n}
79
+ \frac{1}{i^2}}
80
+
81
+ Examples
82
+ --------
83
+ >>> from geolysis.core.spt import WeightedSPT
84
+ >>> wgt = WeightedSPT([7.0, 15.0, 18.0])
85
+ >>> wgt.spt_n_design()
86
+ 9.3673
87
+ """
88
+
89
+ spt_numbers: Sequence[float]
90
+
91
+ @round_
92
+ def spt_n_design(self) -> float:
93
+ """SPT N-design.
94
+
95
+ Raises
96
+ ------
97
+ StatisticError
98
+ Raised if ``spt_numbers`` is empty.
99
+ """
100
+ if not self.spt_numbers:
101
+ err_msg = "method requires at least one data point."
102
+ raise StatisticsError(err_msg)
103
+
104
+ sum_total = 0.0
105
+ total_wgts = 0.0
106
+
107
+ for i, corrected_spt in enumerate(self.spt_numbers, start=1):
108
+ wgt = 1 / i**2
109
+ sum_total += wgt * corrected_spt
110
+ total_wgts += wgt
111
+
112
+ return sum_total / total_wgts
113
+
114
+
115
+ @dataclass
116
+ class AverageSPT:
117
+ r"""Calculates the average of the corrected SPT N-values within the
118
+ foundation influence zone.
119
+
120
+ Parameters
121
+ ----------
122
+ spt_numbers : Sequence[float]
123
+ SPT N-values within the foundation influence zone. ``spt_numbers``
124
+ can either be **corrected** or **uncorrected** SPT N-values.
125
+
126
+ Examples
127
+ --------
128
+ >>> from geolysis.core.spt import AverageSPT
129
+ >>> wgt = AverageSPT([7.0, 15.0, 18.0])
130
+ >>> wgt.spt_n_design()
131
+ 13.3333
132
+ """
133
+
134
+ spt_numbers: Sequence[float]
135
+
136
+ @round_
137
+ def spt_n_design(self) -> float:
138
+ """SPT N-design.
139
+
140
+ Raises
141
+ ------
142
+ StatisticError
143
+ Raised if ``spt_numbers`` is empty.
144
+ """
145
+ try:
146
+ return mean(self.spt_numbers)
147
+ except StatisticsError as e:
148
+ err_msg = "method requires at least one data point."
149
+ raise StatisticsError(err_msg) from None
150
+
151
+
152
+ @dataclass
153
+ class MinSPT:
154
+ """The lowest N-value within the influence zone can be taken as the
155
+ :math:`N_{design}` as suggested by ``Terzaghi & Peck (1948)``.
156
+
157
+ Parameters
158
+ ----------
159
+ spt_numbers : Sequence[float]
160
+ SPT N-values within the foundation influence zone. i.e. ``spt_numbers``
161
+ can either be **corrected** or **uncorrected** SPT N-values.
162
+
163
+ Examples
164
+ --------
165
+ >>> from geolysis.core.spt import MinSPT
166
+ >>> wgt = MinSPT([7.0, 15.0, 18.0])
167
+ >>> wgt.spt_n_design()
168
+ 7.0
169
+ """
170
+
171
+ spt_numbers: Sequence[float]
172
+
173
+ @round_
174
+ def spt_n_design(self) -> float:
175
+ """SPT N-design.
176
+
177
+ Raises
178
+ ------
179
+ StatisticError
180
+ Raised if ``spt_numbers`` is empty.
181
+ """
182
+ try:
183
+ return min(self.spt_numbers)
184
+ except ValueError as e:
185
+ err_msg = "method requires at least one data point."
186
+ raise StatisticsError(err_msg) from e
187
+
188
+
189
+ @dataclass
190
+ class EnergyCorrection:
191
+ r"""SPT N-value standardized for field procedures.
192
+
193
+ On the basis of field observations, it appears reasonable to standardize
194
+ the field SPT N-value as a function of the input driving energy and its
195
+ dissipation around the sampler around the surrounding soil. The variations
196
+ in testing procedures may be at least partially compensated by converting
197
+ the measured N-value to :math:`N_{60}` assuming 60% hammer energy being
198
+ transferred to the tip of the standard split spoon.
199
+
200
+ Parameters
201
+ ----------
202
+ recorded_spt_number : int
203
+ Recorded SPT N-value from field.
204
+ energy_percentage : float, default=0.6
205
+ Energy percentage reaching the tip of the sampler.
206
+ hammer_efficiency : float, default=0.6
207
+ Hammer efficiency, defaults to 0.6
208
+ borehole_diameter_correction : float, default=1.0
209
+ Borehole diameter correction
210
+ sampler_correction : float, default=1.0
211
+ Sampler correction
212
+ rod_length_correction : float, default=0.75
213
+ Rod length correction
214
+
215
+ Attributes
216
+ ----------
217
+ correction : float
218
+ corrected_spt_number : float
219
+
220
+ Notes
221
+ -----
222
+ Energy correction is given by the formula:
223
+
224
+ .. math::
225
+
226
+ N_{ENERGY} = \dfrac{E_H \cdot C_B \cdot C_S \cdot C_R \cdot N}{ENERGY}
227
+
228
+ ``ENERGY``: 0.6, 0.55, etc
229
+
230
+ Examples
231
+ --------
232
+ >>> from geolysis.core.spt import EnergyCorrection
233
+ >>> energy_cor = EnergyCorrection(recorded_spt_number=30)
234
+ >>> energy_cor.correction
235
+ 0.75
236
+ >>> energy_cor.corrected_spt_number
237
+ 22.5
238
+ """
239
+
240
+ recorded_spt_number: float
241
+
242
+ _: KW_ONLY
243
+
244
+ energy_percentage: float = 0.6
245
+ hammer_efficiency: float = 0.6
246
+ borehole_diameter_correction: float = 1.0
247
+ sampler_correction: float = 1.0
248
+ rod_length_correction: float = 0.75
249
+
250
+ @property
251
+ @round_
252
+ def correction(self) -> float:
253
+ """SPT Correction."""
254
+ return (
255
+ self.hammer_efficiency
256
+ * self.borehole_diameter_correction
257
+ * self.sampler_correction
258
+ * self.rod_length_correction
259
+ ) / self.energy_percentage
260
+
261
+ @property
262
+ @round_
263
+ def corrected_spt_number(self) -> float:
264
+ """Corrected SPT N-value."""
265
+ return self.correction * self.recorded_spt_number
266
+
267
+
268
+ @dataclass
269
+ class GibbsHoltzOPC(_OPC):
270
+ r"""Overburden Pressure Correction according to ``Gibbs & Holtz (1957)``.
271
+
272
+ Parameters
273
+ ----------
274
+ std_spt_number : float
275
+ SPT N-value standardized for field procedures.
276
+ eop : float, :math:`kN/m^2`
277
+ Effective overburden pressure.
278
+
279
+ Attributes
280
+ ----------
281
+ correction : float
282
+ corrected_spt_number : float
283
+
284
+ Notes
285
+ -----
286
+ Overburden Pressure Correction is given by the formula:
287
+
288
+ .. math:: C_N = \dfrac{350}{\sigma_o + 70} \, \sigma_o \le 280kN/m^2
289
+
290
+ :math:`\frac{N_c}{N_{60}}` should lie between 0.45 and 2.0, if
291
+ :math:`\frac{N_c}{N_{60}}` is greater than 2.0, :math:`N_c` should be
292
+ divided by 2.0 to obtain the design value used in finding the bearing
293
+ capacity of the soil.
294
+
295
+ Examples
296
+ --------
297
+ >>> from geolysis.core.spt import GibbsHoltzOPC
298
+ >>> opc_cor = GibbsHoltzOPC(std_spt_number=22.5, eop=100.0)
299
+ >>> opc_cor.correction
300
+ 2.0588
301
+ >>> opc_cor.corrected_spt_number
302
+ 23.1615
303
+ """
304
+
305
+ #: Maximum effective overburden pressure. |rarr| :math:`kN/m^2`
306
+ STD_PRESSURE = 280.0
307
+
308
+ def __init__(self, std_spt_number: float, eop: float) -> None:
309
+ self.std_spt_number = std_spt_number
310
+ self.eop = eop
311
+
312
+ def __repr__(self) -> str:
313
+ return f"{type(self).__name__}({self.std_spt_number=}, {self.eop=})"
314
+
315
+ @property
316
+ def eop(self) -> float:
317
+ return self._eop
318
+
319
+ @eop.setter
320
+ def eop(self, __val: float):
321
+ if __val <= 0:
322
+ err_msg = f"eop = {__val} cannot be less than or equal to 0"
323
+ raise OPCError(err_msg)
324
+
325
+ if __val > self.STD_PRESSURE:
326
+ err_msg = f"eop = {__val} should be less than {self.STD_PRESSURE}"
327
+ raise OPCError(err_msg)
328
+ self._eop = __val
329
+
330
+ @property
331
+ @round_
332
+ def correction(self) -> float:
333
+ """SPT Correction."""
334
+ return 350.0 / (self.eop + 70)
335
+
336
+ @property
337
+ @round_
338
+ def corrected_spt_number(self) -> float:
339
+ """Corrected SPT N-value."""
340
+ corrected_spt = self.correction * self.std_spt_number
341
+ spt_ratio = corrected_spt / self.std_spt_number
342
+
343
+ if spt_ratio > 2.0:
344
+ corrected_spt /= 2
345
+
346
+ return min(corrected_spt, 2 * self.std_spt_number)
347
+
348
+
349
+ @dataclass
350
+ class BazaraaPeckOPC(_OPC):
351
+ r"""Overburden Pressure Correction according to ``Bazaraa (1967)``, and
352
+ also by ``Peck and Bazaraa (1969)``.
353
+
354
+ Parameters
355
+ ----------
356
+ std_spt_number : float
357
+ SPT N-value standardized for field procedures.
358
+ eop : float, :math:`kN/m^2`
359
+ Effective overburden pressure.
360
+
361
+ Attributes
362
+ ----------
363
+ correction : float
364
+ corrected_spt_number : float
365
+
366
+ Notes
367
+ -----
368
+ Overburden Pressure Correction is given by the formula:
369
+
370
+ .. math::
371
+
372
+ C_N &= \dfrac{4}{1 + 0.0418 \cdot \sigma_o}, \, \sigma_o \lt 71.8kN/m^2
373
+
374
+ C_N &= \dfrac{4}{3.25 + 0.0104 \cdot \sigma_o}, \, \sigma_o \gt 71.8kN/m^2
375
+
376
+ C_N &= 1 \, , \, \sigma_o = 71.8kN/m^2
377
+
378
+ Examples
379
+ --------
380
+ >>> from geolysis.core.spt import BazaraaPeckOPC
381
+ >>> opc_cor = BazaraaPeckOPC(std_spt_number=22.5, eop=100.0)
382
+ >>> opc_cor.correction
383
+ 0.9324
384
+ >>> opc_cor.corrected_spt_number
385
+ 20.979
386
+ """
387
+
388
+ std_spt_number: float
389
+ eop: float
390
+
391
+ #: Maximum effective overburden pressure. |rarr| :math:`kN/m^2`
392
+ STD_PRESSURE = 71.8
393
+
394
+ @property
395
+ @round_
396
+ def correction(self) -> float:
397
+ """SPT Correction."""
398
+ if isclose(self.eop, self.STD_PRESSURE, rel_tol=ERROR_TOL):
399
+ correction = 1.0
400
+ elif self.eop < self.STD_PRESSURE:
401
+ correction = 4 / (1 + 0.0418 * self.eop)
402
+ else:
403
+ correction = 4 / (3.25 + 0.0104 * self.eop)
404
+
405
+ return correction
406
+
407
+ @property
408
+ def corrected_spt_number(self) -> float:
409
+ """Corrected SPT N-value."""
410
+ return super().corrected_spt_number
411
+
412
+
413
+ @dataclass
414
+ class PeckOPC(_OPC):
415
+ r"""Overburden Pressure Correction according to ``Peck et al (1974)``.
416
+
417
+ Parameters
418
+ ----------
419
+ std_spt_number : float
420
+ SPT N-value standardized for field procedures.
421
+ eop : float, :math:`kN/m^2`
422
+ Effective overburden pressure.
423
+
424
+ Attributes
425
+ ----------
426
+ correction : float
427
+ corrected_spt_number : float
428
+
429
+ Notes
430
+ -----
431
+ Overburden Pressure Correction is given by the formula:
432
+
433
+ .. math:: C_N = 0.77 \log \left( \dfrac{2000}{\sigma_o} \right)
434
+
435
+ Examples
436
+ --------
437
+ >>> from geolysis.core.spt import PeckOPC
438
+ >>> opc_cor = PeckOPC(std_spt_number=22.5, eop=100.0)
439
+ >>> opc_cor.correction
440
+ 1.0
441
+ >>> opc_cor.corrected_spt_number
442
+ 22.5
443
+ """
444
+
445
+ #: Maximum effective overburden pressure. |rarr| :math:`kN/m^2`
446
+ STD_PRESSURE = 24.0
447
+
448
+ def __init__(self, std_spt_number: float, eop: float) -> None:
449
+ self.std_spt_number = std_spt_number
450
+ self.eop = eop
451
+
452
+ def __repr__(self) -> str:
453
+ return f"{type(self).__name__}({self.std_spt_number=}, {self.eop=})"
454
+
455
+ @property
456
+ def eop(self) -> float:
457
+ return self._eop
458
+
459
+ @eop.setter
460
+ def eop(self, __val: float):
461
+ if __val < self.STD_PRESSURE:
462
+ err_msg = f"eop = {__val} cannot be less than 24"
463
+ raise OPCError(err_msg)
464
+ self._eop = __val
465
+
466
+ @property
467
+ @round_
468
+ def correction(self) -> float:
469
+ """SPT Correction."""
470
+ return 0.77 * log10(2000 / self.eop)
471
+
472
+ @property
473
+ def corrected_spt_number(self) -> float:
474
+ """Corrected SPT N-value."""
475
+ return super().corrected_spt_number
476
+
477
+
478
+ @dataclass
479
+ class LiaoWhitmanOPC(_OPC):
480
+ r"""Overburden Pressure Correction according to ``Liao & Whitman (1986)``.
481
+
482
+ Parameters
483
+ ----------
484
+ std_spt_number : float
485
+ SPT N-value standardized for field procedures.
486
+ eop : float, :math:`kN/m^2`
487
+ Effective overburden pressure.
488
+
489
+ Attributes
490
+ ----------
491
+ correction : float
492
+ corrected_spt_number : float
493
+
494
+ Notes
495
+ -----
496
+ Overburden Pressure Correction is given by the formula:
497
+
498
+ .. math:: C_N = \sqrt{\dfrac{100}{\sigma_o}}
499
+
500
+ Examples
501
+ --------
502
+ >>> from geolysis.core.spt import LiaoWhitmanOPC
503
+ >>> opc_cor = LiaoWhitmanOPC(std_spt_number=22.5, eop=100.0)
504
+ >>> opc_cor.correction
505
+ 1.0
506
+ >>> opc_cor.corrected_spt_number
507
+ 22.5
508
+ """
509
+
510
+ def __init__(self, std_spt_number: float, eop: float) -> None:
511
+ self.std_spt_number = std_spt_number
512
+ self.eop = eop
513
+
514
+ def __repr__(self) -> str:
515
+ return f"{type(self).__name__}({self.std_spt_number=}, {self.eop=})"
516
+
517
+ @property
518
+ def eop(self) -> float:
519
+ return self._eop
520
+
521
+ @eop.setter
522
+ def eop(self, __val: float):
523
+ if __val <= 0:
524
+ err_msg = f"eop = {__val} cannot be less than or equal to 0"
525
+ raise OPCError(err_msg)
526
+ self._eop = __val
527
+
528
+ @property
529
+ @round_
530
+ def correction(self) -> float:
531
+ """SPT Correction."""
532
+ return sqrt(100 / self.eop)
533
+
534
+ @property
535
+ def corrected_spt_number(self) -> float:
536
+ """Corrected SPT N-value."""
537
+ return super().corrected_spt_number
538
+
539
+
540
+ @dataclass
541
+ class SkemptonOPC(_OPC):
542
+ r"""Overburden Pressure Correction according to ``Skempton (1986)``.
543
+
544
+ Parameters
545
+ ----------
546
+ std_spt_number : float
547
+ SPT N-value standardized for field procedures.
548
+ eop : float, :math:`kN/m^2`
549
+ Effective overburden pressure.
550
+
551
+ Attributes
552
+ ----------
553
+ correction : float
554
+ corrected_spt_number : float
555
+
556
+ Notes
557
+ -----
558
+ Overburden Pressure Correction is given by the formula:
559
+
560
+ .. math:: C_N = \dfrac{2}{1 + 0.01044 \cdot \sigma_o}
561
+
562
+ Examples
563
+ --------
564
+ >>> from geolysis.core.spt import SkemptonOPC
565
+ >>> opc_cor = SkemptonOPC(std_spt_number=22.5, eop=100.0)
566
+ >>> opc_cor.correction
567
+ 0.9785
568
+ >>> opc_cor.corrected_spt_number
569
+ 22.0163
570
+ """
571
+
572
+ std_spt_number: float
573
+ eop: float
574
+
575
+ @property
576
+ @round_
577
+ def correction(self) -> float:
578
+ """SPT Correction."""
579
+ return 2 / (1 + 0.01044 * self.eop)
580
+
581
+ @property
582
+ def corrected_spt_number(self) -> float:
583
+ """Corrected SPT N-value."""
584
+ return super().corrected_spt_number
585
+
586
+
587
+ @dataclass
588
+ class DilatancyCorrection:
589
+ r"""Dilatancy SPT Correction according to ``Terzaghi & Peck (1948)``.
590
+
591
+ For coarse sand, this correction is not required. In applying this
592
+ correction, overburden pressure correction is applied first and then
593
+ dilatancy correction is applied.
594
+
595
+ Parameters
596
+ ----------
597
+ spt_number : float
598
+ SPT N-value standardized for field procedures or corrected for
599
+ overburden pressure.
600
+
601
+ Attributes
602
+ ----------
603
+ corrected_spt_number : float
604
+
605
+ Notes
606
+ -----
607
+ Dilatancy correction is given by the formula:
608
+
609
+ .. math::
610
+
611
+ (N_1)_{60} &= 15 + \dfrac{1}{2}((N_1)_{60} - 15) \, , \,
612
+ (N_1)_{60} \gt 15
613
+
614
+ (N_1)_{60} &= (N_1)_{60} \, , \, (N_1)_{60} \le 15
615
+
616
+ Examples
617
+ --------
618
+ >>> from geolysis.core.spt import DilatancyCorrection
619
+ >>> dil_cor = DilatancyCorrection(spt_number=22.5)
620
+ >>> dil_cor.corrected_spt_number
621
+ 18.75
622
+ """
623
+
624
+ spt_number: float
625
+
626
+ @property
627
+ @round_
628
+ def corrected_spt_number(self) -> float:
629
+ """Corrected SPT N-value."""
630
+ if self.spt_number <= 15:
631
+ return self.spt_number
632
+
633
+ return 15 + 0.5 * (self.spt_number - 15)