diff --git a/nada_ai/client.py b/nada_ai/client.py index c5108db..b97b172 100644 --- a/nada_ai/client.py +++ b/nada_ai/client.py @@ -5,7 +5,7 @@ import nada_algebra as na import nada_algebra.client as na_client from collections import OrderedDict -from typing import Any, Dict, Iterable, Union +from typing import Any, Dict, Union from sklearn.linear_model import ( LinearRegression, @@ -26,18 +26,20 @@ nillion.PublicVariableInteger, nillion.PublicVariableUnsignedInteger, ] +_Tensor = Union[np.ndarray, torch.Tensor] +_LinearModel = Union[LinearRegression, LogisticRegression, LogisticRegressionCV] class ModelClient: """ML model client""" - def __init__(self, model: Any, state_dict: OrderedDict[str, np.ndarray]) -> None: + def __init__(self, model: Any, state_dict: OrderedDict[str, _Tensor]) -> None: """ Initialization. Args: model (Any): Model object to wrap around. - state_dict (OrderedDict[str, np.ndarray]): Model state. + state_dict (OrderedDict[str, _Tensor]): Model state. """ self.model = model self.state_dict = state_dict @@ -64,31 +66,17 @@ def from_sklearn(cls, model: sklearn.base.BaseEstimator) -> "ModelClient": Args: model (sklearn.base.BaseEstimator): Sklearn estimator object. + Raises: + NotImplementedError: Raised when unsupported Scikit-learn model is passed. + Returns: ModelClient: Instantiated model client. """ - if not isinstance(model, sklearn.base.BaseEstimator): - raise TypeError( - "Cannot interpret type `%s` as Sklearn model. Expected (sub)type of `sklearn.base.BaseEstimator`" - % type(model).__name__ - ) - - if isinstance(model, LinearRegression): - state_dict = OrderedDict( - { - "coef": model.coef_, - "intercept": ( - model.intercept_ - if isinstance(model.intercept_, Iterable) - else np.array([model.intercept_]) - ), - } - ) - elif isinstance(model, (LogisticRegression, LogisticRegressionCV)): + if isinstance(model, _LinearModel): state_dict = OrderedDict( { "coef": model.coef_, - "intercept": model.intercept_, + "intercept": np.array(model.intercept_), } ) else: @@ -119,9 +107,8 @@ def export_state_as_secrets( state_secrets = {} for state_layer_name, state_layer_weight in self.state_dict.items(): layer_name = f"{name}_{state_layer_name}" - state_secret = na_client.array( - self.__ensure_numpy(state_layer_weight), layer_name, nada_type - ) + layer_state = self.__ensure_numpy(state_layer_weight) + state_secret = na_client.array(layer_state, layer_name, nada_type) state_secrets.update(state_secret) return state_secrets diff --git a/nada_ai/nn/activations.py b/nada_ai/nn/activations.py index 6839351..8ce4c2f 100644 --- a/nada_ai/nn/activations.py +++ b/nada_ai/nn/activations.py @@ -3,7 +3,14 @@ from typing import Union import nada_algebra as na from nada_ai.nn.module import Module -from nada_dsl import Integer, NadaType, SecretBoolean, PublicBoolean +from nada_dsl import ( + Integer, + NadaType, + SecretBoolean, + PublicBoolean, + SecretInteger, + PublicInteger, +) class ReLU(Module): @@ -19,11 +26,8 @@ def forward(self, x: na.NadaArray) -> na.NadaArray: Returns: na.NadaArray: Module output. """ - if x.is_rational: - mask = x.apply(self._rational_relu) - else: - mask = x.apply(self._relu) - + relu = self._rational_relu if x.is_rational else self._relu + mask = x.apply(relu) return x * mask @staticmethod @@ -43,7 +47,7 @@ def _rational_relu( return above_zero.if_else(na.rational(1), na.rational(0)) @staticmethod - def _relu(value: NadaType) -> NadaType: + def _relu(value: NadaType) -> Union[PublicInteger, SecretInteger]: """ Element-wise ReLU logic for NadaType values. @@ -51,7 +55,7 @@ def _relu(value: NadaType) -> NadaType: value (NadaType): Input nada value. Returns: - NadaType: Output nada value. + Union[PublicInteger, SecretInteger]: Output nada value. """ above_zero: Union[PublicBoolean, SecretBoolean] = value > Integer(0) return above_zero.if_else(Integer(1), Integer(0)) diff --git a/nada_ai/nn/layers.py b/nada_ai/nn/layers.py index cd09bd8..5d87fdd 100644 --- a/nada_ai/nn/layers.py +++ b/nada_ai/nn/layers.py @@ -67,7 +67,7 @@ def __init__( """ if isinstance(kernel_size, int): kernel_size = (kernel_size, kernel_size) - kernel_height, kernel_width = kernel_size + self.kernel_size = kernel_size if isinstance(padding, int): padding = (padding, padding) @@ -77,9 +77,7 @@ def __init__( stride = (stride, stride) self.stride = stride - self.weight = Parameter( - (out_channels, in_channels, kernel_height, kernel_width) - ) + self.weight = Parameter((out_channels, in_channels, *kernel_size)) self.bias = Parameter(out_channels) if include_bias else None def forward(self, x: na.NadaArray) -> na.NadaArray: @@ -93,18 +91,17 @@ def forward(self, x: na.NadaArray) -> na.NadaArray: na.NadaArray: Module output. """ unbatched = False - if len(x.shape) == 3: + if x.ndim == 3: # Assume unbatched --> assign batch_size of 1 x = x.reshape(1, *x.shape) unbatched = True - batch_size, _, input_rows, input_cols = x.shape + batch_size, _, input_height, input_width = x.shape out_channels, _, kernel_rows, kernel_cols = self.weight.shape if any(pad > 0 for pad in self.padding): - # TODO: avoid side-step to NumPy - padded_input = np.pad( - x.inner, + x = na.pad( + x, [ (0, 0), (0, 0), @@ -113,48 +110,39 @@ def forward(self, x: na.NadaArray) -> na.NadaArray: ], mode="constant", ) - padded_input = np.frompyfunc( - lambda x: Integer(x.item()) if isinstance(x, np.int64) else x, 1, 1 - )(padded_input) - else: - padded_input = x.inner - - output_rows = (input_rows + 2 * self.padding[0] - kernel_rows) // self.stride[ - 0 - ] + 1 - output_cols = (input_cols + 2 * self.padding[1] - kernel_cols) // self.stride[ - 1 - ] + 1 - - output_tensor = np.zeros( - (batch_size, out_channels, output_rows, output_cols) - ).astype(Integer) + + out_height = ( + input_height + 2 * self.padding[0] - self.kernel_size[0] + ) // self.stride[0] + 1 + out_width = ( + input_width + 2 * self.padding[1] - self.kernel_size[1] + ) // self.stride[1] + 1 + + output_tensor = na.zeros((batch_size, out_channels, out_height, out_width)) for b in range(batch_size): for oc in range(out_channels): - for i in range(output_rows): - for j in range(output_cols): + for i in range(out_height): + for j in range(out_width): start_i = i * self.stride[0] start_j = j * self.stride[1] - receptive_field = padded_input[ + receptive_field = x[ b, :, start_i : start_i + kernel_rows, start_j : start_j + kernel_cols, ] - output_tensor[b, oc, i, j] = np.sum( - self.weight.inner[oc] * receptive_field + output_tensor[b, oc, i, j] = na.sum( + self.weight[oc] * receptive_field ) if self.bias is not None: - output_tensor = output_tensor + self.bias.inner.reshape( - 1, out_channels, 1, 1 - ) + output_tensor += self.bias.reshape(1, out_channels, 1, 1) if unbatched: output_tensor = output_tensor[0] - return na.NadaArray(output_tensor) + return output_tensor class AvgPool2d(Module): @@ -199,7 +187,7 @@ def forward(self, x: na.NadaArray) -> na.NadaArray: na.NadaArray: Module output. """ unbatched = False - if len(x.shape) == 3: + if x.ndim == 3: # Assume unbatched --> assign batch_size of 1 x = x.reshape(1, *x.shape) unbatched = True @@ -208,9 +196,8 @@ def forward(self, x: na.NadaArray) -> na.NadaArray: is_rational = x.is_rational if any(pad > 0 for pad in self.padding): - # TODO: avoid side-step to NumPy - padded_input = np.pad( - x.inner, + x = na.pad( + x, ( (0, 0), (0, 0), @@ -219,44 +206,37 @@ def forward(self, x: na.NadaArray) -> na.NadaArray: ), mode="constant", ) - padded_input = np.frompyfunc( - lambda x: Integer(x.item()) if isinstance(x, np.int64) else x, 1, 1 - )(padded_input) - else: - padded_input = x.inner - output_height = ( + out_height = ( input_height + 2 * self.padding[0] - self.kernel_size[0] ) // self.stride[0] + 1 - output_width = ( + out_width = ( input_width + 2 * self.padding[1] - self.kernel_size[1] ) // self.stride[1] + 1 - output_array = np.zeros( - (batch_size, channels, output_height, output_width) - ).astype(Integer) + output_tensor = na.zeros((batch_size, channels, out_height, out_width)) for b in range(batch_size): for c in range(channels): - for i in range(output_height): - for j in range(output_width): + for i in range(out_height): + for j in range(out_width): start_h = i * self.stride[0] start_w = j * self.stride[1] end_h = start_h + self.kernel_size[0] end_w = start_w + self.kernel_size[1] - pool_region = padded_input[b, c, start_h:end_h, start_w:end_w] + pool_region = x[b, c, start_h:end_h, start_w:end_w] if is_rational: pool_size = na.rational(pool_region.size) else: pool_size = Integer(pool_region.size) - output_array[b, c, i, j] = np.sum(pool_region) / pool_size + output_tensor[b, c, i, j] = na.sum(pool_region) / pool_size if unbatched: - output_array = output_array[0] + output_tensor = output_tensor[0] - return na.NadaArray(output_array) + return na.NadaArray(output_tensor) class Flatten(Module): diff --git a/nada_ai/nn/parameter.py b/nada_ai/nn/parameter.py index 9aa0ff1..b0c2df8 100644 --- a/nada_ai/nn/parameter.py +++ b/nada_ai/nn/parameter.py @@ -5,7 +5,6 @@ import numpy as np import nada_algebra as na from nada_ai.exceptions import MismatchedShapesException -from nada_dsl import Integer _ShapeLike = Union[int, Iterable[int]] @@ -20,9 +19,8 @@ def __init__(self, shape: _ShapeLike) -> None: Args: shape (_ShapeLike, optional): Parameter array shape. """ - zeros = np.zeros(shape, dtype=int) - zeros = np.frompyfunc(Integer, 1, 1)(zeros) - super().__init__(inner=zeros) + zeros = na.zeros(shape) + super().__init__(inner=zeros.inner) def numel(self) -> int: """ diff --git a/poetry.lock b/poetry.lock index c78b1b0..e09e6aa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -328,13 +328,13 @@ files = [ [[package]] name = "nada-algebra" -version = "0.3.1" +version = "0.3.2" description = "Nada-Algebra is a Python library designed for algebraic operations on NumPy-like array objects on top of Nada DSL and Nillion Network." optional = false python-versions = "<4.0,>=3.10" files = [ - {file = "nada_algebra-0.3.1-py3-none-any.whl", hash = "sha256:0d34c7088c63fbcab3af84176b29dd208bee132f3f293cba9dddbaba782f998d"}, - {file = "nada_algebra-0.3.1.tar.gz", hash = "sha256:61907e1c8fe028b2fc91745f03f3941190a7217c3b3d4866fe7660037fe298ad"}, + {file = "nada_algebra-0.3.2-py3-none-any.whl", hash = "sha256:d83e7c71854bb6230ad51009a273d2eb9c4f4c4117a5808f8cd485f1f9a83d6f"}, + {file = "nada_algebra-0.3.2.tar.gz", hash = "sha256:1aed21b53ad7e764dccd27aec2d8fd25faaef8159429f81a7341499f5554d09e"}, ] [package.dependencies] @@ -843,31 +843,31 @@ files = [ [[package]] name = "torch" -version = "2.3.0" +version = "2.3.1" description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" optional = false python-versions = ">=3.8.0" files = [ - {file = "torch-2.3.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:d8ea5a465dbfd8501f33c937d1f693176c9aef9d1c1b0ca1d44ed7b0a18c52ac"}, - {file = "torch-2.3.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:09c81c5859a5b819956c6925a405ef1cdda393c9d8a01ce3851453f699d3358c"}, - {file = "torch-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:1bf023aa20902586f614f7682fedfa463e773e26c58820b74158a72470259459"}, - {file = "torch-2.3.0-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:758ef938de87a2653bba74b91f703458c15569f1562bf4b6c63c62d9c5a0c1f5"}, - {file = "torch-2.3.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:493d54ee2f9df100b5ce1d18c96dbb8d14908721f76351e908c9d2622773a788"}, - {file = "torch-2.3.0-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:bce43af735c3da16cc14c7de2be7ad038e2fbf75654c2e274e575c6c05772ace"}, - {file = "torch-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:729804e97b7cf19ae9ab4181f91f5e612af07956f35c8b2c8e9d9f3596a8e877"}, - {file = "torch-2.3.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:d24e328226d8e2af7cf80fcb1d2f1d108e0de32777fab4aaa2b37b9765d8be73"}, - {file = "torch-2.3.0-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:b0de2bdc0486ea7b14fc47ff805172df44e421a7318b7c4d92ef589a75d27410"}, - {file = "torch-2.3.0-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:a306c87a3eead1ed47457822c01dfbd459fe2920f2d38cbdf90de18f23f72542"}, - {file = "torch-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:f9b98bf1a3c8af2d4c41f0bf1433920900896c446d1ddc128290ff146d1eb4bd"}, - {file = "torch-2.3.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:dca986214267b34065a79000cee54232e62b41dff1ec2cab9abc3fc8b3dee0ad"}, - {file = "torch-2.3.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:20572f426965dd8a04e92a473d7e445fa579e09943cc0354f3e6fef6130ce061"}, - {file = "torch-2.3.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e65ba85ae292909cde0dde6369826d51165a3fc8823dc1854cd9432d7f79b932"}, - {file = "torch-2.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:5515503a193781fd1b3f5c474e89c9dfa2faaa782b2795cc4a7ab7e67de923f6"}, - {file = "torch-2.3.0-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:6ae9f64b09516baa4ef890af0672dc981c20b1f0d829ce115d4420a247e88fba"}, - {file = "torch-2.3.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cd0dc498b961ab19cb3f8dbf0c6c50e244f2f37dbfa05754ab44ea057c944ef9"}, - {file = "torch-2.3.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:e05f836559251e4096f3786ee99f4a8cbe67bc7fbedba8ad5e799681e47c5e80"}, - {file = "torch-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:4fb27b35dbb32303c2927da86e27b54a92209ddfb7234afb1949ea2b3effffea"}, - {file = "torch-2.3.0-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:760f8bedff506ce9e6e103498f9b1e9e15809e008368594c3a66bf74a8a51380"}, + {file = "torch-2.3.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:605a25b23944be5ab7c3467e843580e1d888b8066e5aaf17ff7bf9cc30001cc3"}, + {file = "torch-2.3.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f2357eb0965583a0954d6f9ad005bba0091f956aef879822274b1bcdb11bd308"}, + {file = "torch-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:32b05fe0d1ada7f69c9f86c14ff69b0ef1957a5a54199bacba63d22d8fab720b"}, + {file = "torch-2.3.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:7c09a94362778428484bcf995f6004b04952106aee0ef45ff0b4bab484f5498d"}, + {file = "torch-2.3.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:b2ec81b61bb094ea4a9dee1cd3f7b76a44555375719ad29f05c0ca8ef596ad39"}, + {file = "torch-2.3.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:490cc3d917d1fe0bd027057dfe9941dc1d6d8e3cae76140f5dd9a7e5bc7130ab"}, + {file = "torch-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5802530783bd465fe66c2df99123c9a54be06da118fbd785a25ab0a88123758a"}, + {file = "torch-2.3.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:a7dd4ed388ad1f3d502bf09453d5fe596c7b121de7e0cfaca1e2017782e9bbac"}, + {file = "torch-2.3.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:a486c0b1976a118805fc7c9641d02df7afbb0c21e6b555d3bb985c9f9601b61a"}, + {file = "torch-2.3.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:224259821fe3e4c6f7edf1528e4fe4ac779c77addaa74215eb0b63a5c474d66c"}, + {file = "torch-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:e5fdccbf6f1334b2203a61a0e03821d5845f1421defe311dabeae2fc8fbeac2d"}, + {file = "torch-2.3.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:3c333dc2ebc189561514eda06e81df22bf8fb64e2384746b2cb9f04f96d1d4c8"}, + {file = "torch-2.3.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:07e9ba746832b8d069cacb45f312cadd8ad02b81ea527ec9766c0e7404bb3feb"}, + {file = "torch-2.3.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:462d1c07dbf6bb5d9d2f3316fee73a24f3d12cd8dacf681ad46ef6418f7f6626"}, + {file = "torch-2.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff60bf7ce3de1d43ad3f6969983f321a31f0a45df3690921720bcad6a8596cc4"}, + {file = "torch-2.3.1-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:bee0bd33dc58aa8fc8a7527876e9b9a0e812ad08122054a5bff2ce5abf005b10"}, + {file = "torch-2.3.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:aaa872abde9a3d4f91580f6396d54888620f4a0b92e3976a6034759df4b961ad"}, + {file = "torch-2.3.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:3d7a7f7ef21a7520510553dc3938b0c57c116a7daee20736a9e25cbc0e832bdc"}, + {file = "torch-2.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:4777f6cefa0c2b5fa87223c213e7b6f417cf254a45e5829be4ccd1b2a4ee1011"}, + {file = "torch-2.3.1-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:2bb5af780c55be68fe100feb0528d2edebace1d55cb2e351de735809ba7391eb"}, ] [package.dependencies] @@ -888,7 +888,7 @@ nvidia-cusparse-cu12 = {version = "12.1.0.106", markers = "platform_system == \" nvidia-nccl-cu12 = {version = "2.20.5", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} nvidia-nvtx-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} sympy = "*" -triton = {version = "2.3.0", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and python_version < \"3.12\""} +triton = {version = "2.3.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and python_version < \"3.12\""} typing-extensions = ">=4.8.0" [package.extras] @@ -897,17 +897,17 @@ optree = ["optree (>=0.9.1)"] [[package]] name = "triton" -version = "2.3.0" +version = "2.3.1" description = "A language and compiler for custom Deep Learning operations" optional = false python-versions = "*" files = [ - {file = "triton-2.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ce4b8ff70c48e47274c66f269cce8861cf1dc347ceeb7a67414ca151b1822d8"}, - {file = "triton-2.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c3d9607f85103afdb279938fc1dd2a66e4f5999a58eb48a346bd42738f986dd"}, - {file = "triton-2.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:218d742e67480d9581bafb73ed598416cc8a56f6316152e5562ee65e33de01c0"}, - {file = "triton-2.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381ec6b3dac06922d3e4099cfc943ef032893b25415de295e82b1a82b0359d2c"}, - {file = "triton-2.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:038e06a09c06a164fef9c48de3af1e13a63dc1ba3c792871e61a8e79720ea440"}, - {file = "triton-2.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d8f636e0341ac348899a47a057c3daea99ea7db31528a225a3ba4ded28ccc65"}, + {file = "triton-2.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c84595cbe5e546b1b290d2a58b1494df5a2ef066dd890655e5b8a8a92205c33"}, + {file = "triton-2.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9d64ae33bcb3a7a18081e3a746e8cf87ca8623ca13d2c362413ce7a486f893e"}, + {file = "triton-2.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaf80e8761a9e3498aa92e7bf83a085b31959c61f5e8ac14eedd018df6fccd10"}, + {file = "triton-2.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b13bf35a2b659af7159bf78e92798dc62d877aa991de723937329e2d382f1991"}, + {file = "triton-2.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63381e35ded3304704ea867ffde3b7cfc42c16a55b3062d41e017ef510433d66"}, + {file = "triton-2.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d968264523c7a07911c8fb51b4e0d1b920204dae71491b1fe7b01b62a31e124"}, ] [package.dependencies] @@ -932,4 +932,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "2a8a1b0c0913d39bdac07c88127d38c8e058f64628c79ab8f188a6703b4c1ac4" +content-hash = "6e39c59ae056502f6a0512ca45e3f7502694d1c91518b37d223f390baf51e712" diff --git a/pyproject.toml b/pyproject.toml index f9fd847..048d218 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ python = "^3.10" numpy = "^1.26.4" nada-dsl = "^0.2.1" py-nillion-client = "^0.2.1" -nada-algebra = "^0.3.1" +nada-algebra = "^0.3.2" [tool.poetry.group.dev.dependencies]