Coverage for /usr/lib/python3/dist-packages/sympy/discrete/convolutions.py: 12%

103 statements  

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

1""" 

2Convolution (using **FFT**, **NTT**, **FWHT**), Subset Convolution, 

3Covering Product, Intersecting Product 

4""" 

5 

6from sympy.core import S, sympify 

7from sympy.core.function import expand_mul 

8from sympy.discrete.transforms import ( 

9 fft, ifft, ntt, intt, fwht, ifwht, 

10 mobius_transform, inverse_mobius_transform) 

11from sympy.utilities.iterables import iterable 

12from sympy.utilities.misc import as_int 

13 

14 

15def convolution(a, b, cycle=0, dps=None, prime=None, dyadic=None, subset=None): 

16 """ 

17 Performs convolution by determining the type of desired 

18 convolution using hints. 

19 

20 Exactly one of ``dps``, ``prime``, ``dyadic``, ``subset`` arguments 

21 should be specified explicitly for identifying the type of convolution, 

22 and the argument ``cycle`` can be specified optionally. 

23 

24 For the default arguments, linear convolution is performed using **FFT**. 

25 

26 Parameters 

27 ========== 

28 

29 a, b : iterables 

30 The sequences for which convolution is performed. 

31 cycle : Integer 

32 Specifies the length for doing cyclic convolution. 

33 dps : Integer 

34 Specifies the number of decimal digits for precision for 

35 performing **FFT** on the sequence. 

36 prime : Integer 

37 Prime modulus of the form `(m 2^k + 1)` to be used for 

38 performing **NTT** on the sequence. 

39 dyadic : bool 

40 Identifies the convolution type as dyadic (*bitwise-XOR*) 

41 convolution, which is performed using **FWHT**. 

42 subset : bool 

43 Identifies the convolution type as subset convolution. 

44 

45 Examples 

46 ======== 

47 

48 >>> from sympy import convolution, symbols, S, I 

49 >>> u, v, w, x, y, z = symbols('u v w x y z') 

50 

51 >>> convolution([1 + 2*I, 4 + 3*I], [S(5)/4, 6], dps=3) 

52 [1.25 + 2.5*I, 11.0 + 15.8*I, 24.0 + 18.0*I] 

53 >>> convolution([1, 2, 3], [4, 5, 6], cycle=3) 

54 [31, 31, 28] 

55 

56 >>> convolution([111, 777], [888, 444], prime=19*2**10 + 1) 

57 [1283, 19351, 14219] 

58 >>> convolution([111, 777], [888, 444], prime=19*2**10 + 1, cycle=2) 

59 [15502, 19351] 

60 

61 >>> convolution([u, v], [x, y, z], dyadic=True) 

62 [u*x + v*y, u*y + v*x, u*z, v*z] 

63 >>> convolution([u, v], [x, y, z], dyadic=True, cycle=2) 

64 [u*x + u*z + v*y, u*y + v*x + v*z] 

65 

66 >>> convolution([u, v, w], [x, y, z], subset=True) 

67 [u*x, u*y + v*x, u*z + w*x, v*z + w*y] 

68 >>> convolution([u, v, w], [x, y, z], subset=True, cycle=3) 

69 [u*x + v*z + w*y, u*y + v*x, u*z + w*x] 

70 

71 """ 

72 

73 c = as_int(cycle) 

74 if c < 0: 

75 raise ValueError("The length for cyclic convolution " 

76 "must be non-negative") 

77 

78 dyadic = True if dyadic else None 

79 subset = True if subset else None 

80 if sum(x is not None for x in (prime, dps, dyadic, subset)) > 1: 

81 raise TypeError("Ambiguity in determining the type of convolution") 

82 

83 if prime is not None: 

84 ls = convolution_ntt(a, b, prime=prime) 

85 return ls if not c else [sum(ls[i::c]) % prime for i in range(c)] 

86 

87 if dyadic: 

88 ls = convolution_fwht(a, b) 

89 elif subset: 

90 ls = convolution_subset(a, b) 

91 else: 

92 ls = convolution_fft(a, b, dps=dps) 

93 

94 return ls if not c else [sum(ls[i::c]) for i in range(c)] 

95 

96 

97#----------------------------------------------------------------------------# 

98# # 

99# Convolution for Complex domain # 

100# # 

101#----------------------------------------------------------------------------# 

