Coverage for continuous/tests/test_multi_signal.py: 100%

296 statements  

« 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.continuous.multi_signals` module.""" 

2 

3from __future__ import annotations 

4 

5import pickle 

6import textwrap 

7 

8import numpy as np 

9import pytest 

10 

11from colour.algebra import CubicSplineInterpolator, Extrapolator, KernelInterpolator 

12from colour.constants import DTYPE_FLOAT_DEFAULT, TOLERANCE_ABSOLUTE_TESTS 

13from colour.continuous import MultiSignals, Signal 

14from colour.utilities import ( 

15 ColourRuntimeWarning, 

16 attest, 

17 is_pandas_installed, 

18 is_scipy_installed, 

19 tsplit, 

20 tstack, 

21) 

22 

23__author__ = "Colour Developers" 

24__copyright__ = "Copyright 2013 Colour Developers" 

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

26__maintainer__ = "Colour Developers" 

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

28__status__ = "Production" 

29 

30__all__ = [ 

31 "TestMultiSignals", 

32] 

33 

34 

35class TestMultiSignals: 

36 """ 

37 Define :class:`colour.continuous.multi_signals.MultiSignals` class unit 

38 tests methods. 

39 """ 

40 

41 def setup_method(self) -> None: 

42 """Initialise the common tests attributes.""" 

43 

44 self._range_1 = np.linspace(10, 100, 10) 

45 self._range_2 = tstack([self._range_1] * 3) + np.array([0, 10, 20]) 

46 self._domain_1 = np.arange(0, 10, 1) 

47 self._domain_2 = np.arange(100, 1100, 100) 

48 

49 self._multi_signals = MultiSignals(self._range_2) 

50 

51 def test_required_attributes(self) -> None: 

52 """Test the presence of required attributes.""" 

53 

54 required_attributes = ( 

55 "dtype", 

56 "domain", 

57 "range", 

58 "interpolator", 

59 "interpolator_kwargs", 

60 "extrapolator", 

61 "extrapolator_kwargs", 

62 "function", 

63 "signals", 

64 "labels", 

65 "signal_type", 

66 ) 

67 

68 for attribute in required_attributes: 

69 assert attribute in dir(MultiSignals) 

70 

71 def test_required_methods(self) -> None: 

72 """Test the presence of required methods.""" 

73 

74 required_methods = ( 

75 "__init__", 

76 "__str__", 

77 "__repr__", 

78 "__hash__", 

79 "__getitem__", 

80 "__setitem__", 

81 "__contains__", 

82 "__iter__", 

83 "__eq__", 

84 "__ne__", 

85 "arithmetical_operation", 

86 "multi_signals_unpack_data", 

87 "fill_nan", 

88 "domain_distance", 

89 "to_dataframe", 

90 ) 

91 

92 for method in required_methods: 

93 assert method in dir(MultiSignals) 

94 

95 def test_pickling(self) -> None: 

96 """ 

97 Test whether the :class:``colour.continuous.signal.MultiSignals` class 

98 can be pickled. 

99 """ 

100 

101 data = pickle.dumps(self._multi_signals) 

102 data = pickle.loads(data) # noqa: S301 

103 assert self._multi_signals == data 

104 

105 def test_dtype(self) -> None: 

106 """ 

107 Test :func:`colour.continuous.multi_signals.MultiSignals.dtype` 

108 property. 

109 """ 

110 

111 assert self._multi_signals.dtype == DTYPE_FLOAT_DEFAULT 

112 

113 multi_signals = self._multi_signals.copy() 

114 multi_signals.dtype = np.float32 

115 assert multi_signals.dtype == np.float32 

116 

117 def test_domain(self) -> None: 

118 """ 

119 Test :func:`colour.continuous.multi_signals.MultiSignals.domain` 

120 property. 

121 """ 

122 

123 multi_signals = self._multi_signals.copy() 

124 

125 np.testing.assert_allclose( 

126 multi_signals[np.array([0, 1, 2])], 

127 np.array([[10.0, 20.0, 30.0], [20.0, 30.0, 40.0], [30.0, 40.0, 50.0]]), 

128 atol=TOLERANCE_ABSOLUTE_TESTS, 

129 ) 

130 

131 multi_signals.domain = self._domain_1 * 10 

132 

133 np.testing.assert_array_equal(multi_signals.domain, self._domain_1 * 10) 

134 

135 np.testing.assert_allclose( 

136 multi_signals[np.array([0, 1, 2]) * 10], 

137 np.array([[10.0, 20.0, 30.0], [20.0, 30.0, 40.0], [30.0, 40.0, 50.0]]), 

138 atol=TOLERANCE_ABSOLUTE_TESTS, 

139 ) 

140 

141 domain = np.linspace(0, 1, 10) 

142 domain[0] = -np.inf 

143 

144 def assert_warns() -> None: 

145 """Help to test the runtime warning.""" 

146 

147 multi_signals.domain = domain 

148 

149 pytest.warns(ColourRuntimeWarning, assert_warns) 

150 

151 def test_range(self) -> None: 

152 """ 

153 Test :func:`colour.continuous.multi_signals.MultiSignals.range` 

154 property. 

