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

1"""Define the unit tests for the :mod:`colour.continuous.multi_signals` module."""

2

3 from __future__ import annotations

4

5 import pickle

6 import textwrap

7

8 import numpy as np

9 import pytest

10

11 from colour.algebra import CubicSplineInterpolator, Extrapolator, KernelInterpolator

12 from colour.constants import DTYPE_FLOAT_DEFAULT, TOLERANCE_ABSOLUTE_TESTS

13 from colour.continuous import MultiSignals, Signal

14 from 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

35 class 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.\

231 interpolator_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.\

280 extrapolator_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.\

308 function` 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.\

821 arithmetical_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.\

950 multi_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.\

1090 domain_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 )