Skip to content

Commit 93deeab

Browse files
committed
Implement support for better ndarray copy management in colour.continuous.Signal class.
1 parent e4dacbc commit 93deeab

File tree

5 files changed

+271
-18
lines changed

5 files changed

+271
-18
lines changed

colour/continuous/signal.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
full,
5151
is_pandas_installed,
5252
multiline_repr,
53+
ndarray_copy,
54+
ndarray_copy_enable,
5355
optional,
5456
required,
5557
runtime_warning,
@@ -329,7 +331,7 @@ def domain(self) -> NDArrayFloat:
329331
Continuous signal independent domain variable :math:`x`.
330332
"""
331333

332-
return np.copy(self._domain)
334+
return ndarray_copy(self._domain)
333335

334336
@domain.setter
335337
def domain(self, value: ArrayLike):
@@ -372,7 +374,7 @@ def range(self) -> NDArrayFloat: # noqa: A003
372374
Continuous signal corresponding range variable :math:`y`.
373375
"""
374376

375-
return np.copy(self._range)
377+
return ndarray_copy(self._range)
376378

377379
@range.setter
378380
def range(self, value: ArrayLike): # noqa: A003
@@ -531,7 +533,7 @@ def function(self) -> Callable:
531533
if self._domain.size != 0 and self._range.size != 0:
532534
self._function = self._extrapolator(
533535
self._interpolator(
534-
self.domain, self.range, **self._interpolator_kwargs
536+
self._domain, self._range, **self._interpolator_kwargs
535537
),
536538
**self._extrapolator_kwargs,
537539
)
@@ -590,7 +592,7 @@ def __str__(self) -> str:
590592
[ 9. 100.]]
591593
"""
592594

593-
return str(tstack([self.domain, self.range]))
595+
return str(tstack([self._domain, self._range]))
594596

595597
def __repr__(self) -> str:
596598
"""
@@ -629,17 +631,17 @@ def __repr__(self) -> str:
629631
[
630632
{
631633
"formatter": lambda x: repr( # noqa: ARG005
632-
tstack([self.domain, self.range])
634+
tstack([self._domain, self._range])
633635
),
634636
},
635637
{
636638
"name": "interpolator",
637-
"formatter": lambda x: self.interpolator.__name__, # noqa: ARG005
639+
"formatter": lambda x: self._interpolator.__name__, # noqa: ARG005
638640
},
639641
{"name": "interpolator_kwargs"},
640642
{
641643
"name": "extrapolator",
642-
"formatter": lambda x: self.extrapolator.__name__, # noqa: ARG005
644+
"formatter": lambda x: self._extrapolator.__name__, # noqa: ARG005
643645
},
644646
{"name": "extrapolator_kwargs"},
645647
],
@@ -657,12 +659,12 @@ def __hash__(self) -> int:
657659

658660
return hash(
659661
(
660-
self.domain.tobytes(),
661-
self.range.tobytes(),
662-
self.interpolator.__name__,
663-
repr(self.interpolator_kwargs),
664-
self.extrapolator.__name__,
665-
repr(self.extrapolator_kwargs),
662+
self._domain.tobytes(),
663+
self._range.tobytes(),
664+
self._interpolator.__name__,
665+
repr(self._interpolator_kwargs),
666+
self._extrapolator.__name__,
667+
repr(self._extrapolator_kwargs),
666668
)
667669
)
668670

@@ -842,6 +844,7 @@ def __contains__(self, x: ArrayLike) -> bool:
842844
)
843845
)
844846

847+
@ndarray_copy_enable(False)
845848
def __eq__(self, other: Any) -> bool:
846849
"""
847850
Return whether the continuous signal is equal to given other object.
@@ -948,7 +951,7 @@ def _fill_domain_nan(
948951
variable.
949952
"""
950953

951-
self.domain = fill_nan(self.domain, method, default)
954+
self.domain = fill_nan(self._domain, method, default)
952955