155 """ 

156 

157 multi_signals = self._multi_signals.copy() 

158 

159 np.testing.assert_allclose( 

160 multi_signals[np.array([0, 1, 2])], 

161 np.array([[10.0, 20.0, 30.0], [20.0, 30.0, 40.0], [30.0, 40.0, 50.0]]), 

162 atol=TOLERANCE_ABSOLUTE_TESTS, 

163 ) 

164 

165 multi_signals.range = self._range_1 * 10 

166 

167 np.testing.assert_array_equal( 

168 multi_signals.range, tstack([self._range_1] * 3) * 10 

169 ) 

170 

171 np.testing.assert_allclose( 

172 multi_signals[np.array([0, 1, 2])], 

173 np.array([[10.0, 10.0, 10.0], [20.0, 20.0, 20.0], [30.0, 30.0, 30.0]]) * 10, 

174 atol=TOLERANCE_ABSOLUTE_TESTS, 

175 ) 

176 

177 multi_signals.range = self._range_2 * 10 

178 

179 np.testing.assert_array_equal(multi_signals.range, self._range_2 * 10) 

180 

181 np.testing.assert_allclose( 

182 multi_signals[np.array([0, 1, 2])], 

183 np.array([[10.0, 20.0, 30.0], [20.0, 30.0, 40.0], [30.0, 40.0, 50.0]]) * 10, 

184 atol=TOLERANCE_ABSOLUTE_TESTS, 

185 ) 

186 

187 def test_interpolator(self) -> None: 

188 """ 

189 Test :func:`colour.continuous.multi_signals.MultiSignals.interpolator` 

190 property. 

191 """ 

192 

193 if not is_scipy_installed(): # pragma: no cover 

194 return 

195 

196 multi_signals = self._multi_signals.copy() 

197 

198 np.testing.assert_allclose( 

199 multi_signals[np.linspace(0, 5, 5)], 

200 np.array( 

201 [ 

202 [10.00000000, 20.00000000, 30.00000000], 

203 [22.83489024, 32.80460562, 42.77432100], 

204 [34.80044921, 44.74343470, 54.68642018], 

205 [47.55353925, 57.52325463, 67.49297001], 

206 [60.00000000, 70.00000000, 80.00000000], 

207 ] 

208 ), 

209 atol=TOLERANCE_ABSOLUTE_TESTS, 

210 ) 

211 

212 multi_signals.interpolator = CubicSplineInterpolator 

213 

214 np.testing.assert_allclose( 

215 multi_signals[np.linspace(0, 5, 5)], 

216 np.array( 

217 [ 

218 [10.00000000, 20.00000000, 30.00000000], 

219 [22.50000000, 32.50000000, 42.50000000], 

220 [35.00000000, 45.00000000, 55.00000000], 

221 [47.50000000, 57.50000000, 67.50000000], 

222 [60.00000000, 70.00000000, 80.00000000], 

223 ] 

224 ), 

225 atol=TOLERANCE_ABSOLUTE_TESTS, 

226 ) 

227 

228 def test_interpolator_kwargs(self) -> None: 

229 """ 

230 Test :func:`colour.continuous.multi_signals.MultiSignals.\ 

231interpolator_kwargs` property. 

232 """ 

233 

234 multi_signals = self._multi_signals.copy() 

235 

236 np.testing.assert_allclose( 

237 multi_signals[np.linspace(0, 5, 5)], 

238 np.array( 

239 [ 

240 [10.00000000, 20.00000000, 30.00000000], 

241 [22.83489024, 32.80460562, 42.77432100], 

242 [34.80044921, 44.74343470, 54.68642018], 

243 [47.55353925, 57.52325463, 67.49297001], 

244 [60.00000000, 70.00000000, 80.00000000], 

245 ] 

246 ), 

247 atol=TOLERANCE_ABSOLUTE_TESTS, 

248 ) 

249 

250 multi_signals.interpolator_kwargs = { 

251 "window": 1, 

252 "kernel_kwargs": {"a": 1}, 

253 } 

254 

255 np.testing.assert_allclose( 

256 multi_signals[np.linspace(0, 5, 5)], 

257 np.array( 

258 [ 

259 [10.00000000, 20.00000000, 30.00000000], 

260 [18.91328761, 27.91961505, 36.92594248], 

261 [28.36993142, 36.47562611, 44.58132080], 

262 [44.13100443, 53.13733187, 62.14365930], 

263 [60.00000000, 70.00000000, 80.00000000], 

264 ] 

265 ), 

266 atol=TOLERANCE_ABSOLUTE_TESTS, 

267 ) 

268 

269 def test_extrapolator(self) -> None: 

270 """ 

271 Test :func:`colour.continuous.multi_signals.MultiSignals.extrapolator` 

272 property. 

273 """ 

274 

275 assert isinstance(self._multi_signals.extrapolator(), Extrapolator) 

276 

277 def test_extrapolator_kwargs(self) -> None: 

278 """ 

279 Test :func:`colour.continuous.multi_signals.MultiSignals.\ 

280extrapolator_kwargs` property. 

281 """ 

282 

283 multi_signals = self._multi_signals.copy() 

284 

285 attest(np.all(np.isnan(multi_signals[np.array([-1000, 1000])]))) 

286 

287 multi_signals.extrapolator_kwargs = { 

288 "method": "Linear", 

289 } 

290 

291 np.testing.assert_allclose( 

292 multi_signals[np.array([-1000, 1000])], 

293 np.array([[-9990.0, -9980.0, -9970.0], [10010.0, 10020.0, 10030.0]]), 

294 atol=TOLERANCE_ABSOLUTE_TESTS, 

295 ) 

296 

297 def test_function(self) -> None: 

298 """ 

