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

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) अभी भी बिना किसी बदलाव के पास होते हैं।