Coverage for autodiff_team29/elementaries.py: 99%

159 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-04-15 21:37 +0000

1from typing import Union 

2import numpy as np 

3import math 

4from autodiff_team29 import Node 

5 

6 

7def _check_log_domain_restrictions(x: Node) -> None: 

8 """ 

9 Checks if the value of a given input x is less than or equal to zero and therefore 

10 unable to be used as an input for a logrithmic function. 

11 

12 Parameters 

13 ---------- 

14 x: Node 

15 

16 Returns 

17 ------- 

18 Returns None 

19 if x > 0 

20 

21 Raises 

22 ------ 

23 ValueError 

24 if x <= 0. 

25 

26 Examples 

27 -------- 

28 >>> _check_log_domain_restrictions(Node("1",1,0)) 

29 None 

30 >>> _check_log_domain_restrictions(Node("0",0,0)) 

31 ValueError: Value 0 not valid for a logarithmic function 

32 >>> _check_log_domain_restrictions(Node("-1",-1,0)) 

33 ValueError: Value '-1' not valid for a logarithmic functionNone 

34 

35 """ 

36 if x.value <= 0: 

37 raise ValueError(f"Value '{x.value} 'not valid for a logarithmic function") 

38 

39 

40def _check_sqrt_domain_restrictions(x: Node) -> None: 

41 """ 

42 Checks if the value of a given input x is less zero and therefore 

43 unable to be used as an input for a square root function. 

44 

45 Parameters 

46 ---------- 

47 x : Node 

48 

49 Returns 

50 ------- 

51 None 

52 if x >= 0 

53 

54 Raises 

55 ------ 

56 ValueError 

57 if x < 0. 

58 

59 Examples 

60 -------- 

61 >>> _check_sqrt_domain_restrictions(Node("1",1,0)) 

62 None 

63 >>> _check_sqrt_domain_restrictions(Node("0",0,0)) 

64 None 

65 >>> _check_sqrt_domain_restrictions(Node("-1",-1,0)) 

66 ValueError: Square roots of negative numbers not supported 

67 

68 """ 

69 if x.value < 0: 

70 raise ValueError("Square roots of negative numbers not supported") 

71 

72 

73def _check_tan_domain_restrictions(x: Node) -> None: 

74 """ 

75 Checks if cosine of given value is zero and thus invalid for tangent. 

76 

77 Parameters 

78 ---------- 

79 x : Node 

80 

81 Returns 

82 ------- 

83 None 

84 if cos(x) != 0 

85 

86 Raises 

87 ------ 

88 ValueError 

89 if cos(x) == 0. 

90 

91 Examples 

92 -------- 

93 >>> _check_tan_domain_restrictions(Node("1",1,0)) 

94 None 

95 >>> _check_tan_domain_restrictions(Node("0",0,0)) 

96 None 

97 >>> _check_tan_domain_restrictions(Node("pi",np.pi/2,0)) 

98 ValueError: Value, pi/2, not within domain of tan 

99 

100 """ 

101 if np.cos(x.value) == 0: 

102 raise ValueError(f"Value, {x.value}, not within domain of tan") 

103 

104 

105def _check_arccos_domain_restrictions(x: Node) -> None: 

106 """ 

107 Checks if the value of a given input x is not -1 ≤ x ≤ 1 therefore 

108 unable to be used as an input for the arccos function. 

109 

110 Parameters 

111 ---------- 

112 x : Node 

113 

114 Returns 

115 ------- 

116 None 

117 if -1 ≤ x ≤ 1 

118 

119 Raises 

120 ------ 

121 ValueError 

122 if |x| > 1. 

123 

124 Examples 

125 -------- 

126 >>> _check_arccos_domain_restrictions(Node("1",1,0)) 

127 None 

128 >>> _check_arccos_domain_restrictions(Node("0",0,0)) 

129 None 

130 >>> _check_arccos_domain_restrictions(Node("-5",-1,0)) 

131 ValueError: '-5' is not within the domain [-1,1] of f(x)=arccos(x) 

132 

133 """ 

