Coverage for phenomena/tests/test_tmm.py: 100%
216 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
1"""Define the unit tests for the :mod:`colour.phenomena.tmm` module."""
3from __future__ import annotations
5import numpy as np
7from colour.constants import TOLERANCE_ABSOLUTE_TESTS
8from colour.phenomena.interference import matrix_transfer_tmm
9from 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)
17from colour.utilities import ignore_numpy_errors
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"
26__all__ = [
27 "TestSnellLaw",
28 "TestPolarisedLightMagnitudeElements",
29 "TestPolarisedLightReflectionAmplitude",
30 "TestPolarisedLightReflectionCoefficient",
31 "TestPolarisedLightTransmissionAmplitude",
32 "TestPolarisedLightTransmissionCoefficient",
33 "TestMatrixTransferTmm",
34]
37class TestSnellLaw:
38 """
39 Define :func:`colour.phenomena.tmm.snell_law` definition unit tests
40 methods.
41 """
43 def test_snell_law(self) -> None:
44 """Test :func:`colour.phenomena.tmm.snell_law` definition."""
46 np.testing.assert_allclose(
47 snell_law(1.0, 1.5, 30.0),
48 19.4712206345,
49 atol=TOLERANCE_ABSOLUTE_TESTS,
50 )
52 np.testing.assert_allclose(
53 snell_law(1.0, 1.33, 45.0),
54 32.117631278,
55 atol=TOLERANCE_ABSOLUTE_TESTS,
56 )
58 np.testing.assert_allclose(
59 snell_law(1.5, 1.0, 19.47),
60 30.0,
61 atol=0.01,
62 )
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 )
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 """
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)
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 )
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 )
98 @ignore_numpy_errors
99 def test_nan_snell_law(self) -> None:
100 """Test :func:`colour.phenomena.tmm.snell_law` definition nan support."""
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 )
109class TestPolarisedLightMagnitudeElements:
110 """
111 Define :func:`colour.phenomena.tmm.polarised_light_magnitude_elements`
112 definition unit tests methods.
113 """
115 def test_polarised_light_magnitude_elements(self) -> None:
116 """
117 Test :func:`colour.phenomena.tmm.polarised_light_magnitude_elements`
118 definition.
119 """
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)
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
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 """
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)
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 )
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 )
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 """
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 )
194class TestPolarisedLightReflectionAmplitude:
195 """
196 Define :func:`colour.phenomena.tmm.polarised_light_reflection_amplitude`
197 definition unit tests methods.
198 """
200 def test_polarised_light_reflection_amplitude(self) -> None:
201 """
202 Test :func:`colour.phenomena.tmm.polarised_light_reflection_amplitude`
203 definition.
204 """
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 )
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 )
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 """
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)
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 )
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 )
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 """
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 )
267class TestPolarisedLightReflectionCoefficient:
268 """
269 Define :func:`colour.phenomena.tmm.polarised_light_reflection_coefficient`
270 definition unit tests methods.
271 """
273 def test_polarised_light_reflection_coefficient(self) -> None:
274 """
275 Test :func:`colour.phenomena.tmm.polarised_light_reflection_coefficient`
276 definition.
277 """
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 )
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)
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 """
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)
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 )
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 )
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 """
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 )
339class TestPolarisedLightTransmissionAmplitude:
340 """
341 Define :func:`colour.phenomena.tmm.polarised_light_transmission_amplitude`
342 definition unit tests methods.
343 """
345 def test_polarised_light_transmission_amplitude(self) -> None:
346 """
347 Test :func:`colour.phenomena.tmm.polarised_light_transmission_amplitude`
348 definition.
349 """
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 )
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 """
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)
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 )
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 )
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 """
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 )
406class TestPolarisedLightTransmissionCoefficient:
407 """
408 Define :func:`colour.phenomena.tmm.polarised_light_transmission_coefficient`
409 definition unit tests methods.
410 """
412 def test_polarised_light_transmission_coefficient(self) -> None:
413 """
414 Test :func:`colour.phenomena.tmm.polarised_light_transmission_coefficient`
415 definition.
416 """
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 )
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 )
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 """
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)
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 )
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 )
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 """
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 )
482class TestMatrixTransferTmm:
483 """
484 Define :func:`colour.phenomena.tmm.matrix_transfer_tmm`
485 definition unit tests methods.
486 """
488 def test_matrix_transfer_tmm(self) -> None:
489 """
490 Test :func:`colour.phenomena.tmm.matrix_transfer_tmm`
491 definition.
492 """
494 # Single layer structure
495 result = matrix_transfer_tmm(
496 n=[1.0, 1.5, 1.0], t=[250], theta=0, wavelength=550
497 )
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
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 )
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)
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]
527 def test_matrix_transfer_tmm_multilayer(self) -> None:
528 """
529 Test :func:`colour.phenomena.tmm.matrix_transfer_tmm`
530 with multiple layers.
531 """
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 )
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
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 )
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 """
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 )
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)
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)
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 """
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 )
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 )
606 # Transfer matrices should be complex
607 assert np.iscomplexobj(result.M_s)
608 assert np.iscomplexobj(result.M_p)
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 """
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 )
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 )
626 # Check that angle changes in layer (Snell's law)
627 assert result.theta[0, 1] != theta_i # Should be refracted
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)
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 """
638 # Build transfer matrix
639 result = matrix_transfer_tmm(
640 n=[1.0, 1.5, 1.0], t=[250], theta=0, wavelength=550
641 )
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
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])
651 # Extract incident and substrate from result.n
652 n_incident = result.n[0, 0]
653 n_substrate = result.n[-1, 0]
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
660 # Energy conservation for lossless media: R + T = 1
661 np.testing.assert_allclose(R_s + T_s, 1.0, atol=TOLERANCE_ABSOLUTE_TESTS)
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 """
669 wl = 555
670 result = matrix_transfer_tmm(n=[1.0, 1.5, 1.0], t=[250], theta=0, wavelength=wl)
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 )
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)
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 )