मुख्य कंटेंट तक स्किप करें

F-ऑर्डर किए गए ऐरे में सुधार

समस्या विवरण

अगर NumPy ऐरे F-ऑर्डर में हैं, तो उनका Protobuf एनकोड/डिकोड विफल हो जाता है

परीक्षण

from mcbackend.npproto.utils import ndarray_to_numpy, ndarray_from_numpy

arrC = numpy.array([
[1,2,3],
[4,5,6],
], order="C")

arrF = numpy.array([
[1,2,3],
[4,5,6],
], order="F")

assert arrC.strides != arrF.strides

numpy.testing.assert_array_equal(
arrC,
ndarray_to_numpy(ndarray_from_numpy(arrC)),
)
numpy.testing.assert_array_equal(
arrF,
ndarray_to_numpy(ndarray_from_numpy(arrF)),
)

कोड में बदलाव

mcbackend/__init__.py

diff --git a/mcbackend/__init__.py b/mcbackend/__init__.py
index 35ef7b9..c6a43eb 100644
--- a/mcbackend/__init__.py
+++ b/mcbackend/__init__.py
@@ -1,23 +1,23 @@
"""
A framework agnostic implementation for storage of MCMC draws.
"""
from .backends.numpy import NumPyBackend
from .core import Backend, Chain, Run
from .meta import ChainMeta, Coordinate, DataVariable, ExtendedValue, RunMeta, Variable

# Backends
try:
from .backends import clickhouse
from .backends.clickhouse import ClickHouseBackend
except ModuleNotFoundError:
pass

# Adapters
try:
from .adapters import pymc
from .adapters.pymc import TraceBackend
except ModuleNotFoundError:
pass


-__version__ = "0.2.3"
+__version__ = "0.2.4"

mcbackend/npproto/utils.py

diff --git a/mcbackend/npproto/utils.py b/mcbackend/npproto/utils.py
index 27bc98c..8ae5396 100644
--- a/mcbackend/npproto/utils.py
+++ b/mcbackend/npproto/utils.py
@@ -1,40 +1,46 @@
"""
Helper functions such as converters between ``ndarray`` and ``Ndarray``.
"""
import numpy

from . import Ndarray


def ndarray_from_numpy(arr: numpy.ndarray) -> Ndarray:
dt = str(arr.dtype)
if "datetime64" in dt:
# datetime64 doesn't support the buffer protocol.
# See https://github.com/numpy/numpy/issues/4983
# This is a hack that automatically encodes it as int64.
arr = arr.astype("int64")
+ # With non-C-ordered arrays (e.g. Fortran-ordered) the underlying buffer
+ # does not match the strides of the original array anymore, because
+ # ``bytes(arr.data)`` always returns the data in C-order.
+ # Therefore, the array is made C-contiguous before extracting data and strides.
+ if not arr.flags.c_contiguous:
+ arr = numpy.ascontiguousarray(arr)
return Ndarray(
shape=list(arr.shape),
dtype=dt,
data=bytes(arr.data),
strides=list(arr.strides),
)


def ndarray_to_numpy(nda: Ndarray) -> numpy.ndarray:
arr: numpy.ndarray
if "datetime64" in nda.dtype:
# Backwards conversion: The data was stored as int64.
arr = numpy.ndarray(
buffer=nda.data,
shape=nda.shape,
dtype="int64",
strides=nda.strides,
).astype(nda.dtype)
else:
arr = numpy.ndarray(
buffer=nda.data,
shape=nda.shape,
dtype=numpy.dtype(nda.dtype),
strides=nda.strides,
)

mcbackend/test_npproto.py

diff --git a/mcbackend/test_npproto.py b/mcbackend/test_npproto.py
index fc88d29..fd88e7c 100644
--- a/mcbackend/test_npproto.py
+++ b/mcbackend/test_npproto.py
@@ -1,45 +1,48 @@
from datetime import datetime

import numpy
import pytest

from mcbackend import npproto
from mcbackend.npproto import utils


class TestUtils:
@pytest.mark.parametrize(
"arr",
[
numpy.arange(5),
numpy.random.uniform(size=(2, 3)),
numpy.array(5),
numpy.array(["hello", "world"]),
numpy.array([datetime(2020, 3, 4, 5, 6, 7, 8), datetime(2020, 3, 4, 5, 6, 7, 9)]),
numpy.array(
[datetime(2020, 3, 4, 5, 6, 7, 8), datetime(2020, 3, 4, 5, 6, 7, 9)],
dtype="datetime64",
),
numpy.array([(1, 2), (3, 2, 1)], dtype=object),
+ numpy.array([[1, 2, 3], [4, 5, 6]], order="C"),
+ numpy.array([[1, 2, 3], [4, 5, 6]], order="F"),
+ numpy.arange(12).reshape(3, 4)[::2, ::2],
],
)
def test_conversion(self, arr: numpy.ndarray):
nda = utils.ndarray_from_numpy(arr)
enc = bytes(nda)
dec = npproto.Ndarray().parse(enc)
assert isinstance(dec.data, bytes)
result = utils.ndarray_to_numpy(dec)
numpy.testing.assert_array_equal(result, arr)
pass

@pytest.mark.parametrize("shape", [(5,), (2, 3), (2, 3, 5), (5, 2, 1, 7)])
@pytest.mark.parametrize("order", "CF")
def test_byteorders(self, shape, order):
arr = numpy.arange(numpy.prod(shape)).reshape(shape, order=order)