134 if np.abs(x.value) > 1: 

135 raise ValueError( 

136 f"'{x.value}' is not within the domain [-1,1] of f(x)=arccos(x)" 

137 ) 

138 

139 

140def _check_arcsin_domain_restrictions(x: Node) -> None: 

141 """ 

142 Checks if the value of a given input x is not -1 ≤ x ≤ 1 and therefore 

143 unable to be used as an input for the arcsin function. 

144 

145 Parameters 

146 ---------- 

147 x : Node 

148 

149 Returns 

150 ------- 

151 None 

152 if -1 ≤ x ≤ 1 

153 

154 Raises 

155 ------ 

156 ValueError 

157 if x < 0. 

158 

159 Examples 

160 -------- 

161 >>> _check_arcsin_domain_restrictions(Node("1",1,0)) 

162 None 

163 >>> _check_arcsin_domain_restrictions(Node("0",0,0)) 

164 None 

165 >>> _check_arcsin_domain_restrictions(Node("-5",-1,0)) 

166 ValueError: '-5' is not within the domain [-1,1] of f(x)=arcsin(x) 

167 """ 

168 if np.abs(x.value) > 1: 

169 raise ValueError(f"{x.value} is not within the domain [-1,1] of f(x)=arcsin(x)") 

170 

171 

172def sqrt(x: Union[int, float, Node]) -> Node: 

173 """ 

174 Takes in an instance of the Node class and returns a new node with its symbolic 

175 representation, forward trace, and tangent trace, which are based of the square 

176 root of the input node x. 

177 

178 Parameters 

179 ---------- 

180 x : Union[int, float, Node] 

181 

182 Returns 

183 ------- 

184 Node 

185 

186 Examples 

187 -------- 

188 >>> sqrt(Node("1",1,0)) 

189 Node("sqrt(1)", 1, 0) 

190 >>> sqrt(Node("0",0,0)) 

191 Node("sqrt(0)", 0, 0) 

192 >>> sqrt(Node("-1",-1,0)) 

193 ValueError: Square roots of negative numbers not supported 

194 

195 """ 

196 symbolic_representation = "sqrt({})".format(str(x)) 

197 if Node._check_node_exists(symbolic_representation): 

198 return Node._get_existing_node(symbolic_representation) 

199 

200 x = Node._convert_numeric_type_to_node(x) 

201 

202 _check_sqrt_domain_restrictions(x) 

203 

204 forward_trace = np.sqrt(x.value) 

205 tangent_trace = x.derivative / (2 * np.sqrt(x.value)) 

206 new_node = Node(symbolic_representation, forward_trace, tangent_trace) 

207 

208 return new_node 

209 

210 

211def ln(x: Union[int, float, Node]) -> Node: 

212 """ 

213 Takes in an instance of the Node class and returns a new node with its symbolic 

214 representation, forward trace, and tangent trace, which are based on the natural log 

215 of the input node x. 

216 

217 Parameters 

218 ---------- 

219 x : Union[int, float, Node] 

220 

221 Returns 

222 ------- 

223 Node 

224 

225 Examples 

226 -------- 

227 >>> ln(Node("1",1,0)) 

228 Node("ln(1)", 0, 1) 

229 >>> ln(Node("0",0,0)) 

230 ValueError: Value 0 not valid for a logarithmic function 

231 >>> ln(-1) 

232 ValueError: Value '-1' not valid for a logarithmic functionNone 

233 

234 """ 

235 symbolic_representation = "ln({})".format(str(x)) 

236 if Node._check_node_exists(symbolic_representation): 

237 return Node._get_existing_node(symbolic_representation) 

238 

239 x = Node._convert_numeric_type_to_node(x) 

240 

241 _check_log_domain_restrictions(x) 

242 

243 forward_trace = np.log(x.value) 

244 tangent_trace = 1 / x.value 

245 new_node = Node(symbolic_representation, forward_trace, tangent_trace) 

246 

247 return new_node 

248 

249 

250def log(x: Union[int, float, Node], base: Union[int, float, Node] = np.e) -> Node: 

