geolysis 0.3.0__py3-none-any.whl → 0.4.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
geolysis/spt.py ADDED
@@ -0,0 +1,447 @@
1
+ import enum
2
+ from abc import abstractmethod
3
+ from typing import Final, Sequence
4
+
5
+ from geolysis import validators
6
+ from geolysis.utils import isclose, log10, mean, round_, sqrt
7
+
8
+ __all__ = ["EnergyCorrection", "GibbsHoltzOPC", "BazaraaPeckOPC", "PeckOPC",
9
+ "LiaoWhitmanOPC", "SkemptonOPC", "DilatancyCorrection"]
10
+
11
+
12
+ class SPTDesign:
13
+ """ SPT Design Calculations.
14
+
15
+ Due to uncertainty in field procedure in standard penetration test and also
16
+ to consider all the N-value in the influence zone of a foundation, a method
17
+ was suggested to calculate the design N-value which should be used in
18
+ calculating the allowable bearing capacity of shallow foundation rather
19
+ than using a particular N-value. All the N-value from the influence zone is
20
+ taken under consideration by giving the highest weightage to the closest
21
+ N-value from the base.
22
+ """
23
+
24
+ def __init__(self, spt_n_values: Sequence[float]) -> None:
25
+ """
26
+ :param spt_n_values: SPT N-values within the foundation influence zone.
27
+ ``spt_n_values`` can either be **corrected** or
28
+ **uncorrected** SPT N-values.
29
+ :type spt_n_values: Sequence[float]
30
+ """
31
+ self.spt_n_values = spt_n_values
32
+
33
+ @round_(ndigits=1)
34
+ def average_spt_n_design(self) -> float:
35
+ """Calculates the average of the corrected SPT N-values within the
36
+ foundation influence zone.
37
+ """
38
+ return mean(self.spt_n_values)
39
+
40
+ @round_(ndigits=1)
41
+ def minimum_spt_n_design(self):
42
+ """The lowest SPT N-value within the influence zone can be taken as the
43
+ :math:`N_{design}` as suggested by ``Terzaghi & Peck (1948)``.
44
+ """
45
+ return min(self.spt_n_values)
46
+
47
+ @round_(ndigits=1)
48
+ def weighted_spt_n_design(self):
49
+ r"""Calculates the weighted average of the corrected SPT N-values
50
+ within the foundation influence zone.
51
+
52
+ Weighted average is given by the formula:
53
+
54
+ .. math::
55
+
56
+ N_{design} = \dfrac{\sum_{i=1}^{n} \frac{N_i}{i^2}}
57
+ {\sum_{i=1}^{n}\frac{1}{i^2}}
58
+ """
59
+
60
+ sum_total = 0.0
61
+ sum_wgts = 0.0
62
+
63
+ for i, corrected_spt in enumerate(self.spt_n_values, start=1):
64
+ wgt = 1 / i ** 2
65
+ sum_total += wgt * corrected_spt
66
+ sum_wgts += wgt
67
+
68
+ return sum_total / sum_wgts
69
+
70
+
71
+ class HammerType(enum.StrEnum):
72
+ """Enumeration of hammer types."""
73
+ AUTOMATIC = enum.auto()
74
+ DONUT_1 = enum.auto()
75
+ DONUT_2 = enum.auto()
76
+ SAFETY = enum.auto()
77
+ DROP = PIN = enum.auto()
78
+
79
+
80
+ class SamplerType(enum.StrEnum):
81
+ """Enumeration of sampler types."""
82
+ STANDARD = enum.auto()
83
+ NON_STANDARD = enum.auto()
84
+
85
+
86
+ class EnergyCorrection:
87
+ r"""SPT N-value standardized for field procedures.
88
+
89
+ On the basis of field observations, it appears reasonable to standardize
90
+ the field SPT N-value as a function of the input driving energy and its
91
+ dissipation around the sampler around the surrounding soil. The variations
92
+ in testing procedures may be at least partially compensated by converting
93
+ the measured N-value to :math:`N_{60}` assuming 60% hammer energy being
94
+ transferred to the tip of the standard split spoon.
95
+
96
+ Energy correction is given by the formula:
97
+
98
+ .. math::
99
+
100
+ N_{ENERGY} = \dfrac{E_H \cdot C_B \cdot C_S \cdot C_R \cdot N}{ENERGY}
101
+
102
+ ``ENERGY``: 0.6, 0.55, etc
103
+ """
104
+
105
+ HAMMER_EFFICIENCY_FACTORS = {HammerType.AUTOMATIC: 0.70,
106
+ HammerType.DONUT_1: 0.60,
107
+ HammerType.DONUT_2: 0.50,
108
+ HammerType.SAFETY: 0.55,
109
+ HammerType.DROP: 0.45,
110
+ HammerType.PIN: 0.45}
111
+
112
+ SAMPLER_CORRECTION_FACTORS = {SamplerType.STANDARD: 1.00,
113
+ SamplerType.NON_STANDARD: 1.20}
114
+
115
+ def __init__(self, recorded_spt_n_value: int, *, energy_percentage=0.6,
116
+ borehole_diameter=65.0, rod_length=3.0,
117
+ hammer_type=HammerType.DONUT_1,
118
+ sampler_type=SamplerType.STANDARD):
119
+ """
120
+ :param recorded_spt_n_value: Recorded SPT N-value from field.
121
+ :type recorded_spt_n_value: int
122
+
123
+ :param energy_percentage: Energy percentage reaching the tip of the
124
+ sampler, defaults to 0.6
125
+ :type energy_percentage: float, optional
126
+
127
+ :param borehole_diameter: Borehole diameter, defaults to 65.0. (mm)
128
+ :type borehole_diameter: float, optional
129
+
130
+ :param rod_length: Rod length, defaults to 3.0. (m)
131
+ :type rod_length: float, optional
132
+
133
+ :param hammer_type: Hammer type, defaults to :attr:`HammerType.DONUT_1`
134
+ :type hammer_type: HammerType, optional
135
+
136
+ :param sampler_type: Sampler type, defaults to :attr:`SamplerType.STANDARD`
137
+ :type sampler_type: SamplerType, optional
138
+ """
139
+
140
+ self.recorded_spt_n_value = recorded_spt_n_value
141
+ self.energy_percentage = energy_percentage
142
+ self.borehole_diameter = borehole_diameter
143
+ self.rod_length = rod_length
144
+ self.hammer_type = hammer_type
145
+ self.sampler_type = sampler_type
146
+
147
+ @property
148
+ def recorded_spt_n_value(self) -> int:
149
+ return self._recorded_spt_value
150
+
151
+ @recorded_spt_n_value.setter
152
+ @validators.le(100)
153
+ @validators.gt(0)
154
+ def recorded_spt_n_value(self, val: int) -> None:
155
+ self._recorded_spt_value = val
156
+
157
+ @property
158
+ def energy_percentage(self) -> float:
159
+ return self._energy_percentage
160
+
161
+ @energy_percentage.setter
162
+ @validators.le(1.0)
163
+ @validators.gt(0.0)
164
+ def energy_percentage(self, val: float) -> None:
165
+ self._energy_percentage = val
166
+
167
+ @property
168
+ def borehole_diameter(self) -> float:
169
+ return self._borehole_diameter
170
+
171
+ @borehole_diameter.setter
172
+ @validators.le(200.0)
173
+ @validators.ge(65.0)
174
+ def borehole_diameter(self, val: float) -> None:
175
+ self._borehole_diameter = val
176
+
177
+ @property
178
+ def rod_length(self) -> float:
179
+ return self._rod_length
180
+
181
+ @rod_length.setter
182
+ @validators.gt(0.0)
183
+ def rod_length(self, val: float) -> None:
184
+ self._rod_length = val
185
+
186
+ @property
187
+ def hammer_efficiency(self) -> float:
188
+ """Hammer efficiency correction factor."""
189
+ return self.HAMMER_EFFICIENCY_FACTORS[self.hammer_type]
190
+
191
+ @property
192
+ def borehole_diameter_correction(self) -> float:
193
+ """Borehole diameter correction factor."""
194
+ if 65 <= self.borehole_diameter <= 115:
195
+ corr = 1.00
196
+ elif 115 < self.borehole_diameter <= 150:
197
+ corr = 1.05
198
+ else:
199
+ corr = 1.15
200
+
201
+ return corr
202
+
203
+ @property
204
+ def sampler_correction(self) -> float:
205
+ """Sampler correction factor."""
206
+ return self.SAMPLER_CORRECTION_FACTORS[self.sampler_type]
207
+
208
+ @property
209
+ def rod_length_correction(self) -> float:
210
+ """Rod length correction factor."""
211
+ if 3.0 <= self.rod_length <= 4.0:
212
+ corr = 0.75
213
+ elif 4.0 < self.rod_length <= 6.0:
214
+ corr = 0.85
215
+ elif 6.0 < self.rod_length <= 10.0:
216
+ corr = 0.95
217
+ else:
218
+ corr = 1.00
219
+
220
+ return corr
221
+
222
+ def correction(self) -> float:
223
+ """Energy correction factor."""
224
+ numerator = (self.hammer_efficiency
225
+ * self.borehole_diameter_correction
226
+ * self.sampler_correction
227
+ * self.rod_length_correction)
228
+ return numerator / self.energy_percentage
229
+
230
+ @round_(ndigits=1)
231
+ def corrected_spt_n_value(self) -> float:
232
+ """Corrected SPT N-value."""
233
+ return self.correction() * self.recorded_spt_n_value
234
+
235
+
236
+ class OPC:
237
+ """Base class for Overburden Pressure Correction (OPC)."""
238
+
239
+ def __init__(self, std_spt_n_value: float, eop: float) -> None:
240
+ """
241
+ :param std_spt_n_value: SPT N-value standardized for field procedures.
242
+ :type std_spt_n_value: float
243
+
244
+ :param eop: Effective overburden pressure (:math:`kN/m^2`)
245
+ :type eop: float
246
+ """
247
+ self.std_spt_n_value = std_spt_n_value
248
+ self.eop = eop
249
+
250
+ @property
251
+ def std_spt_n_value(self) -> float:
252
+ return self._std_spt_n_value
253
+
254
+ @std_spt_n_value.setter
255
+ @validators.gt(0.0)
256
+ def std_spt_n_value(self, val: float) -> None:
257
+ self._std_spt_n_value = val
258
+
259
+ @round_(ndigits=1)
260
+ def corrected_spt_n_value(self) -> float:
261
+ """Corrected SPT N-value."""
262
+ corrected_spt = self.correction() * self.std_spt_n_value
263
+ # Corrected SPT should not be more
264
+ # than 2 times the Standardized SPT
265
+ return min(corrected_spt, 2 * self.std_spt_n_value)
266
+
267
+ @abstractmethod
268
+ def correction(self) -> float:
269
+ raise NotImplementedError
270
+
271
+
272
+ class GibbsHoltzOPC(OPC):
273
+ r"""Overburden Pressure Correction according to ``Gibbs & Holtz (1957)``.
274
+
275
+ Overburden Pressure Correction is given by the formula:
276
+
277
+ .. math:: C_N = \dfrac{350}{\sigma_o + 70} \, \sigma_o \le 280kN/m^2
278
+
279
+ :math:`\frac{N_c}{N_{60}}` should lie between 0.45 and 2.0, if
280
+ :math:`\frac{N_c}{N_{60}}` is greater than 2.0, :math:`N_c` should be
281
+ divided by 2.0 to obtain the design value used in finding the bearing
282
+ capacity of the soil.
283
+ """
284
+
285
+ @property
286
+ def eop(self) -> float:
287
+ return self._eop
288
+
289
+ @eop.setter
290
+ @validators.le(280.0)
291
+ @validators.gt(0.0)
292
+ def eop(self, val: float) -> None:
293
+ self._eop = val
294
+
295
+ def correction(self) -> float:
296
+ """SPT Correction."""
297
+ corr = 350.0 / (self.eop + 70.0)
298
+ if corr > 2.0:
299
+ corr /= 2.0
300
+ return corr
301
+
302
+
303
+ class BazaraaPeckOPC(OPC):
304
+ r"""Overburden Pressure Correction according to ``Bazaraa (1967)``, and
305
+ also by ``Peck and Bazaraa (1969)``.
306
+
307
+ Overburden Pressure Correction is given by the formula:
308
+
309
+ .. math::
310
+
311
+ C_N &= \dfrac{4}{1 + 0.0418 \cdot \sigma_o}, \, \sigma_o \lt 71.8kN/m^2
312
+
313
+ C_N &= \dfrac{4}{3.25 + 0.0104 \cdot \sigma_o},
314
+ \, \sigma_o \gt 71.8kN/m^2
315
+
316
+ C_N &= 1 \, , \, \sigma_o = 71.8kN/m^2
317
+ """
318
+
319
+ #: Maximum effective overburden pressure. (:math:`kN/m^2`)
320
+ STD_PRESSURE: Final = 71.8
321
+
322
+ @property
323
+ def eop(self) -> float:
324
+ return self._eop
325
+
326
+ @eop.setter
327
+ @validators.ge(0.0)
328
+ def eop(self, val: float) -> None:
329
+ self._eop = val
330
+
331
+ def correction(self) -> float:
332
+ """SPT Correction."""
333
+ if isclose(self.eop, self.STD_PRESSURE, rel_tol=0.01):
334
+ corr = 1.0
335
+ elif self.eop < self.STD_PRESSURE:
336
+ corr = 4.0 / (1.0 + 0.0418 * self.eop)
337
+ else:
338
+ corr = 4.0 / (3.25 + 0.0104 * self.eop)
339
+ return corr
340
+
341
+
342
+ class PeckOPC(OPC):
343
+ r"""Overburden Pressure Correction according to ``Peck et al. (1974)``.
344
+
345
+ Overburden Pressure Correction is given by the formula:
346
+
347
+ .. math:: C_N = 0.77 \log \left(\dfrac{2000}{\sigma_o} \right)
348
+ """
349
+
350
+ @property
351
+ def eop(self) -> float:
352
+ return self._eop
353
+
354
+ @eop.setter
355
+ @validators.ge(24.0)
356
+ def eop(self, val: float) -> None:
357
+ self._eop = val
358
+
359
+ def correction(self) -> float:
360
+ """SPT Correction."""
361
+ return 0.77 * log10(2000.0 / self.eop)
362
+
363
+
364
+ class LiaoWhitmanOPC(OPC):
365
+ r"""Overburden Pressure Correction according to ``Liao & Whitman (1986)``.
366
+
367
+ Overburden Pressure Correction is given by the formula:
368
+
369
+ .. math:: C_N = \sqrt{\dfrac{100}{\sigma_o}}
370
+ """
371
+
372
+ @property
373
+ def eop(self) -> float:
374
+ return self._eop
375
+
376
+ @eop.setter
377
+ @validators.gt(0.0)
378
+ def eop(self, val: float) -> None:
379
+ self._eop = val
380
+
381
+ def correction(self) -> float:
382
+ """SPT Correction."""
383
+ return sqrt(100.0 / self.eop)
384
+
385
+
386
+ class SkemptonOPC(OPC):
387
+ r"""Overburden Pressure Correction according to ``Skempton (1986)``.
388
+
389
+ Overburden Pressure Correction is given by the formula:
390
+
391
+ .. math:: C_N = \dfrac{2}{1 + 0.01044 \cdot \sigma_o}
392
+ """
393
+
394
+ @property
395
+ def eop(self) -> float:
396
+ return self._eop
397
+
398
+ @eop.setter
399
+ @validators.ge(0.0)
400
+ def eop(self, val: float) -> None:
401
+ self._eop = val
402
+
403
+ def correction(self) -> float:
404
+ """SPT Correction."""
405
+ return 2.0 / (1.0 + 0.01044 * self.eop)
406
+
407
+
408
+ class DilatancyCorrection:
409
+ r"""Dilatancy SPT Correction according to ``Terzaghi & Peck (1948)``.
410
+
411
+ For coarse sand, this correction is not required. In applying this
412
+ correction, overburden pressure correction is applied first and then
413
+ dilatancy correction is applied.
414
+
415
+ Dilatancy correction is given by the formula:
416
+
417
+ .. math::
418
+
419
+ (N_1)_{60} &= 15 + \dfrac{1}{2}((N_1)_{60} - 15) \, , \,
420
+ (N_1)_{60} \gt 15
421
+
422
+ (N_1)_{60} &= (N_1)_{60} \, , \, (N_1)_{60} \le 15
423
+ """
424
+
425
+ def __init__(self, std_spt_n_value: float) -> None:
426
+ """
427
+ :param std_spt_n_value: SPT N-value standardized for field procedures
428
+ and/or corrected for overburden pressure.
429
+ :type std_spt_n_value: float
430
+ """
431
+ self.std_spt_n_value = std_spt_n_value
432
+
433
+ @property
434
+ def std_spt_n_value(self) -> float:
435
+ return self._std_spt_n_value
436
+
437
+ @std_spt_n_value.setter
438
+ @validators.gt(0.0)
439
+ def std_spt_n_value(self, val: float) -> None:
440
+ self._std_spt_n_value = val
441
+
442
+ @round_(ndigits=1)
443
+ def corrected_spt_n_value(self) -> float:
444
+ """Corrected SPT N-value."""
445
+ if self.std_spt_n_value <= 15.0:
446
+ return self.std_spt_n_value
447
+ return 15.0 + 0.5 * (self.std_spt_n_value - 15.0)
geolysis/utils.py ADDED
@@ -0,0 +1,78 @@
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
+ __all__ = ["inf", "pi", "deg2rad", "rad2deg", "tan", "cot", "sin", "cos",
8
+ "arctan", "round_", "mean", "exp", "isclose", "log10", "sqrt"]
9
+
10
+
11
+ def deg2rad(x: float, /) -> float:
12
+ """Convert angle x from degrees to radians."""
13
+ return math.radians(x)
14
+
15
+
16
+ def rad2deg(x: float, /) -> float:
17
+ """Convert angle x from radians to degrees."""
18
+ return math.degrees(x)
19
+
20
+
21
+ def tan(x: float, /) -> float:
22
+ """Return the tangent of x (measured in degrees)."""
23
+ return math.tan(deg2rad(x))
24
+
25
+
26
+ def cot(x: float, /) -> float:
27
+ """Return the cotangent of x (measured in degrees)."""
28
+ return 1 / tan(x)
29
+
30
+
31
+ def sin(x: float, /) -> float:
32
+ """Return the sine of x (measured in degrees)."""
33
+ return math.sin(deg2rad(x))
34
+
35
+
36
+ def cos(x: float, /) -> float:
37
+ """Return the cosine of x (measured in degrees)."""
38
+ return math.cos(deg2rad(x))
39
+
40
+
41
+ def arctan(x: float, /) -> float:
42
+ """Return the arc tangent (measured in degrees) of x."""
43
+ return rad2deg(math.atan(x))
44
+
45
+
46
+ def round_(ndigits: int | Callable[..., SupportsRound]) -> Callable:
47
+ """A decorator that rounds the result of a callable to a specified number
48
+ of decimal places.
49
+
50
+ The returned value of the callable should support the ``__round__`` dunder
51
+ method and should be a numeric value. ``ndigits`` can either be an int
52
+ which will indicate the number of decimal places to round to or a
53
+ callable. If ``ndigits`` is callable the default decimal places is 2.
54
+
55
+ TypeError is raised when ``ndigits`` is neither an int nor a callable.
56
+ """
57
+
58
+ default_dp = 2
59
+
60
+ def dec(fn) -> Callable[..., float]:
61
+ @functools.wraps(fn)
62
+ def wrapper(*args, **kwargs) -> float:
63
+ dp = ndigits if not callable(ndigits) else default_dp
64
+ res = fn(*args, **kwargs)
65
+ return round(res, ndigits=dp)
66
+
67
+ return wrapper
68
+
69
+ # See if we're being called as @round_ or @round_().
70
+ # We're called with parens.
71
+ if isinstance(ndigits, int):
72
+ return dec
73
+
74
+ # We're called as @round_ without parens.
75
+ if callable(ndigits):
76
+ return dec(ndigits)
77
+
78
+ raise TypeError("ndigits must be an int or a callable")
geolysis/validators.py ADDED
@@ -0,0 +1,54 @@
1
+ import operator
2
+ from typing import Callable, TypeAlias
3
+
4
+
5
+ class _NumberValidator:
6
+ def __init__(self, bound: float,
7
+ compare_op: str,
8
+ compare_fn: Callable,
9
+ exc_type=ValueError,
10
+ err_msg=None) -> None:
11
+ self.bound = bound
12
+ self.compare_op = compare_op
13
+ self.compare_fn = compare_fn
14
+ self.exc_type = exc_type
15
+ self.err_msg = err_msg
16
+
17
+ def __call__(self, fn):
18
+ def wrapper(obj, val):
19
+ if not self.compare_fn(val, self.bound):
20
+ if self.err_msg:
21
+ msg = self.err_msg
22
+ else:
23
+ msg = f"{fn.__name__} must be {self.compare_op} {self.bound}"
24
+ raise self.exc_type(msg)
25
+ return fn(obj, val)
26
+
27
+ return wrapper
28
+
29
+
30
+ Number: TypeAlias = int | float
31
+
32
+
33
+ def lt(val: Number, /, *, exc_type=ValueError, err_msg=None):
34
+ return _NumberValidator(val, "<", operator.lt, exc_type, err_msg)
35
+
36
+
37
+ def le(val: Number, /, *, exc_type=ValueError, err_msg=None):
38
+ return _NumberValidator(val, "<=", operator.le, exc_type, err_msg)
39
+
40
+
41
+ def eq(val: Number, /, *, exc_type=ValueError, err_msg=None):
42
+ return _NumberValidator(val, "==", operator.eq, exc_type, err_msg)
43
+
44
+
45
+ def ne(val: Number, /, *, exc_type=ValueError, err_msg=None):
46
+ return _NumberValidator(val, "!=", operator.ne, exc_type, err_msg)
47
+
48
+
49
+ def ge(val: Number, /, *, exc_type=ValueError, err_msg=None):
50
+ return _NumberValidator(val, ">=", operator.ge, exc_type, err_msg)
51
+
52
+
53
+ def gt(val: Number, /, *, exc_type=ValueError, err_msg=None):
54
+ return _NumberValidator(val, ">", operator.gt, exc_type, err_msg)
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 geolysis
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2024 geolysis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.