Coverage for phenomena/tests/test_tmm.py: 100%

1"""Define the unit tests for the :mod:`colour.phenomena.tmm` module."""

2

3 from __future__ import annotations

4

5 import numpy as np

6

7 from colour.constants import TOLERANCE_ABSOLUTE_TESTS

8 from colour.phenomena.interference import matrix_transfer_tmm

9 from colour.phenomena.tmm import (

10 polarised_light_magnitude_elements,

11 polarised_light_reflection_amplitude,

12 polarised_light_reflection_coefficient,

13 polarised_light_transmission_amplitude,

14 polarised_light_transmission_coefficient,

15 snell_law,

16)

17 from colour.utilities import ignore_numpy_errors

18

19 __author__ = "Colour Developers"

20 __copyright__ = "Copyright 2013 Colour Developers"

21 __license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"

22 __maintainer__ = "Colour Developers"

23 __email__ = "colour-developers@colour-science.org"

24 __status__ = "Production"

25

26 __all__ = [

27 "TestSnellLaw",

28 "TestPolarisedLightMagnitudeElements",

29 "TestPolarisedLightReflectionAmplitude",

30 "TestPolarisedLightReflectionCoefficient",

31 "TestPolarisedLightTransmissionAmplitude",

32 "TestPolarisedLightTransmissionCoefficient",

33 "TestMatrixTransferTmm",

34]

35

36

37 class TestSnellLaw:

38 """

39 Define :func:`colour.phenomena.tmm.snell_law` definition unit tests

40 methods.

41 """

42

43 def test_snell_law(self) -> None:

44 """Test :func:`colour.phenomena.tmm.snell_law` definition."""

45

46 np.testing.assert_allclose(

47 snell_law(1.0, 1.5, 30.0),

48 19.4712206345,

49 atol=TOLERANCE_ABSOLUTE_TESTS,

50 )

51

52 np.testing.assert_allclose(

53 snell_law(1.0, 1.33, 45.0),

54 32.117631278,

55 atol=TOLERANCE_ABSOLUTE_TESTS,

56 )

57

58 np.testing.assert_allclose(

59 snell_law(1.5, 1.0, 19.47),

60 30.0,

61 atol=0.01,

62 )

63

64 # Test normal incidence (0 degrees)

65 np.testing.assert_allclose(

66 snell_law(1.0, 1.5, 0.0),

67 0.0,

68 atol=TOLERANCE_ABSOLUTE_TESTS,

69 )

70

71 def test_n_dimensional_snell_law(self) -> None:

72 """

73 Test :func:`colour.phenomena.tmm.snell_law` definition n-dimensional

74 arrays support.

75 """

76

77 n_1 = 1.0

78 n_2 = 1.5

79 theta_i = 30.0

80 theta_t = snell_law(n_1, n_2, theta_i)

81

82 theta_i = np.tile(theta_i, 6)

83 theta_t = np.tile(theta_t, 6)

84 np.testing.assert_allclose(

85 snell_law(n_1, n_2, theta_i),

86 theta_t,

87 atol=TOLERANCE_ABSOLUTE_TESTS,

88 )

89

90 theta_i = np.reshape(theta_i, (2, 3))

91 theta_t = np.reshape(theta_t, (2, 3))

92 np.testing.assert_allclose(

93 snell_law(n_1, n_2, theta_i),

94 theta_t,

95 atol=TOLERANCE_ABSOLUTE_TESTS,

96 )

97

98 @ignore_numpy_errors

99 def test_nan_snell_law(self) -> None:

100 """Test :func:`colour.phenomena.tmm.snell_law` definition nan support."""

101

102 snell_law(

103 np.array([-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]),

104 1.5,

105 30.0,

106 )

107

108

109 class TestPolarisedLightMagnitudeElements:

110 """

111 Define :func:`colour.phenomena.tmm.polarised_light_magnitude_elements`

112 definition unit tests methods.

113 """