299 Test :func:`colour.continuous.multi_signals.MultiSignals.function` 

300 property. 

301 """ 

302 

303 attest(callable(self._multi_signals.function)) 

304 

305 def test_raise_exception_function(self) -> None: 

306 """ 

307 Test :func:`colour.continuous.signal.multi_signals.MultiSignals.\ 

308function` property raised exception. 

309 """ 

310 

311 pytest.raises((ValueError, TypeError), MultiSignals().function, 0) 

312 

313 def test_signals(self) -> None: 

314 """ 

315 Test :func:`colour.continuous.multi_signals.MultiSignals.signals` 

316 property. 

317 """ 

318 

319 multi_signals = self._multi_signals.copy() 

320 

321 multi_signals.signals = self._range_1 

322 np.testing.assert_array_equal(multi_signals.domain, self._domain_1) 

323 np.testing.assert_array_equal(multi_signals.range, self._range_1[:, None]) 

324 

325 def test_labels(self) -> None: 

326 """ 

327 Test :func:`colour.continuous.multi_signals.MultiSignals.labels` 

328 property. 

329 """ 

330 

331 assert self._multi_signals.labels == ["0", "1", "2"] 

332 

333 multi_signals = self._multi_signals.copy() 

334 

335 multi_signals.labels = ["a", "b", "c"] 

336 

337 assert multi_signals.labels == ["a", "b", "c"] 

338 

339 def test_signal_type(self) -> None: 

340 """ 

341 Test :func:`colour.continuous.multi_signals.MultiSignals.signal_type` 

342 property. 

343 """ 

344 

345 multi_signals = MultiSignals(signal_type=Signal) 

346 

347 assert multi_signals.signal_type == Signal 

348 

349 def test__init__(self) -> None: 

350 """ 

351 Test :meth:`colour.continuous.multi_signals.MultiSignals.__init__` 

352 method. 

353 """ 

354 

355 multi_signals = MultiSignals(self._range_1) 

356 np.testing.assert_array_equal(multi_signals.domain, self._domain_1) 

357 np.testing.assert_array_equal(multi_signals.range, self._range_1[:, None]) 

358 

359 multi_signals = MultiSignals(self._range_1, self._domain_2) 

360 np.testing.assert_array_equal(multi_signals.domain, self._domain_2) 

361 np.testing.assert_array_equal(multi_signals.range, self._range_1[:, None]) 

362 

363 multi_signals = MultiSignals(self._range_2, self._domain_2) 

364 np.testing.assert_array_equal(multi_signals.domain, self._domain_2) 

365 np.testing.assert_array_equal(multi_signals.range, self._range_2) 

366 

367 multi_signals = MultiSignals( 

368 dict(zip(self._domain_2, self._range_2, strict=True)) 

369 ) 

370 np.testing.assert_array_equal(multi_signals.domain, self._domain_2) 

371 np.testing.assert_array_equal(multi_signals.range, self._range_2) 

372 

373 multi_signals = MultiSignals(multi_signals) 

374 np.testing.assert_array_equal(multi_signals.domain, self._domain_2) 

375 np.testing.assert_array_equal(multi_signals.range, self._range_2) 

376 

377 class NotSignal(Signal): 

378 """Not :class:`Signal` class.""" 

379 

380 multi_signals = MultiSignals(self._range_1, signal_type=NotSignal) 

381 assert isinstance(multi_signals.signals["0"], NotSignal) 

382 np.testing.assert_array_equal(multi_signals.domain, self._domain_1) 

383 np.testing.assert_array_equal(multi_signals.range, self._range_1[:, None]) 

384 

385 if is_pandas_installed(): 

386 from pandas import DataFrame, Series # noqa: PLC0415 

387 

388 multi_signals = MultiSignals( 

389 Series(dict(zip(self._domain_2, self._range_1, strict=True))) 

390 ) 

391 np.testing.assert_array_equal(multi_signals.domain, self._domain_2) 

392 np.testing.assert_array_equal(multi_signals.range, self._range_1[:, None]) 

393 

394 data = dict(zip(["a", "b", "c"], tsplit(self._range_2), strict=True)) 

395 multi_signals = MultiSignals(DataFrame(data, self._domain_2)) 

396 np.testing.assert_array_equal(multi_signals.domain, self._domain_2) 

397 np.testing.assert_array_equal(multi_signals.range, self._range_2) 

398 

399 def test__hash__(self) -> None: 

400 """ 

401 Test :meth:`colour.continuous.multi_signals.MultiSignals.__hash__` 

402 method. 

403 """ 

404 

405 assert isinstance(hash(self._multi_signals), int) 

406 

407 def test__str__(self) -> None: 

408 """ 

409 Test :meth:`colour.continuous.multi_signals.MultiSignals.__str__` 

410 method. 

411 """ 

412 

413 assert ( 

414 str(self._multi_signals) 

415 == ( 

416 textwrap.dedent( 

417 """ 

418 [[ 0. 10. 20. 30.] 

419 [ 1. 20. 30. 40.] 

420 [ 2. 30. 40. 50.] 

421 [ 3. 40. 50. 60.] 

422 [ 4. 50. 60. 70.] 

423 [ 5. 60. 70. 80.] 

424 [ 6. 70. 80. 90.] 

425 [ 7. 80. 90. 100.] 

426 [ 8. 90. 100. 110.] 

427 [ 9. 100. 110. 120.]]""" 

428 )[1:] 

429 ) 

430 ) 

431 

432 assert isinstance(str(MultiSignals()), str) 

433 

434 def test__repr__(self) -> None: 

435 """ 