251 """ 

252 Takes in an instance of the Node class and returns a new node with its symbolic 

253 representation, forward trace, and tangent trace, which are based on the logarithm 

254 of the input node x and the provided base. 

255 

256 Parameters 

257 ---------- 

258 x : Union[int, float, Node] 

259 

260 base : Union[int, float] 

261 The desired base of the logorithm. Must be an integer or float greater than 1. 

262 

263 Returns 

264 ------- 

265 Node 

266 

267 Examples 

268 -------- 

269 >>> log(Node("1",1,0), 10) 

270 Node("log10(1)", 0, 0.4343) 

271 >>> log(Node("1",1,0), 2) 

272 Node("log2(1)", 0, 1.4427) 

273 >>> log(Node("0",0,0)) 

274 ValueError: Value 0 not valid for a logarithmic function 

275 >>> log(Node("-1",-1,0)) 

276 ValueError: Value -1 not valid for a logarithmic function 

277 

278 """ 

279 if not base > 1: 

280 raise ValueError("Base must be greater than 1") 

281 

282 symbolic_representation = f"log{str(base)}({str(x)})" 

283 if Node._check_node_exists(symbolic_representation): 

284 return Node._get_existing_node(symbolic_representation) 

285 

286 x = Node._convert_numeric_type_to_node(x) 

287 

288 _check_log_domain_restrictions(x) 

289 

290 forward_trace = math.log(x.value, base) 

291 tangent_trace = 1 / (x.value * np.log(base)) 

292 new_node = Node(symbolic_representation, forward_trace, tangent_trace) 

293 

294 return new_node 

295 

296 

297def exp(x: Union[int, float, Node]) -> Node: 

298 """ 

299 Takes in an instance of the Node class and returns a new node with its symbolic 

300 representation, forward trace, and tangent trace, which are based on the exponential 

301 value of the input node x. 

302 

303 Parameters 

304 ---------- 

305 x : Union[int, float, Node] 

306 

307 Returns 

308 ------- 

309 Node 

310 

311 Examples 

312 -------- 

313 >>> exp(Node("1",1,0)) 

314 Node("exp(1)", 2.7183, 0) 

315 >>> exp(Node("0",0,0)) 

316 Node("exp(0)", 1, 0) 

317 >>> exp(Node("-1",-1,0)) 

318 Node("exp(-1)", 0.3679, 0) 

319 

320 """ 

321 symbolic_representation = "exp({})".format(str(x)) 

322 if Node._check_node_exists(symbolic_representation): 

323 return Node._get_existing_node(symbolic_representation) 

324 

325 x = Node._convert_numeric_type_to_node(x) 

326 

327 forward_trace = np.exp(x.value) 

328 tangent_trace = x.derivative * forward_trace 

329 new_node = Node(symbolic_representation, forward_trace, tangent_trace) 

330 

331 return new_node 

332 

333 

334def sin(x: Union[int, float, Node]) -> Node: 

335 """ 

336 Takes in an instance of the Node class and returns a new node with its symbolic 

337 representation, forward trace, and tangent trace, which are based on the sine 

338 of the input node x. 

339 

340 Parameters 

341 ---------- 

342 x : Union[int, float, Node] 

343 

344 Returns 

345 ------- 

346 Node 

347 

348 Examples 

349 -------- 

350 >>> sin(Node("1",1,0)) 

351 Node("sin(1)", 0.8415, 0) 

352 >>> sin(Node("0",0,0)) 

353 Node("sin(0)", 0, 0) 

354 >>> sin(Node("-1",-1,0)) 

355 Node("sin(-1)", -0.8415, 0) 

356 

357 """ 

358 symbolic_representation = "sin({})".format(str(x)) 

359 if Node._check_node_exists(symbolic_representation): 

360 return Node._get_existing_node(symbolic_representation) 

361 

362 x = Node._convert_numeric_type_to_node(x) 

363 

364 forward_trace = np.sin(x.value) 

365 tangent_trace = np.cos(x.value) * x.derivative 