114

115 def test_polarised_light_magnitude_elements(self) -> None:

116 """

117 Test :func:`colour.phenomena.tmm.polarised_light_magnitude_elements`

118 definition.

119 """

120

121 result = polarised_light_magnitude_elements(1.0, 1.5, 0.0, 0.0)

122 np.testing.assert_allclose(result[0], 1.0 + 0j, atol=TOLERANCE_ABSOLUTE_TESTS)

123 np.testing.assert_allclose(result[1], 1.0 + 0j, atol=TOLERANCE_ABSOLUTE_TESTS)

124 np.testing.assert_allclose(result[2], 1.5 + 0j, atol=TOLERANCE_ABSOLUTE_TESTS)

125 np.testing.assert_allclose(result[3], 1.5 + 0j, atol=TOLERANCE_ABSOLUTE_TESTS)

126

127 # Test at 45 degrees

128 result_45 = polarised_light_magnitude_elements(1.0, 1.5, 45.0, 30.0)

129 assert len(result_45) == 4

130

131 def test_n_dimensional_polarised_light_magnitude_elements(self) -> None:

132 """

133 Test :func:`colour.phenomena.tmm.polarised_light_magnitude_elements`

134 definition n-dimensional arrays support.

135 """

136

137 n_1 = 1.0

138 n_2 = 1.5

139 theta_i = 0.0

140 theta_t = 0.0

141 m0, m1, m2, m3 = polarised_light_magnitude_elements(n_1, n_2, theta_i, theta_t)

142

143 theta_i_array = np.tile(theta_i, 6)

144 theta_t_array = np.tile(theta_t, 6)

145 m0_array, m1_array, m2_array, m3_array = polarised_light_magnitude_elements(

146 n_1, n_2, theta_i_array, theta_t_array

147 )

148 np.testing.assert_allclose(

149 m0_array, np.tile(m0, 6), atol=TOLERANCE_ABSOLUTE_TESTS

150 )

151 np.testing.assert_allclose(

152 m1_array, np.tile(m1, 6), atol=TOLERANCE_ABSOLUTE_TESTS

153 )

154 np.testing.assert_allclose(

155 m2_array, np.tile(m2, 6), atol=TOLERANCE_ABSOLUTE_TESTS

156 )

157 np.testing.assert_allclose(

158 m3_array, np.tile(m3, 6), atol=TOLERANCE_ABSOLUTE_TESTS

159 )

160

161 theta_i_array = np.reshape(theta_i_array, (2, 3))

162 theta_t_array = np.reshape(theta_t_array, (2, 3))

163 m0_array, m1_array, m2_array, m3_array = polarised_light_magnitude_elements(

164 n_1, n_2, theta_i_array, theta_t_array

165 )

166 np.testing.assert_allclose(

167 m0_array, np.tile(m0, 6).reshape(2, 3), atol=TOLERANCE_ABSOLUTE_TESTS

168 )

169 np.testing.assert_allclose(

170 m1_array, np.tile(m1, 6).reshape(2, 3), atol=TOLERANCE_ABSOLUTE_TESTS

171 )

172 np.testing.assert_allclose(

173 m2_array, np.tile(m2, 6).reshape(2, 3), atol=TOLERANCE_ABSOLUTE_TESTS

174 )

175 np.testing.assert_allclose(

176 m3_array, np.tile(m3, 6).reshape(2, 3), atol=TOLERANCE_ABSOLUTE_TESTS

177 )

178

179 @ignore_numpy_errors

180 def test_nan_polarised_light_magnitude_elements(self) -> None:

181 """

182 Test :func:`colour.phenomena.tmm.polarised_light_magnitude_elements`

183 definition nan support.

184 """

185

186 polarised_light_magnitude_elements(

187 np.array([-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]),

188 1.5,

189 0.0,

190 0.0,

191 )

192

193