436 Test :meth:`colour.continuous.multi_signals.MultiSignals.__repr__` 

437 method. 

438 """ 

439 

440 assert repr(self._multi_signals) == ( 

441 textwrap.dedent( 

442 """ 

443 MultiSignals([[ 0., 10., 20., 30.], 

444 [ 1., 20., 30., 40.], 

445 [ 2., 30., 40., 50.], 

446 [ 3., 40., 50., 60.], 

447 [ 4., 50., 60., 70.], 

448 [ 5., 60., 70., 80.], 

449 [ 6., 70., 80., 90.], 

450 [ 7., 80., 90., 100.], 

451 [ 8., 90., 100., 110.], 

452 [ 9., 100., 110., 120.]], 

453 ['0', '1', '2'], 

454 KernelInterpolator, 

455 {}, 

456 Extrapolator, 

457 {'method': 'Constant', 'left': nan, 'right': nan}) 

458 """ 

459 ).strip() 

460 ) 

461 

462 assert isinstance(repr(MultiSignals()), str) 

463 

464 def test__getitem__(self) -> None: 

465 """ 

466 Test :meth:`colour.continuous.multi_signals.MultiSignals.__getitem__` 

467 method. 

468 """ 

469 

470 np.testing.assert_allclose( 

471 self._multi_signals[0], 

472 np.array([10.0, 20.0, 30.0]), 

473 atol=TOLERANCE_ABSOLUTE_TESTS, 

474 ) 

475 

476 np.testing.assert_allclose( 

477 self._multi_signals[np.array([0, 1, 2])], 

478 np.array( 

479 [ 

480 [10.0, 20.0, 30.0], 

481 [20.0, 30.0, 40.0], 

482 [30.0, 40.0, 50.0], 

483 ] 

484 ), 

485 atol=TOLERANCE_ABSOLUTE_TESTS, 

486 ) 

487 

488 np.testing.assert_allclose( 

489 self._multi_signals[np.linspace(0, 5, 5)], 

490 np.array( 

491 [ 

492 [10.00000000, 20.00000000, 30.00000000], 

493 [22.83489024, 32.80460562, 42.77432100], 

494 [34.80044921, 44.74343470, 54.68642018], 

495 [47.55353925, 57.52325463, 67.49297001], 

496 [60.00000000, 70.00000000, 80.00000000], 

497 ] 

498 ), 

499 atol=TOLERANCE_ABSOLUTE_TESTS, 

500 ) 

501 

502 attest(np.all(np.isnan(self._multi_signals[np.array([-1000, 1000])]))) 

503 

504 np.testing.assert_allclose( 

505 self._multi_signals[:], 

506 self._multi_signals.range, 

507 atol=TOLERANCE_ABSOLUTE_TESTS, 

508 ) 

509 

510 np.testing.assert_allclose( 

511 self._multi_signals[:, :], # pyright: ignore 

512 self._multi_signals.range, 

513 atol=TOLERANCE_ABSOLUTE_TESTS, 

514 ) 

515 

516 np.testing.assert_allclose( 

517 self._multi_signals[0:3], 

518 np.array( 

519 [ 

520 [10.0, 20.0, 30.0], 

521 [20.0, 30.0, 40.0], 

522 [30.0, 40.0, 50.0], 

523 ] 

524 ), 

525 atol=TOLERANCE_ABSOLUTE_TESTS, 

526 ) 

527 

528 np.testing.assert_allclose( 

529 self._multi_signals[:, 0:2], # pyright: ignore 

530 np.array( 

531 [ 

532 [10.0, 20.0], 

533 [20.0, 30.0], 

534 [30.0, 40.0], 

535 [40.0, 50.0], 

536 [50.0, 60.0], 

537 [60.0, 70.0], 

538 [70.0, 80.0], 

539 [80.0, 90.0], 

540 [90.0, 100.0], 

541 [100.0, 110.0], 

542 ] 

543 ), 

544 atol=TOLERANCE_ABSOLUTE_TESTS, 

545 ) 

546 

547 multi_signals = self._multi_signals.copy() 

548 multi_signals.extrapolator_kwargs = { 

549 "method": "Linear", 

550 } 

551 np.testing.assert_array_equal( 

552 multi_signals[np.array([-1000, 1000])], 

553 np.array( 

554 [ 

555 [-9990.0, -9980.0, -9970.0], 

556 [10010.0, 10020.0, 10030.0], 

557 ] 

558 ), 

559 ) 

560 

561 multi_signals.extrapolator_kwargs = { 

562 "method": "Constant", 

563 "left": 0, 

564 "right": 1, 

565 } 

566 np.testing.assert_array_equal( 

567 multi_signals[np.array([-1000, 1000])], 

568 np.array([[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]]), 

569 ) 

570 

571 def test__setitem__(self) -> None: 

572 """ 

573 Test :meth:`colour.continuous.multi_signals.MultiSignals.__setitem__` 

574 method. 

