Coverage for /usr/lib/python3/dist-packages/sympy/ntheory/egyptian_fraction.py: 13%

87 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1from sympy.core.containers import Tuple 

2from sympy.core.numbers import (Integer, Rational) 

3from sympy.core.singleton import S 

4import sympy.polys 

5 

6from math import gcd 

7 

8 

9def egyptian_fraction(r, algorithm="Greedy"): 

10 """ 

11 Return the list of denominators of an Egyptian fraction 

12 expansion [1]_ of the said rational `r`. 

13 

14 Parameters 

15 ========== 

16 

17 r : Rational or (p, q) 

18 a positive rational number, ``p/q``. 

19 algorithm : { "Greedy", "Graham Jewett", "Takenouchi", "Golomb" }, optional 

20 Denotes the algorithm to be used (the default is "Greedy"). 

21 

22 Examples 

23 ======== 

24 

25 >>> from sympy import Rational 

26 >>> from sympy.ntheory.egyptian_fraction import egyptian_fraction 

27 >>> egyptian_fraction(Rational(3, 7)) 

28 [3, 11, 231] 

29 >>> egyptian_fraction((3, 7), "Graham Jewett") 

30 [7, 8, 9, 56, 57, 72, 3192] 

31 >>> egyptian_fraction((3, 7), "Takenouchi") 

32 [4, 7, 28] 

33 >>> egyptian_fraction((3, 7), "Golomb") 

34 [3, 15, 35] 

35 >>> egyptian_fraction((11, 5), "Golomb") 

36 [1, 2, 3, 4, 9, 234, 1118, 2580] 

37 

38 See Also 

39 ======== 

40 

41 sympy.core.numbers.Rational 

42 

43 Notes 

44 ===== 

45 

46 Currently the following algorithms are supported: 

47 

48 1) Greedy Algorithm 

49 

50 Also called the Fibonacci-Sylvester algorithm [2]_. 

51 At each step, extract the largest unit fraction less 

52 than the target and replace the target with the remainder. 

53 

54 It has some distinct properties: 

55 

56 a) Given `p/q` in lowest terms, generates an expansion of maximum 

57 length `p`. Even as the numerators get large, the number of 

58 terms is seldom more than a handful. 

59 

60 b) Uses minimal memory. 

61 

62 c) The terms can blow up (standard examples of this are 5/121 and 

63 31/311). The denominator is at most squared at each step 

64 (doubly-exponential growth) and typically exhibits 

65 singly-exponential growth. 

66 

67 2) Graham Jewett Algorithm 

68 

69 The algorithm suggested by the result of Graham and Jewett. 

70 Note that this has a tendency to blow up: the length of the 

71 resulting expansion is always ``2**(x/gcd(x, y)) - 1``. See [3]_. 

72 

73 3) Takenouchi Algorithm 

74 

75 The algorithm suggested by Takenouchi (1921). 

76 Differs from the Graham-Jewett algorithm only in the handling 

77 of duplicates. See [3]_. 

78 

79 4) Golomb's Algorithm 

80 

81 A method given by Golumb (1962), using modular arithmetic and 

82 inverses. It yields the same results as a method using continued 

83 fractions proposed by Bleicher (1972). See [4]_. 

84 

85 If the given rational is greater than or equal to 1, a greedy algorithm 

86 of summing the harmonic sequence 1/1 + 1/2 + 1/3 + ... is used, taking 

87 all the unit fractions of this sequence until adding one more would be 

88 greater than the given number. This list of denominators is prefixed 

89 to the result from the requested algorithm used on the remainder. For 

90 example, if r is 8/3, using the Greedy algorithm, we get [1, 2, 3, 4, 

91 5, 6, 7, 14, 420], where the beginning of the sequence, [1, 2, 3, 4, 5, 

92 6, 7] is part of the harmonic sequence summing to 363/140, leaving a 

93 remainder of 31/420, which yields [14, 420] by the Greedy algorithm. 

94 The result of egyptian_fraction(Rational(8, 3), "Golomb") is [1, 2, 3, 

95 4, 5, 6, 7, 14, 574, 2788, 6460, 11590, 33062, 113820], and so on. 

96 

97 References 

98 ========== 

99 

100 .. [1] https://en.wikipedia.org/wiki/Egyptian_fraction 

101 .. [2] https://en.wikipedia.org/wiki/Greedy_algorithm_for_Egyptian_fractions 

102 .. [3] https://www.ics.uci.edu/~eppstein/numth/egypt/conflict.html 

103 .. [4] https://web.archive.org/web/20180413004012/https://ami.ektf.hu/uploads/papers/finalpdf/AMI_42_from129to134.pdf 

104 

105 """ 

