# Copyright 2021 National Technology & Engineering Solutions
# of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS,
# the U.S. Government retains certain rights in this software.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Defines abstract interfaces for network communication."""
from abc import ABCMeta, abstractmethod
import enum
[docs]
class Communicator(metaclass=ABCMeta):
"""Abstract base class for objects that manage collective communications for secure multiparty computation.
"""
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.free()
return False
[docs]
@abstractmethod
def allgather(self, value):
"""All-to-all communication.
Note
----
This method is a collective operation that *must* be called
by all players that are members of the communicator.
Parameters
----------
value: Any picklable :class:`object`, required
Local value that will be sent to all players.
Returns
-------
values: sequence of :class:`object`
Collection of objects gathered from every player, in rank order.
"""
pass # pragma: no cover
[docs]
@abstractmethod
def barrier(self):
"""Block the local process until all players have entered the barrier.
Note
----
This method is a collective operation that *must* be called
by all players that are members of the communicator.
"""
pass # pragma: no cover
[docs]
@abstractmethod
def broadcast(self, *, src, value):
"""One-to-all communication.
The `src` player broadcasts a single object to all players.
Note
----
This method is a collective operation that *must* be called
by all players that are members of the communicator.
Parameters
----------
src: :class:`int`, required
Rank of the player who is broadcasting.
value: Any picklable :class:`object` or `None`, required
Value to be broadcast by `src`. Ignored for all other players.
Returns
-------
value: :class:`object`
The broadcast value.
"""
pass # pragma: no cover
[docs]
@abstractmethod
def free(self):
"""Free the communicator.
This should be called if the communicator is no longer needed so that
resources can be freed. Note that communicators cannot be reused after
they have been freed, a new communicator must be created instead.
"""
pass # pragma: no cover
[docs]
@abstractmethod
def gather(self, *, value, dst):
"""All-to-one communication.
Every player sends a value to `dst`.
Note
----
This method is a collective operation that *must* be called
by all players that are members of the communicator.
Parameters
----------
value: Any picklable :class:`object`, required
Value to be sent to `dst`.
dst: :class:`int`, required
Rank of the player who will receive all of the values.
Returns
-------
values: sequence of :class:`object` or None
For the destination player, a sequence of `world_size` objects
received from every player in rank order. For all other players,
`None`.
"""
pass # pragma: no cover
[docs]
@abstractmethod
def gatherv(self, *, src, value, dst):
"""Many-to-one communication.
A subset of players each sends a value to `dst`.
Note
----
This method is a collective operation that *must* be called
by all players that are members of the communicator.
Parameters
----------
src: sequence of :class:`int`, required
Rank of each player sending a value.
value: Any picklable :class:`object`, or :any:`None`, required
Value to be sent to `dst`.
dst: :class:`int`, required
Rank of the player who will receive all of the values.
Returns
-------
values: sequence of :class:`object` or None
For the destination player, the sequence of values received from
the `src` players in the same order as `src`. For all other
players, :any:`None`.
"""
pass # pragma: no cover
[docs]
@abstractmethod
def irecv(self, *, src, tag):
"""Non-blocking one-to-one communication.
One player (the sender) sends an object to one player (the destination).
Note
----
Unlike collective operations, this method is only called by the receiver.
It must be matched by a call to :meth:`send` by the sender.
See Also
--------
recv
Blocking one-to-one communication.
Parameters
----------
src: :class:`int`, required
Rank of the sending player.
tag: :class:`int` or :class:`~cicada.communicator.interface.Tag`, required
User- or library-defined tag identifying the message type to match.
Returns
-------
result: :class:`object`
A special result object that can be used to wait for and access the
value sent by the sender. The result object will have a property
`is_completed` which returns a boolean value indicating whether the
result has been received; method `wait`, which will block
indefinitely until the result is received; and property `value`
which returns the received value or raises an exception if the
value has not been received yet.
"""
pass # pragma: no cover
[docs]
@abstractmethod
def isend(self, *, value, dst, tag):
"""Non-blocking one-to-one communication.
One player (the sender) sends an object to one player (the destination).
Note
----
Unlike collective operations, this method is only called by the sender.
It must be matched by a call to :meth:`recv` by the destination.
See Also
--------
send
Blocking one-to-one communication.
Parameters
----------
value: Picklable :class:`object`, required
Value to be sent.
dst: :class:`int`, required
Rank of the destination player.
tag: :class:`int` or :class:`~cicada.communicator.interface.Tag`, required
User- or library-defined tag identifying the message type.
Returns
-------
result: :class:`object`
A special result object that can be used to wait until the message
has been sent. The result object will have a property
`is_completed` which returns a boolean value indicating whether the
result has been sent; and a method `wait` which will block until
the message is sent.
"""
pass # pragma: no cover
@property
@abstractmethod
def rank(self):
"""Rank of the local player.
Returns
-------
rank: :class:`int`
Player rank, in the range :math:`[0, \\text{world_size})`.
"""
pass # pragma: no cover
@property
def ranks(self):
"""List of all ranks managed by this communicator.
Returns
-------
ranks: sequence of :class:`int`
The set of all ranks managed by this communicator.
"""
return list(range(self.world_size))
[docs]
@abstractmethod
def recv(self, *, src, tag):
"""Blocking one-to-one communication.
One player (the sender) sends an object to one player (the destination).
Note
----
Unlike collective operations, this method is only called by the receiver.
It must be matched by a call to :meth:`send` by the sender.
See Also
--------
irecv
Non-blocking one-to-one communication.
Parameters
----------
src: :class:`int`, required
Rank of the sending player.
tag: :class:`int` or :class:`~cicada.communicator.interface.Tag`, required
User- or library-defined tag identifying the message type to match.
Returns
-------
value: :class:`object`
The value sent by the sender.
"""
pass # pragma: no cover
[docs]
@abstractmethod
def scatter(self, *, src, values):
"""One-to-all communication.
One player (the sender) sends a different object to every player.
Note
----
This method is a collective operation that *must* be called
by all players that are members of the communicator.
Parameters
----------
src: :class:`int`, required
Rank of the sending player.
values: sequence of picklable :class:`object`, or `None`, required
Collection of objects to be sent, one per player, in rank order.
Returns
-------
value: :class:`object`
The object received by this player.
"""
pass # pragma: no cover
[docs]
@abstractmethod
def scatterv(self, *, src, values, dst):
"""One-to-many communication.
One player (the sender) sends a different object to each in a subset of players.
Note
----
This method is a collective operation that *must* be called
by all players that are members of the communicator.
Parameters
----------
src: :class:`int`, required
Rank of the sending player.
values: sequence of picklable :class:`object`, or `None`, required
Collection of objects to be sent, one per recipient.
dst: sequence of :class:`int`, required
Rank of each player receiving an object, in the same order as `values`.
Returns
-------
value: :class:`object` or None
The object received by this player, or `None` if this player wasn't
in the list of recipients.
"""
pass # pragma: no cover
[docs]
@abstractmethod
def send(self, value, dst, tag):
"""Blocking one-to-one communication.
One player (the sender) sends an object to one player (the destination).
Note
----
Unlike collective operations, this method is only called by the sender.
It must be matched by a call to :meth:`recv` by the destination.
See Also
--------
isend
Non-blocking one-to-one communication.
Parameters
----------
value: Picklable :class:`object`, required
Value to be sent.
dst: :class:`int`, required
Rank of the destination player.
tag: :class:`int` or :class:`~cicada.communicator.interface.Tag`, required
User- or library-defined tag identifying the message type.
"""
pass # pragma: no cover
@property
@abstractmethod
def world_size(self):
"""Number of players sharing this communicator.
Returns
-------
world_size: :class:`int`
The number of players sharing this communicator.
"""
pass # pragma: no cover
[docs]
class Tag(enum.IntEnum):
"""Message tags used internally by the library.
Callers can use these tags, or any other :class:`int`, when calling
:meth:`Communicator.send`, :meth:`Communicator.recv`,
:meth:`Communicator.isend`, and :meth:`Communicator.irecv`. Note that
negative integers are reserved for use by the library.
"""
# Collective operations.
ALLGATHER = -1
BARRIER = -2
BEACON = -3
BROADCAST = -4
GATHER = -5
GATHERV = -6
REVOKE = -7
SCATTER = -8
SCATTERV = -9
SHRINK = -10
# Logging operations.
LOGSYNC = -20
# Protocol-specific operations.
PRZS = -30 # Pseudorandom Zero-Sharing.
[docs]
def tagname(tag):
"""Return a human-readable name for a tag.
Parameters
----------
tag: :class:`int` or :class:`~cicada.communicator.interface.Tag`, required
"""
try:
tag = Tag(tag)
return tag.name
except:
return str(tag)