nda = utils.ndarray_from_numpy(arr)
assert nda.order == "CF"[arr.flags.f_contiguous]

dec = utils.ndarray_to_numpy(nda)
numpy.testing.assert_array_equal(arr, dec)
pass

कमिट संदेश

गैर-सन्निहित (non-contiguous) NumPy ऐरे के एनकोडिंग को ठीक करें

`bytes(arr.data)` हमेशा बफर सामग्री को C क्रम में लौटाता है, लेकिन
Fortran-ऑर्डर (या अन्य गैर-C सन्निहित) ऐरे को एनकोड करने से
`ndarray_from_numpy` और `ndarray_to_numpy` के माध्यम से डेटा राउंड-ट्रिप में खराबी आती है।
जब इसके मूल स्ट्राइड्स संग्रहीत किए जाते हैं, तो ऐरे अव्यवस्थित हो जाता है।

स्ट्राइड्स और डेटा निकालने से पहले ऐरे को C-सन्निहित बनाने के लिए
`numpy.ascontiguousarray` का उपयोग करें। अब बफर और स्ट्राइड्स
सुसंगत रहते हैं।

Closes #2124

नोट: संस्करण `0.2.4` C-ऑर्डर, F-ऑर्डर और गैर-सन्निहित ऐरे के लिए राउंड-ट्रिप परीक्षण जोड़ता है।

पुल रिक्वेस्ट

सारांश

NumPy ऐरे की Protobuf राउंड-ट्रिपिंग चुपचाप डेटा को भ्रष्ट कर देती है जब स्रोत ऐरे C-सन्निहित नहीं होता है (उदाहरण के लिए, Fortran-ऑर्डर)। यह सुधार सुनिश्चित करता है कि सभी मेमोरी लेआउट एक एनकोड/डिकोड चक्र में सुरक्षित रहें।

arrF = numpy.array([[1, 2, 3], [4, 5, 6]], order="F")
numpy.testing.assert_array_equal(
arrF,
ndarray_to_numpy(ndarray_from_numpy(arrF)), # इस सुधार से पहले विफल हुआ
)

मूल कारण

ndarray_from_numpy में एनकोड पथ इनपुट ऐरे के कच्चे बफर और स्ट्राइड्स दोनों को संग्रहीत करता है। समस्या दोनों के बीच बेमेल होने की है:

  1. एनकोडिंग: bytes(arr.data) अंतर्निहित मेमोरी बफर को कॉपी करता है। गैर-C-सन्निहित ऐरे के लिए, यह कॉपी चुपचाप C क्रम में पुनर्व्यवस्थित हो जाती है।
  2. स्ट्राइड्स जस के तस रखे गए: संबंधित strides को उनके मूल (गैर-C) प्रारूप में सहेजा जाता है, क्योंकि उन पर कोई पुनर्व्यवस्था लागू नहीं होती है।
  3. डिकोडिंग: numpy.ndarray(buffer=..., strides=...) मूल स्ट्राइड्स के साथ C-ऑर्डर बफर की व्याख्या करके ऐरे का पुनर्निर्माण करता है, जो कि एक बेमेल है और एक अव्यवस्थित ऐरे उत्पन्न करता है।

सुधार

बफर और स्ट्राइड्स को निकालने से पहले numpy.ascontiguousarray को कॉल करें, ताकि दोनों एक ही मेमोरी लेआउट को दर्शाएं:

  • mcbackend/npproto/utils.py: data और strides निकालने से पहले ऐरे को C-सन्निहित बनाएं। संग्रहीत स्ट्राइड्स अब हमेशा संग्रहीत बफर लेआउट से मेल खाते हैं। जो ऐरे पहले से ही C-सन्निहित हैं, वे अप्रभावित रहते हैं।
  • mcbackend/test_npproto.py: C-ऑर्डर, F-ऑर्डर और गैर-सन्निहित (स्लाइस) ऐरे के लिए स्पष्ट राउंड-ट्रिप परीक्षण मामले जोड़ें।

परीक्षण

TestUtils.test_conversion में परीक्षण मैट्रिक्स अब कवर करता है:

  • numpy.array([[1, 2, 3], [4, 5, 6]], order="C")
  • numpy.array([[1, 2, 3], [4, 5, 6]], order="F")
  • numpy.arange(12).reshape(3, 4)[::2, ::2] (गैर-सन्निहित दृश्य)

सभी मौजूदा dtype मामले (int, float, str, datetime, object) बिना किसी बदलाव के पास होते रहते हैं।

नोट्स

  • संस्करण को 0.2.4 पर अपग्रेड करता है।
  • ndarray_to_numpy अपरिवर्तित है; डिकोडिंग पक्ष में किसी बदलाव की आवश्यकता नहीं है।

कैसे परीक्षण करें

  1. pytest mcbackend/test_npproto.py चलाएं। तीन नए पैरामीट्राइज्ड मामले (C-ऑर्डर, F-ऑर्डर, गैर-सन्निहित स्लाइस) पास होने चाहिए।
  2. सत्यापित करें कि सभी पूर्व-मौजूदा dtype मामले (int, float, str, datetime, object) अभी भी बिना किसी बदलाव के पास होते हैं।