geolysis 0.4.3__py3-none-any.whl → 0.4.4__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.
@@ -0,0 +1,258 @@
1
+ """ Terzaghi ultimate bearing capacity module.
2
+
3
+ Classes
4
+ =======
5
+
6
+ .. autosummary::
7
+ :toctree: _autosummary
8
+
9
+ TerzaghiUBC4StripFooting
10
+ TerzaghiUBC4CircularFooting
11
+ TerzaghiUBC4SquareFooting
12
+ TerzaghiUBC4RectangularFooting
13
+ """
14
+ from abc import ABC
15
+
16
+ from geolysis.bearing_capacity.ubc import UltimateBearingCapacity
17
+ from geolysis.utils import cos, cot, deg2rad, exp, isclose, pi, round_, tan
18
+
19
+ __all__ = ["TerzaghiUBC4StripFooting",
20
+ "TerzaghiUBC4CircularFooting",
21
+ "TerzaghiUBC4SquareFooting",
22
+ "TerzaghiUBC4RectangularFooting"]
23
+
24
+
25
+ @round_
26
+ def n_c(friction_angle: float) -> float:
27
+ if isclose(friction_angle, 0.0):
28
+ return 5.7
29
+ return cot(friction_angle) * (n_q(friction_angle) - 1.0)
30
+
31
+
32
+ @round_
33
+ def n_q(friction_angle: float) -> float:
34
+ return (exp((3.0 * pi / 2.0 - deg2rad(friction_angle))
35
+ * tan(friction_angle))
36
+ / (2.0 * (cos(45.0 + friction_angle / 2.0)) ** 2.0))
37
+
38
+
39
+ @round_
40
+ def n_gamma(friction_angle: float) -> float:
41
+ return (n_q(friction_angle) - 1.0) * tan(1.4 * friction_angle)
42
+
43
+
44
+ class TerzaghiUltimateBearingCapacity(UltimateBearingCapacity, ABC):
45
+
46
+ @property
47
+ def n_c(self) -> float:
48
+ r"""Bearing capacity factor :math:`N_c`.
49
+
50
+ :Equation:
51
+
52
+ .. math:: N_c = \cot(\phi) \cdot (N_q - 1)
53
+ """
54
+ return n_c(self.friction_angle)
55
+
56
+ @property
57
+ def n_q(self) -> float:
58
+ r"""Bearing capacity factor :math:`N_q`.
59
+
60
+ :Equation:
61
+
62
+ .. math::
63
+
64
+ N_q = \dfrac{e^{(\frac{3\pi}{2} - \phi)\tan\phi}}
65
+ {2\cos^2(45 + \frac{\phi}{2})}
66
+ """
67
+ return n_q(self.friction_angle)
68
+
69
+ @property
70
+ def n_gamma(self) -> float:
71
+ r"""Bearing capacity factor :math:`N_{\gamma}`.
72
+
73
+ :Equation:
74
+
75
+ .. math:: N_{\gamma} &= (N_q - 1) \cdot \tan(1.4\phi)
76
+ """
77
+ return n_gamma(self.friction_angle)
78
+
79
+
80
+ class TerzaghiUBC4StripFooting(TerzaghiUltimateBearingCapacity):
81
+ r"""Ultimate bearing capacity for strip footing according to
82
+ ``Terzaghi 1943``.
83
+
84
+ :Equation:
85
+
86
+ .. math:: q_u = cN_c + qN_q + 0.5 \gamma BN_{\gamma}
87
+
88
+ .. list-table::
89
+ :widths: auto
90
+ :header-rows: 1
91
+
92
+ * - Symbol
93
+ - Description
94
+ - Unit
95
+ * - :math:`q_u`
96
+ - Ultimate bearing capacity
97
+ - :math:`kPa`
98
+ * - :math:`c`
99
+ - Cohesion of soil
100
+ - :math:`kPa`
101
+ * - :math:`q`
102
+ - Overburden pressure of soil
103
+ - :math:`kPa`
104
+ * - :math:`\gamma`
105
+ - Unit weight of soil
106
+ - :math:`kN/m^3`
107
+ * - :math:`B`
108
+ - Width of foundation footing
109
+ - :math:`m`
110
+ * - :math:`N_c`, :math:`N_q`, :math:`N_{\gamma}`
111
+ - Bearing capacity factors
112
+ - —
113
+ """
114
+
115
+ @round_
116
+ def bearing_capacity(self) -> float:
117
+ """Calculates ultimate bearing capacity for strip footing."""
118
+ return (self._cohesion_term(1.0)
119
+ + self._surcharge_term()
120
+ + self._embedment_term(0.5))
121
+
122
+
123
+ class TerzaghiUBC4CircularFooting(TerzaghiUltimateBearingCapacity):
124
+ r"""Ultimate bearing capacity for circular footing according to
125
+ ``Terzaghi 1943``.
126
+
127
+ :Equation:
128
+
129
+ .. math:: q_u = 1.3cN_c + qN_q + 0.3 \gamma BN_{\gamma}
130
+
131
+ .. list-table::
132
+ :widths: auto
133
+ :header-rows: 1
134
+
135
+ * - Symbol
136
+ - Description
137
+ - Unit
138
+ * - :math:`q_u`
139
+ - Ultimate bearing capacity
140
+ - :math:`kPa`
141
+ * - :math:`c`
142
+ - Cohesion of soil
143
+ - :math:`kPa`
144
+ * - :math:`q`
145
+ - Overburden pressure of soil
146
+ - :math:`kPa`
147
+ * - :math:`\gamma`
148
+ - Unit weight of soil
149
+ - :math:`kN/m^3`
150
+ * - :math:`B`
151
+ - Width of foundation footing
152
+ - :math:`m`
153
+ * - :math:`N_c`, :math:`N_q`, :math:`N_{\gamma}`
154
+ - Bearing capacity factors
155
+ - —
156
+ """
157
+
158
+ @round_
159
+ def bearing_capacity(self) -> float:
160
+ """Calculates ultimate bearing capacity for circular footing."""
161
+ return (self._cohesion_term(1.3)
162
+ + self._surcharge_term()
163
+ + self._embedment_term(0.3))
164
+
165
+
166
+ class TerzaghiUBC4RectangularFooting(TerzaghiUltimateBearingCapacity):
167
+ r"""Ultimate bearing capacity for rectangular footing according to
168
+ ``Terzaghi 1943``.
169
+
170
+ :Equation:
171
+
172
+ .. math::
173
+
174
+ q_u = \left(1 + 0.3 \dfrac{B}{L} \right) c N_c + qN_q
175
+ + \left(1 - 0.2 \dfrac{B}{L} \right) 0.5 B \gamma N_{\gamma}
176
+
177
+ .. list-table::
178
+ :widths: auto
179
+ :header-rows: 1
180
+
181
+ * - Symbol
182
+ - Description
183
+ - Unit
184
+ * - :math:`q_u`
185
+ - Ultimate bearing capacity
186
+ - :math:`kPa`
187
+ * - :math:`c`
188
+ - Cohesion of soil
189
+ - :math:`kPa`
190
+ * - :math:`q`
191
+ - Overburden pressure of soil
192
+ - :math:`kPa`
193
+ * - :math:`\gamma`
194
+ - Unit weight of soil
195
+ - :math:`kN/m^3`
196
+ * - :math:`B`
197
+ - Width of foundation footing
198
+ - :math:`m`
199
+ * - :math:`L`
200
+ - Length of foundation footing
201
+ - :math:`m`
202
+ * - :math:`N_c`, :math:`N_q`, :math:`N_{\gamma}`
203
+ - Bearing capacity factors
204
+ - —
205
+ """
206
+
207
+ @round_
208
+ def bearing_capacity(self) -> float:
209
+ """Calculates ultimate bearing capacity for rectangular footing."""
210
+ width = self.foundation_size.width
211
+ length = self.foundation_size.length
212
+ coh_coef = 1.0 + 0.3 * (width / length)
213
+ emb_coef = (1.0 - 0.2 * (width / length)) / 2.0
214
+
215
+ return (self._cohesion_term(coh_coef)
216
+ + self._surcharge_term()
217
+ + self._embedment_term(emb_coef))
218
+
219
+
220
+ class TerzaghiUBC4SquareFooting(TerzaghiUBC4RectangularFooting):
221
+ r"""Ultimate bearing capacity for square footing according to
222
+ ``Terzaghi 1943``.
223
+
224
+ :Equation:
225
+
226
+ .. math:: q_u = 1.3cN_c + qN_q + 0.4 \gamma BN_{\gamma}
227
+
228
+ .. list-table::
229
+ :widths: auto
230
+ :header-rows: 1
231
+
232
+ * - Symbol
233
+ - Description
234
+ - Unit
235
+ * - :math:`q_u`
236
+ - Ultimate bearing capacity
237
+ - :math:`kPa`
238
+ * - :math:`c`
239
+ - Cohesion of soil
240
+ - :math:`kPa`
241
+ * - :math:`q`
242
+ - Overburden pressure of soil
243
+ - :math:`kPa`
244
+ * - :math:`\gamma`
245
+ - Unit weight of soil
246
+ - :math:`kN/m^3`
247
+ * - :math:`B`
248
+ - Width of foundation footing
249
+ - :math:`m`
250
+ * - :math:`N_c`, :math:`N_q`, :math:`N_{\gamma}`
251
+ - Bearing capacity factors
252
+ - —
253
+ """
254
+
255
+ def bearing_capacity(self):
256
+ """Calcalates ultimate bearing capacity for square footing.
257
+ """
258
+ return super().bearing_capacity()
@@ -0,0 +1,302 @@
1
+ """Vesic ultimate bearing capacity module.
2
+
3
+ Classes
4
+ =======
5
+
6
+ .. autosummary::
7
+ :toctree: _autosummary
8
+
9
+ VesicUltimateBearingCapacity
10
+ """
11
+ from geolysis.bearing_capacity.ubc import UltimateBearingCapacity
12
+ from geolysis.bearing_capacity.ubc import hansen_ubc
13
+ from geolysis.foundation import FoundationSize, Shape
14
+ from geolysis.utils import isclose, round_, sin, tan
15
+
16
+ __all__ = ["VesicUltimateBearingCapacity"]
17
+
18
+
19
+ @round_
20
+ def n_c(friction_angle: float) -> float:
21
+ return hansen_ubc.n_c(friction_angle)
22
+
23
+
24
+ @round_
25
+ def n_q(friction_angle: float) -> float:
26
+ return hansen_ubc.n_q(friction_angle)
27
+
28
+
29
+ @round_
30
+ def n_gamma(friction_angle: float) -> float:
31
+ return 2.0 * (n_q(friction_angle) + 1.0) * tan(friction_angle)
32
+
33
+
34
+ @round_
35
+ def s_c(friction_angle: float,
36
+ f_width: float,
37
+ f_length: float,
38
+ f_shape: Shape) -> float:
39
+ _n_q = n_q(friction_angle)
40
+ _n_c = n_c(friction_angle)
41
+
42
+ if f_shape == Shape.STRIP:
43
+ return 1.0
44
+ elif f_shape == Shape.RECTANGLE:
45
+ return 1.0 + (f_width / f_length) * (_n_q / _n_c)
46
+ else: # SQUARE, CIRCLE
47
+ return 1.0 + (_n_q / _n_c)
48
+
49
+
50
+ @round_
51
+ def s_q(friction_angle: float,
52
+ f_width: float,
53
+ f_length: float,
54
+ f_shape: Shape) -> float:
55
+ if f_shape == Shape.STRIP:
56
+ return 1.0
57
+ elif f_shape == Shape.RECTANGLE:
58
+ return 1.0 + (f_width / f_length) * tan(friction_angle)
59
+ else: # SQUARE, CIRCLE
60
+ return 1.0 + tan(friction_angle)
61
+
62
+
63
+ @round_
64
+ def s_gamma(f_width: float, f_length: float, f_shape: Shape) -> float:
65
+ if f_shape == Shape.STRIP:
66
+ return 1.0
67
+ elif f_shape == Shape.RECTANGLE:
68
+ return 1.0 - 0.4 * (f_width / f_length)
69
+ else: # SQUARE, CIRCLE
70
+ return 0.6
71
+
72
+
73
+ @round_
74
+ def d_c(f_depth: float, f_width: float) -> float:
75
+ return 1.0 + 0.4 * f_depth / f_width
76
+
77
+
78
+ @round_
79
+ def d_q(friction_angle: float, f_depth: float, f_width: float) -> float:
80
+ return (1.0 + 2.0 * tan(friction_angle)
81
+ * (1.0 - sin(friction_angle)) ** 2.0
82
+ * (f_depth / f_width))
83
+
84
+
85
+ @round_
86
+ def d_gamma() -> float:
87
+ return 1.0
88
+
89
+
90
+ @round_
91
+ def i_c(load_angle: float) -> float:
92
+ return (1.0 - load_angle / 90.0) ** 2.0
93
+
94
+
95
+ @round_
96
+ def i_q(load_angle: float) -> float:
97
+ return i_c(load_angle)
98
+
99
+
100
+ @round_
101
+ def i_gamma(friction_angle: float, load_angle: float) -> float:
102
+ if isclose(friction_angle, 0.0):
103
+ return 1.0
104
+ return (1.0 - load_angle / friction_angle) ** 2.0
105
+
106
+
107
+ class VesicUltimateBearingCapacity(UltimateBearingCapacity):
108
+ r"""Ultimate bearing capacity for soils according to ``Vesic (1973)``.
109
+
110
+ :Equation:
111
+
112
+ .. math::
113
+
114
+ q_u = cN_c s_c d_c i_c + qN_q s_q d_q i_q
115
+ + 0.5 \gamma B N_{\gamma} s_{\gamma} d_{\gamma} i_{\gamma}
116
+
117
+ .. list-table::
118
+ :widths: auto
119
+ :header-rows: 1
120
+
121
+ * - Symbol
122
+ - Description
123
+ - Unit
124
+ * - :math:`q_u`
125
+ - Ultimate bearing capacity
126
+ - :math:`kPa`
127
+ * - :math:`c`
128
+ - Cohesion of soil
129
+ - :math:`kPa`
130
+ * - :math:`q`
131
+ - Overburden pressure of soil
132
+ - :math:`kPa`
133
+ * - :math:`\gamma`
134
+ - Unit weight of soil
135
+ - :math:`kN/m^3`
136
+ * - :math:`B`
137
+ - Width of foundation footing
138
+ - :math:`m`
139
+ * - :math:`N_c`, :math:`N_q`, :math:`N_{\gamma}`
140
+ - Bearing capacity factors
141
+ - —
142
+ * - :math:`s_c`, :math:`s_q`, :math:`s_{\gamma}`
143
+ - Shape factors
144
+ - —
145
+ * - :math:`d_c`, :math:`d_q`, :math:`d_{\gamma}`
146
+ - Depth factors
147
+ - —
148
+ * - :math:`i_c`, :math:`i_q`, :math:`i_{\gamma}`
149
+ - Inclination factors
150
+ - —
151
+ """
152
+
153
+ @property
154
+ def n_c(self) -> float:
155
+ r"""Bearing capacity factor :math:`N_c`.
156
+
157
+ :Equation:
158
+
159
+ .. math:: N_c = \cot(\phi) \left(N_q - 1\right)
160
+ """
161
+ return n_c(self.friction_angle)
162
+
163
+ @property
164
+ def n_q(self) -> float:
165
+ r"""Bearing capacity factor :math:`N_q`.
166
+
167
+ :Equation:
168
+
169
+ .. math:: N_q = \tan^2\left(45 + \frac{\phi}{2}\right) \cdot
170
+ e^{\pi \tan(\phi)}
171
+ """
172
+ return n_q(self.friction_angle)
173
+
174
+ @property
175
+ def n_gamma(self) -> float:
176
+ r"""Bearing capacity factor :math:`N_{\gamma}`.
177
+
178
+ :Equation:
179
+
180
+ .. math:: N_{\gamma} = 2(N_q + 1) \tan(\phi)
181
+ """
182
+ return n_gamma(self.friction_angle)
183
+
184
+ @property
185
+ def s_c(self) -> float:
186
+ r"""Shape factor :math:`S_c`.
187
+
188
+ :Equation:
189
+
190
+ .. math::
191
+
192
+ s_c &= 1.0 \rightarrow \text{Strip footing}
193
+
194
+ s_c &= 1 + \dfrac{B}{L} \cdot \dfrac{N_q}{N_c} \rightarrow
195
+ \text{Rectangular footing}
196
+
197
+ s_c &= 1 + \dfrac{N_q}{N_c} \rightarrow
198
+ \text{Square or circular footing}
199
+ """
200
+ width, length, shape = self.foundation_size.footing_params()
201
+ return s_c(self.friction_angle, width, length, shape)
202
+
203
+ @property
204
+ def s_q(self) -> float:
205
+ r"""Shape factor :math:`S_q`.
206
+
207
+ :Equation:
208
+
209
+ .. math::
210
+
211
+ s_q &= 1.0 \rightarrow \text{Strip footing}
212
+
213
+ s_q &= 1 + \dfrac{B}{L} \cdot \tan(\phi) \rightarrow
214
+ \text{Rectangular footing}
215
+
216
+ s_q &= 1 + \tan(\phi) \rightarrow \text{Square or circular footing}
217
+ """
218
+ width, length, shape = self.foundation_size.footing_params()
219
+ return s_q(self.friction_angle, width, length, shape)
220
+
221
+ @property
222
+ def s_gamma(self) -> float:
223
+ r"""Shape factor :math:`S_{\gamma}`.
224
+
225
+ :Equation:
226
+
227
+ .. math::
228
+
229
+ s_{\gamma} &= 1.0 \rightarrow \text{Strip footing}
230
+
231
+ s_{\gamma} &= 1.0 - 0.4 \dfrac{B}{L} \rightarrow
232
+ \text{Rectangular footing}
233
+
234
+ s_{\gamma} &= 0.6 \rightarrow \text{Square or circular footing}
235
+ """
236
+ width, length, shape = self.foundation_size.footing_params()
237
+ return s_gamma(width, length, shape)
238
+
239
+ @property
240
+ def d_c(self) -> float:
241
+ r"""Depth factor :math:`D_c`.
242
+
243
+ :Equation:
244
+
245
+ .. math:: d_c = 1 + 0.4 \dfrac{D_f}{B}
246
+ """
247
+ depth, width = self.foundation_size.depth, self.foundation_size.width
248
+ return d_c(depth, width)
249
+
250
+ @property
251
+ def d_q(self) -> float:
252
+ r"""Depth factor :math:`D_q`.
253
+
254
+ :Equation:
255
+
256
+ .. math::
257
+
258
+ d_q = 1 + 2 \tan(\phi) \cdot (1 - \sin(\phi))^2
259
+ \cdot \dfrac{D_f}{B}
260
+ """
261
+ depth, width = self.foundation_size.depth, self.foundation_size.width
262
+ return d_q(self.friction_angle, depth, width)
263
+
264
+ @property
265
+ def d_gamma(self) -> float:
266
+ r"""Depth factor :math:`D_{\gamma}`.
267
+
268
+ :Equation:
269
+
270
+ .. math:: d_{\gamma} = 1.0
271
+ """
272
+ return d_gamma()
273
+
274
+ @property
275
+ def i_c(self) -> float:
276
+ r"""Inclination factor :math:`I_c`.
277
+
278
+ :Equation:
279
+
280
+ .. math:: i_c = (1 - \dfrac{\alpha}{90})^2
281
+ """
282
+ return i_c(self.load_angle)
283
+
284
+ @property
285
+ def i_q(self) -> float:
286
+ r"""Inclination factor :math:`I_q`.
287
+
288
+ :Equation:
289
+
290
+ .. math:: i_q = (1 - \dfrac{\alpha}{90})^2
291
+ """
292
+ return i_q(self.load_angle)
293
+
294
+ @property
295
+ def i_gamma(self) -> float:
296
+ r"""Inclination factor :math:`I_{\gamma}`.
297
+
298
+ :Equation:
299
+
300
+ .. math:: i_{\gamma} = \left(1 - \dfrac{\alpha}{\phi} \right)^2
301
+ """
302
+ return i_gamma(self.friction_angle, self.load_angle)
geolysis/foundation.py CHANGED
@@ -35,7 +35,7 @@ import enum
35
35
  from abc import ABC, abstractmethod
