Coverage for /home/runner/work/AutoDiff/AutoDiff/autodiff/dual.py: 100%


Generated by Amelia Li for AutoDiff. (GitHub Profile)

92 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-07 04:22 +0000

1# File : dual.py 

2# Description: Dual class to store dual numbers and overload operators and  

3# functions to perform basic artihmetic 

4import numpy as np 

5 

6class Dual(): 

7 """Dual number implementation to perform basic arithmetic and geometric operations.""" 

8 

9 _supported_scalars = (int, float) 

10 

11 def __init__(self, real, dual = 1.0): 

12 """ 

13 Initialize a dual number based on inputs 'real' and 'dual'. 

14 

15 Parameters 

16 ---------- 

17 real : integer or float 

18 Input number to initialize the real part of a dual number. 

19  

20 dual : integer or float 

21 Input number to initialize the dual part of a dual number. 

22 

23 """ 

24 self.real = real 

25 self.dual = dual 

26 

27 ### Elementary Functions ### 

28 def __add__(self, other): 

29 """ 

30 Compute the addition of two dual numbers or of one dual number and one real number. 

31 

32 Parameters 

33 ---------- 

34 other : Dual, Scalar 

35 Input number which is added to a dual number. 

36 

37 Returns 

38 ------- 

39 Dual 

40 The method returns the value of adding two dual numbers or one dual number and one real number. 

41  

42 Raises 

43 ------ 

44 TypeError 

45 This method raises a `TypeError` if the type of input number 'other' is not supported. 

46 

47 """ 

48 # check if other is of a supported type 

49 if not isinstance(other, (*self._supported_scalars, Dual)): 

50 raise TypeError(f"Unsupported type '{type(other)}'") 

51 if isinstance(other, self._supported_scalars): 

52 # scalar 

53 return Dual(other + self.real, self.dual) 

54 else: 

55 # dual 

56 return Dual(self.real + other.real, self.dual + other.dual) 

57 

58 def __radd__(self, other): 

59 """ 

60 Overload the addition function to compute the addition of two real numbers. 

61 

62 Parameters 

63 ---------- 

64 other : Scalar 

65 Input number which is added to a real number. 

66 

67 Returns 

68 ------- 

69 Dual 

70 The method returns the value of adding two real numbers. 

71  

72 """ 

73 return self.__add__(other) 

74 

75 def __sub__(self, other): 

76 """ 

77 Compute the subtraction of 'other' dual number from 'self' dual number. 

78 

79 Parameters 

80 ---------- 

81 other : Dual, Scalar 

82 Input number which is subtracted from a dual number. 

83 

84 Returns 

85 ------- 

86 Dual 

87 The method returns the resulting value of subtaction. 

88  

89 Raises 

90 ------ 

91 TypeError 

92 This method raises a `TypeError` if the type of input number other is not supported. 

93 

94 """ 

95 # check if other is of a supported type 

96 if not isinstance(other, (*self._supported_scalars, Dual)): 

97 raise TypeError(f"Unsupported type '{type(other)}'") 

98 if isinstance(other, self._supported_scalars): 

99 # scalar 

100 return Dual(self.real - other, self.dual) 

101 else: 

102 # dual 

103 return Dual(self.real - other.real, self.dual - other.dual) 

104 

105 def __rsub__(self, other): 

106 """ 

107 Compute the subtraction of 'self' dual number from 'other' dual number. 

108 

109 Parameters 

110 ---------- 

111 other : Dual, Scalar 

112 Input number which is subtracted by a dual number. 

113 

114 Returns 

115 ------- 

116 Dual 

117 The method returns the resulting value of subtaction. 

118 

119 """ 

120 return -self + other 

121 

122 def __mul__(self, other): 

123 """ 

124 Compute the multiplication of two dual numbers or of one dual number and one real number. 

125 

126 Parameters 

127 ---------- 

128 other : Dual, Scalar 

129 Input number which is multiplied by a dual number. 

130 

131 Returns 

132 ------- 

133 Dual 

134 The method returns the value of multiplying two dual numbers or of one dual number and one real number. 

135  

136 Raises 

137 ------ 

138 TypeError 

139 This method raises a `TypeError` if the type of input number other is not supported. 

140 

141 """ 

142 # check if other is of a supported type 

143 if not isinstance(other, (*self._supported_scalars, Dual)): 

144 raise TypeError(f"Unsupported type '{type(other)}'") 

145 if isinstance(other, self._supported_scalars): 

146 # scalar 

147 return Dual(other * self.real, other * self.dual) 

148 else: 

149 # dual 

150 real = self.real * other.real 

151 dual = self.real * other.dual + self.dual * other.real 

152 return Dual(real, dual) 

153 

154 def __rmul__(self, other): 

155 """ 

156 Overload the multiplication function to compute the multiplication of two real numbers. 

157 

158 Parameters 

159 ---------- 

160 other : Scalar 

161 Input number which is multiplied by a real number. 

162 

163 Returns 

164 ------- 

165 Dual 

166 The method returns the value of multiplying two real numbers. 

167  

168 """ 