575 """ 

576 

577 multi_signals = self._multi_signals.copy() 

578 

579 multi_signals[0] = 20 

580 np.testing.assert_allclose( 

581 multi_signals[0], 

582 np.array([20.0, 20.0, 20.0]), 

583 atol=TOLERANCE_ABSOLUTE_TESTS, 

584 ) 

585 

586 multi_signals[np.array([0, 1, 2])] = 30 

587 np.testing.assert_allclose( 

588 multi_signals[np.array([0, 1, 2])], 

589 np.array( 

590 [ 

591 [30.0, 30.0, 30.0], 

592 [30.0, 30.0, 30.0], 

593 [30.0, 30.0, 30.0], 

594 ] 

595 ), 

596 atol=TOLERANCE_ABSOLUTE_TESTS, 

597 ) 

598 

599 multi_signals[np.linspace(0, 5, 5)] = 50 

600 np.testing.assert_allclose( 

601 multi_signals.domain, 

602 np.array( 

603 [ 

604 0.00, 

605 1.00, 

606 1.25, 

607 2.00, 

608 2.50, 

609 3.00, 

610 3.75, 

611 4.00, 

612 5.00, 

613 6.00, 

614 7.00, 

615 8.00, 

616 9.00, 

617 ] 

618 ), 

619 atol=TOLERANCE_ABSOLUTE_TESTS, 

620 ) 

621 np.testing.assert_allclose( 

622 multi_signals.range, 

623 np.array( 

624 [ 

625 [50.0, 50.0, 50.0], 

626 [30.0, 30.0, 30.0], 

627 [50.0, 50.0, 50.0], 

628 [30.0, 30.0, 30.0], 

629 [50.0, 50.0, 50.0], 

630 [40.0, 50.0, 60.0], 

631 [50.0, 50.0, 50.0], 

632 [50.0, 60.0, 70.0], 

633 [50.0, 50.0, 50.0], 

634 [70.0, 80.0, 90.0], 

635 [80.0, 90.0, 100.0], 

636 [90.0, 100.0, 110.0], 

637 [100.0, 110.0, 120.0], 

638 ] 

639 ), 

640 atol=TOLERANCE_ABSOLUTE_TESTS, 

641 ) 

642 

643 multi_signals[np.array([0, 1, 2])] = np.array([10, 20, 30]) 

644 np.testing.assert_allclose( 

645 multi_signals.range, 

646 np.array( 

647 [ 

648 [10.0, 20.0, 30.0], 

649 [10.0, 20.0, 30.0], 

650 [50.0, 50.0, 50.0], 

651 [10.0, 20.0, 30.0], 

652 [50.0, 50.0, 50.0], 

653 [40.0, 50.0, 60.0], 

654 [50.0, 50.0, 50.0], 

655 [50.0, 60.0, 70.0], 

656 [50.0, 50.0, 50.0], 

657 [70.0, 80.0, 90.0], 

658 [80.0, 90.0, 100.0], 

659 [90.0, 100.0, 110.0], 

660 [100.0, 110.0, 120.0], 

661 ] 

662 ), 

663 atol=TOLERANCE_ABSOLUTE_TESTS, 

664 ) 

665 

666 multi_signals[np.array([0, 1, 2])] = np.reshape(np.arange(1, 10, 1), (3, 3)) 

667 np.testing.assert_allclose( 

668 multi_signals.range, 

669 np.array( 

670 [ 

671 [1.0, 2.0, 3.0], 

672 [4.0, 5.0, 6.0], 

673 [50.0, 50.0, 50.0], 

674 [7.0, 8.0, 9.0], 

675 [50.0, 50.0, 50.0], 

676 [40.0, 50.0, 60.0], 

677 [50.0, 50.0, 50.0], 

678 [50.0, 60.0, 70.0], 

679 [50.0, 50.0, 50.0], 

680 [70.0, 80.0, 90.0], 

681 [80.0, 90.0, 100.0], 

682 [90.0, 100.0, 110.0], 

683 [100.0, 110.0, 120.0], 

684 ] 

685 ), 

686 atol=TOLERANCE_ABSOLUTE_TESTS, 

687 ) 

688 

689 multi_signals[:] = 40 

690 np.testing.assert_allclose( 

691 multi_signals.range, 40, atol=TOLERANCE_ABSOLUTE_TESTS 

692 ) 

693 

694 multi_signals[:, :] = 50 # pyright: ignore 

695 np.testing.assert_allclose( 

696 multi_signals.range, 50, atol=TOLERANCE_ABSOLUTE_TESTS 

697 ) 

698 

699 multi_signals = self._multi_signals.copy() 

700 multi_signals[0:3] = 40 

701 np.testing.assert_allclose( 

702 multi_signals[0:3], 

703 np.array( 

704 [ 

705 [40.0, 40.0, 40.0], 

706 [40.0, 40.0, 40.0], 

707 [40.0, 40.0, 40.0], 

708 ] 

709 ), 

710 atol=TOLERANCE_ABSOLUTE_TESTS, 

711 ) 

712 

713 multi_signals[:, 0:2] = 50 # pyright: ignore 

714 np.testing.assert_allclose( 

715 multi_signals.range, 

716 np.array( 

717 [ 

718 [50.0, 50.0, 40.0], 

719 [50.0, 50.0, 40.0], 

720 [50.0, 50.0, 40.0], 

721 [50.0, 50.0, 60.0], 

722 [50.0, 50.0, 70.0], 

723 [50.0, 50.0, 80.0], 

724 [50.0, 50.0, 90.0], 

725 [50.0, 50.0, 100.0], 

726 [50.0, 50.0, 110.0], 

727 [50.0, 50.0, 120.0], 

728 ] 

729 ), 

730 atol=TOLERANCE_ABSOLUTE_TESTS, 

731 ) 

732 

733 def test__contains__(self) -> None: 

734 """ 