953956
def _fill_range_nan(
954957
self,
@@ -973,8 +976,9 @@ def _fill_range_nan(
973976
variable.
974977
"""
975978

976-
self.range = fill_nan(self.range, method, default)
979+
self.range = fill_nan(self._range, method, default)
977980

981+
@ndarray_copy_enable(False)
978982
def arithmetical_operation(
979983
self,
980984
a: ArrayLike | AbstractContinuousFunction,
@@ -1073,7 +1077,7 @@ def arithmetical_operation(
10731077
exclusive_or = np.setxor1d(self._domain, a.domain)
10741078
self[exclusive_or] = full(exclusive_or.shape, np.nan)
10751079
else:
1076-
self.range = ioperator(self.range, a)
1080+
self.range = ioperator(self._range, a)
10771081

10781082
return self
10791083
else:

colour/utilities/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@
9999
from_range_100,
100100
from_range_degrees,
101101
from_range_int,
102+
is_ndarray_copy_enabled,
103+
set_ndarray_copy_enable,
104+
ndarray_copy_enable,
105+
ndarray_copy,
102106
closest_indexes,
103107
closest,
104108
interval,
@@ -217,6 +221,10 @@
217221
"from_range_100",
218222
"from_range_degrees",
219223
"from_range_int",
224+
"is_ndarray_copy_enabled",
225+
"set_ndarray_copy_enable",
226+
"ndarray_copy_enable",
227+
"ndarray_copy",
220228
"closest_indexes",
221229
"closest",
222230
"normalise_maximum",

colour/utilities/array.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@
8888
"from_range_100",
8989
"from_range_degrees",
9090
"from_range_int",
91+
"is_ndarray_copy_enabled",
92+
"set_ndarray_copy_enable",
93+
"ndarray_copy_enable",
94+
"ndarray_copy",
9195
"closest_indexes",
9296
"closest",
9397
"interval",
@@ -1788,6 +1792,142 @@ def from_range_int(
17881792
return a
17891793

17901794

1795+
_NDARRAY_COPY_ENABLED: bool = True
1796+
"""
1797+
Global variable storing the current *Colour* state for
1798+
:class:`numpy.ndarray` copy.
1799+
"""
1800+
1801+
1802+
def is_ndarray_copy_enabled() -> bool:
1803+
"""
1804+
Return whether *Colour* :class:`numpy.ndarray` copy is enabled: Various API
1805+
objects return a copy of their internal :class:`numpy.ndarray` for safety
1806+
purposes but this can be a slow operation impacting performance.
1807+
1808+
Returns
1809+
-------
1810+
:class:`bool`
1811+
Whether *Colour* :class:`numpy.ndarray` copy is enabled.
1812+
1813+
Examples
1814+
--------
1815+
>>> with ndarray_copy_enable(False):
1816+
... is_ndarray_copy_enabled()
1817+
...
1818+
False
1819+
>>> with ndarray_copy_enable(True):
1820+
... is_ndarray_copy_enabled()
1821+
...
1822+
True
1823+
"""
1824+
1825+
return _NDARRAY_COPY_ENABLED
1826+
1827+
1828+
def set_ndarray_copy_enable(enable: bool):
1829+
"""
1830+
Set *Colour* :class:`numpy.ndarray` copy enabled state.
1831+
1832+
Parameters
1833+
----------
1834+
enable
1835+
Whether to enable *Colour* :class:`numpy.ndarray` copy.
1836+
1837+
Examples
1838+
--------
1839+
>>> with ndarray_copy_enable(is_ndarray_copy_enabled()):
1840+
... print(is_ndarray_copy_enabled())
1841+
... set_ndarray_copy_enable(False)
1842+
... print(is_ndarray_copy_enabled())
1843+
...
1844+
True
1845+
False
1846+
"""
1847+
1848+
global _NDARRAY_COPY_ENABLED
1849+
1850+
_NDARRAY_COPY_ENABLED = enable
1851+
1852+
1853+
class ndarray_copy_enable:
1854+
"""
1855+
Define a context manager and decorator temporarily setting *Colour*
1856+
:class:`numpy.ndarray` copy enabled state.
1857+
1858+
Parameters
1859+
----------
1860+
enable
1861+
Whether to enable or disable *Colour* :class:`numpy.ndarray` copy.
1862+
"""
1863+
1864+
def __init__(self, enable: bool) -> None:
1865+
self._enable = enable
1866+
self._previous_state = is_ndarray_copy_enabled()
1867+
1868+
def __enter__(self) -> ndarray_copy_enable:
1869+
"""
1870+
Set the *Colour* :class:`numpy.ndarray` copy enabled state
1871+
upon entering the context manager.
1872+
"""
1873+
1874+
set_ndarray_copy_enable(self._enable)
1875+
1876+
return self
1877+
1878+
def __exit__(self, *args: Any):
1879+
"""
1880+
Set the *Colour* :class:`numpy.ndarray` copy enabled state
1881+
upon exiting the context manager.
1882+
"""
1883+
1884+
set_ndarray_copy_enable(self._previous_state)
1885+
1886+
def __call__(self, function: Callable) -> Callable:
1887+
"""Call the wrapped definition."""
1888+
1889+
@functools.wraps(function)
1890+
def wrapper(*args: Any, **kwargs: Any) -> Any:
1891+
with self:
1892+
return function(*args, **kwargs)
1893+
1894+
return wrapper
1895+
1896+
1897+
def ndarray_copy(a: NDArray) -> NDArray:
1898+
"""
1899+
Return a :class:`numpy.ndarray` copy if the relevant *Colour* state is
1900+
enabled: Various API objects return a copy of their internal
1901+
:class:`numpy.ndarray` for safety purposes but this can be a slow operation
1902+
impacting performance.
1903+
1904+
Parameters
1905+
----------
1906+
a
1907+
Array :math:`a` to return a copy of.
1908+
1909+
Returns
1910+
-------
1911+
:class:`numpy.ndarray`
1912+
Array :math:`a` copy according to *Colour* state.
1913+
1914+
Examples
1915+
--------
1916+
>>> a = np.linspace(0, 1, 10)
1917+
>>> id(a) == id(ndarray_copy(a))
1918+
False
1919+
>>> with ndarray_copy_enable(False):
1920+
... id(a) == id(ndarray_copy(a))
1921+
...
1922+
True
1923+
"""
1924+
1925+
if _NDARRAY_COPY_ENABLED:
1926+
return np.copy(a)
1927+
else:
1928+
return a
1929+
1930+
17911931
def closest_indexes(a: ArrayLike, b: ArrayLike) -> NDArray:
17921932
"""
17931933
Return the array :math:`a` closest element indexes to the reference array

0 commit comments

Comments
 (0)