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/__init__.py +1 -3
- geolysis/foundation.py +393 -0
- geolysis/soil_classifier.py +781 -0
- geolysis/spt.py +492 -0
- {geolysis-0.3.0.dist-info → geolysis-0.4.3.dist-info}/LICENSE.txt +21 -21
- geolysis-0.4.3.dist-info/METADATA +193 -0
- geolysis-0.4.3.dist-info/RECORD +9 -0
- {geolysis-0.3.0.dist-info → geolysis-0.4.3.dist-info}/WHEEL +1 -1
- geolysis/core/__init__.py +0 -9
- geolysis/core/abc_4_cohl_soils.py +0 -495
- geolysis/core/constants.py +0 -47
- geolysis/core/estimators.py +0 -549
- geolysis/core/foundation.py +0 -543
- geolysis/core/soil_classifier.py +0 -859
- geolysis/core/spt.py +0 -633
- geolysis/core/utils.py +0 -113
- geolysis-0.3.0.dist-info/METADATA +0 -223
- geolysis-0.3.0.dist-info/RECORD +0 -14
- {geolysis-0.3.0.dist-info → geolysis-0.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,781 @@
|
|
1
|
+
""" Soil classification module.
|
2
|
+
|
3
|
+
Exceptions
|
4
|
+
==========
|
5
|
+
|
6
|
+
.. autosummary::
|
7
|
+
:toctree: _autosummary
|
8
|
+
|
9
|
+
SizeDistError
|
10
|
+
|
11
|
+
Enums
|
12
|
+
=====
|
13
|
+
|
14
|
+
.. autosummary::
|
15
|
+
:toctree: _autosummary
|
16
|
+
:nosignatures:
|
17
|
+
|
18
|
+
CLF_TYPE
|
19
|
+
USCSSymbol
|
20
|
+
AASHTOSymbol
|
21
|
+
|
22
|
+
Classes
|
23
|
+
=======
|
24
|
+
|
25
|
+
.. autosummary::
|
26
|
+
:toctree: _autosummary
|
27
|
+
|
28
|
+
AtterbergLimits
|
29
|
+
PSD
|
30
|
+
AASHTO
|
31
|
+
USCS
|
32
|
+
|
33
|
+
Functions
|
34
|
+
=========
|
35
|
+
|
36
|
+
.. autosummary::
|
37
|
+
:toctree: _autosummary
|
38
|
+
|
39
|
+
create_soil_classifier
|
40
|
+
"""
|
41
|
+
import enum
|
42
|
+
from abc import abstractmethod
|
43
|
+
from typing import Protocol
|
44
|
+
from typing import NamedTuple, Optional, Sequence
|
45
|
+
|
46
|
+
from geolysis.utils import enum_repr, isclose, round_, validators
|
47
|
+
|
48
|
+
__all__ = ["CLF_TYPE",
|
49
|
+
"AtterbergLimits",
|
50
|
+
"PSD",
|
51
|
+
"AASHTO",
|
52
|
+
"USCS",
|
53
|
+
"create_soil_classifier"]
|
54
|
+
|
55
|
+
|
56
|
+
class SizeDistError(ZeroDivisionError):
|
57
|
+
"""Exception raised when size distribution is not provided."""
|
58
|
+
|
59
|
+
|
60
|
+
@enum_repr
|
61
|
+
class _Clf(tuple, enum.Enum):
|
62
|
+
|
63
|
+
def __str__(self) -> str:
|
64
|
+
return self.name
|
65
|
+
|
66
|
+
@property
|
67
|
+
def symbol(self) -> str:
|
68
|
+
return self.value[0]
|
69
|
+
|
70
|
+
@property
|
71
|
+
def description(self) -> str:
|
72
|
+
return self.value[1]
|
73
|
+
|
74
|
+
|
75
|
+
class USCSSymbol(_Clf):
|
76
|
+
"""Unified Soil Classification System (USCS) symbols and descriptions."""
|
77
|
+
G = GRAVEL = ("G", "Gravel")
|
78
|
+
S = SAND = ("S", "Sand")
|
79
|
+
M = SILT = ("M", "Silt")
|
80
|
+
C = CLAY = ("C", "Clay")
|
81
|
+
O = ORGANIC = ("O", "Organic")
|
82
|
+
W = WELL_GRADED = ("W", "Well graded")
|
83
|
+
P = POORLY_GRADED = ("P", "Poorly graded")
|
84
|
+
L = LOW_PLASTICITY = ("L", "Low plasticity")
|
85
|
+
H = HIGH_PLASTICITY = ("H", "High plasticity")
|
86
|
+
GW = ("GW", "Well graded gravels")
|
87
|
+
GP = ("GP", "Poorly graded gravels")
|
88
|
+
GM = ("GM", "Silty gravels")
|
89
|
+
GC = ("GC", "Clayey gravels")
|
90
|
+
GM_GC = ("GM-GC", "Gravelly clayey silt")
|
91
|
+
GW_GM = ("GW-GM", "Well graded gravel with silt")
|
92
|
+
GP_GM = ("GP-GM", "Poorly graded gravel with silt")
|
93
|
+
GW_GC = ("GW-GC", "Well graded gravel with clay")
|
94
|
+
GP_GC = ("GP-GC", "Poorly graded gravel with clay")
|
95
|
+
SW = ("SW", "Well graded sands")
|
96
|
+
SP = ("SP", "Poorly graded sands")
|
97
|
+
SM = ("SM", "Silty sands")
|
98
|
+
SC = ("SC", "Clayey sands")
|
99
|
+
SM_SC = ("SM-SC", "Sandy clayey silt")
|
100
|
+
SW_SM = ("SW-SM", "Well graded sand with silt")
|
101
|
+
SP_SM = ("SP-SM", "Poorly graded sand with silt")
|
102
|
+
SW_SC = ("SW-SC", "Well graded sand with clay")
|
103
|
+
SP_SC = ("SP-SC", "Poorly graded sand with clay")
|
104
|
+
ML = ("ML", "Inorganic silts with low plasticity")
|
105
|
+
CL = ("CL", "Inorganic clays with low plasticity")
|
106
|
+
ML_CL = ("ML-CL", "Clayey silt with low plasticity")
|
107
|
+
OL = ("OL", "Organic clays with low plasticity")
|
108
|
+
MH = ("MH", "Inorganic silts with high plasticity")
|
109
|
+
CH = ("CH", "Inorganic clays with high plasticity")
|
110
|
+
OH = ("OH", "Organic silts with high plasticity")
|
111
|
+
Pt = ("Pt", "Highly organic soils")
|
112
|
+
|
113
|
+
|
114
|
+
class AASHTOSymbol(_Clf):
|
115
|
+
"""AASHTO soil classification symbols and descriptions."""
|
116
|
+
A_1_a = ("A-1-a", "Stone fragments, gravel, and sand")
|
117
|
+
A_1_b = ("A-1-b", "Stone fragments, gravel, and sand")
|
118
|
+
A_3 = ("A-3", "Fine sand")
|
119
|
+
A_2_4 = ("A-2-4", "Silty or clayey gravel and sand")
|
120
|
+
A_2_5 = ("A-2-5", "Silty or clayey gravel and sand")
|
121
|
+
A_2_6 = ("A-2-6", "Silty or clayey gravel and sand")
|
122
|
+
A_2_7 = ("A-2-7", "Silty or clayey gravel and sand")
|
123
|
+
A_4 = ("A-4", "Silty soils")
|
124
|
+
A_5 = ("A-5", "Silty soils")
|
125
|
+
A_6 = ("A-6", "Clayey soils")
|
126
|
+
A_7_5 = ("A-7-5", "Clayey soils")
|
127
|
+
A_7_6 = ("A-7-6", "Clayey soils")
|
128
|
+
|
129
|
+
|
130
|
+
class AtterbergLimits:
|
131
|
+
"""Represents the water contents at which soil changes from one state to
|
132
|
+
the other.
|
133
|
+
"""
|
134
|
+
|
135
|
+
class __A_LINE:
|
136
|
+
|
137
|
+
def __get__(self, obj, objtype=None) -> float:
|
138
|
+
return 0.73 * (obj.liquid_limit - 20.0)
|
139
|
+
|
140
|
+
#: The ``A-line`` determines if a soil is clayey or silty.
|
141
|
+
#: :math:`A = 0.73(LL - 20.0)`
|
142
|
+
A_LINE = __A_LINE()
|
143
|
+
|
144
|
+
def __init__(self, liquid_limit: float, plastic_limit: float):
|
145
|
+
"""
|
146
|
+
:param liquid_limit: Water content beyond which soils flows under their
|
147
|
+
own weight (%). It can also be defined as the
|
148
|
+
minimum moisture content at which a soil flows upon
|
149
|
+
application of a very small shear force.
|
150
|
+
:type liquid_limit: float
|
151
|
+
|
152
|
+
:param plastic_limit: Water content at which plastic deformation can be
|
153
|
+
initiated (%). It is also the minimum water
|
154
|
+
content at which soil can be rolled into a thread
|
155
|
+
3mm thick. (molded without breaking)
|
156
|
+
:type plastic_limit: float
|
157
|
+
"""
|
158
|
+
if liquid_limit < plastic_limit:
|
159
|
+
raise ValueError("liquid_limit cannot be less than plastic_limit")
|
160
|
+
|
161
|
+
self.liquid_limit = liquid_limit
|
162
|
+
self.plastic_limit = plastic_limit
|
163
|
+
|
164
|
+
@property
|
165
|
+
def liquid_limit(self) -> float:
|
166
|
+
return self._liquid_limit
|
167
|
+
|
168
|
+
@liquid_limit.setter
|
169
|
+
@validators.ge(0.0)
|
170
|
+
def liquid_limit(self, val: float) -> None:
|
171
|
+
self._liquid_limit = val
|
172
|
+
|
173
|
+
@property
|
174
|
+
def plastic_limit(self) -> float:
|
175
|
+
return self._plastic_limit
|
176
|
+
|
177
|
+
@plastic_limit.setter
|
178
|
+
@validators.ge(0.0)
|
179
|
+
def plastic_limit(self, val: float) -> None:
|
180
|
+
self._plastic_limit = val
|
181
|
+
|
182
|
+
@property
|
183
|
+
@round_
|
184
|
+
def plasticity_index(self) -> float:
|
185
|
+
"""Plasticity index (PI) is the range of water content over which the
|
186
|
+
soil remains in the plastic state.
|
187
|
+
|
188
|
+
It is also the numerical difference between the liquid limit and
|
189
|
+
plastic limit of the soil.
|
190
|
+
|
191
|
+
:Equation:
|
192
|
+
|
193
|
+
.. math:: PI = LL - PL
|
194
|
+
"""
|
195
|
+
return self.liquid_limit - self.plastic_limit
|
196
|
+
|
197
|
+
@property
|
198
|
+
def fine_material_type(self) -> USCSSymbol:
|
199
|
+
"""
|
200
|
+
Checks whether the soil is either clay or silt.
|
201
|
+
"""
|
202
|
+
return USCSSymbol.CLAY if self.above_A_LINE() else USCSSymbol.SILT
|
203
|
+
|
204
|
+
def above_A_LINE(self) -> bool:
|
205
|
+
"""Checks if the soil sample is above A-Line."""
|
206
|
+
return self.plasticity_index > self.A_LINE
|
207
|
+
|
208
|
+
def limit_plot_in_hatched_zone(self) -> bool:
|
209
|
+
"""Checks if soil sample plot in the hatched zone on the atterberg
|
210
|
+
chart.
|
211
|
+
"""
|
212
|
+
return 4 <= self.plasticity_index <= 7 and 10 < self.liquid_limit < 30
|
213
|
+
|
214
|
+
@round_
|
215
|
+
def liquidity_index(self, nmc: float) -> float:
|
216
|
+
r"""Return the liquidity index of the soil.
|
217
|
+
|
218
|
+
Liquidity index of a soil indicates the nearness of its ``natural water
|
219
|
+
content`` to its ``liquid limit``. When the soil is at the plastic
|
220
|
+
limit its liquidity index is zero. Negative values of the liquidity
|
221
|
+
index indicate that the soil is in a hard (desiccated) state. It is
|
222
|
+
also known as Water-Plasticity ratio.
|
223
|
+
|
224
|
+
:param float nmc: Moisture contents of the soil in natural condition.
|
225
|
+
|
226
|
+
:Equation:
|
227
|
+
|
228
|
+
.. math:: I_l = \dfrac{w - PL}{PI} \cdot 100
|
229
|
+
"""
|
230
|
+
return ((nmc - self.plastic_limit) / self.plasticity_index) * 100.0
|
231
|
+
|
232
|
+
@round_
|
233
|
+
def consistency_index(self, nmc: float) -> float:
|
234
|
+
r"""Return the consistency index of the soil.
|
235
|
+
|
236
|
+
Consistency index indicates the consistency (firmness) of soil. It
|
237
|
+
shows the nearness of the ``natural water content`` of the soil to its
|
238
|
+
``plastic limit``. When the soil is at the liquid limit, the
|
239
|
+
consistency index is zero. The soil at consistency index of zero will
|
240
|
+
be extremely soft and has negligible shear strength. A soil at a water
|
241
|
+
content equal to the plastic limit has consistency index of 100%
|
242
|
+
indicating that the soil is relatively firm. A consistency index of
|
243
|
+
greater than 100% shows the soil is relatively strong
|
244
|
+
(semi-solid state). A negative value indicate the soil is in the liquid
|
245
|
+
state. It is also known as Relative Consistency.
|
246
|
+
|
247
|
+
:param float nmc: Moisture contents of the soil in natural condition.
|
248
|
+
|
249
|
+
:Equation:
|
250
|
+
|
251
|
+
.. math:: I_c = \dfrac{LL - w}{PI} \cdot 100
|
252
|
+
"""
|
253
|
+
return ((self.liquid_limit - nmc) / self.plasticity_index) * 100.0
|
254
|
+
|
255
|
+
|
256
|
+
class _SizeDistribution:
|
257
|
+
"""Particle size distribution of soil sample.
|
258
|
+
|
259
|
+
Features obtained from the Particle Size Distribution graph.
|
260
|
+
"""
|
261
|
+
|
262
|
+
def __init__(self, d_10: float = 0, d_30: float = 0, d_60: float = 0):
|
263
|
+
self.d_10 = d_10
|
264
|
+
self.d_30 = d_30
|
265
|
+
self.d_60 = d_60
|
266
|
+
|
267
|
+
def __iter__(self):
|
268
|
+
return iter([self.d_10, self.d_30, self.d_60])
|
269
|
+
|
270
|
+
@property
|
271
|
+
def coeff_of_curvature(self) -> float:
|
272
|
+
return (self.d_30 ** 2.0) / (self.d_60 * self.d_10)
|
273
|
+
|
274
|
+
@property
|
275
|
+
def coeff_of_uniformity(self) -> float:
|
276
|
+
return self.d_60 / self.d_10
|
277
|
+
|
278
|
+
def grade(self, coarse_soil: USCSSymbol) -> USCSSymbol:
|
279
|
+
"""Grade of soil sample. Soil grade can either be well graded or poorly
|
280
|
+
graded.
|
281
|
+
|
282
|
+
:param coarse_soil: Coarse fraction of the soil sample. Valid arguments
|
283
|
+
are ``USCSSymbol.GRAVEL`` and ``USCSSymbol.SAND``.
|
284
|
+
"""
|
285
|
+
if coarse_soil is USCSSymbol.GRAVEL:
|
286
|
+
if 1 < self.coeff_of_curvature < 3 and self.coeff_of_uniformity >= 4:
|
287
|
+
grade = USCSSymbol.WELL_GRADED
|
288
|
+
else:
|
289
|
+
grade = USCSSymbol.POORLY_GRADED
|
290
|
+
return grade
|
291
|
+
|
292
|
+
# coarse soil is sand
|
293
|
+
if 1 < self.coeff_of_curvature < 3 and self.coeff_of_uniformity >= 6:
|
294
|
+
grade = USCSSymbol.WELL_GRADED
|
295
|
+
else:
|
296
|
+
grade = USCSSymbol.POORLY_GRADED
|
297
|
+
return grade
|
298
|
+
|
299
|
+
|
300
|
+
class PSD:
|
301
|
+
"""Quantitative proportions by mass of various sizes of particles present
|
302
|
+
in a soil.
|
303
|
+
"""
|
304
|
+
|
305
|
+
def __init__(self, fines: float, sand: float,
|
306
|
+
d_10: float = 0, d_30: float = 0, d_60: float = 0):
|
307
|
+
"""
|
308
|
+
:param fines: Percentage of fines in soil sample (%) i.e. The
|
309
|
+
percentage of soil sample passing through No. 200 sieve
|
310
|
+
(0.075mm).
|
311
|
+
:type fines: float
|
312
|
+
|
313
|
+
:param sand: Percentage of sand in soil sample (%).
|
314
|
+
:type sand: float
|
315
|
+
|
316
|
+
:param d_10: Diameter at which 10% of the soil by weight is finer.
|
317
|
+
:type d_10: float
|
318
|
+
:param d_30: Diameter at which 30% of the soil by weight is finer.
|
319
|
+
:type d_30: float
|
320
|
+
:param d_60: Diameter at which 60% of the soil by weight is finer.
|
321
|
+
:type d_60: float
|
322
|
+
"""
|
323
|
+
self.fines = fines
|
324
|
+
self.sand = sand
|
325
|
+
self.size_dist = _SizeDistribution(d_10=d_10, d_30=d_30, d_60=d_60)
|
326
|
+
|
327
|
+
@property
|
328
|
+
def gravel(self):
|
329
|
+
"""Percentage of gravel in soil sample (%)."""
|
330
|
+
return 100.0 - (self.fines + self.sand)
|
331
|
+
|
332
|
+
@property
|
333
|
+
def coarse_material_type(self) -> USCSSymbol:
|
334
|
+
"""Determines whether the soil is either gravel or sand."""
|
335
|
+
if self.gravel > self.sand:
|
336
|
+
return USCSSymbol.GRAVEL
|
337
|
+
return USCSSymbol.SAND
|
338
|
+
|
339
|
+
@property
|
340
|
+
@round_
|
341
|
+
def coeff_of_curvature(self) -> float:
|
342
|
+
r"""Coefficient of curvature of soil sample.
|
343
|
+
|
344
|
+
:Equation:
|
345
|
+
|
346
|
+
.. math:: C_c = \dfrac{D^2_{30}}{D_{60} \cdot D_{10}}
|
347
|
+
|
348
|
+
For the soil to be well graded, the value of :math:`C_c` must be
|
349
|
+
between 1 and 3.
|
350
|
+
"""
|
351
|
+
return self.size_dist.coeff_of_curvature
|
352
|
+
|
353
|
+
@property
|
354
|
+
@round_
|
355
|
+
def coeff_of_uniformity(self) -> float:
|
356
|
+
r"""Coefficient of uniformity of soil sample.
|
357
|
+
|
358
|
+
:Equation:
|
359
|
+
|
360
|
+
.. math:: C_u = \dfrac{D_{60}}{D_{10}}
|
361
|
+
|
362
|
+
:math:`C_u` value greater than 4 to 6 classifies the soil as well
|
363
|
+
graded for gravels and sands respectively. When :math:`C_u` is less
|
364
|
+
than 4, it is classified as poorly graded or uniformly graded soil.
|
365
|
+
|
366
|
+
Higher values of :math:`C_u` indicates that the soil mass consists of
|
367
|
+
soil particles with different size ranges.
|
368
|
+
"""
|
369
|
+
return self.size_dist.coeff_of_uniformity
|
370
|
+
|
371
|
+
def has_particle_sizes(self) -> bool:
|
372
|
+
"""Checks if soil sample has particle sizes."""
|
373
|
+
return any(self.size_dist)
|
374
|
+
|
375
|
+
def grade(self) -> USCSSymbol:
|
376
|
+
r"""Return the grade of the soil sample, either well graded or poorly
|
377
|
+
graded.
|
378
|
+
|
379
|
+
Conditions for a well-graded soil:
|
380
|
+
|
381
|
+
:Equation:
|
382
|
+
|
383
|
+
- :math:`1 \lt C_c \lt 3` and :math:`C_u \ge 4` (for gravels)
|
384
|
+
- :math:`1 \lt C_c \lt 3` and :math:`C_u \ge 6` (for sands)
|
385
|
+
"""
|
386
|
+
return self.size_dist.grade(coarse_soil=self.coarse_material_type)
|
387
|
+
|
388
|
+
|
389
|
+
class SoilClf(NamedTuple):
|
390
|
+
symbol: str
|
391
|
+
description: str
|
392
|
+
|
393
|
+
|
394
|
+
class AASHTO:
|
395
|
+
r"""American Association of State Highway and Transportation Officials
|
396
|
+
(AASHTO) classification system.
|
397
|
+
|
398
|
+
The AASHTO classification system is useful for classifying soils for
|
399
|
+
highways. It categorizes soils for highways based on particle size analysis
|
400
|
+
and plasticity characteristics. It classifies both coarse-grained and
|
401
|
+
fine-grained soils into eight main groups (A1-A7) with subgroups, along with
|
402
|
+
a separate category (A8) for organic soils.
|
403
|
+
|
404
|
+
- ``A1 ~ A3`` (Granular Materials) :math:`\le` 35% pass No. 200 sieve
|
405
|
+
- ``A4 ~ A7`` (Silt-clay Materials) :math:`\ge` 36% pass No. 200 sieve
|
406
|
+
- ``A8`` (Organic Materials)
|
407
|
+
|
408
|
+
The Group Index ``(GI)`` is used to further evaluate soils within a group.
|
409
|
+
|
410
|
+
.. note::
|
411
|
+
|
412
|
+
The ``GI`` must be mentioned even when it is zero, to indicate that the
|
413
|
+
soil has been classified as per AASHTO system.
|
414
|
+
|
415
|
+
:Equation:
|
416
|
+
|
417
|
+
.. math::
|
418
|
+
|
419
|
+
GI = (F_{200} - 35)[0.2 + 0.005(LL - 40)] + 0.01(F_{200} - 15)(PI - 10)
|
420
|
+
"""
|
421
|
+
|
422
|
+
def __init__(self, atterberg_limits: AtterbergLimits,
|
423
|
+
fines: float, add_group_idx=True):
|
424
|
+
"""
|
425
|
+
:param atterberg_limits: Atterberg limits of soil sample.
|
426
|
+
:type atterberg_limits: AtterbergLimits
|
427
|
+
|
428
|
+
:param fines: Percentage of fines in soil sample (%) i.e. The percentage
|
429
|
+
of soil sample passing through No. 200 sieve (0.075mm).
|
430
|
+
:type fines: float
|
431
|
+
|
432
|
+
:param add_group_idx: Used to indicate whether the group index should
|
433
|
+
be added to the classification or not, defaults
|
434
|
+
to True.
|
435
|
+
:type add_group_idx: bool, optional
|
436
|
+
"""
|
437
|
+
self.atterberg_limits = atterberg_limits
|
438
|
+
self.fines = fines
|
439
|
+
self.add_group_idx = add_group_idx
|
440
|
+
|
441
|
+
@property
|
442
|
+
def fines(self) -> float:
|
443
|
+
return self._fines
|
444
|
+
|
445
|
+
@fines.setter
|
446
|
+
@validators.ge(0.0)
|
447
|
+
def fines(self, val: float) -> None:
|
448
|
+
self._fines = val
|
449
|
+
|
450
|
+
@round_(ndigits=0)
|
451
|
+
def group_index(self) -> float:
|
452
|
+
"""Return the Group Index (GI) of the soil sample."""
|
453
|
+
|
454
|
+
liquid_lmt = self.atterberg_limits.liquid_limit
|
455
|
+
plasticity_idx = self.atterberg_limits.plasticity_index
|
456
|
+
fines = self.fines
|
457
|
+
|
458
|
+
var_a = 1.0 if (x_0 := fines - 35.0) < 0.0 else min(x_0, 40.0)
|
459
|
+
var_b = 1.0 if (x_0 := liquid_lmt - 40.0) < 0.0 else min(x_0, 20.0)
|
460
|
+
var_c = 1.0 if (x_0 := fines - 15.0) < 0.0 else min(x_0, 40.0)
|
461
|
+
var_d = 1.0 if (x_0 := plasticity_idx - 10.0) < 0.0 else min(x_0, 20.0)
|
462
|
+
|
463
|
+
return var_a * (0.2 + 0.005 * var_b) + 0.01 * var_c * var_d
|
464
|
+
|
465
|
+
def classify(self) -> SoilClf:
|
466
|
+
"""Return the AASHTO classification of the soil."""
|
467
|
+
soil_clf = self._classify()
|
468
|
+
|
469
|
+
symbol, description = soil_clf.symbol, soil_clf.description
|
470
|
+
|
471
|
+
if self.add_group_idx:
|
472
|
+
symbol = f"{symbol}({self.group_index():.0f})"
|
473
|
+
|
474
|
+
return SoilClf(symbol, description)
|
475
|
+
|
476
|
+
def _classify(self) -> AASHTOSymbol:
|
477
|
+
# Silts A4-A7
|
478
|
+
if self.fines > 35:
|
479
|
+
soil_clf = self._fine_soil_classifier()
|
480
|
+
# Coarse A1-A3
|
481
|
+
else:
|
482
|
+
soil_clf = self._coarse_soil_classifier()
|
483
|
+
|
484
|
+
return soil_clf
|
485
|
+
|
486
|
+
def _fine_soil_classifier(self) -> AASHTOSymbol:
|
487
|
+
# A-4 -> A-5, Silty Soils
|
488
|
+
# A-6 -> A-7, Clayey Soils
|
489
|
+
liquid_lmt = self.atterberg_limits.liquid_limit
|
490
|
+
plasticity_idx = self.atterberg_limits.plasticity_index
|
491
|
+
|
492
|
+
if liquid_lmt <= 40:
|
493
|
+
if plasticity_idx <= 10.0:
|
494
|
+
soil_clf = AASHTOSymbol.A_4
|
495
|
+
else:
|
496
|
+
soil_clf = AASHTOSymbol.A_6
|
497
|
+
else:
|
498
|
+
if plasticity_idx <= 10.0:
|
499
|
+
soil_clf = AASHTOSymbol.A_5
|
500
|
+
else:
|
501
|
+
if plasticity_idx <= (liquid_lmt - 30.0):
|
502
|
+
soil_clf = AASHTOSymbol.A_7_5
|
503
|
+
else:
|
504
|
+
soil_clf = AASHTOSymbol.A_7_6
|
505
|
+
|
506
|
+
return soil_clf
|
507
|
+
|
508
|
+
def _coarse_soil_classifier(self) -> AASHTOSymbol:
|
509
|
+
# A-3, Fine sand
|
510
|
+
liquid_lmt = self.atterberg_limits.liquid_limit
|
511
|
+
plasticity_idx = self.atterberg_limits.plasticity_index
|
512
|
+
|
513
|
+
if self.fines <= 10.0 and isclose(plasticity_idx, 0.0, rel_tol=0.01):
|
514
|
+
soil_clf = AASHTOSymbol.A_3
|
515
|
+
# A-1-a -> A-1-b, Stone fragments, gravel, and sand
|
516
|
+
elif self.fines <= 15 and plasticity_idx <= 6:
|
517
|
+
soil_clf = AASHTOSymbol.A_1_a
|
518
|
+
elif self.fines <= 25 and plasticity_idx <= 6:
|
519
|
+
soil_clf = AASHTOSymbol.A_1_b
|
520
|
+
# A-2-4 -> A-2-7, Silty or clayey gravel and sand
|
521
|
+
elif liquid_lmt <= 40:
|
522
|
+
if plasticity_idx <= 10:
|
523
|
+
soil_clf = AASHTOSymbol.A_2_4
|
524
|
+
else:
|
525
|
+
soil_clf = AASHTOSymbol.A_2_6
|
526
|
+
else:
|
527
|
+
if plasticity_idx <= 10:
|
528
|
+
soil_clf = AASHTOSymbol.A_2_5
|
529
|
+
else:
|
530
|
+
soil_clf = AASHTOSymbol.A_2_7
|
531
|
+
|
532
|
+
return soil_clf
|
533
|
+
|
534
|
+
|
535
|
+
class USCS:
|
536
|
+
"""Unified Soil Classification System (USCS).
|
537
|
+
|
538
|
+
The Unified Soil Classification System, initially developed by Casagrande
|
539
|
+
in 1948 and later modified in 1952, is widely utilized in engineering
|
540
|
+
projects involving soils. It is the most popular system for soil
|
541
|
+
classification and is similar to Casagrande's Classification System. The
|
542
|
+
system relies on particle size analysis and atterberg limits for
|
543
|
+
classification.
|
544
|
+
|
545
|
+
In this system, soils are first classified into two categories:
|
546
|
+
|
547
|
+
- Coarse grained soils: If more than 50% of the soils is retained on
|
548
|
+
No. 200 (0.075 mm) sieve, it is designated as coarse-grained soil.
|
549
|
+
|
550
|
+
- Fine grained soils: If more than 50% of the soil passes through No. 200
|
551
|
+
sieve, it is designated as fine-grained soil.
|
552
|
+
|
553
|
+
Highly Organic soils are identified by visual inspection. These soils are
|
554
|
+
termed as Peat. (:math:`P_t`)
|
555
|
+
"""
|
556
|
+
|
557
|
+
def __init__(self, atterberg_limits: AtterbergLimits,
|
558
|
+
psd: PSD, organic=False):
|
559
|
+
"""
|
560
|
+
:param atterberg_limits: Atterberg limits of the soil.
|
561
|
+
:type atterberg_limits: AtterbergLimits
|
562
|
+
|
563
|
+
:param psd: Particle size distribution of the soil.
|
564
|
+
:type psd: PSD
|
565
|
+
|
566
|
+
:param organic: Indicates whether soil is organic or not, defaults to
|
567
|
+
False.
|
568
|
+
:type organic: bool, optional
|
569
|
+
"""
|
570
|
+
self.atterberg_limits = atterberg_limits
|
571
|
+
self.psd = psd
|
572
|
+
self.organic = organic
|
573
|
+
|
574
|
+
def classify(self) -> SoilClf:
|
575
|
+
"""Return the USCS classification of the soil."""
|
576
|
+
soil_clf = self._classify()
|
577
|
+
|
578
|
+
# Ensure soil_clf is of type USCSSymbol
|
579
|
+
if isinstance(soil_clf, str):
|
580
|
+
soil_clf = USCSSymbol[soil_clf]
|
581
|
+
|
582
|
+
if isinstance(soil_clf, USCSSymbol):
|
583
|
+
return SoilClf(soil_clf.symbol, soil_clf.description)
|
584
|
+
|
585
|
+
# Handling tuple or list case for dual classification
|
586
|
+
first_clf, second_clf = map(lambda clf: USCSSymbol[clf], soil_clf)
|
587
|
+
|
588
|
+
comb_symbol = f"{first_clf.symbol},{second_clf.symbol}"
|
589
|
+
comb_desc = f"{first_clf.description},{second_clf.description}"
|
590
|
+
|
591
|
+
return SoilClf(comb_symbol, comb_desc)
|
592
|
+
|
593
|
+
def _classify(self) -> USCSSymbol | str | Sequence[str]:
|
594
|
+
# Fine-grained, Run Atterberg
|
595
|
+
if self.psd.fines > 50.0:
|
596
|
+
return self._fine_soil_classifier()
|
597
|
+
# Coarse grained, Run Sieve Analysis
|
598
|
+
# Gravel or Sand
|
599
|
+
return self._coarse_soil_classifier()
|
600
|
+
|
601
|
+
def _fine_soil_classifier(self) -> USCSSymbol:
|
602
|
+
liquid_lmt = self.atterberg_limits.liquid_limit
|
603
|
+
plasticity_idx = self.atterberg_limits.plasticity_index
|
604
|
+
|
605
|
+
if liquid_lmt < 50.0:
|
606
|
+
# Low LL
|
607
|
+
# Above A-line and PI > 7
|
608
|
+
if self.atterberg_limits.above_A_LINE() and plasticity_idx > 7.0:
|
609
|
+
soil_clf = USCSSymbol.CL
|
610
|
+
|
611
|
+
# Limit plot in hatched area on plasticity chart
|
612
|
+
elif self.atterberg_limits.limit_plot_in_hatched_zone():
|
613
|
+
soil_clf = USCSSymbol.ML_CL
|
614
|
+
|
615
|
+
# Below A-line or PI < 4
|
616
|
+
else:
|
617
|
+
soil_clf = USCSSymbol.OL if self.organic else USCSSymbol.ML
|
618
|
+
|
619
|
+
# High LL
|
620
|
+
else:
|
621
|
+
# Above A-Line
|
622
|
+
if self.atterberg_limits.above_A_LINE():
|
623
|
+
soil_clf = USCSSymbol.CH
|
624
|
+
|
625
|
+
# Below A-Line
|
626
|
+
else:
|
627
|
+
soil_clf = USCSSymbol.OH if self.organic else USCSSymbol.MH
|
628
|
+
|
629
|
+
return soil_clf
|
630
|
+
|
631
|
+
def _coarse_soil_classifier(self) -> USCSSymbol | str | Sequence[str]:
|
632
|
+
coarse_material_type = self.psd.coarse_material_type
|
633
|
+
|
634
|
+
# More than 12% pass No. 200 sieve
|
635
|
+
if self.psd.fines > 12.0:
|
636
|
+
# Above A-line
|
637
|
+
if self.atterberg_limits.above_A_LINE():
|
638
|
+
soil_clf = f"{coarse_material_type}{USCSSymbol.CLAY}"
|
639
|
+
|
640
|
+
# Limit plot in hatched zone on plasticity chart
|
641
|
+
elif self.atterberg_limits.limit_plot_in_hatched_zone():
|
642
|
+
if coarse_material_type == USCSSymbol.GRAVEL:
|
643
|
+
soil_clf = USCSSymbol.GM_GC
|
644
|
+
else:
|
645
|
+
soil_clf = USCSSymbol.SM_SC
|
646
|
+
|
647
|
+
# Below A-line
|
648
|
+
else:
|
649
|
+
if coarse_material_type == USCSSymbol.GRAVEL:
|
650
|
+
soil_clf = USCSSymbol.GM
|
651
|
+
else:
|
652
|
+
soil_clf = USCSSymbol.SM
|
653
|
+
|
654
|
+
elif 5.0 <= self.psd.fines <= 12.0:
|
655
|
+
# Requires dual symbol based on graduation and plasticity chart
|
656
|
+
if self.psd.has_particle_sizes():
|
657
|
+
soil_clf = self._dual_soil_classifier()
|
658
|
+
else:
|
659
|
+
fine_material_type = self.atterberg_limits.fine_material_type
|
660
|
+
|
661
|
+
soil_clf = (f"{coarse_material_type}{USCSSymbol.WELL_GRADED}_"
|
662
|
+
f"{coarse_material_type}{fine_material_type}",
|
663
|
+
f"{coarse_material_type}{USCSSymbol.POORLY_GRADED}_"
|
664
|
+
f"{coarse_material_type}{fine_material_type}")
|
665
|
+
|
666
|
+
# Less than 5% pass No. 200 sieve
|
667
|
+
# Obtain Cc and Cu from grain size graph
|
668
|
+
else:
|
669
|
+
if self.psd.has_particle_sizes():
|
670
|
+
soil_clf = f"{coarse_material_type}{self.psd.grade()}"
|
671
|
+
|
672
|
+
else:
|
673
|
+
soil_clf = (f"{coarse_material_type}{USCSSymbol.WELL_GRADED}",
|
674
|
+
f"{coarse_material_type}{USCSSymbol.POORLY_GRADED}")
|
675
|
+
|
676
|
+
return soil_clf
|
677
|
+
|
678
|
+
def _dual_soil_classifier(self) -> str:
|
679
|
+
fine_material_type = self.atterberg_limits.fine_material_type
|
680
|
+
coarse_material_type = self.psd.coarse_material_type
|
681
|
+
|
682
|
+
return (f"{coarse_material_type}{self.psd.grade()}_"
|
683
|
+
f"{coarse_material_type}{fine_material_type}")
|
684
|
+
|
685
|
+
|
686
|
+
@enum_repr
|
687
|
+
class CLF_TYPE(enum.StrEnum):
|
688
|
+
"""Enumeration of soil classification types."""
|
689
|
+
AASHTO = enum.auto()
|
690
|
+
USCS = enum.auto()
|
691
|
+
|
692
|
+
|
693
|
+
class SoilClassifier(Protocol):
|
694
|
+
@abstractmethod
|
695
|
+
def classify(self) -> SoilClf: ...
|
696
|
+
|
697
|
+
|
698
|
+
def create_soil_classifier(liquid_limit: float,
|
699
|
+
plastic_limit: float,
|
700
|
+
fines: float,
|
701
|
+
sand: Optional[float] = None,
|
702
|
+
d_10: float = 0, d_30: float = 0, d_60: float = 0,
|
703
|
+
add_group_idx: bool = True,
|
704
|
+
organic: bool = False,
|
705
|
+
clf_type: Optional[CLF_TYPE | str] = None
|
706
|
+
) -> SoilClassifier:
|
707
|
+
""" A factory function that encapsulates the creation of a soil classifier.
|
708
|
+
|
709
|
+
:param liquid_limit: Water content beyond which soils flows under their own
|
710
|
+
weight (%). It can also be defined as the minimum
|
711
|
+
moisture content at which a soil flows upon application
|
712
|
+
of a very small shear force.
|
713
|
+
:type liquid_limit: float
|
714
|
+
|
715
|
+
:param plastic_limit: Water content at which plastic deformation can be
|
716
|
+
initiated (%). It is also the minimum water content at
|
717
|
+
which soil can be rolled into a thread 3mm thick.
|
718
|
+
(molded without breaking)
|
719
|
+
:type plastic_limit: float
|
720
|
+
|
721
|
+
:param fines: Percentage of fines in soil sample (%) i.e. The percentage of
|
722
|
+
soil sample passing through No. 200 sieve (0.075mm).
|
723
|
+
:type fines: float
|
724
|
+
|
725
|
+
:param sand: Percentage of sand in soil sample (%).
|
726
|
+
:type sand: float
|
727
|
+
|
728
|
+
:param d_10: Diameter at which 10% of the soil by weight is finer.
|
729
|
+
:type d_10: float
|
730
|
+
|
731
|
+
:param d_30: Diameter at which 30% of the soil by weight is finer.
|
732
|
+
:type d_30: float
|
733
|
+
|
734
|
+
:param d_60: Diameter at which 60% of the soil by weight is finer.
|
735
|
+
:type d_60: float
|
736
|
+
|
737
|
+
:param add_group_idx: Used to indicate whether the group index should
|
738
|
+
be added to the classification or not, defaults to
|
739
|
+
True.
|
740
|
+
:type add_group_idx: bool, optional
|
741
|
+
|
742
|
+
:param organic: Indicates whether soil is organic or not, defaults to False.
|
743
|
+
:type organic: bool, optional
|
744
|
+
|
745
|
+
:param clf_type: Used to indicate which type of soil classifier should be
|
746
|
+
used, defaults to None.
|
747
|
+
:type clf_type: CLF_TYPE | str
|
748
|
+
|
749
|
+
:raises ValueError: Raises ValueError if ``clf_type`` is not supported or
|
750
|
+
None
|
751
|
+
:raises ValueError: Raises ValueError if ``sand`` is not provided for
|
752
|
+
:class:`USCS` classification.
|
753
|
+
"""
|
754
|
+
msg = (f"{clf_type = } is not supported, Supported "
|
755
|
+
f"types are: {list(CLF_TYPE)}")
|
756
|
+
|
757
|
+
if clf_type is None:
|
758
|
+
raise ValueError(msg)
|
759
|
+
|
760
|
+
try:
|
761
|
+
clf_type = CLF_TYPE(str(clf_type).casefold())
|
762
|
+
except ValueError as e:
|
763
|
+
raise ValueError(msg) from e
|
764
|
+
|
765
|
+
atterberg_lmts = AtterbergLimits(liquid_limit=liquid_limit,
|
766
|
+
plastic_limit=plastic_limit)
|
767
|
+
|
768
|
+
if clf_type == CLF_TYPE.AASHTO:
|
769
|
+
clf = AASHTO(atterberg_limits=atterberg_lmts,
|
770
|
+
fines=fines,
|
771
|
+
add_group_idx=add_group_idx)
|
772
|
+
return clf
|
773
|
+
|
774
|
+
# USCS classification
|
775
|
+
if sand is None:
|
776
|
+
raise ValueError("sand must be specified for USCS classification")
|
777
|
+
|
778
|
+
psd = PSD(fines=fines, sand=sand, d_10=d_10, d_30=d_30, d_60=d_60)
|
779
|
+
clf = USCS(atterberg_limits=atterberg_lmts, psd=psd, organic=organic)
|
780
|
+
|
781
|
+
return clf
|