Skip to content

Commit

Permalink
Add np.flip() etc. for secure arrays. Use winloop._version.
Browse files Browse the repository at this point in the history
  • Loading branch information
lschoe authored Apr 29, 2024
1 parent ba3c572 commit 0cf4ece
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 13 deletions.
9 changes: 7 additions & 2 deletions mpyc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
and statistics (securely mimicking Python’s statistics module).
"""

__version__ = '0.10'
__version__ = '0.10.1'
__license__ = 'MIT License'

import os
Expand Down Expand Up @@ -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__}')
Expand Down
85 changes: 74 additions & 11 deletions mpyc/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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.
Expand Down Expand Up @@ -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."""
Expand Down
18 changes: 18 additions & 0 deletions tests/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 0cf4ece

Please sign in to comment.