102 

103def convolution_fft(a, b, dps=None): 

104 """ 

105 Performs linear convolution using Fast Fourier Transform. 

106 

107 Parameters 

108 ========== 

109 

110 a, b : iterables 

111 The sequences for which convolution is performed. 

112 dps : Integer 

113 Specifies the number of decimal digits for precision. 

114 

115 Examples 

116 ======== 

117 

118 >>> from sympy import S, I 

119 >>> from sympy.discrete.convolutions import convolution_fft 

120 

121 >>> convolution_fft([2, 3], [4, 5]) 

122 [8, 22, 15] 

123 >>> convolution_fft([2, 5], [6, 7, 3]) 

124 [12, 44, 41, 15] 

125 >>> convolution_fft([1 + 2*I, 4 + 3*I], [S(5)/4, 6]) 

126 [5/4 + 5*I/2, 11 + 63*I/4, 24 + 18*I] 

127 

128 References 

129 ========== 

130 

131 .. [1] https://en.wikipedia.org/wiki/Convolution_theorem 

132 .. [2] https://en.wikipedia.org/wiki/Discrete_Fourier_transform_(general%29 

133 

134 """ 

135 

136 a, b = a[:], b[:] 

137 n = m = len(a) + len(b) - 1 # convolution size 

138 

139 if n > 0 and n&(n - 1): # not a power of 2 

140 n = 2**n.bit_length() 

141 

142 # padding with zeros 

143 a += [S.Zero]*(n - len(a)) 

144 b += [S.Zero]*(n - len(b)) 

145 

146 a, b = fft(a, dps), fft(b, dps) 

147 a = [expand_mul(x*y) for x, y in zip(a, b)] 

148 a = ifft(a, dps)[:m] 

149 

150 return a 

151 

152 

153#----------------------------------------------------------------------------# 

154# # 

155# Convolution for GF(p) # 

156# # 

157#----------------------------------------------------------------------------# 

158 

159def convolution_ntt(a, b, prime): 

160 """ 

161 Performs linear convolution using Number Theoretic Transform. 

162 

163 Parameters 

164 ========== 

165 

166 a, b : iterables 

167 The sequences for which convolution is performed. 

168 prime : Integer 

169 Prime modulus of the form `(m 2^k + 1)` to be used for performing 

170 **NTT** on the sequence. 

171 

172 Examples 

173 ======== 

174 

175 >>> from sympy.discrete.convolutions import convolution_ntt 

176 >>> convolution_ntt([2, 3], [4, 5], prime=19*2**10 + 1) 

177 [8, 22, 15] 

178 >>> convolution_ntt([2, 5], [6, 7, 3], prime=19*2**10 + 1) 

179 [12, 44, 41, 15] 

180 >>> convolution_ntt([333, 555], [222, 666], prime=19*2**10 + 1) 

181 [15555, 14219, 19404] 

182 

183 References 

184 ========== 

185 

186 .. [1] https://en.wikipedia.org/wiki/Convolution_theorem 

187 .. [2] https://en.wikipedia.org/wiki/Discrete_Fourier_transform_(general%29 

188 

189 """ 

190 

191 a, b, p = a[:], b[:], as_int(prime) 

192 n = m = len(a) + len(b) - 1 # convolution size 

193 

194 if n > 0 and n&(n - 1): # not a power of 2 

195 n = 2**n.bit_length() 

196 

197 # padding with zeros 

198 a += [0]*(n - len(a)) 

199 b += [0]*(n - len(b)) 

200 

201 a, b = ntt(a, p), ntt(b, p) 

202 a = [x*y % p for x, y in zip(a, b)] 

203 a = intt(a, p)[:m] 

204 

205 return a 

206 

207 

208#----------------------------------------------------------------------------# 

209# # 

210# Convolution for 2**n-group # 

211# # 

212#----------------------------------------------------------------------------# 

213 

214def convolution_fwht(a, b): 