366 new_node = Node(symbolic_representation, forward_trace, tangent_trace) 

367 

368 return new_node 

369 

370 

371def cos(x: Union[int, float, Node]) -> Node: 

372 """ 

373 Takes in an instance of the Node class and returns a new node with its symbolic 

374 representation, forward trace, and tangent trace, which are based on the cosine 

375 of the input node x. 

376 

377 Parameters 

378 ---------- 

379 x : Union[int, float, Node] 

380 

381 Returns 

382 ------- 

383 Node 

384 

385 Examples 

386 -------- 

387 >>> cos(Node("1",1,0)) 

388 Node("cos(1)", 0.5403, 0) 

389 >>> cos(Node("0",1,0)) 

390 Node("cos(0)", 1, 0) 

391 >>> cos(Node("-1",-1,0)) 

392 Node("cos(-1)", -0.5403, 0) 

393 

394 """ 

395 symbolic_representation = "cos({})".format(str(x)) 

396 if Node._check_node_exists(symbolic_representation): 

397 return Node._get_existing_node(symbolic_representation) 

398 

399 x = Node._convert_numeric_type_to_node(x) 

400 

401 forward_trace = np.cos(x.value) 

402 tangent_trace = -np.sin(x.value) * x.derivative 

403 new_node = Node(symbolic_representation, forward_trace, tangent_trace) 

404 

405 return new_node 

406 

407 

408def tan(x: Union[int, float, Node]) -> Node: 

409 """ 

410 Takes in an instance of the Node class and returns a new node with its symbolic 

411 representation, forward trace, and tangent trace, which are based on the tangent 

412 of the input node x. 

413 

414 Parameters 

415 ---------- 

416 x : Union[int, float, Node] 

417 

418 Returns 

419 ------- 

420 Node 

421 

422 Examples 

423 -------- 

424 >>> tan(Node("1",1,0)) 

425 Node("tan(1)", 1.557, 0) 

426 >>> tan(Node("0",0,0)) 

427 Node("tan(0)", 0, 0) 

428 >>> tan(Node("-1",-1,0)) 

429 Node("tan(-1)", -1.557, 0) 

430 

431 """ 

432 symbolic_representation = "tan({})".format(str(x)) 

433 if Node._check_node_exists(symbolic_representation): 

434 return Node._get_existing_node(symbolic_representation) 

435 

436 x = Node._convert_numeric_type_to_node(x) 

437 

438 _check_tan_domain_restrictions(x) 

439 

440 forward_trace = np.tan(x.value) 

441 tangent_trace = x.derivative / (np.cos(x.value) ** 2) 

442 new_node = Node(symbolic_representation, forward_trace, tangent_trace) 

443 

444 return new_node 

445 

446 

447def arcsin(x: Union[int, float, Node]) -> Node: 

448 """ 

449 Takes in an instance of the Node class and returns a new node with its symbolic 

450 representation, forward trace, and tangent trace, which are based on the arcsin 

451 of the input node x. 

452 

453 Parameters 

454 ---------- 

455 x : Union[int, float, Node] 

456 

457 Returns 

458 ------- 

459 Node 

460 

461 Examples 

462 -------- 

463 >>> arcsin(Node("1",1,0)) 

464 Node("arcsin(1)", 1.5708, 0) 

465 >>> arcsin(Node("0",0,0)) 

466 Node("arcsin(0)", 0, 0) 

467 >>> arcsin(Node("-1",-1,0)) 

468 Node("arcsin(-1)", -1.5708, 0) 

469 

470 """ 

471 symbolic_representation = "arcsin({})".format(str(x)) 

472 if Node._check_node_exists(symbolic_representation): 

473 return Node._get_existing_node(symbolic_representation) 

474 

475 x = Node._convert_numeric_type_to_node(x) 

476 

477 _check_arcsin_domain_restrictions(x) 

478 

479 forward_trace = np.arcsin(x.value) 

480 tangent_trace = x.derivative / np.sqrt(1 - x.value ** 2) 