194 class TestPolarisedLightReflectionAmplitude:

195 """

196 Define :func:`colour.phenomena.tmm.polarised_light_reflection_amplitude`

197 definition unit tests methods.

198 """

199

200 def test_polarised_light_reflection_amplitude(self) -> None:

201 """

202 Test :func:`colour.phenomena.tmm.polarised_light_reflection_amplitude`

203 definition.

204 """

205

206 np.testing.assert_allclose(

207 polarised_light_reflection_amplitude(1.0, 1.5, 0.0, 0.0),

208 np.array([-0.2 + 0j, -0.2 + 0j]),

209 atol=TOLERANCE_ABSOLUTE_TESTS,

210 )

211

212 np.testing.assert_allclose(

213 polarised_light_reflection_amplitude(1.0, 1.5, 30.0, 19.47),

214 np.array([-0.24041175 + 0j, -0.15889613 + 0j]),

215 atol=TOLERANCE_ABSOLUTE_TESTS,

216 )

217

218 def test_n_dimensional_polarised_light_reflection_amplitude(self) -> None:

219 """

220 Test :func:`colour.phenomena.tmm.polarised_light_reflection_amplitude`

221 definition n-dimensional arrays support.

222 """

223

224 n_1 = 1.0

225 n_2 = 1.5

226 theta_i = 0.0

227 theta_t = 0.0

228 r = polarised_light_reflection_amplitude(n_1, n_2, theta_i, theta_t)

229

230 theta_i_array = np.tile(theta_i, 6)

231 theta_t_array = np.tile(theta_t, 6)

232 r_array = polarised_light_reflection_amplitude(

233 n_1, n_2, theta_i_array, theta_t_array

234 )

235 np.testing.assert_allclose(

236 r_array,

237 np.tile(r, (6, 1)),

238 atol=TOLERANCE_ABSOLUTE_TESTS,

239 )

240

241 theta_i_array = np.reshape(theta_i_array, (2, 3))

242 theta_t_array = np.reshape(theta_t_array, (2, 3))

243 r_array = polarised_light_reflection_amplitude(

244 n_1, n_2, theta_i_array, theta_t_array

245 )

246 np.testing.assert_allclose(

247 r_array,

248 np.tile(r, (6, 1)).reshape(2, 3, 2),

249 atol=TOLERANCE_ABSOLUTE_TESTS,

250 )

251

252 @ignore_numpy_errors

253 def test_nan_polarised_light_reflection_amplitude(self) -> None:

254 """

255 Test :func:`colour.phenomena.tmm.polarised_light_reflection_amplitude`

256 definition nan support.

257 """

258

259 polarised_light_reflection_amplitude(

260 np.array([-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]),

261 1.5,

262 0.0,

263 0.0,

264 )

265

266

267 class TestPolarisedLightReflectionCoefficient:

268 """

269 Define :func:`colour.phenomena.tmm.polarised_light_reflection_coefficient`

270 definition unit tests methods.

271 """

272

273 def test_polarised_light_reflection_coefficient(self) -> None:

274 """

275 Test :func:`colour.phenomena.tmm.polarised_light_reflection_coefficient`

276 definition.

277 """

278

279 np.testing.assert_allclose(

280 polarised_light_reflection_coefficient(1.0, 1.5, 0.0, 0.0),

281 np.array([0.04 + 0j, 0.04 + 0j]),

282 atol=TOLERANCE_ABSOLUTE_TESTS,

283 )

284

285 # Test that reflectance is always between 0 and 1

286 R = polarised_light_reflection_coefficient(1.0, 1.5, 30.0, 19.47)

287 assert np.all(np.real(R) >= 0)

288 assert np.all(np.real(R) <= 1)

289

290 def test_n_dimensional_polarised_light_reflection_coefficient(self) -> None:

291 """

292 Test :func:`colour.phenomena.tmm.polarised_light_reflection_coefficient`

293 definition n-dimensional arrays support.

294 """

