Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/cardinal_pythonlib/maths_py.py : 27%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/env python
2# cardinal_pythonlib/maths_py.py
4"""
5===============================================================================
7 Original code copyright (C) 2009-2021 Rudolf Cardinal (rudolf@pobox.com).
9 This file is part of cardinal_pythonlib.
11 Licensed under the Apache License, Version 2.0 (the "License");
12 you may not use this file except in compliance with the License.
13 You may obtain a copy of the License at
15 https://www.apache.org/licenses/LICENSE-2.0
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
23===============================================================================
25**Miscellaneous mathematical functions in pure Python.**
27"""
29import math
30import sys
31from typing import Optional, Sequence, Union
33from cardinal_pythonlib.logs import get_brace_style_log_with_null_handler
35log = get_brace_style_log_with_null_handler(__name__)
38# =============================================================================
39# Mean
40# =============================================================================
42def mean(values: Sequence[Union[int, float, None]]) -> Optional[float]:
43 """
44 Returns the mean of a list of numbers.
46 Args:
47 values: values to mean, ignoring any values that are ``None``
49 Returns:
50 the mean, or ``None`` if :math:`n = 0`
52 """
53 total = 0.0 # starting with "0.0" causes automatic conversion to float
54 n = 0
55 for x in values:
56 if x is not None:
57 total += x
58 n += 1
59 return total / n if n > 0 else None
62# =============================================================================
63# logit
64# =============================================================================
66def safe_logit(p: Union[float, int]) -> Optional[float]:
67 r"""
68 Returns the logit (log odds) of its input probability
70 .. math::
72 \alpha = logit(p) = log(x / (1 - x))
74 Args:
75 p: :math:`p`
77 Returns:
78 :math:`\alpha`, or ``None`` if ``x`` is not in the range [0, 1].
80 """
81 if p > 1 or p < 0:
82 return None # can't take log of negative number
83 if p == 1:
84 return float("inf")
85 if p == 0:
86 return float("-inf")
87 return math.log(p / (1 - p))
90# =============================================================================
91# Rounding
92# =============================================================================
94def normal_round_float(x: float, dp: int = 0) -> float:
95 """
96 Hmpf. Shouldn't need to have to implement this, but...
98 Conventional rounding to integer via the "round half away from zero"
99 method, e.g.
101 .. code-block:: none
103 1.1 -> 1
104 1.5 -> 2
105 1.6 -> 2
106 2.0 -> 2
108 -1.6 -> -2
109 etc.
111 ... or the equivalent for a certain number of decimal places.
113 Note that round() implements "banker's rounding", which is never what
114 we want:
115 - https://stackoverflow.com/questions/33019698/how-to-properly-round-up-half-float-numbers-in-python # noqa
116 """
117 if not math.isfinite(x):
118 return x
119 factor = pow(10, dp)
120 x = x * factor
121 if x >= 0:
122 x = math.floor(x + 0.5)
123 else:
124 x = math.ceil(x - 0.5)
125 x = x / factor
126 return x
129def normal_round_int(x: float) -> int:
130 """
131 Version of :func:`normal_round_float` but guaranteed to return an `int`.
132 """
133 if not math.isfinite(x):
134 raise ValueError("Input to normal_round_int() is not finite")
135 if x >= 0:
136 # noinspection PyTypeChecker
137 return math.floor(x + 0.5)
138 else:
139 # noinspection PyTypeChecker
140 return math.ceil(x - 0.5)
143def round_sf(x: float, n: int = 2) -> float:
144 """
145 Round to a certain number of significant figures.
147 As per https://code.activestate.com/lists/python-tutor/70739/, linked to
148 from
149 https://stackoverflow.com/questions/3410976/how-to-round-a-number-to-significant-figures-in-python
151 Args:
152 x: quantity to round
153 n: number of significant figures
155 Returns:
156 float: x, rounded to n significant figures
158 This does proper rounding:
160 .. code-block:: none
162 round_sf(0.55, 1) # 0.6
163 round_sf(0.549, 1) # 0.5
164 round_sf(-0.55, 1) # -0.6
165 round_sf(-0.549, 1) # -0.5
167 round_sf(0.000123456, 3) # 0.000123
168 round_sf(1234567890000, 3) # 1230000000000
169 round_sf(9876543210000, 3) # 9880000000000
171 """ # noqa
172 y = abs(x)
173 if y <= sys.float_info.min:
174 return 0.0
175 return round(x, int(n - math.ceil(math.log10(y))))
178# =============================================================================
179# Addition, permutation
180# =============================================================================
182def sum_of_integers_in_inclusive_range(a: int, b: int) -> int:
183 """
184 Returns the sum of all integers in the range ``[a, b]``, i.e. from ``a`` to
185 ``b`` inclusive.
187 See
189 - https://math.stackexchange.com/questions/1842152/finding-the-sum-of-numbers-between-any-two-given-numbers
190 """ # noqa
191 return int((b - a + 1) * (a + b) / 2)
194def n_permutations(n: int, k: int) -> int:
195 """
196 Returns the number of permutations of length ``k`` from a list of length
197 ``n``.
199 See https://en.wikipedia.org/wiki/Permutation#k-permutations_of_n.
200 """
201 assert n > 0 and 0 < k <= n
202 return int(math.factorial(n) / math.factorial(n - k))