106 

107 if not isinstance(r, Rational): 

108 if isinstance(r, (Tuple, tuple)) and len(r) == 2: 

109 r = Rational(*r) 

110 else: 

111 raise ValueError("Value must be a Rational or tuple of ints") 

112 if r <= 0: 

113 raise ValueError("Value must be positive") 

114 

115 # common cases that all methods agree on 

116 x, y = r.as_numer_denom() 

117 if y == 1 and x == 2: 

118 return [Integer(i) for i in [1, 2, 3, 6]] 

119 if x == y + 1: 

120 return [S.One, y] 

121 

122 prefix, rem = egypt_harmonic(r) 

123 if rem == 0: 

124 return prefix 

125 # work in Python ints 

126 x, y = rem.p, rem.q 

127 # assert x < y and gcd(x, y) = 1 

128 

129 if algorithm == "Greedy": 

130 postfix = egypt_greedy(x, y) 

131 elif algorithm == "Graham Jewett": 

132 postfix = egypt_graham_jewett(x, y) 

133 elif algorithm == "Takenouchi": 

134 postfix = egypt_takenouchi(x, y) 

135 elif algorithm == "Golomb": 

136 postfix = egypt_golomb(x, y) 

137 else: 

138 raise ValueError("Entered invalid algorithm") 

139 return prefix + [Integer(i) for i in postfix] 

140 

141 

142def egypt_greedy(x, y): 

143 # assumes gcd(x, y) == 1 

144 if x == 1: 

145 return [y] 

146 else: 

147 a = (-y) % x 

148 b = y*(y//x + 1) 

149 c = gcd(a, b) 

150 if c > 1: 

151 num, denom = a//c, b//c 

152 else: 

153 num, denom = a, b 

154 return [y//x + 1] + egypt_greedy(num, denom) 

155 

156 

157def egypt_graham_jewett(x, y): 

158 # assumes gcd(x, y) == 1 

159 l = [y] * x 

160 

161 # l is now a list of integers whose reciprocals sum to x/y. 

162 # we shall now proceed to manipulate the elements of l without 

163 # changing the reciprocated sum until all elements are unique. 

164 

165 while len(l) != len(set(l)): 

166 l.sort() # so the list has duplicates. find a smallest pair 

167 for i in range(len(l) - 1): 

168 if l[i] == l[i + 1]: 

169 break 

170 # we have now identified a pair of identical 

171 # elements: l[i] and l[i + 1]. 

172 # now comes the application of the result of graham and jewett: 

173 l[i + 1] = l[i] + 1 

174 # and we just iterate that until the list has no duplicates. 

175 l.append(l[i]*(l[i] + 1)) 

176 return sorted(l) 

177 

178 

179def egypt_takenouchi(x, y): 

180 # assumes gcd(x, y) == 1 

181 # special cases for 3/y 

182 if x == 3: 

183 if y % 2 == 0: 

184 return [y//2, y] 

185 i = (y - 1)//2 

186 j = i + 1 

187 k = j + i 

188 return [j, k, j*k] 

189 l = [y] * x 

190 while len(l) != len(set(l)): 

191 l.sort() 

192 for i in range(len(l) - 1): 

193 if l[i] == l[i + 1]: 

194 break 

195 k = l[i] 

196 if k % 2 == 0: 

197 l[i] = l[i] // 2 

198 del l[i + 1] 

199 else: 

200 l[i], l[i + 1] = (k + 1)//2, k*(k + 1)//2 

201 return sorted(l) 

202 

203 

204def egypt_golomb(x, y): 

205 # assumes x < y and gcd(x, y) == 1 

206 if x == 1: 

207 return [y] 

208 xp = sympy.polys.ZZ.invert(int(x), int(y)) 

209 rv = [xp*y] 

210 rv.extend(egypt_golomb((x*xp - 1)//y, xp)) 

211 return sorted(rv) 

212 

213 

214def egypt_harmonic(r): 

215 # assumes r is Rational 

216 rv = [] 

217 d = S.One 

218 acc = S.Zero 

219 while acc + 1/d <= r: 

220 acc += 1/d 

221 rv.append(d) 

222 d += 1 

223 return (rv, r - acc)