295

296 n_1 = 1.0

297 n_2 = 1.5

298 theta_i = 0.0

299 theta_t = 0.0

300 R = polarised_light_reflection_coefficient(n_1, n_2, theta_i, theta_t)

301

302 theta_i_array = np.tile(theta_i, 6)

303 theta_t_array = np.tile(theta_t, 6)

304 R_array = polarised_light_reflection_coefficient(

305 n_1, n_2, theta_i_array, theta_t_array

306 )

307 np.testing.assert_allclose(

308 R_array,

309 np.tile(R, (6, 1)),

310 atol=TOLERANCE_ABSOLUTE_TESTS,

311 )

312

313 theta_i_array = np.reshape(theta_i_array, (2, 3))

314 theta_t_array = np.reshape(theta_t_array, (2, 3))

315 R_array = polarised_light_reflection_coefficient(

316 n_1, n_2, theta_i_array, theta_t_array

317 )

318 np.testing.assert_allclose(

319 R_array,

320 np.tile(R, (6, 1)).reshape(2, 3, 2),

321 atol=TOLERANCE_ABSOLUTE_TESTS,

322 )

323

324 @ignore_numpy_errors

325 def test_nan_polarised_light_reflection_coefficient(self) -> None:

326 """

327 Test :func:`colour.phenomena.tmm.polarised_light_reflection_coefficient`

328 definition nan support.

329 """

330

331 polarised_light_reflection_coefficient(

332 np.array([-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]),

333 1.5,

334 0.0,

335 0.0,

336 )

337

338

339 class TestPolarisedLightTransmissionAmplitude:

340 """

341 Define :func:`colour.phenomena.tmm.polarised_light_transmission_amplitude`

342 definition unit tests methods.

343 """

344

345 def test_polarised_light_transmission_amplitude(self) -> None:

346 """

347 Test :func:`colour.phenomena.tmm.polarised_light_transmission_amplitude`

348 definition.

349 """

350

351 np.testing.assert_allclose(

352 polarised_light_transmission_amplitude(1.0, 1.5, 0.0, 0.0),

353 np.array([0.8 + 0j, 0.8 + 0j]),

354 atol=TOLERANCE_ABSOLUTE_TESTS,

355 )

356

357 def test_n_dimensional_polarised_light_transmission_amplitude(self) -> None:

358 """

359 Test :func:`colour.phenomena.tmm.polarised_light_transmission_amplitude`

360 definition n-dimensional arrays support.

361 """

362

363 n_1 = 1.0

364 n_2 = 1.5

365 theta_i = 0.0

366 theta_t = 0.0

367 t = polarised_light_transmission_amplitude(n_1, n_2, theta_i, theta_t)

368

369 theta_i_array = np.tile(theta_i, 6)

370 theta_t_array = np.tile(theta_t, 6)

371 t_array = polarised_light_transmission_amplitude(

372 n_1, n_2, theta_i_array, theta_t_array

373 )

374 np.testing.assert_allclose(

375 t_array,

376 np.tile(t, (6, 1)),

377 atol=TOLERANCE_ABSOLUTE_TESTS,

378 )

379

380 theta_i_array = np.reshape(theta_i_array, (2, 3))

381 theta_t_array = np.reshape(theta_t_array, (2, 3))

382 t_array = polarised_light_transmission_amplitude(

383 n_1, n_2, theta_i_array, theta_t_array

384 )

385 np.testing.assert_allclose(

386 t_array,

387 np.tile(t, (6, 1)).reshape(2, 3, 2),

388 atol=TOLERANCE_ABSOLUTE_TESTS,

389 )

390

391 @ignore_numpy_errors

392 def test_nan_polarised_light_transmission_amplitude(self) -> None:

393 """

394 Test :func:`colour.phenomena.tmm.polarised_light_transmission_amplitude`

395 definition nan support.

396 """

