geolysis 0.11.0__py3-none-any.whl → 0.13.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.
@@ -2,9 +2,9 @@ import enum
2
2
  from dataclasses import dataclass
3
3
  from typing import Annotated, Sequence
4
4
 
5
- from func_validator import validate_func_args, MustBeNonNegative
5
+ from func_validator import validate_params, MustBeNonNegative
6
6
 
7
- from .utils import isclose, round_
7
+ from .utils import isclose, round_, nan, isnan
8
8
 
9
9
  __all__ = [
10
10
  "AtterbergLimits",
@@ -256,9 +256,9 @@ class AtterbergLimits:
256
256
  return self._liquid_limit
257
257
 
258
258
  @liquid_limit.setter
259
- @validate_func_args
260
- def liquid_limit(self, val: Annotated[float, MustBeNonNegative]):
261
- self._liquid_limit = val
259
+ @validate_params
260
+ def liquid_limit(self, liquid_limit: Annotated[float, MustBeNonNegative]):
261
+ self._liquid_limit = liquid_limit
262
262
 
263
263
  @property
264
264
  def plastic_limit(self) -> float:
@@ -268,16 +268,16 @@ class AtterbergLimits:
268
268
  return self._plastic_limit
269
269
 
270
270
  @plastic_limit.setter
271
- @validate_func_args
272
- def plastic_limit(self, val: Annotated[float, MustBeNonNegative]):
273
- if self.liquid_limit < val:
271
+ @validate_params
272
+ def plastic_limit(self, plastic_limit: Annotated[float, MustBeNonNegative]):
273
+ if self.liquid_limit < plastic_limit:
274
274
  msg = (
275
- f"plastic_limit: {val} cannot be greater than "
275
+ f"plastic_limit: {plastic_limit} cannot be greater than "
276
276
  f"liquid_limit: {self.liquid_limit}"
277
277
  )
278
278
  raise ValueError(msg)
279
279
 
280
- self._plastic_limit = val
280
+ self._plastic_limit = plastic_limit
281
281
 
282
282
  @property
283
283
  @round_(2)
@@ -353,8 +353,7 @@ class _SizeDistribution:
353
353
  Features obtained from the Particle Size Distribution graph.
354
354
  """
355
355
 
356
- def __init__(self, d_10: float = 0.0, d_30: float = 0.0,
357
- d_60: float = 0.0):
356
+ def __init__(self, d_10: float, d_30: float, d_60: float):
358
357
  self.d_10 = d_10
359
358
  self.d_30 = d_30
360
359
  self.d_60 = d_60
@@ -364,7 +363,7 @@ class _SizeDistribution:
364
363
 
365
364
  @property
366
365
  def coeff_of_curvature(self) -> float:
367
- return (self.d_30 ** 2.0) / (self.d_60 * self.d_10)
366
+ return (self.d_30**2.0) / (self.d_60 * self.d_10)
368
367
 
369
368
  @property
370
369
  def coeff_of_uniformity(self) -> float:
@@ -390,6 +389,12 @@ class _SizeDistribution:
390
389
  grade = USCSSymbol.POORLY_GRADED
391
390
  return grade
392
391
 
392
+ def has_particle_sizes(self) -> bool:
393
+ """Checks if particle sizes are provided."""
394
+ if isnan(self.d_10) or isnan(self.d_30) or isnan(self.d_60):
395
+ return False
396
+ return True
397
+
393
398
 
394
399
  class PSD:
395
400
  """Quantitative proportions by mass of various sizes of particles
@@ -397,12 +402,12 @@ class PSD:
397
402
  """
398
403
 
399
404
  def __init__(
400
- self,
401
- fines: float,
402
- sand: float,
403
- d_10: float = 0,
404
- d_30: float = 0,
405
- d_60: float = 0,
405
+ self,
406
+ fines: float,
407
+ sand: float,
408
+ d_10: float = nan,
409
+ d_30: float = nan,
410
+ d_60: float = nan,
406
411
  ):
407
412
  """
408
413
  :param fines: Percentage of fines in soil sample (%) i.e. The
@@ -460,7 +465,7 @@ class PSD:
460
465
 
461
466
  def has_particle_sizes(self) -> bool:
462
467
  """Checks if soil sample has particle sizes."""
463
- return any(self.size_dist)
468
+ return self.size_dist.has_particle_sizes()
464
469
 
465
470
  def grade(self) -> USCSSymbol:
466
471
  r"""Return the grade of the soil sample, either well graded or
@@ -474,7 +479,7 @@ class PSD:
474
479
  return self.size_dist.grade(coarse_soil=self.coarse_material_type)
475
480
 
476
481
 
477
- @dataclass
482
+ @dataclass(frozen=True, slots=True)
478
483
  class AASHTOResult:
479
484
  symbol: str
480
485
  symbol_no_group_idx: str
@@ -528,9 +533,9 @@ class AASHTO:
528
533
  return self._fines
529
534
 
530
535
  @fines.setter
531
- @validate_func_args
532
- def fines(self, val: Annotated[float, MustBeNonNegative]):
533
- self._fines = val
536
+ @validate_params
537
+ def fines(self, fines: Annotated[float, MustBeNonNegative]):
538
+ self._fines = fines
534
539
 
535
540
  @round_(ndigits=0)
536
541
  def group_index(self) -> float:
@@ -620,7 +625,7 @@ class AASHTO:
620
625
  return soil_clf
621
626
 
622
627
 
623
- @dataclass
628
+ @dataclass(frozen=True, slots=True)
624
629
  class USCSResult:
625
630
  symbol: str
626
631
  description: str
@@ -649,10 +654,10 @@ class USCS:
649
654
  """
650
655
 
651
656
  def __init__(
652
- self,
653
- atterberg_limits: AtterbergLimits,
654
- psd: PSD,
655
- organic=False,
657
+ self,
658
+ atterberg_limits: AtterbergLimits,
659
+ psd: PSD,
660
+ organic=False,
656
661
  ):
657
662
  """
658
663
  :param atterberg_limits: Atterberg limits of the soil.
@@ -673,7 +678,8 @@ class USCS:
673
678
 
674
679
  if isinstance(soil_clf, USCSSymbol):
675
680
  soil_clf = USCSResult(
676
- symbol=soil_clf.symbol, description=soil_clf.description
681
+ symbol=soil_clf.symbol,
682
+ description=soil_clf.description,
677
683
  )
678
684
  else:
679
685
  # Handling tuple or list case for dual classification
@@ -779,9 +785,9 @@ class USCS:
779
785
 
780
786
 
781
787
  def create_aashto_classifier(
782
- liquid_limit: float,
783
- plastic_limit: float,
784
- fines: float,
788
+ liquid_limit: float,
789
+ plastic_limit: float,
790
+ fines: float,
785
791
  ) -> AASHTO:
786
792
  """A helper function that encapsulates the creation of a AASHTO
787
793
  classifier.
@@ -791,12 +797,10 @@ def create_aashto_classifier(
791
797
  the minimum moisture content at which a soil
792
798
  flows upon application of a very small shear
793
799
  force.
794
-
795
800
  :param plastic_limit: Water content at which plastic deformation can
796
801
  be initiated (%). It is also the minimum water
797
802
  content at which soil can be rolled into a
798
803
  thread 3mm thick (molded without breaking).
799
-
800
804
  :param fines: Percentage of fines in soil sample (%) i.e. The
801
805
  percentage of soil sample passing through No. 200
802
806
  sieve (0.075mm).
@@ -806,14 +810,14 @@ def create_aashto_classifier(
806
810
 
807
811
 
808
812
  def create_uscs_classifier(
809
- liquid_limit: float,
810
- plastic_limit: float,
811
- fines: float,
812
- sand: float,
813
- d_10: float = 0,
814
- d_30: float = 0,
815
- d_60: float = 0,
816
- organic: bool = False,
813
+ liquid_limit: float,
814
+ plastic_limit: float,
815
+ fines: float,
816
+ sand: float,
817
+ d_10: float = nan,
818
+ d_30: float = nan,
819
+ d_60: float = nan,
820
+ organic: bool = False,
817
821
  ):
818
822
  """A helper function that encapsulates the creation of a USCS
819
823
  classifier.
@@ -823,24 +827,17 @@ def create_uscs_classifier(
823
827
  the minimum moisture content at which a soil
824
828
  flows upon application of a very small shear
825
829
  force.
826
-
827
830
  :param plastic_limit: Water content at which plastic deformation can
828
831
  be initiated (%). It is also the minimum water
829
832
  content at which soil can be rolled into a
830
833
  thread 3mm thick. (molded without breaking)
831
-
832
834
  :param fines: Percentage of fines in soil sample (%) i.e. The
833
835
  percentage of soil sample passing through No. 200
834
836
  sieve (0.075mm).
835
-
836
837
  :param sand: Percentage of sand in soil sample (%).
837
-
838
838
  :param d_10: Diameter at which 10% of the soil by weight is finer.
839
-
840
839
  :param d_30: Diameter at which 30% of the soil by weight is finer.
841
-
842
840
  :param d_60: Diameter at which 60% of the soil by weight is finer.
843
-
844
841
  :param organic: Indicates whether soil is organic or not.
845
842
  """
846
843
  atterberg_lmts = AtterbergLimits(liquid_limit, plastic_limit)
geolysis/spt.py CHANGED
@@ -3,7 +3,7 @@ from abc import abstractmethod
3
3
  from typing import Annotated, Final, Sequence
4
4
 
5
5
  from func_validator import (
6
- validate_func_args,
6
+ validate_params,
7
7
  MustBeBetween,
8
8
  MustBePositive,
9
9
  MustBeMemberOf,
@@ -61,9 +61,9 @@ class SPT:
61
61
  """
62
62
 
63
63
  def __init__(
64
- self,
65
- corrected_spt_n_values: Sequence[float],
66
- method: SPTDesignMethod.WEIGHTED = "wgt",
64
+ self,
65
+ corrected_spt_n_values: Sequence[float],
66
+ method: SPTDesignMethod.WEIGHTED = "wgt",
67
67
  ):
68
68
  """
69
69
  :param corrected_spt_n_values: Corrected SPT N-values within the
@@ -80,14 +80,14 @@ class SPT:
80
80
  return self._corrected_spt_n_values
81
81
 
82
82
  @corrected_spt_n_values.setter
83
- @validate_func_args
83
+ @validate_params
84
84
  def corrected_spt_n_values(
85
- self,
86
- corrected_spt_n_values: Annotated[
87
- Sequence[float],
88
- MustHaveLengthGreaterThan(1),
89
- MustHaveValuesBetween(min_value=1.0, max_value=100.0),
90
- ],
85
+ self,
86
+ corrected_spt_n_values: Annotated[
87
+ Sequence[float],
88
+ MustHaveLengthGreaterThan(1),
89
+ MustHaveValuesBetween(min_value=1.0, max_value=100.0),
90
+ ],
91
91
  ) -> None:
92
92
  self._corrected_spt_n_values = corrected_spt_n_values
93
93
 
@@ -96,9 +96,9 @@ class SPT:
96
96
  return self._method
97
97
 
98
98
  @method.setter
99
- @validate_func_args
100
- def method(self, val: Annotated[str, MustBeMemberOf(SPTDesignMethod)]):
101
- self._method = val
99
+ @validate_params
100
+ def method(self, method: Annotated[str, MustBeMemberOf(SPTDesignMethod)]):
101
+ self._method = method
102
102
 
103
103
  @staticmethod
104
104
  def _avg_spt_n_design(vals) -> float:
@@ -111,15 +111,15 @@ class SPT:
111
111
  @staticmethod
112
112
  def _wgt_spt_n_design(vals):
113
113
 
114
- sum_total = 0.0
115
- sum_wgts = 0.0
114
+ total_wgted_spt = 0.0
115
+ total_wgt = 0.0
116
116
 
117
117
  for i, corr_spt_n_val in enumerate(vals, start=1):
118
- wgt = 1 / i ** 2
119
- sum_total += wgt * corr_spt_n_val
120
- sum_wgts += wgt
118
+ wgt = 1 / i**2
119
+ total_wgted_spt += wgt * corr_spt_n_val
120
+ total_wgt += wgt
121
121
 
122
- return sum_total / sum_wgts
122
+ return total_wgted_spt / total_wgt
123
123
 
124
124
  @round_(ndigits=1)
125
125
  def n_design(self):
@@ -220,14 +220,14 @@ class EnergyCorrection:
220
220
  }
221
221
 
222
222
  def __init__(
223
- self,
224
- recorded_spt_n_value: int,
225
- *,
226
- energy_percentage=0.6,
227
- borehole_diameter=65.0,
228
- rod_length=3.0,
229
- hammer_type=HammerType.DONUT_1,
230
- sampler_type=SamplerType.STANDARD,
223
+ self,
224
+ recorded_spt_n_value: int,
225
+ *,
226
+ energy_percentage=0.6,
227
+ borehole_diameter=65.0,
228
+ rod_length=3.0,
229
+ hammer_type=HammerType.DONUT_1,
230
+ sampler_type=SamplerType.STANDARD,
231
231
  ):
232
232
  """
233
233
  :param recorded_spt_n_value: Recorded SPT N-value from field.
@@ -252,12 +252,12 @@ class EnergyCorrection:
252
252
  return self._recorded_spt_value
253
253
 
254
254
  @recorded_spt_n_value.setter
255
- @validate_func_args
255
+ @validate_params
256
256
  def recorded_spt_n_value(
257
- self,
258
- val: Annotated[int, MustBeBetween(min_value=0, max_value=100)]
257
+ self,
258
+ recorded_spt_n_value: Annotated[int, MustBeBetween(min_value=0, max_value=100)],
259
259
  ) -> None:
260
- self._recorded_spt_value = val
260
+ self._recorded_spt_value = recorded_spt_n_value
261
261
 
262
262
  @property
263
263
  def energy_percentage(self) -> float:
@@ -265,12 +265,14 @@ class EnergyCorrection:
265
265
  return self._energy_percentage
266
266
 
267
267
  @energy_percentage.setter
268
- @validate_func_args
268
+ @validate_params
269
269
  def energy_percentage(
270
- self,
271
- val: Annotated[float, MustBeBetween(min_value=0.0, max_value=1.0)]
270
+ self,
271
+ energy_percentage: Annotated[
272
+ float, MustBeBetween(min_value=0.0, max_value=1.0)
273
+ ],
272
274
  ) -> None:
273
- self._energy_percentage = val
275
+ self._energy_percentage = energy_percentage
274
276
 
275
277
  @property
276
278
  def borehole_diameter(self) -> float:
@@ -278,12 +280,14 @@ class EnergyCorrection:
278
280
  return self._borehole_diameter
279
281
 
280
282
  @borehole_diameter.setter
281
- @validate_func_args
283
+ @validate_params
282
284
  def borehole_diameter(
283
- self, val: Annotated[
284
- float, MustBeBetween(min_value=65.0, max_value=200.0)]
285
+ self,
286
+ borehole_diameter: Annotated[
287
+ float, MustBeBetween(min_value=65.0, max_value=200.0)
288
+ ],
285
289
  ) -> None:
286
- self._borehole_diameter = val
290
+ self._borehole_diameter = borehole_diameter
287
291
 
288
292
  @property
289
293
  def rod_length(self) -> float:
@@ -291,19 +295,19 @@ class EnergyCorrection:
291
295
  return self._rod_length
292
296
 
293
297
  @rod_length.setter
294
- @validate_func_args
295
- def rod_length(self, val: Annotated[float, MustBePositive]):
296
- self._rod_length = val
298
+ @validate_params
299
+ def rod_length(self, rod_length: Annotated[float, MustBePositive]):
300
+ self._rod_length = rod_length
297
301
 
298
302
  @property
299
303
  def hammer_type(self) -> HammerType:
300
304
  return self._hammer_type
301
305
 
302
306
  @hammer_type.setter
303
- @validate_func_args
307
+ @validate_params
304
308
  def hammer_type(
305
- self,
306
- hammer_type: Annotated[HammerType, MustBeMemberOf(HammerType)]
309
+ self,
310
+ hammer_type: Annotated[HammerType, MustBeMemberOf(HammerType)],
307
311
  ):
308
312
  self._hammer_type = hammer_type
309
313
 
@@ -312,10 +316,11 @@ class EnergyCorrection:
312
316
  return self._sampler_type
313
317
 
314
318
  @sampler_type.setter
315
- @validate_func_args
316
- def sampler_type(self,
317
- val: Annotated[SamplerType, MustBeMemberOf(SamplerType)]):
318
- self._sampler_type = val
319
+ @validate_params
320
+ def sampler_type(
321
+ self, sampler_type: Annotated[SamplerType, MustBeMemberOf(SamplerType)]
322
+ ):
323
+ self._sampler_type = sampler_type
319
324
 
320
325
  @property
321
326
  def hammer_efficiency(self) -> float:
@@ -362,10 +367,10 @@ class EnergyCorrection:
362
367
  `ENERGY`: 0.6, 0.55, etc
363
368
  """
364
369
  numerator = (
365
- self.hammer_efficiency
366
- * self.borehole_diameter_correction
367
- * self.sampler_correction
368
- * self.rod_length_correction
370
+ self.hammer_efficiency
371
+ * self.borehole_diameter_correction
372
+ * self.sampler_correction
373
+ * self.rod_length_correction
369
374
  )
370
375
  return numerator / self.energy_percentage
371
376
 
@@ -393,10 +398,10 @@ class OPC:
393
398
  return self._eop
394
399
 
395
400
  @eop.setter
396
- @validate_func_args
397
- def eop(self, val: Annotated[float, MustBeNonNegative]):
401
+ @validate_params
402
+ def eop(self, eop: Annotated[float, MustBeNonNegative]):
398
403
  """Effective overburden pressure ($kPa$)."""
399
- self._eop = val
404
+ self._eop = eop
400
405
 
401
406
  @property
402
407
  def std_spt_n_value(self) -> float:
@@ -404,12 +409,14 @@ class OPC:
404
409
  return self._std_spt_n_value
405
410
 
406
411
  @std_spt_n_value.setter
407
- @validate_func_args
412
+ @validate_params
408
413
  def std_spt_n_value(
409
- self, val: Annotated[
410
- float, MustBeBetween(min_value=0.0, max_value=100.0)]
414
+ self,
415
+ std_spt_n_value: Annotated[
416
+ float, MustBeBetween(min_value=0.0, max_value=100.0)
417
+ ],
411
418
  ):
412
- self._std_spt_n_value = val
419
+ self._std_spt_n_value = std_spt_n_value
413
420
 
414
421
  @round_(ndigits=1)
415
422
  def corrected_spt_n_value(self) -> float:
@@ -435,10 +442,12 @@ class GibbsHoltzOPC(OPC):
435
442
  return self._eop
436
443
 
437
444
  @eop.setter
438
- @validate_func_args
439
- def eop(self, val: Annotated[
440
- float, MustBeBetween(min_value=0.0, max_value=280.0)]):
441
- self._eop = val
445
+ @validate_params
446
+ def eop(
447
+ self,
448
+ eop: Annotated[float, MustBeBetween(min_value=0.0, max_value=280.0)],
449
+ ):
450
+ self._eop = eop
442
451
 
443
452
  def correction(self) -> float:
444
453
  r"""SPT Correction."""
@@ -476,9 +485,9 @@ class PeckOPC(OPC):
476
485
  return self._eop
477
486
 
478
487
  @eop.setter
479
- @validate_func_args
480
- def eop(self, val: Annotated[float, MustBeGreaterThanOrEqual(24.0)]):
481
- self._eop = val
488
+ @validate_params
489
+ def eop(self, eop: Annotated[float, MustBeGreaterThanOrEqual(24.0)]):
490
+ self._eop = eop
482
491
 
483
492
  def correction(self) -> float:
484
493
  r"""SPT Correction."""
@@ -527,12 +536,14 @@ class DilatancyCorrection:
527
536
  return self._corr_spt_n_value
528
537
 
529
538
  @corr_spt_n_value.setter
530
- @validate_func_args
539
+ @validate_params
531
540
  def corr_spt_n_value(
532
- self, val: Annotated[
533
- float, MustBeBetween(min_value=0.0, max_value=100.0)]
541
+ self,
542
+ corr_spt_n_value: Annotated[
543
+ float, MustBeBetween(min_value=0.0, max_value=100.0)
544
+ ],
534
545
  ):
535
- self._corr_spt_n_value = val
546
+ self._corr_spt_n_value = corr_spt_n_value
536
547
 
537
548
  @round_(ndigits=1)
538
549
  def corrected_spt_n_value(self) -> float:
@@ -574,11 +585,11 @@ _opctypes = {
574
585
  }
575
586
 
576
587
 
577
- @validate_func_args
588
+ @validate_params
578
589
  def create_overburden_pressure_correction(
579
- std_spt_n_value: float,
580
- eop: float,
581
- opc_type: Annotated[OPCType | str, MustBeMemberOf(OPCType)] = "gibbs",
590
+ std_spt_n_value: float,
591
+ eop: float,
592
+ opc_type: Annotated[OPCType | str, MustBeMemberOf(OPCType)] = "gibbs",
582
593
  ):
583
594
  """A factory function that encapsulates the creation of overburden
584
595
  pressure correction.
@@ -1,31 +1,11 @@
1
1
  import enum
2
2
  import functools
3
- import math
4
- from math import exp, inf, isclose, log10, pi, sqrt
5
- from statistics import fmean as mean
6
3
  from typing import Callable
7
4
 
8
- from func_validator import ValidationError
9
-
10
- __all__ = [
11
- "AbstractStrEnum",
12
- "ValidationError",
13
- "inf",
14
- "pi",
15
- "deg2rad",
16
- "rad2deg",
17
- "tan",
18
- "cot",
19
- "sin",
20
- "cos",
21
- "arctan",
22
- "round_",
23
- "mean",
24
- "exp",
25
- "isclose",
26
- "log10",
27
- "sqrt",
28
- ]
5
+ from .math import *
6
+ from . import math as m
7
+
8
+ __all__ = ["AbstractStrEnum", "ValidationError", "add_repr", "round_"] + m.__all__
29
9
 
30
10
 
31
11
  class StrEnumMeta(enum.EnumMeta):
@@ -46,39 +26,21 @@ class AbstractStrEnum(enum.StrEnum, metaclass=StrEnumMeta):
46
26
  """
47
27
 
48
28
 
49
- def deg2rad(x: float, /) -> float:
50
- """Convert angle x from degrees to radians."""
51
- return math.radians(x)
52
-
53
-
54
- def rad2deg(x: float, /) -> float:
55
- """Convert angle x from radians to degrees."""
56
- return math.degrees(x)
57
-
58
-
59
- def tan(x: float, /) -> float:
60
- """Return the tangent of x (measured in degrees)."""
61
- return math.tan(deg2rad(x))
62
-
63
-
64
- def cot(x: float, /) -> float:
65
- """Return the cotangent of x (measured in degrees)."""
66
- return 1 / tan(x)
67
-
68
-
69
- def sin(x: float, /) -> float:
70
- """Return the sine of x (measured in degrees)."""
71
- return math.sin(deg2rad(x))
29
+ def add_repr(cls):
30
+ """A class decorator that adds a __repr__ method to the class."""
72
31
 
32
+ def __repr__(self) -> str:
33
+ inst_attrs = self.__dict__
34
+ attrs = (f"{key.strip('_')}={val}" for key, val in inst_attrs.items())
35
+ return f"{type(self).__name__}({', '.join(attrs)})"
73
36
 
74
- def cos(x: float, /) -> float:
75
- """Return the cosine of x (measured in degrees)."""
76
- return math.cos(deg2rad(x))
37
+ def __str__(self) -> str:
38
+ return repr(self)
77
39
 
40
+ cls.__repr__ = __repr__
41
+ cls.__str__ = __str__
78
42
 
79
- def arctan(x: float, /) -> float:
80
- """Return the arc tangent (measured in degrees) of x."""
81
- return rad2deg(math.atan(x))
43
+ return cls
82
44
 
83
45
 
84
46
  def round_(ndigits: int) -> Callable:
geolysis/utils/math.py ADDED
@@ -0,0 +1,59 @@
1
+ import math
2
+ from math import exp, inf, isclose, log10, pi, sqrt, isinf, atan, nan, isnan
3
+ from statistics import fmean as mean
4
+
5
+ __all__ = [
6
+ "atan",
7
+ "inf",
8
+ "isinf",
9
+ "pi",
10
+ "deg2rad",
11
+ "rad2deg",
12
+ "tandeg",
13
+ "cotdeg",
14
+ "sindeg",
15
+ "cosdeg",
16
+ "arctandeg",
17
+ "mean",
18
+ "exp",
19
+ "isclose",
20
+ "log10",
21
+ "sqrt",
22
+ "nan",
23
+ "isnan",
24
+ ]
25
+
26
+
27
+ def deg2rad(x: float, /) -> float:
28
+ """Convert angle x from degrees to radians."""
29
+ return math.radians(x)
30
+
31
+
32
+ def rad2deg(x: float, /) -> float:
33
+ """Convert angle x from radians to degrees."""
34
+ return math.degrees(x)
35
+
36
+
37
+ def tandeg(x: float, /) -> float:
38
+ """Return the tangent of x (measured in degrees)."""
39
+ return math.tan(deg2rad(x))
40
+
41
+
42
+ def cotdeg(x: float, /) -> float:
43
+ """Return the cotangent of x (measured in degrees)."""
44
+ return 1 / tandeg(x)
45
+
46
+
47
+ def sindeg(x: float, /) -> float:
48
+ """Return the sine of x (measured in degrees)."""
49
+ return math.sin(deg2rad(x))
50
+
51
+
52
+ def cosdeg(x: float, /) -> float:
53
+ """Return the cosine of x (measured in degrees)."""
54
+ return math.cos(deg2rad(x))
55
+
56
+
57
+ def arctandeg(x: float, /) -> float:
58
+ """Return the arc tangent (measured in degrees) of x."""
59
+ return rad2deg(math.atan(x))