215 """ 

216 Performs dyadic (*bitwise-XOR*) convolution using Fast Walsh Hadamard 

217 Transform. 

218 

219 The convolution is automatically padded to the right with zeros, as the 

220 *radix-2 FWHT* requires the number of sample points to be a power of 2. 

221 

222 Parameters 

223 ========== 

224 

225 a, b : iterables 

226 The sequences for which convolution is performed. 

227 

228 Examples 

229 ======== 

230 

231 >>> from sympy import symbols, S, I 

232 >>> from sympy.discrete.convolutions import convolution_fwht 

233 

234 >>> u, v, x, y = symbols('u v x y') 

235 >>> convolution_fwht([u, v], [x, y]) 

236 [u*x + v*y, u*y + v*x] 

237 

238 >>> convolution_fwht([2, 3], [4, 5]) 

239 [23, 22] 

240 >>> convolution_fwht([2, 5 + 4*I, 7], [6*I, 7, 3 + 4*I]) 

241 [56 + 68*I, -10 + 30*I, 6 + 50*I, 48 + 32*I] 

242 

243 >>> convolution_fwht([S(33)/7, S(55)/6, S(7)/4], [S(2)/3, 5]) 

244 [2057/42, 1870/63, 7/6, 35/4] 

245 

246 References 

247 ========== 

248 

249 .. [1] https://www.radioeng.cz/fulltexts/2002/02_03_40_42.pdf 

250 .. [2] https://en.wikipedia.org/wiki/Hadamard_transform 

251 

252 """ 

253 

254 if not a or not b: 

255 return [] 

256 

257 a, b = a[:], b[:] 

258 n = max(len(a), len(b)) 

259 

260 if n&(n - 1): # not a power of 2 

261 n = 2**n.bit_length() 

262 

263 # padding with zeros 

264 a += [S.Zero]*(n - len(a)) 

265 b += [S.Zero]*(n - len(b)) 

266 

267 a, b = fwht(a), fwht(b) 

268 a = [expand_mul(x*y) for x, y in zip(a, b)] 

269 a = ifwht(a) 

270 

271 return a 

272 

273 

274#----------------------------------------------------------------------------# 

275# # 

276# Subset Convolution # 

277# # 

278#----------------------------------------------------------------------------# 

279 

280def convolution_subset(a, b): 

281 """ 

282 Performs Subset Convolution of given sequences. 

283 

284 The indices of each argument, considered as bit strings, correspond to 

285 subsets of a finite set. 

286 

287 The sequence is automatically padded to the right with zeros, as the 

288 definition of subset based on bitmasks (indices) requires the size of 

289 sequence to be a power of 2. 

290 

291 Parameters 

292 ========== 

293 

294 a, b : iterables 

295 The sequences for which convolution is performed. 

296 

297 Examples 

298 ======== 

299 

300 >>> from sympy import symbols, S 

301 >>> from sympy.discrete.convolutions import convolution_subset 

302 >>> u, v, x, y, z = symbols('u v x y z') 

303 

304 >>> convolution_subset([u, v], [x, y]) 

305 [u*x, u*y + v*x] 

306 >>> convolution_subset([u, v, x], [y, z]) 

307 [u*y, u*z + v*y, x*y, x*z] 

308 

309 >>> convolution_subset([1, S(2)/3], [3, 4]) 

310 [3, 6] 

311 >>> convolution_subset([1, 3, S(5)/7], [7]) 

312 [7, 21, 5, 0] 

313 

314 References 

315 ========== 

316 

317 .. [1] https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf 

318 

319 """ 

320 

321 if not a or not b: 

322 return [] 

323 

324 if not iterable(a) or not iterable(b): 

325 raise TypeError("Expected a sequence of coefficients for convolution") 

326 

327 a = [sympify(arg) for arg in a] 

328 b = [sympify(arg) for arg in b] 

329 n = max(len(a), len(b)) 

330 

331 if n&(n - 1): # not a power of 2 

332 n = 2**n.bit_length() 

333 

334 # padding with zeros 

335 a += [S.Zero]*(n - len(a)) 

336 b += [S.Zero]*(n - len(b)) 

337 

338 c = [S.Zero]*n 

339 

340 for mask in range(n): 

341 smask = mask 

342 while smask > 0: 

343 c[mask] += expand_mul(a[smask] * b[mask^smask]) 

344 smask = (smask - 1)&mask 

345 

346 c[mask] += expand_mul(a[smask] * b[mask^smask]) 

347 

348 return c 

349 

350 

351#----------------------------------------------------------------------------# 

352# # 

353# Covering Product # 

354# # 

355#----------------------------------------------------------------------------# 

356 

357def covering_product(a, b): 