169 return self.__mul__(other) 

170 

171 def __truediv__(self, other): 

172 """ 

173 Compute the division of 'self' dual number by 'other' dual number. 

174 

175 Parameters 

176 ---------- 

177 other : Dual, Scalar 

178 Input number which divides a dual number. 

179 

180 Returns 

181 ------- 

182 Dual 

183 The method returns the value of the division. 

184 

185 """ 

186 return self * other ** (-1) 

187 

188 def __rtruediv__(self, other): 

189 """ 

190 Overload the division function to compute the divition of one real number by another real number. 

191 

192 Parameters 

193 ---------- 

194 other : Scalar 

195 Input number which divides a real number. 

196 

197 Returns 

198 ------- 

199 Dual 

200 The method returns the value of dividing one real number by another real number. 

201  

202 """ 

203 return other * self ** (-1) 

204 

205 def __neg__(self): 

206 """ 

207 Compute the negation of one dual number. 

208 

209 Returns 

210 ------- 

211 Dual 

212 The method returns the value of the negation of one dual number. 

213 

214 """ 

215 return Dual(-self.real, -self.dual) 

216 

217 def __pow__(self, other): 

218 """ 

219 Compute the exponentiation of raising one dual number to the power of another dual number or of one real number. 

220 

221 Parameters 

222 ---------- 

223 other : Dual, Scalar 

224 Input exponent to which the base dual number will be raised. 

225 

226 Returns 

227 ------- 

228 Dual 

229 The method returns the value of raising one dual number to the power of another dual number or of one real number. 

230  

231 Raises 

232 ------ 

233 TypeError 

234 This method raises a `TypeError` if the type of input exponent other is not supported. 

235 

236 """ 

237 # check if other is of supported type 

238 if not isinstance(other, (*self._supported_scalars, Dual)): 

239 raise TypeError(f"Unsupported type '{type(other)}'") 

240 if isinstance(other, self._supported_scalars): 

241 # scalar 

242 real = self.real ** other 

243 dual = self.dual * other * (self.real ** (other - 1)) 

244 return Dual(real, dual) 

245 else: 

246 # dual 

247 real = self.real ** other.real 

248 dual = other.real * self.dual * self.real ** (other.real - 1) + np.log(self.real) * other.dual * self.real ** other.real 

249 return Dual(real, dual) 

250 

251 def __rpow__(self, other): 

252 """ 

253 Overload the exponentiation function to compute the exponentiation of raising a real number to the power of another real  

254 number. 

255 

256 Parameters 

257 ---------- 

258 other : Scalar 

259 Input exponent to which the base real number will be raised. 

260 

261 Returns 

262 ------- 

263 Dual 

264 The method returns the value of raising one real number to the power of another real number. 

265  

266 Raises 

267 ------ 

268 TypeError 

269 This method raises a `TypeError` if the type of input exponent other is not supported. 

270 

271 """ 

272 # check if other is of supported type 

273 if not isinstance(other, self._supported_scalars): 

274 raise TypeError(f"Unsupported type '{type(other)}'") 

275 real = other ** self.real 

276 dual = (other ** self.real) * np.log(other) * self.dual 

277 return Dual(real, dual) 

278 

279 ### Square Root Function ### 

280 def sqrt(self): 

281 """ 

282 Compute the square root of one dual number. 

283 

284 Returns 

285 ------- 

286 Dual 

287 The method returns the square root of one dual number. 

288  

289 Raises 

290 ------ 

291 ValueError 

292 This method raises a `ValueError` if the value of input dual number is less than zero. 

293 

294 """ 

295 # check that the real component of the dual number is greater than or equal to 0. 

296 if self.real < 0: 

297 raise ValueError("Cannot square root: real part of dual number is lesser than 0.") 

298 return Dual(self.real ** (1/2), self.dual * ((1/2) * (self.real ** (-1/2)))) 

299 

300 ### Exponential Function ###  

301 def exp(self): 

302 """ 

303 Compute the exponentiation of raising the natural number to the power of one dual number. 

304 

305 Returns 

306 ------- 

307 Dual 

308 The method returns the value of raising the natural number to the power of one dual number. 

309 

310 """ 

311 return Dual(np.exp(self.real), np.exp(self.real) * self.dual) 

312 

313 ### Logarithmic Function ### 

314 def log(self, base): 

315 """ 

316 Compute the logarithm to find the power to which the input base must be raised to yield the given dual number. 

317 

318 Parameters 

319 ---------- 

320 base : Dual, Scalar 

321 Input base which is raised to yield a given dual number. 

322 

323 Returns 

324 ------- 

325 Dual 

326 The method returns the value of the exponent to which the input base must be raised to yield the given dual number. 

327  

328 Raises 

329 ------ 

330 TypeError 

331 This method raises a `TypeError` if the type of input base number is not supported. 

332  

333 ValueError 

334 This method raises a `ValueError` if the real part of the dual number or the value of input base is less than zero. 

335 

336 """ 

337 # check if base is of supported type 

338 if not isinstance(base, self._supported_scalars): 

