Superposed Sending


from random import randrange
from itertools import combinations
from math import floor

# Superposed Sending


class Actor(object):
    "Superposed Sending Actor"

    def __init__(self, name, alphabet=24, collisions=30, dbg=False):
        self.name = name
        self.partners = dict()
        self.symbols = alphabet * collisions
        self.collisions = collisions
        self.xMit = False
        self.msg = 0
        self.dbg = dbg

    def __repr__(self):
        return self.name

    def AddPartner(self, otherActor):
        randSymbol = randrange(self.symbols)
        randSense = randrange(self.collisions)
        if (self.dbg > 1):
            print("Negotiating secret: " + self.name + "->" + otherActor.name)
            print("  value:" + repr(randSymbol))
        otherActor.partners.update([[self,
                                     [randSense,
                                      randSymbol]]])
        self.partners.update([[otherActor,
                               [self.collisions - randSense,
                                self.symbols - randSymbol]]])

    def Renegotiate(self):
        for other in list(self.partners.keys()):
            self.AddPartner(other)

    def Prepare(self):
        self.senseChannel = (
            sum([x[0] for x in list(self.partners.values())])) % self.collisions
        self.msgChannel = (
            sum([x[1] for x in list(self.partners.values())])) % self.symbols

    def StoreMsg(self, msg):
        self.msg = msg
        self.xMit = True

    def Notify(self, x):
        if self.msg == x:
            self.xMit = False

    def BroadcastMsg(self, pivot='Nil', dir=+1):
        self.Prepare()
        if dir == +1:
            def tst(x, y):
                if y == 'Nil':
                    return True
                else:
                    return x < y
        else:
            def tst(x, y):
                if y == 'Nil':
                    return True
                else:
                    return x >= y
        if (self.xMit & tst(self.msg, pivot)):
            self.public_senseChannel = (
                self.senseChannel + 1) % self.collisions
            self.public_msgChannel = (
                self.msgChannel + self.msg) % self.symbols
        else:
            self.public_senseChannel = (self.senseChannel) % self.collisions
            self.public_msgChannel = (self.msgChannel) % self.symbols
        return [self.public_senseChannel,
                self.public_msgChannel]


class SuperposedSending(object):
    "Superposed Sending with collision resolution"

    def __init__(self, Actors, alphabet=26, dbg=False):
        if type(Actors) == int:
            Cr = []
            for i in range(0, Actors):
                Cr.append(Actor('Actor #' + repr(i + 1),
                                (alphabet + 1),
                                Actors, dbg))
        Actors = Cr
        self.alphabet = alphabet
        self.Actors = Actors
        self.symbols = (alphabet) * len(Actors)
        self.dbg = dbg

    def __getitem__(self, i):
        return self.Actors[i]

    def negotiate(self):
        for [A, B] in combinations(self.Actors, 2):
            A.AddPartner(B)
        for A in self.Actors:
            A.Prepare()

    def Renegotiate(self):
        for A in self.Actors:
            A.Renegotiate()

    def decodeSense(self, thr='Nil', dir=+1):
        "Decode Sense Channel"
        senders = 0
        actualMsg = 0
        for A in self.Actors:
            [Sense, Msg] = A.BroadcastMsg(thr, dir)
            senders += Sense
            actualMsg += Msg
        return [int(senders) % (len(self.Actors)),
                int(actualMsg) % self.symbols]

    def NotifyAll(self, msg):
        for A in self.Actors:
            A.Notify(msg)

    def decodeChannel(self, avg='Nil', dir=+1):
        [sense, msg] = self.decodeSense(avg, dir)
        if (self.dbg > 0):
            if (dir > 0):
                print("(I) pivot=", avg, " sense=", sense)
            if (dir < 0):
                print("(D) pivot=", avg, " sense=", sense)
        while not(sense == 0):
            avg0 = avg
            avg = int(msg) / int(sense)
            if not(avg0 == 'Nil'):
                if (floor(avg0) == floor(avg)):
                    print("Got ", int(floor(avg)), "*", sense)
                    self.NotifyAll(int(floor(avg)))
                    return [int(floor(avg))]
            if (self.dbg > 0):
                print("sense =", sense, end=' ')
                print("value =", msg, end=' ')
                print("pivot =", avg)
            if sense == 1:
                print("Got ", msg)
                self.NotifyAll(msg)
                return [int(msg)]
            else:
                if (self.dbg > 0):
                    print("Renegotiate channel!")
                self.Renegotiate()
                return self.decodeChannel(avg, -1) + self.decodeChannel(avg, +1)
        return []

    def SelfTest(self, r=3):
        allmsg = []
        for A in self.Actors:
            rnd = randrange(0, self.alphabet)
            t = randrange(0, r)
            if t == 1:
                A.StoreMsg(rnd)
                allmsg.append(rnd)
                if (self.dbg > 0):
                    print(repr(A) + " wishes to send " + repr(rnd))
        print(self.decodeSense())
        res = self.decodeChannel()
        print("Senders/Total :" + repr(len(allmsg)) +
              "/" + repr(len(self.Actors)))
        print("Input :", allmsg)
        print("Output :", res)
        return set(allmsg) == set(res)

X = SuperposedSending(20, 26, 1)