From 161072fa3cbf89660da59b4499cd97c6d622f97a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Cabrero-Holgueras?= Date: Mon, 3 Jun 2024 17:41:44 +0200 Subject: [PATCH] feat: Added integration of rationals with NadaArray and bump version number (#16) * feat: Added integration of rationals with NadaArray * chore: bump version number --- nada_algebra/array.py | 59 +++++++++++++------ nada_algebra/client.py | 4 +- nada_algebra/funcs.py | 34 ++++++----- pyproject.toml | 2 +- tests/nada-tests/nada-project.toml | 4 ++ tests/nada-tests/src/rational_array.py | 31 ++++++++++ tests/nada-tests/tests/rational_array.yaml | 66 ++++++++++++++++++++++ tests/test_all.py | 1 + 8 files changed, 168 insertions(+), 33 deletions(-) create mode 100644 tests/nada-tests/src/rational_array.py create mode 100644 tests/nada-tests/tests/rational_array.yaml diff --git a/nada_algebra/array.py b/nada_algebra/array.py index 7e75af7..a9c1558 100644 --- a/nada_algebra/array.py +++ b/nada_algebra/array.py @@ -18,7 +18,7 @@ Integer, UnsignedInteger, ) -from nada_algebra.types import Rational, SecretRational +from nada_algebra.types import Rational, SecretRational, RationalConfig _NadaOperand = Union[ "NadaArray", @@ -380,7 +380,12 @@ def array( party: Party, prefix: str, nada_type: Union[ - SecretInteger, SecretUnsignedInteger, PublicInteger, PublicUnsignedInteger + SecretInteger, + SecretUnsignedInteger, + PublicInteger, + PublicUnsignedInteger, + SecretRational, + Rational, ] = SecretInteger, ) -> "NadaArray": """ @@ -394,22 +399,33 @@ def array( Returns: NadaArray: The created NadaArray. - """ + + Raises: + ValueError: Raised if the nada_type is not supported. + """ + generator = None + if nada_type in (Rational, SecretRational): + generator = lambda name, party: nada_type(name=name, party=party) + elif nada_type in ( + SecretInteger, + SecretUnsignedInteger, + PublicInteger, + PublicUnsignedInteger, + ): + generator = lambda name, party: nada_type(Input(name=name, party=party)) + else: + raise ValueError(f"Unsupported nada_type: {nada_type}") + return NadaArray( - np.array( - NadaArray.create_list( - dims, - party, - prefix, - lambda name, party: nada_type(Input(name=name, party=party)), - ) - ) + np.array(NadaArray.create_list(dims, party, prefix, generator)) ) @staticmethod def random( dims: list, - nada_type: Union[SecretInteger, SecretUnsignedInteger] = SecretInteger, + nada_type: Union[ + SecretInteger, SecretUnsignedInteger, SecretRational + ] = SecretInteger, ) -> "NadaArray": """ Create a random NadaArray with the specified dimensions. @@ -420,14 +436,21 @@ def random( Returns: NadaArray: The created random NadaArray. + + Raises: + ValueError: Raised if the nada_type is not supported. """ - return NadaArray( - np.array( - NadaArray.create_list( - dims, None, None, lambda name, party: nada_type.random() - ) + generator = None + if nada_type is SecretRational: + generator = lambda name, party: SecretRational.from_parts( + SecretInteger.random(), RationalConfig.LOG_SCALE ) - ) + elif nada_type in (SecretInteger, SecretUnsignedInteger): + generator = lambda name, party: nada_type.random() + else: + raise ValueError(f"Unsupported nada_type: {nada_type}") + + return NadaArray(np.array(NadaArray.create_list(dims, None, None, generator))) def __getattr__(self, name: str) -> Any: """Routes other attributes to the inner NumPy array. diff --git a/nada_algebra/client.py b/nada_algebra/client.py index dc0390f..17c5f00 100644 --- a/nada_algebra/client.py +++ b/nada_algebra/client.py @@ -11,7 +11,7 @@ PublicVariableUnsignedInteger, ) import numpy as np -from nada_algebra.types import RationalConfig +from nada_algebra.types import RationalConfig, Rational, SecretRational def parties(num: int, prefix: str = "Party") -> list: @@ -36,6 +36,8 @@ def array( SecretUnsignedInteger, PublicVariableInteger, PublicVariableUnsignedInteger, + Rational, + SecretRational, ] = SecretInteger, ) -> dict: """ diff --git a/nada_algebra/funcs.py b/nada_algebra/funcs.py index 96749b6..a735130 100644 --- a/nada_algebra/funcs.py +++ b/nada_algebra/funcs.py @@ -3,7 +3,7 @@ and manipulation of arrays and party objects. """ -from typing import Any, Iterable +from typing import Any, Iterable, Union from nada_dsl import ( Party, SecretInteger, @@ -13,8 +13,14 @@ Integer, UnsignedInteger, ) + import numpy as np from nada_algebra.array import NadaArray +from nada_algebra.types import Rational, SecretRational + + +_NadaCleartextType = Union[Integer, UnsignedInteger, Rational] +""" A Nada Cleartext Type is: `Integer`, `UnsignedInteger`, or `Rational` """ def parties(num: int, prefix: str = "Party") -> list: @@ -31,7 +37,7 @@ def parties(num: int, prefix: str = "Party") -> list: return [Party(name=f"{prefix}{i}") for i in range(num)] -def __from_numpy(arr: np.ndarray, nada_type: Integer | UnsignedInteger) -> list: +def __from_numpy(arr: np.ndarray, nada_type: _NadaCleartextType) -> list: """ Recursively convert a n-dimensional NumPy array to a nested list of NadaInteger objects. @@ -43,11 +49,13 @@ def __from_numpy(arr: np.ndarray, nada_type: Integer | UnsignedInteger) -> list: list: A nested list of NadaInteger objects. """ if len(arr.shape) == 1: + if isinstance(nada_type, Rational): + return [nada_type(elem) for elem in arr] return [nada_type(int(elem)) for elem in arr] return [__from_numpy(arr[i], nada_type) for i in range(arr.shape[0])] -def from_list(lst: list, nada_type: Integer | UnsignedInteger = Integer) -> NadaArray: +def from_list(lst: list, nada_type: _NadaCleartextType = Integer) -> NadaArray: """ Create a cleartext NadaArray from a list of integers. @@ -63,9 +71,7 @@ def from_list(lst: list, nada_type: Integer | UnsignedInteger = Integer) -> Nada return NadaArray(np.array(__from_numpy(lst, nada_type))) -def ones( - dims: Iterable[int], nada_type: Integer | UnsignedInteger = Integer -) -> NadaArray: +def ones(dims: Iterable[int], nada_type: _NadaCleartextType = Integer) -> NadaArray: """ Create a cleartext NadaArray filled with ones. @@ -80,7 +86,7 @@ def ones( def ones_like( - a: np.ndarray | NadaArray, nada_type: Integer | UnsignedInteger = Integer + a: np.ndarray | NadaArray, nada_type: _NadaCleartextType = Integer ) -> NadaArray: """ Create a cleartext NadaArray filled with one with the same shape and type as a given array. @@ -97,9 +103,7 @@ def ones_like( return from_list(np.ones_like(a), nada_type) -def zeros( - dims: Iterable[int], nada_type: Integer | UnsignedInteger = Integer -) -> NadaArray: +def zeros(dims: Iterable[int], nada_type: _NadaCleartextType = Integer) -> NadaArray: """ Create a cleartext NadaArray filled with zeros. @@ -114,7 +118,7 @@ def zeros( def zeros_like( - a: np.ndarray | NadaArray, nada_type: Integer | UnsignedInteger = Integer + a: np.ndarray | NadaArray, nada_type: _NadaCleartextType = Integer ) -> NadaArray: """ Create a cleartext NadaArray filled with zeros with the same shape and type as a given array. @@ -168,7 +172,11 @@ def array( party: Party, prefix: str, nada_type: ( - SecretInteger | SecretUnsignedInteger | PublicInteger | PublicUnsignedInteger + SecretInteger + | SecretUnsignedInteger + | PublicInteger + | PublicUnsignedInteger + | SecretRational ) = SecretInteger, ) -> NadaArray: """ @@ -188,7 +196,7 @@ def array( def random( dims: Iterable[int], - nada_type: SecretInteger | SecretUnsignedInteger = SecretInteger, + nada_type: SecretInteger | SecretUnsignedInteger | SecretRational = SecretInteger, ) -> NadaArray: """ Create a random NadaArray with the specified dimensions. diff --git a/pyproject.toml b/pyproject.toml index f435bba..a51468e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nada-algebra" -version = "0.2.0" +version = "0.2.1" description = "" authors = ["José Cabrero-Holgueras "] readme = "README.md" diff --git a/tests/nada-tests/nada-project.toml b/tests/nada-tests/nada-project.toml index 432adc1..7845199 100644 --- a/tests/nada-tests/nada-project.toml +++ b/tests/nada-tests/nada-project.toml @@ -108,4 +108,8 @@ prime_size = 128 [[programs]] path = "src/chained_rational_operations.py" +prime_size = 128 + +[[programs]] +path = "src/rational_array.py" prime_size = 128 \ No newline at end of file diff --git a/tests/nada-tests/src/rational_array.py b/tests/nada-tests/src/rational_array.py new file mode 100644 index 0000000..065dccd --- /dev/null +++ b/tests/nada-tests/src/rational_array.py @@ -0,0 +1,31 @@ +from nada_dsl import * +import nada_algebra as na + + +def nada_main(): + parties = na.parties(2) + + a = na.array([3], parties[0], "A", nada_type=na.SecretRational) + b = na.array([3], parties[0], "B", nada_type=na.SecretRational) + c = na.ones([3], na.Rational) + + out_0 = a + b + out_1 = a - b + out_2 = a * b + out_3 = a / b + + out_4 = a + c + out_5 = a - c + out_6 = a * c + out_7 = a / c + + return ( + out_0.output(parties[1], "out_0") + + out_1.output(parties[1], "out_1") + + out_2.output(parties[1], "out_2") + + out_3.output(parties[1], "out_3") + + out_4.output(parties[1], "out_4") + + out_5.output(parties[1], "out_5") + + out_6.output(parties[1], "out_6") + + out_7.output(parties[1], "out_7") + ) diff --git a/tests/nada-tests/tests/rational_array.yaml b/tests/nada-tests/tests/rational_array.yaml new file mode 100644 index 0000000..fb64206 --- /dev/null +++ b/tests/nada-tests/tests/rational_array.yaml @@ -0,0 +1,66 @@ +--- +program: rational_array +inputs: + secrets: + A_0: + SecretInteger: "0" # 0 + A_1: + SecretInteger: "65536" # 1 + A_2: + SecretInteger: "131072" # 2 + B_0: + SecretInteger: "65536" # 1 + B_1: + SecretInteger: "131072" # 2 + B_2: + SecretInteger: "196608" # 3 + public_variables: {} +expected_outputs: + out_0_0: + SecretInteger: "65536" + out_0_1: + SecretInteger: "196608" + out_0_2: + SecretInteger: "327680" + out_1_0: + SecretInteger: "-65536" + out_1_1: + SecretInteger: "-65536" + out_1_2: + SecretInteger: "-65536" + out_2_0: + SecretInteger: "0" + out_2_1: + SecretInteger: "131072" + out_2_2: + SecretInteger: "393216" + out_3_0: + SecretInteger: "0" + out_3_1: + SecretInteger: "32768" + out_3_2: + SecretInteger: "43690" + out_4_0: + SecretInteger: "65536" + out_4_1: + SecretInteger: "131072" + out_4_2: + SecretInteger: "196608" + out_5_0: + SecretInteger: "-65536" + out_5_1: + SecretInteger: "0" + out_5_2: + SecretInteger: "65536" + out_6_0: + SecretInteger: "0" + out_6_1: + SecretInteger: "65536" + out_6_2: + SecretInteger: "131072" + out_7_0: + SecretInteger: "0" + out_7_1: + SecretInteger: "65536" + out_7_2: + SecretInteger: "131072" diff --git a/tests/test_all.py b/tests/test_all.py index 4430df9..9ecf761 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -28,6 +28,7 @@ "secret_rational_arithmetic", "secret_rational_comparison", "chained_rational_operations", + "rational_array", # Not supported yet # "unsigned_matrix_inverse", # "private_inverse"