481 new_node = Node(symbolic_representation, forward_trace, tangent_trace) 

482 

483 return new_node 

484 

485 

486def arccos(x: Union[int, float, Node]) -> Node: 

487 """ 

488 Takes in an instance of the Node class and returns a new node with its symbolic 

489 representation, forward trace, and tangent trace, which are based on the arccos 

490 of the input node x. 

491 

492 Parameters 

493 ---------- 

494 x : Union[int, float, Node] 

495 

496 Returns 

497 ------- 

498 Node 

499 

500 Examples 

501 -------- 

502 >>> arccos(Node("1",1,0)) 

503 Node("arccos(1)", 3.1416, 0) 

504 >>> arccos(Node("0",0,0)) 

505 Node("arccos(0)", 1.5708, 0) 

506 >>> arccos(Node("-1",-1,0)) 

507 Node("arccos(-1)", -3.1416, 0) 

508 

509 """ 

510 symbolic_representation = "arccos({})".format(str(x)) 

511 if Node._check_node_exists(symbolic_representation): 

512 return Node._get_existing_node(symbolic_representation) 

513 

514 x = Node._convert_numeric_type_to_node(x) 

515 

516 _check_arccos_domain_restrictions(x) 

517 

518 forward_trace = np.arccos(x.value) 

519 tangent_trace = -x.derivative / np.sqrt(1 - x.value ** 2) 

520 new_node = Node(symbolic_representation, forward_trace, tangent_trace) 

521 

522 return new_node 

523 

524 

525def arctan(x: Union[int, float, Node]) -> Node: 

526 """ 

527 Takes in an instance of the Node class and returns a new node with its symbolic 

528 representation, forward trace, and tangent trace, which are based on the arctan 

529 of the input node x. 

530 

531 Parameters 

532 ---------- 

533 x : Union[int, float, Node] 

534 

535 Returns 

536 ------- 

537 Node 

538 

539 Examples 

540 -------- 

541 >>> arctan(Node("1",1,0)) 

542 Node("arctan(1)", 0.7854, 0) 

543 >>> arctan(Node("0",0,0)) 

544 Node("arctan(0)", 0, 0) 

545 >>> arctan(Node("-1",-1,0)) 

546 Node("arctan(-1)", -0.7854, 0) 

547 

548 """ 

549 symbolic_representation = "arctan({})".format(str(x)) 

550 if Node._check_node_exists(symbolic_representation): 

551 return Node._get_existing_node(symbolic_representation) 

552 

553 x = Node._convert_numeric_type_to_node(x) 

554 

555 forward_trace = np.arctan(x.value) 

556 tangent_trace = x.derivative / (1 + x.value ** 2) 

557 new_node = Node( 

558 symbolic_representation, 

559 forward_trace, 

560 tangent_trace, 

561 ) 

562 

563 return new_node 

564 

565 

566def power(base: Union[int, float, Node], exponent: Union[int, float, Node]) -> Node: 

567 """ 

568 Takes in an instance of the Node class and returns a new node with its symbolic 

569 representation, forward trace, and tangent trace, which are based on the power 

570 of the input node x. 

571 

572 Parameters 

573 ---------- 

574 base : Union[int, float, Node] 

575 exponent : Union[int, float, Node] 

576 

577 Returns 

578 ------- 

579 Node 

580 

581 Examples 

582 -------- 

583 >>> power(3,2) 

584 Node("3**2", 9, 0) 

585 

586 """ 

587 symbolic_representation = f"({base}**{exponent})" 

588 if Node._check_node_exists(symbolic_representation): 

589 return Node._get_existing_node(symbolic_representation) 

590 

591 base = Node._convert_numeric_type_to_node(base) 

592 

593 return base ** exponent 

594 

595 

596def sinh(x: Union[int, float, Node]) -> Node: 

597 """ 

598 Takes in an instance of the Node class and returns a new node with its symbolic 

599 representation, forward trace, and tangent trace, which are based on the sinh 

600 of the input node x. 

601 

602 Parameters 

603 ---------- 

604 x : Union[int, float, Node] 

605 

606 Returns 

607 ------- 

608 Node 

609 

610 

611 Examples 

612 -------- 

613 >>> sinh(1) 

614 Node("sinh(1)", 1.1752011936438014, 0) 

615 

616 """ 