339 raise TypeError(f"Unsupported base type '{type(base)}'") 

340 # check that the base is above 0. 

341 if base <= 0: 

342 raise ValueError("Cannot log: Base is lesser than or equal to 0.") 

343 # check that the real component of the dual number is above 0. 

344 if self.real <= 0: 

345 raise ValueError("Cannot log: Real part of the dual number is lesser than or equal to 0.") 

346 return Dual(np.log(self.real) / np.log(base), 1 / (np.log(base) * self.real) * self.dual) 

347 

348 ### Logistic Function ### 

349 def standard_logistic(self): 

350 """ 

351 Compute the value of the standard logistic function with the given dual number as input parameter. 

352 

353 Returns 

354 ------- 

355 Dual 

356 The method returns the value of the standard logistic function with the given dual number as input parameter. 

357 

358 """ 

359 return 1 / (1 + Dual.exp(-self)) 

360 

361 ### Trigonometric Functions ###  

362 def sin(self): 

363 """ 

364 Compute the value of the sine function with the given dual number as input parameter. 

365 

366 Returns 

367 ------- 

368 Dual 

369 The method returns the value of the sine function with the given dual number as input parameter. 

370 

371 """ 

372 return Dual(np.sin(self.real), np.cos(self.real) * self.dual) 

373 

374 def cos(self): 

375 """ 

376 Compute the value of the cosine function with the given dual number as input parameter. 

377 

378 Returns 

379 ------- 

380 Dual 

381 The method returns the value of the cosine function with the given dual number as input parameter. 

382 

383 """ 

384 return Dual(np.cos(self.real), np.sin(self.real) * -self.dual) 

385 

386 def tan(self): 

387 """ 

388 Compute the value of the tangent function with the given dual number as input parameter. 

389 

390 Returns 

391 ------- 

392 Dual 

393 The method returns the value of the tangent function with the given dual number as input parameter. 

394 

395 """ 

396 return Dual(np.tan(self.real), self.dual / (np.cos(self.real) ** 2)) 

397 

398 ### Inverse Trigonometric Functions ### 

399 def arcsin(self): 

400 """ 

401 Compute the value of the arcsine function with the given dual number as input parameter. 

402 

403 Returns 

404 ------- 

405 Dual 

406 The method returns the value of the arcsine function with the given dual number as input parameter. 

407  

408 Raises 

409 ------ 

410 ValueError 

411 This method raises a `ValueError` if the real part of the dual number is smaller than -1 or greater than 1. 

412 

413 """ 

414 # check that the real component of the dual number is between -1 and 1. 

415 if self.real >= 1 or self.real <= -1: 

416 raise ValueError("Cannot arcsin: Real part of dual number is not between -1 and 1.") 

417 return Dual(np.arcsin(self.real), self.dual / np.sqrt(1 - self.real ** 2)) 

418 

419 def arccos(self): 

420 """ 

421 Compute the value of the arccosine function with the given dual number as input parameter. 

422 

423 Returns 

424 ------- 

425 Dual 

426 The method returns the value of the arccosine function with the given dual number as input parameter. 

427  

428 Raises 

429 ------ 

430 ValueError 

431 This method raises a `ValueError` if the real part of the dual number is smaller than -1 or greater than 1. 

432 

433 """ 

434 # check that the real component of the dual number is between -1 and 1. 

435 if self.real >= 1 or self.real <= -1: 

436 raise ValueError("Cannot arccos: Real part of dual number is not between -1 and 1.") 

437 return Dual(np.arccos(self.real), - self.dual / np.sqrt(1 - self.real ** 2)) 

438 

439 def arctan(self): 

440 """ 

441 Compute the value of the arctangent function with the given dual number as input parameter. 

442 

443 Returns 

444 ------- 

445 Dual 

446 The method returns the value of the arctangent function with the given dual number as input parameter. 

447  

448 """ 

449 return Dual(np.arctan(self.real), self.dual / ((self.real ** 2) + 1)) 

450 

451 ### Hyperbolic Functions ### 

452 def sinh(self): 

453 """ 

454 Compute the value of the hyperbolic sine function with the given dual number as input parameter. 

455 

456 Returns 

457 ------- 

458 Dual 

459 The method returns the value of the hyperbolic sine function with the given dual number as input parameter. 

460  

461 """ 

462 return (self.exp() - (Dual.exp(-self))) / 2 

463 

464 def cosh(self): 

465 """ 

466 Compute the value of the hyperbolic cosine function with the given dual number as input parameter. 

467 

468 Returns 

469 ------- 

470 Dual 

471 The method returns the value of the hyperbolic cosine function with the given dual number as input parameter. 

472  

473 """ 

474 return (self.exp() + (Dual.exp(-self))) / 2 

475 

476 def tanh(self): 

477 """ 

478 Compute the value of the hyperbolic tangent function with the given dual number as input parameter. 

479 

480 Returns 

481 ------- 

482 Dual 

483 The method returns the value of the hyperbolic tangent function with the given dual number as input parameter. 

484  

485 """ 

486 return self.sinh() / self.cosh()