735 Test :meth:`colour.continuous.multi_signals.MultiSignals.__contains__` 

736 method. 

737 """ 

738 

739 assert 0 in self._multi_signals 

740 assert 0.5 in self._multi_signals 

741 assert 1000 not in self._multi_signals 

742 

743 def test__iter__(self) -> None: 

744 """Test :func:`colour.continuous.signal.Signal.__iter__` method.""" 

745 

746 domain = np.arange(0, 10) 

747 for i, domain_range_value in enumerate(self._multi_signals): 

748 np.testing.assert_array_equal(domain_range_value[0], domain[i]) 

749 np.testing.assert_array_equal(domain_range_value[1:], self._range_2[i]) 

750 

751 def test__len__(self) -> None: 

752 """ 

753 Test :meth:`colour.continuous.multi_signals.MultiSignals.__len__` 

754 method. 

755 """ 

756 

757 assert len(self._multi_signals) == 10 

758 

759 def test__eq__(self) -> None: 

760 """ 

761 Test :meth:`colour.continuous.multi_signals.MultiSignals.__eq__` 

762 method. 

763 """ 

764 

765 signal_1 = self._multi_signals.copy() 

766 signal_2 = self._multi_signals.copy() 

767 

768 assert signal_1 == signal_2 

769 

770 assert signal_1 != () 

771 

772 def test__ne__(self) -> None: 

773 """ 

774 Test :meth:`colour.continuous.multi_signals.MultiSignals.__ne__` 

775 method. 

776 """ 

777 

778 multi_signals_1 = self._multi_signals.copy() 

779 multi_signals_2 = self._multi_signals.copy() 

780 

781 multi_signals_2[0] = 20 

782 assert multi_signals_1 != multi_signals_2 

783 

784 multi_signals_2[0] = np.array([10, 20, 30]) 

785 assert multi_signals_1 == multi_signals_2 

786 

787 multi_signals_2.interpolator = CubicSplineInterpolator 

788 assert multi_signals_1 != multi_signals_2 

789 

790 multi_signals_2.interpolator = KernelInterpolator 

791 assert multi_signals_1 == multi_signals_2 

792 

793 multi_signals_2.interpolator_kwargs = {"window": 1} 

794 assert multi_signals_1 != multi_signals_2 

795 

796 multi_signals_2.interpolator_kwargs = {} 

797 assert multi_signals_1 == multi_signals_2 

798 

799 class NotExtrapolator(Extrapolator): 

800 """Not :class:`Extrapolator` class.""" 

801 

802 multi_signals_2.extrapolator = NotExtrapolator 

803 assert multi_signals_1 != multi_signals_2 

804 

805 multi_signals_2.extrapolator = Extrapolator 

806 assert multi_signals_1 == multi_signals_2 

807 

808 multi_signals_2.extrapolator_kwargs = {} 

809 assert multi_signals_1 != multi_signals_2 

810 

811 multi_signals_2.extrapolator_kwargs = { 

812 "method": "Constant", 

813 "left": np.nan, 

814 "right": np.nan, 

815 } 

816 assert multi_signals_1 == multi_signals_2 

817 

818 def test_arithmetical_operation(self) -> None: 

819 """ 

820 Test :func:`colour.continuous.multi_signals.MultiSignals.\ 

821arithmetical_operation` method. 

822 """ 

823 

824 np.testing.assert_allclose( 

825 self._multi_signals.arithmetical_operation(10, "+", False).range, 

826 self._range_2 + 10, 

827 atol=TOLERANCE_ABSOLUTE_TESTS, 

828 ) 

829 

830 np.testing.assert_allclose( 

831 self._multi_signals.arithmetical_operation(10, "-", False).range, 

832 self._range_2 - 10, 

833 atol=TOLERANCE_ABSOLUTE_TESTS, 

834 ) 

835 

836 np.testing.assert_allclose( 

837 self._multi_signals.arithmetical_operation(10, "*", False).range, 

838 self._range_2 * 10, 

839 atol=TOLERANCE_ABSOLUTE_TESTS, 

840 ) 

841 

842 np.testing.assert_allclose( 

843 self._multi_signals.arithmetical_operation(10, "/", False).range, 

844 self._range_2 / 10, 

845 atol=TOLERANCE_ABSOLUTE_TESTS, 

846 ) 

847 

848 np.testing.assert_allclose( 

849 self._multi_signals.arithmetical_operation(10, "**", False).range, 

850 self._range_2**10, 

851 atol=TOLERANCE_ABSOLUTE_TESTS, 

852 ) 

853 

854 np.testing.assert_allclose( 

855 (self._multi_signals + 10).range, 

856 self._range_2 + 10, 

857 atol=TOLERANCE_ABSOLUTE_TESTS, 

858 ) 

859 

860 np.testing.assert_allclose( 

861 (self._multi_signals - 10).range, 

862 self._range_2 - 10, 

863 atol=TOLERANCE_ABSOLUTE_TESTS, 

864 ) 

865 

866 np.testing.assert_allclose( 

867 (self._multi_signals * 10).range, 

868 self._range_2 * 10, 

869 atol=TOLERANCE_ABSOLUTE_TESTS, 

870 ) 

871 

872 np.testing.assert_allclose( 

873 (self._multi_signals / 10).range, 

874 self._range_2 / 10, 

875 atol=TOLERANCE_ABSOLUTE_TESTS, 

876 ) 

877 

878 np.testing.assert_allclose( 

879 (self._multi_signals**10).range, 

880 self._range_2**10, 

881 atol=TOLERANCE_ABSOLUTE_TESTS, 

882 ) 

883 

884 multi_signals = self._multi_signals.copy() 

885 

886 np.testing.assert_allclose( 

887 multi_signals.arithmetical_operation(10, "+", True).range, 

888 self._range_2 + 10, 

889 atol=TOLERANCE_ABSOLUTE_TESTS, 

890 ) 

891 

892 np.testing.assert_allclose( 

893 multi_signals.arithmetical_operation(10, "-", True).range, 

894 self._range_2, 

895 atol=TOLERANCE_ABSOLUTE_TESTS, 

896 ) 

897 

898 np.testing.assert_allclose( 

899 multi_signals.arithmetical_operation(10, "*", True).range, 

900 self._range_2 * 10, 

901 atol=TOLERANCE_ABSOLUTE_TESTS, 

902 ) 

903 

904 np.testing.assert_allclose( 

905 multi_signals.arithmetical_operation(10, "/", True).range, 

906 self._range_2, 

907 atol=TOLERANCE_ABSOLUTE_TESTS, 

908 ) 

909 

910 np.testing.assert_allclose( 

911 multi_signals.arithmetical_operation(10, "**", True).range, 

912 self._range_2**10, 

913 atol=TOLERANCE_ABSOLUTE_TESTS, 

914 ) 

915 

916 multi_signals = self._multi_signals.copy() 

917 np.testing.assert_allclose( 

918 multi_signals.arithmetical_operation(self._range_2, "+", False).range, 

919 self._range_2 + self._range_2, 

920 atol=TOLERANCE_ABSOLUTE_TESTS, 

921 ) 

922 

923 np.testing.assert_allclose( 

924 multi_signals.arithmetical_operation(multi_signals, "+", False).range, 

925 self._range_2 + self._range_2, 

926 atol=TOLERANCE_ABSOLUTE_TESTS, 

927 ) 

928 

929 def test_is_uniform(self) -> None: 

930 """ 

