Hide keyboard shortcuts

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 

3 

4""" 

5=============================================================================== 

6 

7 Original code copyright (C) 2009-2021 Rudolf Cardinal (rudolf@pobox.com). 

8 

9 This file is part of cardinal_pythonlib. 

10 

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 

14 

15 https://www.apache.org/licenses/LICENSE-2.0 

16 

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. 

22 

23=============================================================================== 

24 

25**Miscellaneous mathematical functions in pure Python.** 

26 

27""" 

28 

29import math 

30import sys 

31from typing import Optional, Sequence, Union 

32 

33from cardinal_pythonlib.logs import get_brace_style_log_with_null_handler 

34 

35log = get_brace_style_log_with_null_handler(__name__) 

36 

37 

38# ============================================================================= 

39# Mean 

40# ============================================================================= 

41 

42def mean(values: Sequence[Union[int, float, None]]) -> Optional[float]: 

43 """ 

44 Returns the mean of a list of numbers. 

45 

46 Args: 

47 values: values to mean, ignoring any values that are ``None`` 

48 

49 Returns: 

50 the mean, or ``None`` if :math:`n = 0` 

51 

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 

60 

61 

62# ============================================================================= 

63# logit 

64# ============================================================================= 

65 

66def safe_logit(p: Union[float, int]) -> Optional[float]: 

67 r""" 

68 Returns the logit (log odds) of its input probability 

69 

70 .. math:: 

71 

72 \alpha = logit(p) = log(x / (1 - x)) 

73 

74 Args: 

75 p: :math:`p` 

76 

77 Returns: 

78 :math:`\alpha`, or ``None`` if ``x`` is not in the range [0, 1]. 

79 

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)) 

88 

89 

90# ============================================================================= 

91# Rounding 

92# ============================================================================= 

93 

94def normal_round_float(x: float, dp: int = 0) -> float: 

95 """ 

96 Hmpf. Shouldn't need to have to implement this, but... 

97 

98 Conventional rounding to integer via the "round half away from zero" 

99 method, e.g. 

100 

101 .. code-block:: none 

102 

103 1.1 -> 1 

104 1.5 -> 2 

105 1.6 -> 2 

106 2.0 -> 2 

107 

108 -1.6 -> -2 

109 etc. 

110 

111 ... or the equivalent for a certain number of decimal places. 

112 

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 

127 

128 

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) 

141 

142 

143def round_sf(x: float, n: int = 2) -> float: 

144 """ 

145 Round to a certain number of significant figures. 

146  

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 

150 

151 Args: 

152 x: quantity to round 

153 n: number of significant figures 

154 

155 Returns: 

156 float: x, rounded to n significant figures 

157  

158 This does proper rounding: 

159  

160 .. code-block:: none 

161 

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 

166  

167 round_sf(0.000123456, 3) # 0.000123 

168 round_sf(1234567890000, 3) # 1230000000000 

169 round_sf(9876543210000, 3) # 9880000000000  

170 

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)))) 

176 

177 

178# ============================================================================= 

179# Addition, permutation 

180# ============================================================================= 

181 

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. 

186 

187 See 

188 

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) 

192 

193 

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``. 

198 

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))