From 0cf4ece677c3f85d8d7a906314f08d608e25c5f5 Mon Sep 17 00:00:00 2001 From: Berry Schoenmakers Date: Mon, 29 Apr 2024 15:16:34 +0200 Subject: [PATCH] Add np.flip() etc. for secure arrays. Use winloop._version. --- mpyc/__init__.py | 9 ++++- mpyc/runtime.py | 85 +++++++++++++++++++++++++++++++++++++------ tests/test_runtime.py | 18 +++++++++ 3 files changed, 99 insertions(+), 13 deletions(-) diff --git a/mpyc/__init__.py b/mpyc/__init__.py index ab19127..6a94720 100644 --- a/mpyc/__init__.py +++ b/mpyc/__init__.py @@ -29,7 +29,7 @@ and statistics (securely mimicking Python’s statistics module). """ -__version__ = '0.10' +__version__ = '0.10.1' __license__ = 'MIT License' import os @@ -174,7 +174,12 @@ def _get_arg_parser(): logging.info('Use of package uvloop (winloop) inside MPyC disabled.') elif sys.platform.startswith('win32'): from winloop import EventLoopPolicy - logging.debug('Load winloop') + try: + from winloop import _version # available in winloop 0.1.3+ + logging.debug(f'Load winloop version {_version.__version__}') + del _version + except ImportError: + logging.debug('Load winloop') else: from uvloop import EventLoopPolicy, _version logging.debug(f'Load uvloop version {_version.__version__}') diff --git a/mpyc/runtime.py b/mpyc/runtime.py index 7d071f2..aa3a894 100644 --- a/mpyc/runtime.py +++ b/mpyc/runtime.py @@ -2814,6 +2814,21 @@ def np_append(self, arr, values, axis=None): """ return self.np_concatenate((arr, values), axis=axis) + @asyncoro.mpc_coro_no_pc + async def np_flip(self, a, axis=None): + """Reverse the order of elements in an array along the given axis. + + The shape of the array is preserved, but the elements are reordered. + """ + stype = type(a) + if issubclass(stype, self.SecureFixedPointArray): + rettype = (stype, a.integral, a.shape) + else: + rettype = (stype, a.shape) + await self.returnType(rettype) + a = await self.gather(a) + return np.flip(a) + @asyncoro.mpc_coro_no_pc async def np_fliplr(self, a): """Reverse the order of elements along axis 1 (left/right). @@ -2830,6 +2845,65 @@ async def np_fliplr(self, a): a = await self.gather(a) return np.fliplr(a) + @asyncoro.mpc_coro_no_pc + async def np_flipud(self, a): + """Reverse the order of elements along axis 0 (up/down). + + For a 2D array, this flips the entries in each column in the up/down direction. + Rows are preserved, but appear in a different order than before. + """ + stype = type(a) + if issubclass(stype, self.SecureFixedPointArray): + rettype = (stype, a.integral, a.shape) + else: + rettype = (stype, a.shape) + await self.returnType(rettype) + a = await self.gather(a) + return np.flipud(a) + + @asyncoro.mpc_coro_no_pc + async def np_roll(self, a, shift, axis=None): + """Roll array elements (cyclically) along a given axis. + + If axis is None (default), array is flattened before cyclic shift, + and original shape is restored afterwards. + """ + stype = type(a) + if issubclass(stype, self.SecureFixedPointArray): + rettype = (stype, a.integral, a.shape) + else: + rettype = (stype, a.shape) + await self.returnType(rettype) + a = await self.gather(a) + return np.roll(a, shift, axis=axis) + + @asyncoro.mpc_coro_no_pc + async def np_rot90(self, a, k=1, axes=(0, 1)): + """Rotate an array k times by 90 degrees in the plane specified by axes. + + Rotation direction is from the first towards the second axis. + """ + if len(axes) != 2: + raise ValueError('len(axes) must be 2.') + + if not (axes[0] - axes[1]) % a.ndim: + raise ValueError('Axes must be different.') + + if not (-a.ndim <= axes[0] < a.ndim and -a.ndim <= axes[1] < a.ndim): + raise ValueError(f'Axes={axes} out of range for array of ndim={a.ndim}.') + + stype = type(a) + shape = list(a.shape) + shape[axes[0]], shape[axes[1]] = shape[axes[1]], shape[axes[0]] + shape = tuple(shape) + if issubclass(stype, self.SecureFixedPointArray): + rettype = (stype, a.integral, shape) + else: + rettype = (stype, shape) + await self.returnType(rettype) + a = await self.gather(a) + return np.rot90(a, k=k, axes=axes) + def np_minimum(self, a, b): """Secure elementwise minimum of a and b. @@ -2985,17 +3059,6 @@ async def np_sum(self, a, axis=None, keepdims=False, initial=0): return np.sum(a, axis=axis, keepdims=keepdims, initial=initial.value) # TODO: handle switch from initial (field elt) to initial.value inside finfields.py - @asyncoro.mpc_coro_no_pc - async def np_roll(self, a, shift, axis=None): - """Roll array elements (cyclically) along a given axis. - - If axis is None (default), array is flattened before cyclic shift, - and original shape is restored afterwards. - """ - await self.returnType((type(a), a.shape)) - a = await self.gather(a) - return np.roll(a, shift, axis=axis) - @asyncoro.mpc_coro_no_pc async def np_negative(self, a): """Secure elementwise negation -a (additive inverse) of a.""" diff --git a/tests/test_runtime.py b/tests/test_runtime.py index b051070..3a08daa 100644 --- a/tests/test_runtime.py +++ b/tests/test_runtime.py @@ -67,7 +67,12 @@ def test_secint_array(self): np.assertEqual(mpc.run(mpc.output(np.vsplit(c, np.array([1]))[0])), np.vsplit(a, [1])[0]) np.assertEqual(mpc.run(mpc.output(np.reshape(c, (-1,)))), np.reshape(a, (-1,))) np.assertEqual(mpc.run(mpc.output(np.reshape(c, -1))), np.reshape(a, -1)) + np.assertEqual(mpc.run(mpc.output(np.flip(c))), np.flip(a)) np.assertEqual(mpc.run(mpc.output(np.fliplr(c))), np.fliplr(a)) + np.assertEqual(mpc.run(mpc.output(np.flipud(c))), np.flipud(a)) + np.assertEqual(mpc.run(mpc.output(np.rot90(c))), np.rot90(a)) + np.assertEqual(mpc.run(mpc.output(np.rot90(d))), np.rot90(b)) + self.assertEqual(np.rot90(d).shape, np.rot90(b).shape) a1, a2 = a[:, :, 1], a[:, 0, :].reshape(2, 1) np.assertEqual(mpc.run(mpc.output(np.add(secint.array(a1), secint.array(a2)))), a1 + a2) np.assertEqual(mpc.run(mpc.output(a1 + secint.array(a[:, 0, :]).reshape(2, 1))), a1 + a2) @@ -401,8 +406,18 @@ def test_secfxp_array(self): self.assertEqual(np.argmax(c1).integral, True) self.assertEqual(np.argmax(c1, axis=0).integral, True) + self.assertEqual(np.flip(c1).integral, False) + self.assertEqual(np.flip(c2).integral, True) self.assertEqual(np.fliplr(c1).integral, False) self.assertEqual(np.fliplr(c2).integral, True) + self.assertEqual(np.flipud(c1).integral, False) + self.assertEqual(np.flipud(c2).integral, True) + self.assertEqual(np.reshape(c1, -1).integral, False) + self.assertEqual(np.reshape(c2, -1).integral, True) + self.assertEqual(np.roll(c1, -1).integral, False) + self.assertEqual(np.roll(c2, -1).integral, True) + self.assertEqual(np.rot90(c1).integral, False) + self.assertEqual(np.rot90(c2).integral, True) self.assertEqual(c1.swapaxes(0, 1).integral, False) self.assertEqual(c2.swapaxes(0, 1).integral, True) self.assertEqual(c1.transpose().integral, False) @@ -484,6 +499,9 @@ def test_np_errors(self): c = secfld.array(np.array([[1, 1], [0, 0]])) self.assertRaises(ValueError, c.reshape, -1, -1) self.assertRaises(ValueError, c.reshape, 3, -1) + self.assertRaises(ValueError, np.rot90, c, 1, (1,)) + self.assertRaises(ValueError, np.rot90, c, 1, (1, 1)) + self.assertRaises(ValueError, np.rot90, c, 1, (1, 2)) def test_async(self): mpc.options.no_async = False