931 Test :meth:`colour.continuous.multi_signals.MultiSignals.is_uniform` 

932 method. 

933 """ 

934 

935 assert self._multi_signals.is_uniform() 

936 

937 multi_signals = self._multi_signals.copy() 

938 multi_signals[0.5] = 1.0 

939 assert not multi_signals.is_uniform() 

940 

941 def test_copy(self) -> None: 

942 """Test :func:`colour.continuous.multi_signals.MultiSignals.copy` method.""" 

943 

944 assert self._multi_signals is not self._multi_signals.copy() 

945 assert self._multi_signals == self._multi_signals.copy() 

946 

947 def test_multi_signals_unpack_data(self) -> None: 

948 """ 

949 Test :func:`colour.continuous.multi_signals.MultiSignals.\ 

950multi_signals_unpack_data` method. 

951 """ 

952 

953 signals = MultiSignals.multi_signals_unpack_data(self._range_1) 

954 assert list(signals.keys()) == ["0"] 

955 np.testing.assert_array_equal(signals["0"].domain, self._domain_1) 

956 np.testing.assert_array_equal(signals["0"].range, self._range_1) 

957 

958 signals = MultiSignals.multi_signals_unpack_data(self._range_1, self._domain_2) 

959 assert list(signals.keys()) == ["0"] 

960 np.testing.assert_array_equal(signals["0"].domain, self._domain_2) 

961 np.testing.assert_array_equal(signals["0"].range, self._range_1) 

962 

963 signals = MultiSignals.multi_signals_unpack_data( 

964 self._range_1, dict(zip(self._domain_2, self._range_1, strict=True)).keys() 

965 ) 

966 np.testing.assert_array_equal(signals["0"].domain, self._domain_2) 

967 

968 signals = MultiSignals.multi_signals_unpack_data(self._range_2, self._domain_2) 

969 assert list(signals.keys()) == ["0", "1", "2"] 

970 np.testing.assert_array_equal(signals["0"].range, self._range_1) 

971 np.testing.assert_array_equal(signals["1"].range, self._range_1 + 10) 

972 np.testing.assert_array_equal(signals["2"].range, self._range_1 + 20) 

973 

974 signals = MultiSignals.multi_signals_unpack_data( 

975 next( 

976 iter( 

977 MultiSignals.multi_signals_unpack_data( 

978 dict(zip(self._domain_2, self._range_2, strict=True)) 

979 ).values() 

980 ) 

981 ) 

982 ) 

983 np.testing.assert_array_equal(signals["0"].range, self._range_1) 

984 

985 signals = MultiSignals.multi_signals_unpack_data( 

986 MultiSignals.multi_signals_unpack_data( 

987 dict(zip(self._domain_2, self._range_2, strict=True)) 

988 ).values() 

989 ) 

990 np.testing.assert_array_equal(signals["0"].range, self._range_1) 

991 np.testing.assert_array_equal(signals["1"].range, self._range_1 + 10) 

992 np.testing.assert_array_equal(signals["2"].range, self._range_1 + 20) 

993 

994 signals = MultiSignals.multi_signals_unpack_data( 

995 dict(zip(self._domain_2, self._range_2, strict=True)) 

996 ) 

997 assert list(signals.keys()) == ["0", "1", "2"] 

998 np.testing.assert_array_equal(signals["0"].range, self._range_1) 

999 np.testing.assert_array_equal(signals["1"].range, self._range_1 + 10) 

1000 np.testing.assert_array_equal(signals["2"].range, self._range_1 + 20) 

1001 

1002 signals = MultiSignals.multi_signals_unpack_data( 

1003 MultiSignals.multi_signals_unpack_data( 

1004 dict(zip(self._domain_2, self._range_2, strict=True)) 

1005 ) 

1006 ) 

1007 assert list(signals.keys()) == ["0", "1", "2"] 

1008 np.testing.assert_array_equal(signals["0"].range, self._range_1) 

1009 np.testing.assert_array_equal(signals["1"].range, self._range_1 + 10) 

1010 np.testing.assert_array_equal(signals["2"].range, self._range_1 + 20) 

1011 

1012 signals = MultiSignals.multi_signals_unpack_data( 

1013 dict(zip(self._domain_2, self._range_2, strict=True)), 

1014 labels=["0", "0", "0"], 

1015 ) 

1016 assert list(signals.keys()) == ["0 - 0", "0 - 1", "0 - 2"] 

1017 

1018 if is_pandas_installed(): 

1019 from pandas import DataFrame, Series # noqa: PLC0415 

1020 

1021 signals = MultiSignals.multi_signals_unpack_data( 

1022 Series(dict(zip(self._domain_1, self._range_1, strict=True))) 

1023 ) 

1024 assert list(signals.keys()) == ["0"] 

1025 np.testing.assert_array_equal(signals["0"].domain, self._domain_1) 

1026 np.testing.assert_array_equal(signals["0"].range, self._range_1) 

1027 

1028 data = dict(zip(["a", "b", "c"], tsplit(self._range_2), strict=True)) 

1029 signals = MultiSignals.multi_signals_unpack_data( 

1030 DataFrame(data, self._domain_1) 

1031 ) 

1032 assert list(signals.keys()) == ["a", "b", "c"] 

1033 np.testing.assert_array_equal(signals["a"].range, self._range_1) 

1034 np.testing.assert_array_equal(signals["b"].range, self._range_1 + 10) 

1035 np.testing.assert_array_equal(signals["c"].range, self._range_1 + 20) 

1036 

1037 def test_fill_nan(self) -> None: 

1038 """ 