358 """ 

359 Returns the covering product of given sequences. 

360 

361 The indices of each argument, considered as bit strings, correspond to 

362 subsets of a finite set. 

363 

364 The covering product of given sequences is a sequence which contains 

365 the sum of products of the elements of the given sequences grouped by 

366 the *bitwise-OR* of the corresponding indices. 

367 

368 The sequence is automatically padded to the right with zeros, as the 

369 definition of subset based on bitmasks (indices) requires the size of 

370 sequence to be a power of 2. 

371 

372 Parameters 

373 ========== 

374 

375 a, b : iterables 

376 The sequences for which covering product is to be obtained. 

377 

378 Examples 

379 ======== 

380 

381 >>> from sympy import symbols, S, I, covering_product 

382 >>> u, v, x, y, z = symbols('u v x y z') 

383 

384 >>> covering_product([u, v], [x, y]) 

385 [u*x, u*y + v*x + v*y] 

386 >>> covering_product([u, v, x], [y, z]) 

387 [u*y, u*z + v*y + v*z, x*y, x*z] 

388 

389 >>> covering_product([1, S(2)/3], [3, 4 + 5*I]) 

390 [3, 26/3 + 25*I/3] 

391 >>> covering_product([1, 3, S(5)/7], [7, 8]) 

392 [7, 53, 5, 40/7] 

393 

394 References 

395 ========== 

396 

397 .. [1] https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf 

398 

399 """ 

400 

401 if not a or not b: 

402 return [] 

403 

404 a, b = a[:], b[:] 

405 n = max(len(a), len(b)) 

406 

407 if n&(n - 1): # not a power of 2 

408 n = 2**n.bit_length() 

409 

410 # padding with zeros 

411 a += [S.Zero]*(n - len(a)) 

412 b += [S.Zero]*(n - len(b)) 

413 

414 a, b = mobius_transform(a), mobius_transform(b) 

415 a = [expand_mul(x*y) for x, y in zip(a, b)] 

416 a = inverse_mobius_transform(a) 

417 

418 return a 

419 

420 

421#----------------------------------------------------------------------------# 

422# # 

423# Intersecting Product # 

424# # 

425#----------------------------------------------------------------------------# 

426 

427def intersecting_product(a, b): 

428 """ 

429 Returns the intersecting product of given sequences. 

430 

431 The indices of each argument, considered as bit strings, correspond to 

432 subsets of a finite set. 

433 

434 The intersecting product of given sequences is the sequence which 

435 contains the sum of products of the elements of the given sequences 

436 grouped by the *bitwise-AND* of the corresponding indices. 

437 

438 The sequence is automatically padded to the right with zeros, as the 

439 definition of subset based on bitmasks (indices) requires the size of 

440 sequence to be a power of 2. 

441 

442 Parameters 

443 ========== 

444 

445 a, b : iterables 

446 The sequences for which intersecting product is to be obtained. 

447 

448 Examples 

449 ======== 

450 

451 >>> from sympy import symbols, S, I, intersecting_product 

452 >>> u, v, x, y, z = symbols('u v x y z') 

453 

454 >>> intersecting_product([u, v], [x, y]) 

455 [u*x + u*y + v*x, v*y] 

456 >>> intersecting_product([u, v, x], [y, z]) 

457 [u*y + u*z + v*y + x*y + x*z, v*z, 0, 0] 

458 

459 >>> intersecting_product([1, S(2)/3], [3, 4 + 5*I]) 

460 [9 + 5*I, 8/3 + 10*I/3] 

461 >>> intersecting_product([1, 3, S(5)/7], [7, 8]) 

462 [327/7, 24, 0, 0] 

463 

464 References 

465 ========== 

466 

467 .. [1] https://people.csail.mit.edu/rrw/presentations/subset-conv.pdf 

468 

469 """ 

470 

471 if not a or not b: 

472 return [] 

473 

474 a, b = a[:], b[:] 

475 n = max(len(a), len(b)) 

476 

477 if n&(n - 1): # not a power of 2 

478 n = 2**n.bit_length() 

479 

480 # padding with zeros 

481 a += [S.Zero]*(n - len(a)) 

482 b += [S.Zero]*(n - len(b)) 

483 

484 a, b = mobius_transform(a, subset=False), mobius_transform(b, subset=False) 

485 a = [expand_mul(x*y) for x, y in zip(a, b)] 

486 a = inverse_mobius_transform(a, subset=False) 

487 

488 return a