397

398 polarised_light_transmission_amplitude(

399 np.array([-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]),

400 1.5,

401 0.0,

402 0.0,

403 )

404

405

406 class TestPolarisedLightTransmissionCoefficient:

407 """

408 Define :func:`colour.phenomena.tmm.polarised_light_transmission_coefficient`

409 definition unit tests methods.

410 """

411

412 def test_polarised_light_transmission_coefficient(self) -> None:

413 """

414 Test :func:`colour.phenomena.tmm.polarised_light_transmission_coefficient`

415 definition.

416 """

417

418 np.testing.assert_allclose(

419 polarised_light_transmission_coefficient(1.0, 1.5, 0.0, 0.0),

420 np.array([0.96 + 0j, 0.96 + 0j]),

421 atol=TOLERANCE_ABSOLUTE_TESTS,

422 )

423

424 # Test energy conservation: R + T = 1

425 R = polarised_light_reflection_coefficient(1.0, 1.5, 0.0, 0.0)

426 T = polarised_light_transmission_coefficient(1.0, 1.5, 0.0, 0.0)

427 np.testing.assert_allclose(

428 np.real(R + T), np.array([1.0, 1.0]), atol=TOLERANCE_ABSOLUTE_TESTS

429 )

430

431 def test_n_dimensional_polarised_light_transmission_coefficient(

432 self,

433 ) -> None:

434 """

435 Test :func:`colour.phenomena.tmm.polarised_light_transmission_coefficient`

436 definition n-dimensional arrays support.

437 """

438

439 n_1 = 1.0

440 n_2 = 1.5

441 theta_i = 0.0

442 theta_t = 0.0

443 T = polarised_light_transmission_coefficient(n_1, n_2, theta_i, theta_t)

444

445 theta_i_array = np.tile(theta_i, 6)

446 theta_t_array = np.tile(theta_t, 6)

447 T_array = polarised_light_transmission_coefficient(

448 n_1, n_2, theta_i_array, theta_t_array

449 )

450 np.testing.assert_allclose(

451 T_array,

452 np.tile(T, (6, 1)),

453 atol=TOLERANCE_ABSOLUTE_TESTS,

454 )

455

456 theta_i_array = np.reshape(theta_i_array, (2, 3))

457 theta_t_array = np.reshape(theta_t_array, (2, 3))

458 T_array = polarised_light_transmission_coefficient(

459 n_1, n_2, theta_i_array, theta_t_array

460 )

461 np.testing.assert_allclose(

462 T_array,

463 np.tile(T, (6, 1)).reshape(2, 3, 2),

464 atol=TOLERANCE_ABSOLUTE_TESTS,

465 )

466

467 @ignore_numpy_errors

468 def test_nan_polarised_light_transmission_coefficient(self) -> None:

469 """

470 Test :func:`colour.phenomena.tmm.polarised_light_transmission_coefficient`

471 definition nan support.

472 """

473

474 polarised_light_transmission_coefficient(

475 np.array([-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]),

476 1.5,

477 0.0,

478 0.0,

479 )

480

481

482 class TestMatrixTransferTmm:

483 """

484 Define :func:`colour.phenomena.tmm.matrix_transfer_tmm`

485 definition unit tests methods.

486 """

487

488 def test_matrix_transfer_tmm(self) -> None:

489 """

490 Test :func:`colour.phenomena.tmm.matrix_transfer_tmm`

491 definition.

492 """

493

494 # Single layer structure

495 result = matrix_transfer_tmm(

496 n=[1.0, 1.5, 1.0], t=[250], theta=0, wavelength=550

497 )

498

499 # Check shapes - (W, A, T, 2, 2)

500 assert result.M_s.shape == (

501 1,

502 1,

503 1,

504 2,

505 2,

506 ) # (wavelengths=1, angles=1, thickness=1, 2, 2)

507 assert result.M_p.shape == (1, 1, 1, 2, 2)