1039 Test :meth:`colour.continuous.multi_signals.MultiSignals.fill_nan` 

1040 method. 

1041 """ 

1042 

1043 multi_signals = self._multi_signals.copy() 

1044 

1045 multi_signals[3:7] = np.nan 

1046 

1047 np.testing.assert_allclose( 

1048 multi_signals.fill_nan().range, 

1049 np.array( 

1050 [ 

1051 [10.0, 20.0, 30.0], 

1052 [20.0, 30.0, 40.0], 

1053 [30.0, 40.0, 50.0], 

1054 [40.0, 50.0, 60.0], 

1055 [50.0, 60.0, 70.0], 

1056 [60.0, 70.0, 80.0], 

1057 [70.0, 80.0, 90.0], 

1058 [80.0, 90.0, 100.0], 

1059 [90.0, 100.0, 110.0], 

1060 [100.0, 110.0, 120.0], 

1061 ] 

1062 ), 

1063 atol=TOLERANCE_ABSOLUTE_TESTS, 

1064 ) 

1065 

1066 multi_signals[3:7] = np.nan 

1067 

1068 np.testing.assert_allclose( 

1069 multi_signals.fill_nan(method="Constant").range, 

1070 np.array( 

1071 [ 

1072 [10.0, 20.0, 30.0], 

1073 [20.0, 30.0, 40.0], 

1074 [30.0, 40.0, 50.0], 

1075 [0.0, 0.0, 0.0], 

1076 [0.0, 0.0, 0.0], 

1077 [0.0, 0.0, 0.0], 

1078 [0.0, 0.0, 0.0], 

1079 [80.0, 90.0, 100.0], 

1080 [90.0, 100.0, 110.0], 

1081 [100.0, 110.0, 120.0], 

1082 ] 

1083 ), 

1084 atol=TOLERANCE_ABSOLUTE_TESTS, 

1085 ) 

1086 

1087 def test_domain_distance(self) -> None: 

1088 """ 

1089 Test :func:`colour.continuous.multi_signals.MultiSignals.\ 

1090domain_distance` method. 

1091 """ 

1092 

1093 np.testing.assert_allclose( 

1094 self._multi_signals.domain_distance(0.5), 

1095 0.5, 

1096 atol=TOLERANCE_ABSOLUTE_TESTS, 

1097 ) 

1098 

1099 np.testing.assert_allclose( 

1100 self._multi_signals.domain_distance(np.linspace(0, 9, 10) + 0.5), 

1101 np.array([0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]), 

1102 atol=TOLERANCE_ABSOLUTE_TESTS, 

1103 ) 

1104 

1105 def test_to_dataframe(self) -> None: 

1106 """ 

1107 Test :meth:`colour.continuous.multi_signals.MultiSignals.to_dataframe` 

1108 method. 

1109 """ 

1110 

1111 if is_pandas_installed(): 

1112 from pandas import DataFrame # noqa: PLC0415 

1113 

1114 data = dict(zip(["a", "b", "c"], tsplit(self._range_2), strict=True)) 

1115 

1116 attest( 

1117 MultiSignals(self._range_2, self._domain_2, labels=["a", "b", "c"]) 

1118 .to_dataframe() 

1119 .equals(DataFrame(data, self._domain_2)) 

1120 )