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.
@@ -1,859 +0,0 @@
1
- from abc import abstractmethod
2
- from typing import NamedTuple, Protocol
3
-
4
- from .constants import ERROR_TOL
5
- from .utils import isclose, round_
6
-
7
- __all__ = ["AtterbergLimits", "PSD", "AASHTO", "USCS"]
8
-
9
-
10
- class PSDAggSumError(ValueError):
11
- pass
12
-
13
-
14
- class SoilGradationError(ZeroDivisionError):
15
- pass
16
-
17
-
18
- class _SoilClassifier(Protocol):
19
-
20
- @property
21
- @abstractmethod
22
- def soil_class(self): ...
23
-
24
- @property
25
- @abstractmethod
26
- def soil_desc(self): ...
27
-
28
-
29
- #: USCS symbol for gravel.
30
- GRAVEL: str = "G"
31
-
32
- #: USCS symbol for sand.
33
- SAND: str = "S"
34
-
35
- #: USCS symbol for silt.
36
- SILT: str = "M"
37
-
38
- #: USCS symbol for clay.
39
- CLAY: str = "C"
40
-
41
- #: USCS symbol for organic material.
42
- ORGANIC: str = "O"
43
-
44
- #: USCS symbol for well-graded material.
45
- WELL_GRADED: str = "W"
46
-
47
- #: USCS symbol for poorly-graded material.
48
- POORLY_GRADED: str = "P"
49
-
50
- #: USCS symbol for low soil plasticity.
51
- LOW_PLASTICITY: str = "L"
52
-
53
- #: USCS symbol for high soil plasticity.
54
- HIGH_PLASTICITY: str = "H"
55
-
56
-
57
- class _SoilGradation(NamedTuple):
58
- """Features obtained from the Particle Size Distribution graph."""
59
-
60
- d_10: float
61
- d_30: float
62
- d_60: float
63
-
64
- ERR_MSG: str = "d_10, d_30, and d_60 cannot be 0"
65
-
66
- @property
67
- @round_
68
- def coeff_of_curvature(self) -> float:
69
- try:
70
- return (self.d_30**2) / (self.d_60 * self.d_10)
71
- except ZeroDivisionError as e:
72
- raise SoilGradationError(self.ERR_MSG) from e
73
-
74
- @property
75
- @round_
76
- def coeff_of_uniformity(self) -> float:
77
- try:
78
- return self.d_60 / self.d_10
79
- except ZeroDivisionError as e:
80
- raise SoilGradationError(self.ERR_MSG) from e
81
-
82
- def grade(self, coarse_soil: str) -> str:
83
- """Grade of soil sample. Soil grade can either be ``WELL_GRADED`` or
84
- ``POORLY_GRADED``.
85
-
86
- Parameters
87
- ----------
88
- coarse_soil : str
89
- Coarse fraction of the soil sample. Valid arguments are :data:`GRAVEL`
90
- or :data:`SAND`.
91
- """
92
-
93
- if coarse_soil == GRAVEL and (
94
- 1 < self.coeff_of_curvature < 3 and self.coeff_of_uniformity >= 4
95
- ):
96
- grade = WELL_GRADED
97
-
98
- elif coarse_soil == SAND and (
99
- 1 < self.coeff_of_curvature < 3 and self.coeff_of_uniformity >= 6
100
- ):
101
- grade = WELL_GRADED
102
-
103
- else:
104
- grade = POORLY_GRADED
105
-
106
- return grade
107
-
108
-
109
- class AtterbergLimits:
110
- """Water contents at which soil changes from one state to the other.
111
-
112
- In 1911, a Swedish agriculture engineer ``Atterberg`` mentioned that a
113
- fined-grained soil can exist in four states, namely, liquid, plastic,
114
- semi-solid or solid state.
115
-
116
- The main use of Atterberg Limits is in the classification of soils.
117
-
118
- Parameters
119
- ----------
120
- liquid_limit : float
121
- Water content beyond which soils flows under their own weight.
122
- It can also be defined as the minimum moisture content at which
123
- a soil flows upon application of a very small shear force.
124
-
125
- plastic_limit : float
126
- Water content at which plastic deformation can be initiated. It
127
- is also the minimum water content at which soil can be rolled into
128
- a thread 3mm thick. (molded without breaking)
129
-
130
- Attributes
131
- ----------
132
- plasticity_index : float
133
- A_line : float
134
- type_of_fines : str
135
-
136
- Methods
137
- -------
138
- above_A_LINE
139
- limit_plot_in_hatched_zone
140
- liquidity_index
141
- consistency_index
142
-
143
- Examples
144
- --------
145
- >>> from geolysis.core.soil_classifier import AtterbergLimits as AL
146
-
147
- >>> atterberg_limits = AL(liquid_limit=55.44, plastic_limit=33.31)
148
- >>> atterberg_limits.plasticity_index
149
- 22.13
150
- >>> atterberg_limits.A_line
151
- 25.87
152
-
153
- >>> soil_type = atterberg_limits.type_of_fines
154
- >>> soil_type
155
- 'M'
156
- >>> USCS.SOIL_DESCRIPTIONS[soil_type]
157
- 'Silt'
158
- >>> atterberg_limits.above_A_LINE()
159
- False
160
- >>> atterberg_limits.limit_plot_in_hatched_zone()
161
- False
162
-
163
- Negative values of liquidity index indicates that the soil is in a hard state.
164
-
165
- >>> atterberg_limits.liquidity_index(nmc=15.26)
166
- -81.56
167
-
168
- A consistency index greater than 100% shows the soil is relatively strong.
169
-
170
- >>> atterberg_limits.consistency_index(nmc=15.26)
171
- 181.56
172
- """
173
-
174
- def __init__(self, liquid_limit: float, plastic_limit: float):
175
- self.liquid_limit = liquid_limit
176
- self.plastic_limit = plastic_limit
177
-
178
- @property
179
- @round_
180
- def plasticity_index(self) -> float:
181
- """Plasticity index (PI) is the range of water content over which the
182
- soil remains in the plastic state.
183
-
184
- It is also the numerical difference between the liquid limit and plastic
185
- limit of the soil.
186
-
187
- .. math:: PI = LL - PL
188
- """
189
- return self.liquid_limit - self.plastic_limit
190
-
191
- @property
192
- @round_
193
- def A_line(self) -> float:
194
- """The ``A-line`` is used to determine if a soil is clayey or silty.
195
-
196
- .. math:: A = 0.73(LL - 20)
197
- """
198
- return 0.73 * (self.liquid_limit - 20)
199
-
200
- @property
201
- def type_of_fines(self) -> str:
202
- """Determines whether the soil is either :data:`CLAY` or :data:`SILT`."""
203
- return CLAY if self.above_A_LINE() else SILT
204
-
205
- def above_A_LINE(self) -> bool:
206
- """Checks if the soil sample is above A-Line."""
207
- return self.plasticity_index > self.A_line
208
-
209
- def limit_plot_in_hatched_zone(self) -> bool:
210
- """Checks if soil sample plot in the hatched zone on the atterberg
211
- chart.
212
- """
213
- return 4 <= self.plasticity_index <= 7 and 10 < self.liquid_limit < 30
214
-
215
- @round_
216
- def liquidity_index(self, nmc: float) -> float:
217
- r"""Return the liquidity index of the soil.
218
-
219
- Liquidity index of a soil indicates the nearness of its ``natural water
220
- content`` to its ``liquid limit``. When the soil is at the plastic limit
221
- its liquidity index is zero. Negative values of the liquidity index
222
- indicate that the soil is in a hard (desiccated) state. It is also known
223
- as Water-Plasticity ratio.
224
-
225
- Parameters
226
- ----------
227
- nmc : float
228
- Moisture contents of the soil in natural condition. (Natural Moisture
229
- Content)
230
-
231
- Notes
232
- -----
233
- The ``liquidity index`` is given by the formula:
234
-
235
- .. math:: I_l = \dfrac{w - PL}{PI} \cdot 100
236
- """
237
- return ((nmc - self.plastic_limit) / self.plasticity_index) * 100
238
-
239
- @round_
240
- def consistency_index(self, nmc: float) -> float:
241
- r"""Return the consistency index of the soil.
242
-
243
- Consistency index indicates the consistency (firmness) of soil. It shows
244
- the nearness of the ``natural water content`` of the soil to its
245
- ``plastic limit``. When the soil is at the liquid limit, the consistency
246
- index is zero. The soil at consistency index of zero will be extremely
247
- soft and has negligible shear strength. A soil at a water content equal
248
- to the plastic limit has consistency index of 100% indicating that the
249
- soil is relatively firm. A consistency index of greater than 100% shows
250
- the soil is relatively strong (semi-solid state). A negative value indicate
251
- the soil is in the liquid state. It is also known as Relative Consistency.
252
-
253
- Parameters
254
- ----------
255
- nmc : float
256
- Moisture contents of the soil in natural condition. (Natural Moisture
257
- Content)
258
-
259
- Notes
260
- -----
261
- The ``consistency index`` is given by the formula:
262
-
263
- .. math:: I_c = \dfrac{LL - w}{PI} \cdot 100
264
- """
265
- return ((self.liquid_limit - nmc) / self.plasticity_index) * 100.0
266
-
267
-
268
- class PSD:
269
- r"""Quantitative proportions by mass of various sizes of particles present
270
- in a soil.
271
-
272
- Particle Size Distribution is a method of separation of soils into
273
- different fractions using a stack of sieves to measure the size of the
274
- particles in a sample and graphing the results to illustrate the
275
- distribution of the particle sizes.
276
-
277
- Parameters
278
- ----------
279
- fines : float
280
- Percentage of fines in soil sample i.e. the percentage of soil sample
281
- passing through No. 200 sieve (0.075mm)
282
- sand : float
283
- Percentage of sand in soil sample.
284
- gravel : float
285
- Percentage of gravel in soil sample.
286
- d_10 : float, unit=millimetre
287
- Diameter at which 10% of the soil by weight is finer.
288
- d_30 : float, unit=millimetre
289
- Diameter at which 30% of the soil by weight is finer.
290
- d_60 : float, unit=millimetre
291
- Diameter at which 60% of the soil by weight is finer.
292
-
293
- Attributes
294
- ----------
295
- coeff_of_curvature : float
296
- coeff_of_uniformity : float
297
- type_of_coarse : str
298
-
299
-
300
- Raises
301
- ------
302
- PSDAggSumError
303
- Raised when soil aggregates does not approximately sum up to 100%.
304
- SoilGradationError
305
- Raised when d_10, d_30, and d_60 are not provided.
306
-
307
- Examples
308
- --------
309
- >>> from geolysis.core.soil_classifier import PSD
310
-
311
- >>> psd = PSD(fines=30.25, sand=53.55, gravel=16.20)
312
- >>> soil_type = psd.type_of_coarse
313
- >>> soil_type
314
- 'S'
315
- >>> USCS.SOIL_DESCRIPTIONS[soil_type]
316
- 'Sand'
317
-
318
- Raises error because parameters d_10, d_30, and d_60 are not provided.
319
-
320
- >>> psd.coeff_of_curvature
321
- Traceback (most recent call last):
322
- ...
323
- SoilGradationError: d_10, d_30, and d_60 cannot be 0
324
-
325
- >>> psd.coeff_of_uniformity
326
- Traceback (most recent call last):
327
- ...
328
- SoilGradationError: d_10, d_30, and d_60 cannot be 0
329
-
330
- >>> psd = PSD(fines=10.29, sand=81.89, gravel=7.83,
331
- ... d_10=0.07, d_30=0.30, d_60=0.8)
332
- >>> psd.d_10, psd.d_30, psd.d_60
333
- (0.07, 0.3, 0.8)
334
- >>> psd.coeff_of_curvature
335
- 1.61
336
- >>> psd.coeff_of_uniformity
337
- 11.43
338
-
339
- >>> soil_grade = psd.grade()
340
- >>> soil_grade
341
- 'W'
342
- >>> USCS.SOIL_DESCRIPTIONS[soil_grade]
343
- 'Well graded'
344
- """
345
-
346
- def __init__(
347
- self,
348
- fines: float,
349
- sand: float,
350
- gravel: float,
351
- d_10: float = 0,
352
- d_30: float = 0,
353
- d_60: float = 0,
354
- ):
355
- self.fines = fines
356
- self.sand = sand
357
- self.gravel = gravel
358
- self.size_dist = _SoilGradation(d_10, d_30, d_60)
359
-
360
- total_agg = self.fines + self.sand + self.gravel
361
-
362
- if not isclose(total_agg, 100.0, rel_tol=ERROR_TOL):
363
- err_msg = f"fines + sand + gravels = 100% not {total_agg}"
364
- raise PSDAggSumError(err_msg)
365
-
366
- @property
367
- def d_10(self) -> float:
368
- """Diameter at which 10% of the soil by weight is finer."""
369
- return self.size_dist.d_10
370
-
371
- @property
372
- def d_30(self) -> float:
373
- """Diameter at which 30% of the soil by weight is finer."""
374
- return self.size_dist.d_30
375
-
376
- @property
377
- def d_60(self) -> float:
378
- """Diameter at which 60% of the soil by weight is finer."""
379
- return self.size_dist.d_60
380
-
381
- @property
382
- def type_of_coarse(self) -> str:
383
- """Determines whether the soil is either :data:`GRAVEL` or
384
- :data:`SAND`.
385
- """
386
- return GRAVEL if self.gravel > self.sand else SAND
387
-
388
- @property
389
- def coeff_of_curvature(self) -> float:
390
- r"""Coefficient of curvature of soil sample.
391
-
392
- Coefficient of curvature :math:`(C_c)` is given by the formula:
393
-
394
- .. math:: C_c = \dfrac{D^2_{30}}{D_{60} \times D_{10}}
395
-
396
- For the soil to be well graded, the value of :math:`C_c` must be
397
- between 1 and 3.
398
- """
399
- return self.size_dist.coeff_of_curvature
400
-
401
- @property
402
- def coeff_of_uniformity(self) -> float:
403
- r"""Coefficient of uniformity of soil sample.
404
-
405
- Coefficient of uniformity :math:`(C_u)` is given by the formula:
406
-
407
- .. math:: C_u = \dfrac{D_{60}}{D_{10}}
408
-
409
- :math:`C_u` value greater than 4 to 6 classifies the soil as well
410
- graded for gravels and sands respectively. When :math:`C_u` is less
411
- than 4, it is classified as poorly graded or uniformly graded soil.
412
- Higher values of :math:`C_u` indicates that the soil mass consists
413
- of soil particles with different size ranges.
414
- """
415
- return self.size_dist.coeff_of_uniformity
416
-
417
- def has_particle_sizes(self) -> bool:
418
- """Checks if soil sample has particle sizes."""
419
- return all(self.size_dist)
420
-
421
- def grade(self) -> str:
422
- r"""Return the grade of the soil sample, either :data:`WELL_GRADED`
423
- or :data:`POORLY_GRADED`.
424
-
425
- Conditions for a well-graded soil:
426
-
427
- - :math:`1 \lt C_c \lt 3` and :math:`C_u \ge 4` (for gravels)
428
- - :math:`1 \lt C_c \lt 3` and :math:`C_u \ge 6` (for sands)
429
- """
430
- return self.size_dist.grade(coarse_soil=self.type_of_coarse)
431
-
432
-
433
- class AASHTO:
434
- r"""American Association of State Highway and Transportation Officials
435
- (AASHTO) classification system.
436
-
437
- The AASHTO classification system is useful for classifying soils for highways.
438
- It categorizes soils for highways based on particle size analysis and
439
- plasticity characteristics. It classifies both coarse-grained and fine-grained
440
- soils into eight main groups (A1-A7) with subgroups, along with a separate
441
- category (A8) for organic soils.
442
-
443
- - ``A1 ~ A3`` (Granular Materials) :math:`\le` 35% pass No. 200 sieve
444
- - ``A4 ~ A7`` (Silt-clay Materials) :math:`\ge` 36% pass No. 200 sieve
445
- - ``A8`` (Organic Materials)
446
-
447
- The Group Index ``(GI)`` is used to further evaluate soils within a group.
448
-
449
- Parameters
450
- ----------
451
- liquid_limit : float
452
- Water content beyond which soils flows under their own weight.
453
- plasticity_index : float
454
- Range of water content over which soil remains in plastic condition.
455
- fines : float
456
- Percentage of fines in soil sample i.e. the percentage of soil sample
457
- passing through No. 200 sieve (0.075mm).
458
- add_group_idx : bool, default=True
459
- Used to indicate whether the group index should be added to the classification
460
- or not. Defaults to True.
461
-
462
- Notes
463
- -----
464
- The ``GI`` must be mentioned even when it is zero, to indicate that the soil has
465
- been classified as per AASHTO system.
466
-
467
- .. math:: GI = (F_{200} - 35)[0.2 + 0.005(LL - 40)] + 0.01(F_{200} - 15)(PI - 10)
468
-
469
- Examples
470
- --------
471
- >>> from geolysis.core.soil_classifier import AASHTO
472
-
473
- >>> aashto_clf = AASHTO(liquid_limit=30.2, plasticity_index=6.3, fines=11.18)
474
- >>> aashto_clf.group_index()
475
- 0.0
476
- >>> aashto_clf.soil_class
477
- 'A-2-4(0)'
478
- >>> aashto_clf.soil_desc
479
- 'Silty or clayey gravel and sand'
480
-
481
- If you would like to exclude the group index from the classification, you can do
482
- the following:
483
-
484
- >>> aashto_clf.add_group_idx = False
485
- >>> aashto_clf.soil_class
486
- 'A-2-4'
487
- """
488
-
489
- SOIL_DESCRIPTIONS = {
490
- "A-1-a": "Stone fragments, gravel, and sand",
491
- "A-1-b": "Stone fragments, gravel, and sand",
492
- "A-3": "Fine sand",
493
- "A-2-4": "Silty or clayey gravel and sand",
494
- "A-2-5": "Silty or clayey gravel and sand",
495
- "A-2-6": "Silty or clayey gravel and sand",
496
- "A-2-7": "Silty or clayey gravel and sand",
497
- "A-4": "Silty soils",
498
- "A-5": "Silty soils",
499
- "A-6": "Clayey soils",
500
- "A-7-5": "Clayey soils",
501
- "A-7-6": "Clayey soils",
502
- }
503
-
504
- def __init__(
505
- self,
506
- liquid_limit: float,
507
- plasticity_index: float,
508
- fines: float,
509
- add_group_idx=True,
510
- ):
511
- self.liquid_limit = liquid_limit
512
- self.plasticity_index = plasticity_index
513
- self.fines = fines
514
- self.add_group_idx = add_group_idx
515
-
516
- def _classify(self) -> str:
517
- # Silts A4-A7
518
- if self.fines > 35:
519
- soil_class = self._fine_soil_classifier()
520
- # Coarse A1-A3
521
- else:
522
- soil_class = self._coarse_soil_classifier()
523
-
524
- return (
525
- f"{soil_class}({self.group_index():.0f})"
526
- if self.add_group_idx
527
- else soil_class
528
- )
529
-
530
- def _coarse_soil_classifier(self) -> str:
531
- # A-3, Fine sand
532
- if self.fines <= 10 and isclose(
533
- self.plasticity_index, 0, rel_tol=ERROR_TOL
534
- ):
535
- soil_class = "A-3"
536
-
537
- # A-1-a -> A-1-b, Stone fragments, gravel, and sand
538
- elif self.fines <= 15 and self.plasticity_index <= 6:
539
- soil_class = "A-1-a"
540
-
541
- elif self.fines <= 25 and self.plasticity_index <= 6:
542
- soil_class = "A-1-b"
543
-
544
- # A-2-4 -> A-2-7, Silty or clayey gravel and sand
545
- elif self.liquid_limit <= 40:
546
- soil_class = "A-2-4" if self.plasticity_index <= 10 else "A-2-6"
547
-
548
- else:
549
- soil_class = "A-2-5" if self.plasticity_index <= 10 else "A-2-7"
550
-
551
- return soil_class
552
-
553
- def _fine_soil_classifier(self) -> str:
554
- # A-4 -> A-5, Silty Soils
555
- # A-6 -> A-7, Clayey Soils
556
- if self.liquid_limit <= 40:
557
- soil_class = "A-4" if self.plasticity_index <= 10 else "A-6"
558
- else:
559
- if self.plasticity_index <= 10:
560
- soil_class = "A-5"
561
- else:
562
- _x = self.liquid_limit - 30
563
- soil_class = (
564
- "A-7-5" if self.plasticity_index <= _x else "A-7-6"
565
- )
566
-
567
- return soil_class
568
-
569
- @property
570
- def soil_class(self) -> str:
571
- """Return the AASHTO classification of the soil."""
572
- return self._classify()
573
-
574
- @property
575
- def soil_desc(self) -> str:
576
- """Return the AASHTO description of the soil."""
577
- tmp_state = self.add_group_idx
578
- try:
579
- self.add_group_idx = False
580
- soil_cls = self.soil_class
581
- return AASHTO.SOIL_DESCRIPTIONS[soil_cls]
582
- finally:
583
- self.add_group_idx = tmp_state
584
-
585
- def group_index(self) -> float:
586
- """Return the Group Index (GI) of the soil sample."""
587
- a = 1 if (x_0 := self.fines - 35) < 0 else min(x_0, 40)
588
- b = 1 if (x_0 := self.liquid_limit - 40) < 0 else min(x_0, 20)
589
- c = 1 if (x_0 := self.fines - 15) < 0 else min(x_0, 40)
590
- d = 1 if (x_0 := self.plasticity_index - 10) < 0 else min(x_0, 20)
591
-
592
- return round(a * (0.2 + 0.005 * b) + 0.01 * c * d, 0)
593
-
594
-
595
- class USCS:
596
- """Unified Soil Classification System (USCS).
597
-
598
- The Unified Soil Classification System, initially developed by Casagrande in
599
- 1948 and later modified in 1952, is widely utilized in engineering projects
600
- involving soils. It is the most popular system for soil classification and is
601
- similar to Casagrande's Classification System. The system relies on particle
602
- size analysis and atterberg limits for classification.
603
-
604
- In this system, soils are first classified into two categories:
605
-
606
- - Coarse grained soils: If more than 50% of the soils is retained on No. 200
607
- (0.075 mm) sieve, it is designated as coarse-grained soil.
608
-
609
- - Fine grained soils: If more than 50% of the soil passes through No. 200 sieve,
610
- it is designated as fine grained soil.
611
-
612
- Highly Organic soils are identified by visual inspection. These soils are termed
613
- as Peat. (:math:`P_t`)
614
-
615
- Parameters
616
- ----------
617
- liquid_limit : float
618
- Water content beyond which soils flows under their own weight. It can also
619
- be defined as the minimum moisture content at which a soil flows upon
620
- application of a very small shear force.
621
- plastic_limit : float
622
- Water content at which plastic deformation can be initiated. It is also the
623
- minimum water content at which soil can be rolled into a thread 3mm thick
624
- (molded without breaking)
625
- fines : float
626
- Percentage of fines in soil sample i.e. The percentage of soil sample passing
627
- through No. 200 sieve (0.075mm)
628
- sand : float
629
- Percentage of sand in soil sample (%)
630
- gravel : float
631
- Percentage of gravel in soil sample (%)
632
- d_10 : float, mm
633
- Diameter at which 10% of the soil by weight is finer.
634
- d_30 : float, mm
635
- Diameter at which 30% of the soil by weight is finer.
636
- d_60 : float, mm
637
- Diameter at which 60% of the soil by weight is finer.
638
- organic : bool, default=False
639
- Indicates whether soil is organic or not.
640
-
641
- Attributes
642
- ----------
643
- atterberg_limits : AtterbergLimits
644
- psd : PSD
645
- soil_class : str
646
- soil_desc : str
647
-
648
- Examples
649
- --------
650
- >>> from geolysis.core.soil_classifier import USCS
651
-
652
- >>> uscs_clf = USCS(liquid_limit=34.1, plastic_limit=21.1,
653
- ... fines=47.88, sand=37.84, gravel=14.28)
654
- >>> uscs_clf.soil_class
655
- 'SC'
656
- >>> uscs_clf.soil_desc
657
- 'Clayey sands'
658
-
659
- >>> uscs_clf = USCS(liquid_limit=27.7, plastic_limit=22.7,
660
- ... fines=18.95, sand=77.21, gravel=3.84)
661
- >>> uscs_clf.soil_class
662
- 'SM-SC'
663
- >>> uscs_clf.soil_desc
664
- 'Sandy clayey silt'
665
-
666
- >>> uscs_clf = USCS(liquid_limit=30.8, plastic_limit=20.7, fines=10.29,
667
- ... sand=81.89, gravel=7.83, d_10=0.07, d_30=0.3, d_60=0.8)
668
- >>> uscs_clf.soil_class
669
- 'SW-SC'
670
- >>> uscs_clf.soil_desc
671
- 'Well graded sand with clay'
672
-
673
- Soil gradation (d_10, d_30, d_60) is needed to obtain soil description for
674
- certain type of soils.
675
-
676
- >>> uscs_clf = USCS(liquid_limit=30.8, plastic_limit=20.7,
677
- ... fines=10.29, sand=81.89, gravel=7.83)
678
- >>> uscs_clf.soil_class
679
- 'SW-SC,SP-SC'
680
- >>> uscs_clf.soil_desc
681
- 'Well graded sand with clay or Poorly graded sand with clay'
682
- """
683
-
684
- SOIL_DESCRIPTIONS = {
685
- "G": "Gravel",
686
- "S": "Sand",
687
- "M": "Silt",
688
- "C": "Clay",
689
- "O": "Organic",
690
- "W": "Well graded",
691
- "P": "Poorly graded",
692
- "L": "Low plasticity",
693
- "H": "High plasticity",
694
- "GW": "Well graded gravels",
695
- "GP": "Poorly graded gravels",
696
- "GM": "Silty gravels",
697
- "GC": "Clayey gravels",
698
- "GM-GC": "Gravelly clayey silt",
699
- "GW-GM": "Well graded gravel with silt",
700
- "GP-GM": "Poorly graded gravel with silt",
701
- "GW-GC": "Well graded gravel with clay",
702
- "GP-GC": "Poorly graded gravel with clay",
703
- "SW": "Well graded sands",
704
- "SP": "Poorly graded sands",
705
- "SM": "Silty sands",
706
- "SC": "Clayey sands",
707
- "SM-SC": "Sandy clayey silt",
708
- "SW-SM": "Well graded sand with silt",
709
- "SP-SM": "Poorly graded sand with silt",
710
- "SW-SC": "Well graded sand with clay",
711
- "SP-SC": "Poorly graded sand with clay",
712
- "ML": "Inorganic silts with low plasticity",
713
- "CL": "Inorganic clays with low plasticity",
714
- "ML-CL": "Clayey silt with low plasticity",
715
- "OL": "Organic clays with low plasticity",
716
- "MH": "Inorganic silts with high plasticity",
717
- "CH": "Inorganic clays with high plasticity",
718
- "OH": "Organic silts with high plasticity",
719
- "Pt": "Highly organic soils",
720
- }
721
-
722
- def __init__(
723
- self,
724
- liquid_limit: float,
725
- plastic_limit: float,
726
- fines: float,
727
- sand: float,
728
- gravel: float,
729
- *,
730
- d_10=0,
731
- d_30=0,
732
- d_60=0,
733
- organic=False,
734
- ):
735
- self._atterberg_limits = AtterbergLimits(liquid_limit, plastic_limit)
736
- self._psd = PSD(fines, sand, gravel, d_10, d_30, d_60)
737
- self.organic = organic
738
-
739
- def _classify(self) -> str:
740
- # Fine grained, Run Atterberg
741
- if self.psd.fines > 50:
742
- return self._fine_soil_classifier()
743
-
744
- # Coarse grained, Run Sieve Analysis
745
- # Gravel or Sand
746
- return self._coarse_soil_classifier()
747
-
748
- def _dual_soil_classifier(self) -> str:
749
- fine_soil = self.atterberg_limits.type_of_fines
750
- coarse_soil = self.psd.type_of_coarse
751
-
752
- return f"{coarse_soil}{self.psd.grade()}-{coarse_soil}{fine_soil}"
753
-
754
- def _coarse_soil_classifier(self) -> str:
755
- coarse_soil = self.psd.type_of_coarse
756
-
757
- # More than 12% pass No. 200 sieve
758
- if self.psd.fines > 12:
759
- # Above A-line
760
- if self.atterberg_limits.above_A_LINE():
761
- soil_class = f"{coarse_soil}{CLAY}"
762
-
763
- # Limit plot in hatched zone on plasticity chart
764
- elif self.atterberg_limits.limit_plot_in_hatched_zone():
765
- soil_class = f"{coarse_soil}{SILT}-{coarse_soil}{CLAY}"
766
-
767
- # Below A-line
768
- else:
769
- soil_class = f"{coarse_soil}{SILT}"
770
-
771
- elif 5 <= self.psd.fines <= 12:
772
- # Requires dual symbol based on graduation and plasticity chart
773
- if self.psd.has_particle_sizes():
774
- soil_class = self._dual_soil_classifier()
775
-
776
- else:
777
- fine_soil = self.atterberg_limits.type_of_fines
778
- soil_class = (
779
- f"{coarse_soil}{WELL_GRADED}-{coarse_soil}{fine_soil},"
780
- f"{coarse_soil}{POORLY_GRADED}-{coarse_soil}{fine_soil}"
781
- )
782
-
783
- # Less than 5% pass No. 200 sieve
784
- # Obtain Cc and Cu from grain size graph
785
- else:
786
- if self.psd.has_particle_sizes():
787
- soil_class = f"{coarse_soil}{self.psd.grade()}"
788
-
789
- else:
790
- soil_class = (
791
- f"{coarse_soil}{WELL_GRADED},{coarse_soil}{POORLY_GRADED}"
792
- )
793
-
794
- return soil_class
795
-
796
- def _fine_soil_classifier(self) -> str:
797
- if self.atterberg_limits.liquid_limit < 50:
798
- # Low LL
799
- # Above A-line and PI > 7
800
- if (self.atterberg_limits.above_A_LINE()) and (
801
- self.atterberg_limits.plasticity_index > 7
802
- ):
803
- soil_class = f"{CLAY}{LOW_PLASTICITY}"
804
-
805
- # Limit plot in hatched area on plasticity chart
806
- elif self.atterberg_limits.limit_plot_in_hatched_zone():
807
- soil_class = f"{SILT}{LOW_PLASTICITY}-{CLAY}{LOW_PLASTICITY}"
808
-
809
- # Below A-line or PI < 4
810
- else:
811
- soil_class = (
812
- f"{ORGANIC}{LOW_PLASTICITY}"
813
- if self.organic
814
- else f"{SILT}{LOW_PLASTICITY}"
815
- )
816
-
817
- # High LL
818
- else:
819
- # Above A-Line
820
- if self.atterberg_limits.above_A_LINE():
821
- soil_class = f"{CLAY}{HIGH_PLASTICITY}"
822
-
823
- # Below A-Line
824
- else:
825
- soil_class = (
826
- f"{ORGANIC}{HIGH_PLASTICITY}"
827
- if self.organic
828
- else f"{SILT}{HIGH_PLASTICITY}"
829
- )
830
-
831
- return soil_class
832
-
833
- @property
834
- def atterberg_limits(self) -> AtterbergLimits:
835
- """Return the atterberg limits of soil."""
836
- return self._atterberg_limits
837
-
838
- @property
839
- def psd(self) -> PSD:
840
- """Return the particle size distribution of soil."""
841
- return self._psd
842
-
843
- @property
844
- def soil_class(self) -> str:
845
- """Return the USCS classification of the soil."""
846
- return self._classify()
847
-
848
- @property
849
- def soil_desc(self) -> str:
850
- """Return the USCS description of the soil."""
851
- soil_cls = self.soil_class
852
- try:
853
- soil_descr = USCS.SOIL_DESCRIPTIONS[soil_cls]
854
- except KeyError:
855
- soil_descr = " or ".join(
856
- map(USCS.SOIL_DESCRIPTIONS.get, soil_cls.split(","))
857
- ) # type: ignore
858
-
859
- return soil_descr