508 # theta has shape (angles, media)

509 assert result.theta.shape == (1, 3) # (angles=1, media=3)

510 assert len(result.n) == 3 # incident, layer, substrate

511

512 # Check refractive indices

513 # n has shape (media_count, wavelengths_count)

514 assert result.n.shape == (3, 1)

515 np.testing.assert_allclose(

516 result.n[:, 0], [1.0, 1.5, 1.0], atol=TOLERANCE_ABSOLUTE_TESTS

517 )

518

519 # Check angles (normal incidence)

520 assert result.theta[0, 0] == 0.0 # incident

521 assert result.theta[0, -1] == 0.0 # substrate (by Snell's law)

522

523 # Check transfer matrix properties (should be 2x2 complex)

524 assert result.M_s.dtype in [np.complex64, np.complex128]

525 assert result.M_p.dtype in [np.complex64, np.complex128]

526

527 def test_matrix_transfer_tmm_multilayer(self) -> None:

528 """

529 Test :func:`colour.phenomena.tmm.matrix_transfer_tmm`

530 with multiple layers.

531 """

532

533 # Two-layer structure

534 result = matrix_transfer_tmm(

535 n=[1.0, 1.5, 2.0, 1.5],

536 t=[250, 150],

537 theta=0,

538 wavelength=550,

539 )

540

541 # Check shapes - (W, A, T, 2, 2)

542 assert result.M_s.shape == (

543 1,

544 1,

545 1,

546 2,

547 2,

548 ) # (wavelengths=1, angles=1, thickness=1, 2, 2)

549 assert result.M_p.shape == (1, 1, 1, 2, 2)

550 # theta has shape (angles, media)

551 assert result.theta.shape == (1, 4) # (angles=1, media=4)

552 assert len(result.n) == 4

553

554 # Check refractive indices

555 # n has shape (media_count, wavelengths_count)

556 assert result.n.shape == (4, 1)

557 np.testing.assert_allclose(

558 result.n[:, 0], [1.0, 1.5, 2.0, 1.5], atol=TOLERANCE_ABSOLUTE_TESTS

559 )

560

561 def test_matrix_transfer_tmm_multiple_wavelengths(self) -> None:

562 """

563 Test :func:`colour.phenomena.tmm.matrix_transfer_tmm`

564 with multiple wavelengths.

565 """

566

567 wavelengths = [400, 500, 600]

568 result = matrix_transfer_tmm(

569 n=[1.0, 1.5, 1.0], t=[250], theta=0, wavelength=wavelengths

570 )

571

572 # Check shapes - (W, A, T, 2, 2)

573 assert result.M_s.shape == (

574 3,

575 1,

576 1,

577 2,

578 2,

579 ) # (wavelengths=3, angles=1, thickness=1, 2, 2)

580 assert result.M_p.shape == (3, 1, 1, 2, 2)

581

582 # theta has shape (angles, media)

583 assert result.theta.shape == (1, 3) # (angles=1, media=3)

584 # n has shape (media_count, wavelengths_count)

585 assert result.n.shape == (3, 3)

586

587 def test_matrix_transfer_tmm_complex_n(self) -> None:

588 """

589 Test :func:`colour.phenomena.tmm.matrix_transfer_tmm`

590 with complex refractive indices.

591 """

592

593 # Absorbing layer

594 n_absorbing = 2.0 + 0.5j

595 result = matrix_transfer_tmm(

596 n=[1.0, n_absorbing, 1.0], t=[250], theta=0, wavelength=550

597 )

598

599 # Check that complex n is preserved

600 # n has shape (media_count, wavelengths_count)

601 assert np.iscomplex(result.n[1, 0])

602 np.testing.assert_allclose(

603 result.n[1, 0], n_absorbing, atol=TOLERANCE_ABSOLUTE_TESTS

604 )

605

606 # Transfer matrices should be complex