617 symbolic_representation = f"sinh({x})" 

618 if Node._check_node_exists(symbolic_representation): 

619 return Node._get_existing_node(symbolic_representation) 

620 

621 x = Node._convert_numeric_type_to_node(x) 

622 

623 forward_trace = np.sinh(x.value) 

624 tangent_trace = np.cosh(x.value) * x.derivative 

625 new_node = Node(symbolic_representation, forward_trace, tangent_trace) 

626 

627 return new_node 

628 

629 

630def cosh(x: Union[int, float, Node]) -> Node: 

631 """ 

632 Takes in an instance of the Node class and returns a new node with its symbolic 

633 representation, forward trace, and tangent trace, which are based on the sinh 

634 of the input node x. 

635 

636 Parameters 

637 ---------- 

638 x : Union[int, float, Node] 

639 

640 Returns 

641 ------- 

642 Node 

643 

644 Examples 

645 -------- 

646 >>> cosh(1) 

647 Node("cosh(1)", 1.5430806348152437, 0) 

648 

649 """ 

650 symbolic_representation = f"cosh({x})" 

651 if Node._check_node_exists(symbolic_representation): 

652 return Node._get_existing_node(symbolic_representation) 

653 

654 x = Node._convert_numeric_type_to_node(x) 

655 

656 forward_trace = np.cosh(x.value) 

657 tangent_trace = np.sinh(x.value) * x.derivative 

658 new_node = Node(symbolic_representation, forward_trace, tangent_trace) 

659 

660 return new_node 

661 

662 

663def tanh(x: Union[int, float, Node]) -> Node: 

664 """ 

665 Takes in an instance of the Node class and returns a new node with its symbolic 

666 representation, forward trace, and tangent trace, which are based on the 

667 of the input node x. 

668 

669 Parameters 

670 ---------- 

671 x : Union[int, float, Node] 

672 

673 Returns 

674 ------- 

675 Node 

676 

677 

678 Examples 

679 -------- 

680 >>> tanh(1) 

681 Node("tanh(1)", 0.76159415595, 0) 

682 

683 """ 

684 symbolic_representation = f"tanh({x})" 

685 if Node._check_node_exists(symbolic_representation): 

686 return Node._get_existing_node(symbolic_representation) 

687 

688 x = Node._convert_numeric_type_to_node(x) 

689 

690 forward_trace = np.tanh(x.value) 

691 tangent_trace = (1 - np.tanh(x.value) ** 2) * x.derivative 

692 new_node = Node(symbolic_representation, forward_trace, tangent_trace) 

693 

694 return new_node 

695 

696 

697def logistic(x: Union[int, float, Node]) -> Node: 

698 """ 

699 Takes in an instance of the Node class and returns a new node with its symbolic 

700 representation, forward trace, and tangent trace, which are based on the 

701 of the input node x. 

702 

703 Parameters 

704 ---------- 

705 x : Union[int, float, Node] 

706 

707 Returns 

708 ------- 

709 Node 

710 

711 Examples 

712 -------- 

713 >>> logistic(1) 

714 Node("logistic(1)", 1.1752011936438014, 0) 

715 

716 """ 

717 symbolic_representation = f"logistic({x})" 

718 if Node._check_node_exists(symbolic_representation): 

719 return Node._get_existing_node(symbolic_representation) 

720 

721 x = Node._convert_numeric_type_to_node(x) 

722 

723 forward_trace = np.exp(-np.logaddexp(0, -x.value)) 

724 tangent_trace = ( 

725 (np.exp(-np.logaddexp(0, -x.value))) 

726 * (1 - np.exp(-np.logaddexp(0, -x.value))) 

727 * x.derivative 

728 ) 

729 new_node = Node(symbolic_representation, forward_trace, tangent_trace) 

730 

731 return new_node