36
36
  from typing import Optional, TypeVar
37
37
 
38
- from geolysis.utils import inf, enum_repr, isclose, validators
38
+ from geolysis.utils import enum_repr, inf, isclose, validators
39
39
 
40
40
  __all__ = ["create_foundation",
41
41
  "FoundationSize",
@@ -40,10 +40,9 @@ Functions
40
40
  """
41
41
  import enum
42
42
  from abc import abstractmethod
43
- from typing import Protocol
44
- from typing import NamedTuple, Optional, Sequence
43
+ from typing import NamedTuple, Optional, Protocol, Sequence
45
44
 
46
- from geolysis.utils import enum_repr, isclose, round_, validators
45
+ from .utils import enum_repr, isclose, round_, validators
47
46
 
48
47
  __all__ = ["CLF_TYPE",
49
48
  "AtterbergLimits",
geolysis/spt.py CHANGED
@@ -29,8 +29,7 @@ import enum
29
29
  from abc import abstractmethod
30
30
  from typing import Final, Sequence
31
31
 
32
- from geolysis.utils import isclose, log10, mean, round_, sqrt, validators, \
33
- enum_repr
32
+ from .utils import enum_repr, isclose, log10, mean, round_, sqrt, validators
34
33
 
35
34
  __all__ = ["SPTNDesign",
36
35
  "HammerType",
@@ -0,0 +1,100 @@
1
+ import functools
2
+ import math
3
+ from math import exp, inf, isclose, log10, pi, sqrt
4
+ from statistics import fmean as mean
5
+ from typing import Callable, SupportsRound
6
+
7
+ from . import validators
8
+
9
+ __all__ = ["enum_repr",
10
+ "inf",
11
+ "pi",
12
+ "deg2rad",
13
+ "rad2deg",
14
+ "tan",
15
+ "cot",
16
+ "sin",
17
+ "cos",
18
+ "arctan",
19
+ "round_",
20
+ "mean",
21
+ "exp",
22
+ "isclose",
23
+ "log10",
24
+ "sqrt",
25
+ "validators"]
26
+
27
+
28
+ def deg2rad(x: float, /) -> float:
29
+ """Convert angle x from degrees to radians."""
30
+ return math.radians(x)
31
+
32
+
33
+ def rad2deg(x: float, /) -> float:
34
+ """Convert angle x from radians to degrees."""
35
+ return math.degrees(x)
36
+
37
+
38
+ def tan(x: float, /) -> float:
39
+ """Return the tangent of x (measured in degrees)."""
40
+ return math.tan(deg2rad(x))
41
+
42
+
43
+ def cot(x: float, /) -> float:
44
+ """Return the cotangent of x (measured in degrees)."""
45
+ return 1 / tan(x)
46
+
47
+
48
+ def sin(x: float, /) -> float:
49
+ """Return the sine of x (measured in degrees)."""
50
+ return math.sin(deg2rad(x))
51
+
52
+
53
+ def cos(x: float, /) -> float:
54
+ """Return the cosine of x (measured in degrees)."""
55
+ return math.cos(deg2rad(x))
56
+
57
+
58
+ def arctan(x: float, /) -> float:
59
+ """Return the arc tangent (measured in degrees) of x."""
60
+ return rad2deg(math.atan(x))
61
+
62
+
63
+ def enum_repr(cls):
64
+ cls.__repr__ = lambda self: f"{self.value}"
65
+ return cls
66
+
67
+
68
+ def round_(ndigits: int | Callable[..., SupportsRound]) -> Callable:
69
+ """A decorator that rounds the result of a callable to a specified number
70
+ of decimal places.
71
+
72
+ The returned value of the callable should support the ``__round__`` dunder
73
+ method and should be a numeric value. ``ndigits`` can either be an int
74
+ which will indicate the number of decimal places to round to or a
75
+ callable. If ``ndigits`` is callable the default decimal places is 2.
76
+
77
+ TypeError is raised when ``ndigits`` is neither an int nor a callable.
78
+ """
79
+
80
+ default_dp = 2
81
+
82
+ def dec(fn) -> Callable[..., float]:
83
+ @functools.wraps(fn)
84
+ def wrapper(*args, **kwargs) -> float:
85
+ dp = ndigits if not callable(ndigits) else default_dp
86
+ res = fn(*args, **kwargs)
87
+ return round(res, ndigits=dp)
88
+
89
+ return wrapper
90
+
91
+ # See if we're being called as @round_ or @round_().
92
+ # We're called with parens.
93
+ if isinstance(ndigits, int):
94
+ return dec
95
+
96
+ # We're called as @round_ without parens.
97
+ if callable(ndigits):
98
+ return dec(ndigits)
99
+
100
+ raise TypeError("ndigits must be an int or a callable")