607 assert np.iscomplexobj(result.M_s)

608 assert np.iscomplexobj(result.M_p)

609

610 def test_matrix_transfer_tmm_oblique_incidence(self) -> None:

611 """

612 Test :func:`colour.phenomena.tmm.matrix_transfer_tmm`

613 with oblique incidence.

614 """

615

616 theta_i = 30.0 # 30 degrees

617 result = matrix_transfer_tmm(

618 n=[1.0, 1.5, 1.0], t=[250], theta=theta_i, wavelength=550

619 )

620

621 # Check incident angle - theta has shape (angles, media)

622 np.testing.assert_allclose(

623 result.theta[0, 0], theta_i, atol=TOLERANCE_ABSOLUTE_TESTS

624 )

625

626 # Check that angle changes in layer (Snell's law)

627 assert result.theta[0, 1] != theta_i # Should be refracted

628

629 # s and p matrices should differ at oblique incidence

630 assert not np.allclose(result.M_s, result.M_p, atol=TOLERANCE_ABSOLUTE_TESTS)

631

632 def test_matrix_transfer_tmm_energy_consistency(self) -> None:

633 """

634 Test that transfer matrices from transfer_matrix_tmm give

635 consistent R and T values.

636 """

637

638 # Build transfer matrix

639 result = matrix_transfer_tmm(

640 n=[1.0, 1.5, 1.0], t=[250], theta=0, wavelength=550

641 )

642

643 # Extract R and T manually - M_s has shape (W, A, T, 2, 2)

644 r_s = result.M_s[0, 0, 0, 1, 0] / result.M_s[0, 0, 0, 0, 0]

645 R_s = np.abs(r_s) ** 2

646

647 t_s = 1.0 / result.M_s[0, 0, 0, 0, 0]

648 theta_i_rad = np.radians(0.0)

649 theta_f_rad = np.radians(result.theta[0, -1])

650

651 # Extract incident and substrate from result.n

652 n_incident = result.n[0, 0]

653 n_substrate = result.n[-1, 0]

654

655 angle_factor = np.real(n_substrate * np.cos(theta_f_rad)) / np.real(

656 n_incident * np.cos(theta_i_rad)

657 )

658 T_s = np.abs(t_s) ** 2 * angle_factor

659

660 # Energy conservation for lossless media: R + T = 1

661 np.testing.assert_allclose(R_s + T_s, 1.0, atol=TOLERANCE_ABSOLUTE_TESTS)

662

663 def test_n_dimensional_matrix_transfer_tmm(self) -> None:

664 """

665 Test :func:`colour.phenomena.tmm.matrix_transfer_tmm`

666 definition n-dimensional arrays support.

667 """

668

669 wl = 555

670 result = matrix_transfer_tmm(n=[1.0, 1.5, 1.0], t=[250], theta=0, wavelength=wl)

671

672 wl_array = np.tile(wl, 6)

673 result_array = matrix_transfer_tmm(

674 n=[1.0, 1.5, 1.0], t=[250], theta=0, wavelength=wl_array

675 )

676

677 # Check shape - (W, A, T, 2, 2)

678 assert result_array.M_s.shape == (

679 6,

680 1,

681 1,

682 2,

683 2,

684 ) # (wavelengths=6, angles=1, thickness=1, 2, 2)

685 assert result_array.M_p.shape == (6, 1, 1, 2, 2)

686

687 # theta shapes: result has (1, 3), result_array has (1, 3)

688 assert result_array.theta.shape == result.theta.shape

689 # n shapes: result has (3, 1), result_array has (3, 6)

690 # For constant n, all wavelength columns should match

691 assert result_array.n.shape == (3, 6)

692 assert result.n.shape == (3, 1)

693 np.testing.assert_allclose(

694 result_array.n[:, 0],

695 result.n[:, 0],

696 atol=TOLERANCE_ABSOLUTE_TESTS,

697 )