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