Separating into files for easier bugfindin

This commit is contained in:
Brandon Goodell 2018-02-01 17:36:30 -07:00
parent 4593f2b8ea
commit 7c8aa16609
7 changed files with 770 additions and 536 deletions

View file

@ -0,0 +1,46 @@
import unittest, random, time
def newIdent(params):
nonce = params
# Generate new random identity.
return hash(str(nonce) + str(random.random()))
#### #### #### #### #### #### #### #### #### #### #### #### #### #### #### ####
class Block(object):
'''
Each block has: an identity, a timestamp of discovery (possibly false),
has a timestamp of arrival at the local node (possibly unnecessary), a
parent block's identity, and a difficulty score.
'''
def __init__(self, params={}):
self.ident = None
self.discoTimestamp = None
self.arrivTimestamp = None
self.parent = None
self.diff = None
try:
assert len(params)==5
except AssertionError:
print("Error in Block(): Tried to add a malformed block. We received params = " + str(params) + ", but should have had something of the form {\"ident\":ident, \"disco\":disco, \"arriv\":arriv, \"parent\":parent, \"diff\":diff}.")
self.ident = params["ident"]
self.discoTimestamp = params["disco"]
self.arrivTimestamp = params["arriv"]
self.parent = params["parent"]
self.diff = params["diff"]
class Test_Block(unittest.TestCase):
def test_b(self):
#bill = Block()
name = newIdent(0)
t = time.time()
s = t+1
diff = 1.0
params = {"ident":name, "disco":t, "arriv":s, "parent":None, "diff":diff}
bill = Block(params)
self.assertEqual(bill.ident,name)
self.assertEqual(bill.discoTimestamp,t)
self.assertEqual(bill.arrivTimestamp,t+1)
self.assertTrue(bill.parent is None)
self.assertEqual(bill.diff,diff)
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Block)
unittest.TextTestRunner(verbosity=1).run(suite)

View file

@ -0,0 +1,158 @@
from Block import *
class Blockchain(object):
'''
Not a true blockchain, of course, but tracks block objects (timestamps) as above.
Each node should be responsible for finding the chain with most cumulative work.
Right now we assume Nakamoto consensus (konsensnakamoto).
'''
def __init__(self, params=[], verbosity=True):
self.blocks = {}
self.leaves = {}
self.miningIdents = None
self.verbose = verbosity
def addBlock(self, blockToAdd):
# In our model we assume difficulty scores of blocks are correct (otherwise they would
# be rejected in the real life network, and we aren't trying to model spam attacks).
try:
assert blockToAdd.ident not in self.blocks
except AssertionError:
print("Error, tried to add block that already exists in blockchain.")
else:
self.blocks.update({blockToAdd.ident:blockToAdd})
self.leaves.update({blockToAdd.ident:blockToAdd})
if blockToAdd.parent in self.leaves:
del self.leaves[blockToAdd.parent]
self.whichLeaf()
def whichLeaf(self):
# Determine which leaf shall be the parent leaf.
# If the chain has forked *ever* this will not be the case.
maxCumDiff = 0.0
self.miningIdents = []
for ident in self.leaves:
tempCumDiff = 0.0
thisBlockIdent = ident
tempCumDiff += self.blocks[thisBlockIdent].diff
while self.blocks[thisBlockIdent].parent is not None:
thisBlockIdent = self.blocks[thisBlockIdent].parent
tempCumDiff += self.blocks[thisBlockIdent].diff
if tempCumDiff > maxCumDiff:
# If more than one leaf ties for maxCumDiff, each node in the
# network should pick one of these two arbitrarily. Since we
# are storing each blockchain in a hash table (unordered!), for
# each node in the network that observes a tie, each possible leaf
# is equally likely to have been the first one found! So
# we don't need to do anything for the node to select which chain
# to work off of.
self.miningIdents = [ident]
maxCumDiff = tempCumDiff
elif tempCumDiff == maxCumDiff:
self.miningIdents.append(ident)
#print("leaf ident = ", str(ident), ", and tempCumDiff = ", str(tempCumDiff), " and maxCumDiff = ", str(maxCumDiff))
class Test_Blockchain(unittest.TestCase):
def test_bc(self):
bill = Blockchain([], verbosity=True)
name = newIdent(0)
t = time.time()
s = t+1
diff = 1.0
params = {"ident":name, "disco":t, "arriv":s, "parent":None, "diff":diff}
genesis = Block(params)
self.assertEqual(genesis.ident,name)
self.assertEqual(genesis.discoTimestamp,t)
self.assertEqual(genesis.arrivTimestamp,t+1)
self.assertTrue(genesis.parent is None)
self.assertEqual(genesis.diff,diff)
bill.addBlock(genesis)
self.assertTrue(genesis.ident in bill.blocks)
self.assertTrue(genesis.ident in bill.leaves)
self.assertEqual(len(bill.miningIdents),1)
self.assertEqual(genesis.ident, bill.miningIdents[0])
name = newIdent(1)
t = time.time()
s = t+1
diff = 2.0
params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff}
blockA = Block(params)
bill.addBlock(blockA)
bill.whichLeaf()
self.assertTrue(blockA.ident in bill.blocks)
self.assertTrue(blockA.ident in bill.leaves)
self.assertFalse(genesis.ident in bill.leaves)
self.assertTrue(genesis.ident in bill.blocks)
self.assertEqual(len(bill.miningIdents),1)
self.assertEqual(blockA.ident, bill.miningIdents[0])
name = newIdent(1)
t = time.time()
s = t+1
diff = 2.0
params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff}
blockB = Block(params)
bill.addBlock(blockB)
self.assertTrue(blockB.ident in bill.blocks)
self.assertTrue(blockB.ident in bill.leaves)
self.assertEqual(bill.blocks[blockB.ident].parent, genesis.ident)
self.assertTrue(blockA.ident in bill.blocks)
self.assertTrue(blockA.ident in bill.leaves)
self.assertEqual(bill.blocks[blockA.ident].parent, genesis.ident)
self.assertTrue(genesis.ident in bill.blocks)
self.assertFalse(genesis.ident in bill.leaves)
self.assertTrue(bill.blocks[genesis.ident].parent is None)
bill.whichLeaf()
print(bill.miningIdents)
self.assertEqual(type(bill.miningIdents), type([]))
self.assertTrue(len(bill.miningIdents), 2)
name = newIdent(2)
t = time.time()
diff = 3.14159
params = {"ident":name, "disco":t, "arriv":s, "parent":blockB.ident, "diff":diff}
blockC = Block(params)
bill.addBlock(blockC)
self.assertTrue(blockC.ident in bill.blocks)
self.assertTrue(blockC.ident in bill.leaves)
self.assertTrue(blockB.ident in bill.blocks)
self.assertFalse(blockB.ident in bill.leaves)
self.assertTrue(blockA.ident in bill.blocks)
self.assertTrue(blockA.ident in bill.leaves)
self.assertTrue(genesis.ident in bill.blocks)
self.assertFalse(genesis.ident in bill.leaves)
bill.whichLeaf()
#for blockIdent in bill.blocks:
# ident = bill.blocks[blockIdent].ident
# disco = bill.blocks[blockIdent].discoTimestamp
# arriv = bill.blocks[blockIdent].arrivTimestamp
# parent = bill.blocks[blockIdent].parent
# diff = bill.blocks[blockIdent].diff
# print(str(ident) + ", " + str(disco) + ", " + str(arriv) + ", " + str(parent) + ", " + str(diff) + ", " + str() + "\n")
#print(bill.miningIdents)
self.assertEqual(len(bill.miningIdents), 1)
self.assertEqual(bill.miningIdents[0], blockC.ident)
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Blockchain)
unittest.TextTestRunner(verbosity=1).run(suite)

View file

@ -0,0 +1,95 @@
from Node import *
class Edge(object):
'''
Edge object. Has an identity, some data, and a dict of nodes.
'''
def __init__(self, params=["", {}, True]):
try:
assert len(params)==3
except AssertionError:
print("Error, tried to create mal-formed edge.")
else:
self.ident = params[0]
self.data = params[1]
self.verbose = params[2]
self.nodes = {}
def getNeighbor(self, nodeIdent):
# Given one node identity, check that the node
# identity is in the edge's node list and
# return the identity of the other adjacent node.
result = (nodeIdent in self.nodes)
if result:
for otherIdent in self.nodes:
if otherIdent != nodeIdent:
result = otherIdent
assert result in self.nodes
return result
class Test_Edge(unittest.TestCase):
def test_e(self):
nellyIdent = newIdent(0)
bill = Blockchain([], verbosity=True)
name = newIdent(0)
t = time.time()
diff = 1.0
params = [name, t, t+1, None, diff, bill.verbose] # Genesis block has no parent, so parent = None
genesis = Block(params)
bill.addBlock(genesis)
time.sleep(10)
name = newIdent(1)
t = time.time()
diff = 1.0
params = [name, t, t+1, genesis.ident, diff, bill.verbose]
blockA = Block(params)
bill.addBlock(blockA)
# Nodes need an identity and a blockchain object and verbosity and difficulty
nelly = Node([nellyIdent, copy.deepcopy(bill), bill.verbosity, diff])
nelly.updateDifficulty(mode="Nakamoto")
time.sleep(9)
name = newIdent(len(nelly.data))
t = time.time()
params = [name, t, t+1, blockA.ident, nelly.diff, nelly.verbose]
blockB = Block(params)
nelly.updateBlockchain({blockB.ident:blockB})
time.sleep(8)
name = newIdent(len(nelly.data))
t = time.time()
params = [name, t, t+1, blockB.ident, nelly.diff, nelly.verbose]
blockC = Block(params)
nelly.updateBlockchain({blockC.ident:blockC})
time.sleep(1)
name = newIdent(len(nelly.data))
t = time.time()
params = [name, t, t+1, blockB.ident, nelly.diff, nelly.verbose] # Fork off
blockD = Block(params)
nelly.updateBlockchain({blockD.ident:blockD})
time.sleep(7)
name = newIdent(len(nelly.data))
t = time.time()
params = [name, t, t+1, blockD.ident, nelly.diff, nelly.verbose]
blockE = Block(params)
nelly.updateBlockchain({blockE.ident:blockE})
time.sleep(6)
name = newIdent(len(nelly.data))
t = time.time()
params = [name, t, t+1, blockE.ident, nelly.diff, nelly.verbose]
blockF = Block(params)
nelly.updateBlockchain({blockF.ident:blockF})
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Edge)
unittest.TextTestRunner(verbosity=1).run(suite)

View file

@ -36,536 +36,6 @@ def newOffset(params):
totalOffset = 60.0*60.0*float(x[0][0] - x[1][0]) + float((x[0][1] - x[1][1])) totalOffset = 60.0*60.0*float(x[0][0] - x[1][0]) + float((x[0][1] - x[1][1]))
return totalOffset return totalOffset
class StochasticProcess(object):
'''
Stochastic processes have a clock and a state.
The clock moves forward, and then the state updates.
More detail requires knowledge of the underlying stochProc.
'''
def __init__(self, params=None):
# initialize with initial data
self.data = params
self.t = 0.0 # should always start at t=0.0
self.state = 0.0 # magic number
self.maxTime = 1000.0 # magic number
self.verbose = True
def go(self):
# Executes stochastic process.
assert self.maxTime > 0.0 # Check loop will eventually terminate.
t = self.t
while t <= self.maxTime:
deltaT = self.getNextTime() # Pick the next "time until event" and a description of the event.
self.updateState(t, deltaT) # Update state with deltaT input
t = self.t
if self.verbose:
print("Recording...")
def getNextTime(self):
return 1 # Magic number right now
def updateState(self, t, deltaT):
# Update the state of the system. In this case,
# we are doing a random walk on the integers.
self.state += random.randrange(-1,2,1) # [-1, 0, 1]
self.t += deltaT
class Test_StochasticProcess(unittest.TestCase):
def test_sp(self):
sally = StochasticProcess()
sally.go()
#suite = unittest.TestLoader().loadTestsFromTestCase(Test_StochasticProcess)
#unittest.TextTestRunner(verbosity=1).run(suite)
class Block(object):
'''
Each block has: an identity, a timestamp of discovery (possibly false),
has a timestamp of arrival at the local node (possibly unnecessary), a pointer to a parent
block's identity, and a difficulty score.
'''
def __init__(self, params=[]):
try:
assert len(params)==6
except AssertionError:
print("Error in Block(): Tried to add a malformed block. We received params = " + str(params) + ", but should have had something of the form [ident, disco, arriv, parent, diff, verbose].")
else:
self.ident = params[0]
self.discoTimestamp = params[1]
self.arrivTimestamp = params[2]
self.parent = params[3]
self.diff = params[4]
self.verbose = params[5]
class Test_Block(unittest.TestCase):
def test_b(self):
#bill = Block()
name = newIdent(0)
t = time.time()
diff = 1.0
params = [name, t, t+1, None, diff, False]
bill = Block(params)
self.assertEqual(bill.ident,name)
self.assertEqual(bill.discoTimestamp,t)
self.assertEqual(bill.arrivTimestamp,t+1)
self.assertTrue(bill.parent is None)
self.assertEqual(bill.diff,diff)
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Block)
unittest.TextTestRunner(verbosity=1).run(suite)
class Blockchain(object):
'''
Not a true blockchain, of course, but tracks block objects (timestamps) as above.
Each node should be responsible for finding the chain with most cumulative work.
Right now we assume Nakamoto consensus (konsensnakamoto).
'''
def __init__(self, params=[], verbosity=True):
self.blocks = {}
self.leaves = {}
self.miningIdent = None
self.verbose = verbosity
def addBlock(self, blockToAdd):
# In our model we assume difficulty scores of blocks are correct (otherwise they would
# be rejected in the real life network, and we aren't trying to model spam attacks).
try:
assert blockToAdd.ident not in self.blocks
except AssertionError:
print("Error, tried to add block that already exists in blockchain.")
else:
self.blocks.update({blockToAdd.ident:blockToAdd})
self.leaves.update({blockToAdd.ident:blockToAdd})
if blockToAdd.parent in self.leaves:
del self.leaves[blockToAdd.parent]
self.whichLeaf()
def whichLeaf(self):
# Determine which leaf shall be the parent leaf.
if len(self.leaves) == 1:
# If the chain has never forked, we have no decision to make:
for ident in self.leaves:
self.miningIdent = ident
elif len(self.leaves) > 1:
# If the chain has forked *ever* this will not be the case.
maxCumDiff = 0.0
for ident in self.leaves:
tempCumDiff = 0.0
tempCumDiff += self.blocks[ident].diff
nextIdent = self.blocks[ident].parent
if nextIdent is not None and nextIdent in self.blocks:
while self.blocks[nextIdent].parent is not None:
tempCumDiff += self.blocks[nextIdent].diff
nextIdent = self.blocks[nextIdent].parent
if tempCumDiff > maxCumDiff:
# If more than one leaf ties for maxCumDiff, each node in the
# network should pick one of these two arbitrarily. Since we
# are storing each blockchain in a hash table (unordered!), for
# each node in the network that observes a tie, each possible leaf
# is equally likely to have been the first one found! So
# we don't need to do anything for the node to select which chain
# to work off of.
self.miningIdent = ident
else:
print("Error, tried to assess an empty blockchain.")
class Test_Blockchain(unittest.TestCase):
def test_bc(self):
bill = Blockchain([], verbosity=True)
name = newIdent(0)
t = time.time()
diff = 1.0
params = [name, t, t+1, None, diff, bill.verbose]
genesis = Block(params)
self.assertEqual(genesis.ident,name)
self.assertEqual(genesis.discoTimestamp,t)
self.assertEqual(genesis.arrivTimestamp,t+1)
self.assertTrue(genesis.parent is None)
self.assertEqual(genesis.diff,diff)
bill.addBlock(genesis)
self.assertTrue(genesis.ident in bill.blocks)
self.assertTrue(genesis.ident in bill.leaves)
self.assertEqual(genesis.ident, bill.miningIdent)
name = newIdent(1)
t = time.time()
diff = 2.0
params = [name, t, t+1, genesis.ident, diff, bill.verbose]
blockA = Block(params)
bill.addBlock(blockA)
self.assertTrue(blockA.ident in bill.blocks)
self.assertTrue(blockA.ident in bill.leaves)
self.assertFalse(genesis.ident in bill.leaves)
self.assertTrue(genesis.ident in bill.blocks)
self.assertEqual(blockA.ident, bill.miningIdent)
name = newIdent(1)
t = time.time()
diff = 2.5
params = [name, t, t+1, None, diff, bill.verbose]
blockB = Block(params)
bill.addBlock(blockB)
self.assertTrue(blockB.ident in bill.blocks)
self.assertTrue(blockB.ident in bill.leaves)
self.assertFalse(genesis.ident in bill.leaves)
self.assertTrue(genesis.ident in bill.blocks)
self.assertTrue(blockA.ident in bill.leaves)
self.assertTrue(blockB.ident in bill.leaves)
self.assertEqual(blockB.ident, bill.miningIdent)
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Blockchain)
unittest.TextTestRunner(verbosity=1).run(suite)
class Node(object):
'''
Node object. params [identity, blockchain (data), verbosity, difficulty]
'''
def __init__(self, params=["", {}, True]):
try:
assert len(params)==4
except AssertionError:
print("Error, Tried to create malformed node.")
else:
self.ident = params[0]
self.data = params[1] #Blockchain object
self.verbose = params[2]
self.diff = params[3]
self.edges = {}
def updateBlockchain(self, incBlocks, diffUpdateRate=1, mode="Nakamoto", targetRate=1.0/1209600.0):
# dataToUpdate shall be a dictionary of block identities (as keys) and their associated blocks (as values)
# to be added to the local data. We assume difficulty scores have been reported honestly for now.
# Stash a copy of incoming blocks so removing keys won't shrink the size of the dictionary over which
# we are looping.
tempData = copy.deepcopy(incBlocks)
for key in incBlocks:
if incBlocks[key].parent in self.data["blockchain"].blocks:
self.data["blockchain"].addBlock(incBlocks[key])
#if len(self.data["blockchain"]) % diffUpdateRate == 0:
# self.updateDifficulty(mode, targetRate)
del tempData[key]
incBlocks = copy.deepcopy(tempData)
while len(incBlocks)>0:
for key in incBlocks:
if incBlocks[key].parent in self.data["blockchain"].blocks:
self.data["blockchain"].addBlock(incBlocks[key])
#if len(self.data["blockchain"]) % diffUpdateRate == 0:
# self.updateDifficulty(mode, targetRate)
del tempData[key]
incBlocks = copy.deepcopy(tempData)
def updateDifficulty(self, mode="Nakamoto", targetRate=1.0/1209600.0):
# Compute the difficulty of the next block
# Note for default, targetRate = two weeks/period, seven days/week, 24 hours/day, 60 minutes/hour, 60 seconds/minute) = 1209600 seconds/period
if mode=="Nakamoto":
# Use MLE estimate of poisson process, compare to targetRate, update by multiplying by resulting ratio.
count = 2016
ident = self.data.miningIdent
topTime = copy.deepcopy(int(round(self.data.blocks[ident].discoTimestamp)))
parent = self.data.blocks[ident].parent
count = count - 1
while count > 0 and parent is not None:
ident = copy.deepcopy(parent)
parent = self.data.blocks[ident].parent
count = count - 1
botTime = copy.deepcopy(int(round(self.data.blocks[ident].discoTimestamp)))
# Algebra is okay:
assert 0 <= 2016 - count and 2016 - count < 2017
assert topTime > botTime
# MLE estimate of arrivals per second:
mleDiscoRate = float(2016 - count)/float(topTime - botTime)
# How much should difficulty change?
self.diff = self.diff*(mleDiscoRate/targetRate)
elif mode=="vanSaberhagen":
# Similar to above, except use 1200 blocks, discard top 120 and bottom 120 after sorting.
# 4 minute blocks in the original cryptonote, I believe... targetRate = 1.0/
# 4 minutes/period, 60 seconds/minute ~ 240 seconds/period
assert targetRate==1.0/240.0
count = 1200
ident = self.data.miningIdent
bl = []
bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp))
parent = self.data.blocks[ident].parent
count = count - 1
while count > 0 and parent is not NOne:
ident = copy.deepcopy(parent)
bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp))
parent = self.data.blocks[ident].parent
count = count-1
# sort
bl = sorted(bl)
# remove outliers
bl = bl[120:-120]
# get topTime and botTime
topTime = bl[-1]
botTime = bl[0]
# Assert algebra will work
assert 0 <= 960 - count and 960 - count < 961
assert topTime > botTime
# Sort of the MLE: # blocks/difference in reported times
# But not the MLE, since the reported times may not be
# the actual times, the "difference in reported times" !=
# "ground truth difference in block discoery times" in general
naiveDiscoRate = (960 - count)/(topTime - botTime)
# How much should difficulty change?
self.diff = self.diff*(naiveDiscoRate/targetRate)
elif mode=="MOM:expModGauss":
# Similar to "vanSaberhagen" except with 2-minute blocks and
# we attempt to take into account that "difference in timestamps"
# can be negative by:
# 1) insisting that the ordering induced by the blockchain and
# 2) modeling timestamps as exponentially modified gaussian.
# If timestamps are T = X + Z where X is exponentially dist-
# ributed with parameter lambda and Z is some Gaussian
# noise with average mu and variance sigma2, then we can est-
# imate sigma2, mu, and lambda:
# mu ~ mean - stdev*(skewness/2)**(1.0/3.0)
# sigma2 ~ variance*(1-(skewness/2)**(2.0/3.0))
# lambda ~ (1.0/(stdev))*(2/skewness)**(1.0/3.0)
assert targetRate==1.0/120.0
count = 1200
ident = self.data.miningIdent
bl = []
bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp))
parent = self.data.blocks[ident].parent
count = count - 1
while count > 0 and parent is not NOne:
ident = copy.deepcopy(parent)
bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp))
parent = self.data.blocks[ident].parent
count = count-1
sk = skew(bl)
va = var(bl)
stdv = sqrt(va)
lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0)
self.diff = self.diff*(lam/targetRate)
else:
print("Error, invalid difficulty mode entered.")
def propagate(self, blockIdent):
for edgeIdent in self.edges:
e = self.edges[edgeIdent]
l = e.data["length"]
toa = self.t + l
mIdent = e.getNeighbor(n.ident)
m = e.nodes[mIdent]
if blockIdent not in m.data["blockchain"]:
pB = e.data["pendingBlocks"]
pendingIdent = newIdent(len(pB))
pendingDat = {"timeOfArrival":toa, "destIdent":mIdent, "block":self.blocks[blockIdent]}
pB.update({pendingIdent:pendingDat})
class Test_Node(unittest.TestCase):
def test_node(self):
nellyIdent = newIdent(0)
bill = Blockchain([], verbosity=True)
name = newIdent(0)
t = time.time()
diff = 1.0
params = [name, t, t+1, None, diff, bill.verbose] # Genesis block has no parent, so parent = None
genesis = Block(params)
bill.addBlock(genesis)
time.sleep(10)
name = newIdent(1)
t = time.time()
diff = 1.0
params = [name, t, t+1, genesis.ident, diff, bill.verbose]
blockA = Block(params)
bill.addBlock(blockA)
# Nodes need an identity and a blockchain object and verbosity and difficulty
nelly = Node([nellyIdent, copy.deepcopy(bill), bill.verbosity, diff])
nelly.updateDifficulty(mode="Nakamoto")
time.sleep(9)
name = newIdent(len(nelly.data))
t = time.time()
params = [name, t, t+1, blockA.ident, nelly.diff, nelly.verbose]
blockB = Block(params)
nelly.updateBlockchain({blockB.ident:blockB})
time.sleep(8)
name = newIdent(len(nelly.data))
t = time.time()
params = [name, t, t+1, blockB.ident, nelly.diff, nelly.verbose]
blockC = Block(params)
nelly.updateBlockchain({blockC.ident:blockC})
time.sleep(1)
name = newIdent(len(nelly.data))
t = time.time()
params = [name, t, t+1, blockB.ident, nelly.diff, nelly.verbose] # Fork off
blockD = Block(params)
nelly.updateBlockchain({blockD.ident:blockD})
time.sleep(7)
name = newIdent(len(nelly.data))
t = time.time()
params = [name, t, t+1, blockD.ident, nelly.diff, nelly.verbose]
blockE = Block(params)
nelly.updateBlockchain({blockE.ident:blockE})
time.sleep(6)
name = newIdent(len(nelly.data))
t = time.time()
params = [name, t, t+1, blockE.ident, nelly.diff, nelly.verbose]
blockF = Block(params)
nelly.updateBlockchain({blockF.ident:blockF})
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Blockchain)
unittest.TextTestRunner(verbosity=1).run(suite)
class Edge(object):
'''
Edge object. Has an identity, some data, and a dict of nodes.
'''
def __init__(self, params=["", {}, True]):
try:
assert len(params)==3
except AssertionError:
print("Error, tried to create mal-formed edge.")
else:
self.ident = params[0]
self.data = params[1]
self.verbose = params[2]
self.nodes = {}
def getNeighbor(self, nodeIdent):
# Given one node identity, check that the node
# identity is in the edge's node list and
# return the identity of the other adjacent node.
result = (nodeIdent in self.nodes)
if result:
for otherIdent in self.nodes:
if otherIdent != nodeIdent:
result = otherIdent
assert result in self.nodes
return result
class Graph(object):
'''
Graph object. Contains some data, a dict of nodes, and a dict of edges.
'''
def __init__(self, params={}, verbosity=True):
self.data=params
self.verbose = verbosity
self.nodes = {}
self.edges = {}
def createGraph(self, numNodes, probEdge, maxNeighbors):
# Create a new random graph with numNodes nodes, a
# likelihood any unordered pair of vertices has an edge
# probEdge, and maximum number of neighbors per node
# maxNeighbors.
# First, include inputted information into self.data
self.data.update({"probEdge":probEdge, "maxNeighbors":maxNeighbors})
# Next, for each node to be added, create the node and name it.
for i in range(numNodes):
nIdent = newIdent(i)
bl = Blockchain([], verbosity=True)
dat = {"blockchain":bl, "intensity":newIntensity(["uniform"]), "offset":newOffset("sumOfSkellams")}
# A node needs an ident, a data object, a verbosity, and a difficulty
n = Node([nIdent, dat, self.verbose, 1.0])
self.nodes.update({n.ident:n})
# Next, for each possible node pair, decide if an edge exists.
touched = {} # Dummy list of node pairs we have already considered.
for nIdent in self.nodes:
n = self.nodes[nIdent] # Pick a node
for mIdent in self.nodes:
m = self.nodes[mIdent] # Pick a pair element
notSameNode = (nIdent != mIdent) # Ensure we aren't dealing with (x,x)
nOpenSlots = (len(n.edges) < self.data["maxNeighbors"]) # ensure both nodes have open slots available for new edges
mOpenSlots = (len(m.edges) < self.data["maxNeighbors"])
untouched = ((nIdent, mIdent) not in touched) # make sure the pair and its transposition have not been touched
dehcuotnu = ((mIdent, nIdent) not in touched)
if notSameNode and nOpenSlots and mOpenSlots and untouched and dehcuotnu:
# Mark pair as touhed
touched.update({(nIdent,mIdent):True, (mIdent,nIdent):True})
if random.random() < self.data["probEdge"]:
# Determine if edge should exist and if so, add it.
nonce = len(self.edges)
e = Edge([newIdent(nonce),{"length":random.random(), "pendingBlocks":[]},self.verbose])
e.nodes.update({n.ident:n, m.ident:m})
self.nodes[nIdent].edges.update({e.ident:e})
self.nodes[mIdent].edges.update({e.ident:e})
self.edges.update({e.ident:e})
def addNode(self):
# Add new node
n = Node([self.newIdent(len(self.nodes)), {}, self.verbose, 1.0])
self.nodes.update({n.ident:n})
for mIdent in self.nodes:
# For every other node, check if an edge should exist and if so add it.
m = self.nodes[mIdent]
notSameNode = (n.ident != mIdent)
nOpenSlots = (len(n.edges) < self.data["maxNeighbors"])
mOpenSlots = (len(m.edges) < self.data["maxNeighbors"])
if notSameNode and nOpenSlots and mOpenSlots and random.random() < self.data["probEdge"]:
nonce = len(self.edges)
e = Edge([self.newIdent(nonce), {"length":random.random(), "pendingBlocks":[]}, self.verbose])
e.nodes.update({n.ident:n, m.ident:m})
n.edges.update({e.ident:e})
self.nodes[mIdent].edges.update({e.ident:e})
self.edges.update({e.ident:e})
return n.ident
def delNode(self, ident):
# Remove a node and wipe all memory of its edges from history.
edgesToDelete = self.nodes[ident].edges
for edgeIdent in edgesToDelete:
e = edgesToDelete[edgeIdent]
otherIdent = e.getNeighbor(ident)
del self.edges[edgeIdent]
del self.nodes[otherIdent].edges[edgeIdent]
del self.nodes[ident]
class Test_Graph(unittest.TestCase):
def test_graph(self):
greg = Graph()
greg.createGraph(3, 0.5, 10)
self.assertEqual(len(greg.nodes),3)
greg.addNode()
self.assertEqual(len(greg.nodes),4)
for edge in greg.edges:
self.assertEqual(len(greg.edges[edge].nodes),2)
nodeToKill = random.choice(list(greg.nodes.keys()))
greg.delNode(nodeToKill)
for edge in greg.edges:
self.assertEqual(len(greg.edges[edge].nodes),2)
for nodeIdent in greg.edges[edge].nodes:
self.assertTrue(nodeIdent in greg.nodes)
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Graph)
unittest.TextTestRunner(verbosity=1).run(suite)
class FishGraph(StochasticProcess): class FishGraph(StochasticProcess):
''' '''
@ -703,7 +173,7 @@ class FishGraph(StochasticProcess):
mIdent = e.getNeighbor(n.ident) mIdent = e.getNeighbor(n.ident)
m = self.state.nodes[mIdent] m = self.state.nodes[mIdent]
mdata = m.data["blockchain"] mdata = m.data["blockchain"]
n.data["blockchain"].update(mdata) n.updateBlockchain(mdata)
y = len(self.state.nodes) y = len(self.state.nodes)
assert y == x + 1 assert y == x + 1
shout += str(y) + "\n" shout += str(y) + "\n"
@ -712,27 +182,67 @@ class FishGraph(StochasticProcess):
elif len(eventTag)==2: elif len(eventTag)==2:
# Block is discovered and plunked into each edge's pendingBlock list. # Block is discovered and plunked into each edge's pendingBlock list.
shout += "DISCOVERY" shout += "DISCOVERY\n"
if self.verbose: if self.verbose:
print(shout) print(shout)
if self.verbose:
print("Checking formation of eventTag = [\"discovery\", nodeIdent]")
assert eventTag[0]=="discovery" assert eventTag[0]=="discovery"
assert eventTag[1] in self.state.nodes assert eventTag[1] in self.state.nodes
if self.verbose:
print("Retrieving discoverer's identity")
nIdent = eventTag[1] # get founding node's identity nIdent = eventTag[1] # get founding node's identity
if self.verbose:
print("Retrieving discoverer")
n = self.state.nodes[nIdent] # get founding node n = self.state.nodes[nIdent] # get founding node
if self.verbose:
print("Computing discoverer's wall clock")
s = self.t + n.data["offset"] # get founding node's wall clock s = self.t + n.data["offset"] # get founding node's wall clock
if self.verbose:
print("Generating new block identity")
newBlockIdent = newIdent(len(n.data["blockchain"].blocks)) # generate new identity newBlockIdent = newIdent(len(n.data["blockchain"].blocks)) # generate new identity
if self.verbose:
print("Setting timestamps")
disco = s disco = s
arriv = s arriv = s
if self.verbose:
print("Retrieving parent")
parent = n.data["blockchain"].miningIdent parent = n.data["blockchain"].miningIdent
if self.verbose:
print("getting difficulty")
diff = copy.deepcopy(n.diff) diff = copy.deepcopy(n.diff)
if self.verbose:
print("setting verbosity")
verbosity = self.verbose verbosity = self.verbose
if self.verbose:
print("Initializing a new block")
newBlock = Block([newBlockIdent, disco, arriv, parent, diff, verbosity]) newBlock = Block([newBlockIdent, disco, arriv, parent, diff, verbosity])
if self.verbose:
print("Updating discovering node's blockchain")
n.updateBlockchain({newBlockIdent:newBlock}) n.updateBlockchain({newBlockIdent:newBlock})
if self.verbose:
print("Computing discoverer's new difficulty")
n.updateDifficulty(mode, targetRate) n.updateDifficulty(mode, targetRate)
n.propagate(newBlockIdent)
if self.verbose:
print("propagating new block.")
n.propagate(self.t, newBlockIdent)
if self.verbose:
print("discovery complete")
elif len(eventTag)==3: elif len(eventTag)==3:
#eventTag = ("arrival", e.ident, pendingIdent) #eventTag = ("arrival", e.ident, pendingIdent)
@ -755,9 +265,9 @@ class FishGraph(StochasticProcess):
arriv = self.t + u + receiver.data["offset"] arriv = self.t + u + receiver.data["offset"]
newBlock = arrivalInfo["block"] newBlock = arrivalInfo["block"]
newBlock.arrivTimestamp = copy.deepcopy(arriv) newBlock.arrivTimestamp = copy.deepcopy(arriv)
receiver.data["blockchain"].updateBlockchain({newBlock.ident:newBlock}) receiver.updateBlockchain({newBlock.ident:newBlock})
receiver.updateDifficulty(mode, targetRate) receiver.updateDifficulty(mode, targetRate)
receiver.propagate(newBlock.ident) receiver.propagate(self.t, newBlock.ident)
else: else:
print("Error: eventTag was not a string, or not an array length 2 or 3. In fact, we have eventTag = ", eventTag) print("Error: eventTag was not a string, or not an array length 2 or 3. In fact, we have eventTag = ", eventTag)
@ -786,7 +296,7 @@ class FishGraph(StochasticProcess):
class Test_FishGraph(unittest.TestCase): class Test_FishGraph(unittest.TestCase):
def test_fishGraph(self): def test_fishGraph(self):
for i in range(10): for i in range(10):
params = {"numNodes":10, "probEdge":0.5, "maxNeighbors":10, "maxTime":10.0, "birthRate":0.001, "deathRate":0.001} params = {"numNodes":10, "probEdge":0.5, "maxNeighbors":10, "maxTime":10.0, "birthRate":0.1, "deathRate":0.1}
greg = FishGraph(params, verbosity=True) greg = FishGraph(params, verbosity=True)
greg.go() greg.go()

View file

@ -0,0 +1,101 @@
from Edge import *
class Graph(object):
'''
Graph object. Contains some data, a dict of nodes, and a dict of edges.
'''
def __init__(self, params={}, verbosity=True):
self.data=params
self.verbose = verbosity
self.nodes = {}
self.edges = {}
def createGraph(self, numNodes, probEdge, maxNeighbors):
# Create a new random graph with numNodes nodes, a
# likelihood any unordered pair of vertices has an edge
# probEdge, and maximum number of neighbors per node
# maxNeighbors.
# First, include inputted information into self.data
self.data.update({"probEdge":probEdge, "maxNeighbors":maxNeighbors})
# Next, for each node to be added, create the node and name it.
for i in range(numNodes):
nIdent = newIdent(i)
bl = Blockchain([], verbosity=True)
dat = {"blockchain":bl, "intensity":newIntensity(["uniform"]), "offset":newOffset("sumOfSkellams")}
# A node needs an ident, a data object, a verbosity, and a difficulty
n = Node([nIdent, dat, self.verbose, 1.0])
self.nodes.update({n.ident:n})
# Next, for each possible node pair, decide if an edge exists.
touched = {} # Dummy list of node pairs we have already considered.
for nIdent in self.nodes:
n = self.nodes[nIdent] # Pick a node
for mIdent in self.nodes:
m = self.nodes[mIdent] # Pick a pair element
notSameNode = (nIdent != mIdent) # Ensure we aren't dealing with (x,x)
nOpenSlots = (len(n.edges) < self.data["maxNeighbors"]) # ensure both nodes have open slots available for new edges
mOpenSlots = (len(m.edges) < self.data["maxNeighbors"])
untouched = ((nIdent, mIdent) not in touched) # make sure the pair and its transposition have not been touched
dehcuotnu = ((mIdent, nIdent) not in touched)
if notSameNode and nOpenSlots and mOpenSlots and untouched and dehcuotnu:
# Mark pair as touhed
touched.update({(nIdent,mIdent):True, (mIdent,nIdent):True})
if random.random() < self.data["probEdge"]:
# Determine if edge should exist and if so, add it.
nonce = len(self.edges)
e = Edge([newIdent(nonce),{"length":random.random(), "pendingBlocks":[]},self.verbose])
e.nodes.update({n.ident:n, m.ident:m})
self.nodes[nIdent].edges.update({e.ident:e})
self.nodes[mIdent].edges.update({e.ident:e})
self.edges.update({e.ident:e})
def addNode(self):
# Add new node
n = Node([newIdent(len(self.nodes)), {}, self.verbose, 1.0])
self.nodes.update({n.ident:n})
for mIdent in self.nodes:
# For every other node, check if an edge should exist and if so add it.
m = self.nodes[mIdent]
notSameNode = (n.ident != mIdent)
nOpenSlots = (len(n.edges) < self.data["maxNeighbors"])
mOpenSlots = (len(m.edges) < self.data["maxNeighbors"])
if notSameNode and nOpenSlots and mOpenSlots and random.random() < self.data["probEdge"]:
nonce = len(self.edges)
e = Edge([newIdent(nonce), {"length":random.random(), "pendingBlocks":[]}, self.verbose])
e.nodes.update({n.ident:n, m.ident:m})
n.edges.update({e.ident:e})
self.nodes[mIdent].edges.update({e.ident:e})
self.edges.update({e.ident:e})
return n.ident
def delNode(self, ident):
# Remove a node and wipe all memory of its edges from history.
edgesToDelete = self.nodes[ident].edges
for edgeIdent in edgesToDelete:
e = edgesToDelete[edgeIdent]
otherIdent = e.getNeighbor(ident)
del self.edges[edgeIdent]
del self.nodes[otherIdent].edges[edgeIdent]
del self.nodes[ident]
class Test_Graph(unittest.TestCase):
def test_graph(self):
greg = Graph()
greg.createGraph(3, 0.5, 10)
self.assertEqual(len(greg.nodes),3)
greg.addNode()
self.assertEqual(len(greg.nodes),4)
for edge in greg.edges:
self.assertEqual(len(greg.edges[edge].nodes),2)
nodeToKill = random.choice(list(greg.nodes.keys()))
greg.delNode(nodeToKill)
for edge in greg.edges:
self.assertEqual(len(greg.edges[edge].nodes),2)
for nodeIdent in greg.edges[edge].nodes:
self.assertTrue(nodeIdent in greg.nodes)
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Graph)
unittest.TextTestRunner(verbosity=1).run(suite)

View file

@ -0,0 +1,271 @@
from Blockchain import *
class Node(object):
'''
Node object. params [identity, blockchain (data), verbosity, difficulty]
'''
def __init__(self, params={}):
try:
assert len(params)==4
except AssertionError:
print("Error, Tried to create malformed node.")
else:
self.ident = params["ident"]
self.data = params["data"]
self.diff = params["diff"]
self.verbose = params["verbose"]
self.edges = {}
def updateBlockchain(self, incBlocks, diffUpdateRate=1, mode="Nakamoto", targetRate=1.0/1209600.0):
# dataToUpdate shall be a dictionary of block identities (as keys) and their associated blocks (as values)
# to be added to the local data. We assume difficulty scores have been reported honestly for now.
# Stash a copy of incoming blocks so removing keys won't shrink the size of the dictionary over which
# we are looping.
if self.verbose:
print("\t\t Updating blockchain.")
if self.verbose:
print("\t\tAdding incoming blocks in order of parentage")
if self.verbose:
print("\t\tFirst step. incBlocks has " + str(len(incBlocks)) + " entries.")
tempData = copy.deepcopy(incBlocks)
if self.verbose:
print("\t\t Now tempData has " + str(len(tempData)) + " entries.")
for key in incBlocks.blocks:
if key in self.data["blockchain"].blocks:
del tempData[key]
elif incBlocks.blocks[key].parent in self.data["blockchain"].blocks or incBlocks[key].parent is None:
self.data["blockchain"].addBlock(incBlocks.blocks[key])
#if len(self.data["blockchain"]) % diffUpdateRate == 0:
# self.updateDifficulty(mode, targetRate)
del tempData[key]
incBlocks = copy.deepcopy(tempData)
if self.verbose:
print("\t\t Now incBlocks has " + str(len(incBlocks.blocks)) + " entries.")
if self.verbose:
print("\t\tRemaining steps (while loop)")
while len(incBlocks)>0:
if self.verbose:
print("\t\t Now tempData has " + str(len(tempData.blocks)) + " entries.")
for key in incBlocks:
if key in self.data["blockchain"].blocks:
del tempData[key]
elif incBlocks.blocks[key].parent in self.data["blockchain"].blocks:
self.data["blockchain"].addBlock(incBlocks.blocks[key])
del tempData[key]
incBlocks = copy.deepcopy(tempData)
if self.verbose:
print("\t\t Now incBlocks has " + str(len(incBlocks)) + " entries.")
def updateDifficulty(self, mode="Nakamoto", targetRate=1.0/1209600.0):
# Compute the difficulty of the next block
# Note for default, targetRate = two weeks/period, seven days/week, 24 hours/day, 60 minutes/hour, 60 seconds/minute) = 1209600 seconds/period
if mode=="Nakamoto":
# Use MLE estimate of poisson process, compare to targetRate, update by multiplying by resulting ratio.
count = 2016
bc = self.data["blockchain"]
ident = bc.miningIdent
topTime = copy.deepcopy(bc.blocks[ident].discoTimestamp)
parent = bc.blocks[ident].parent
count = count - 1
touched = False
while count > 0 and parent is not None:
ident = copy.deepcopy(parent)
parent = bc.blocks[ident].parent
count = count - 1
touched = True
if not touched:
mleDiscoRate = targetRate
else:
botTime = copy.deepcopy(bc.blocks[ident].discoTimestamp)
# Algebra is okay:
assert 0 <= 2016 - count and 2016 - count < 2017
assert topTime != botTime
# MLE estimate of arrivals per second:
mleDiscoRate = float(2016 - count)/float(topTime - botTime)
mleDiscoRate = abs(mleDiscoRate)
# Rate must be positive... so the MLE for block arrival rate
# assuming a Poisson process _is not even well-defined_ as
# an estimate for block arrival rate assuming timestamps are
# inaccurately reported!
# We use it nonetheless.
# How much should difficulty change?
self.diff = self.diff*(mleDiscoRate/targetRate)
elif mode=="vanSaberhagen":
# Similar to above, except use 1200 blocks, discard top 120 and bottom 120 after sorting.
# 4 minute blocks in the original cryptonote, I believe... targetRate = 1.0/
# 4 minutes/period, 60 seconds/minute ~ 240 seconds/period
assert targetRate==1.0/240.0
count = 1200
ident = self.data.miningIdent
bl = []
bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp))
parent = self.data.blocks[ident].parent
count = count - 1
while count > 0 and parent is not NOne:
ident = copy.deepcopy(parent)
bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp))
parent = self.data.blocks[ident].parent
count = count-1
# sort
bl = sorted(bl)
# remove outliers
bl = bl[120:-120]
# get topTime and botTime
topTime = bl[-1]
botTime = bl[0]
# Assert algebra will work
assert 0 <= 960 - count and 960 - count < 961
assert topTime > botTime
# Sort of the MLE: # blocks/difference in reported times
# But not the MLE, since the reported times may not be
# the actual times, the "difference in reported times" !=
# "ground truth difference in block discoery times" in general
naiveDiscoRate = (960 - count)/(topTime - botTime)
# How much should difficulty change?
self.diff = self.diff*(naiveDiscoRate/targetRate)
elif mode=="MOM:expModGauss":
# Similar to "vanSaberhagen" except with 2-minute blocks and
# we attempt to take into account that "difference in timestamps"
# can be negative by:
# 1) insisting that the ordering induced by the blockchain and
# 2) modeling timestamps as exponentially modified gaussian.
# If timestamps are T = X + Z where X is exponentially dist-
# ributed with parameter lambda and Z is some Gaussian
# noise with average mu and variance sigma2, then we can est-
# imate sigma2, mu, and lambda:
# mu ~ mean - stdev*(skewness/2)**(1.0/3.0)
# sigma2 ~ variance*(1-(skewness/2)**(2.0/3.0))
# lambda ~ (1.0/(stdev))*(2/skewness)**(1.0/3.0)
assert targetRate==1.0/120.0
count = 1200
ident = self.data.miningIdent
bl = []
bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp))
parent = self.data.blocks[ident].parent
count = count - 1
while count > 0 and parent is not NOne:
ident = copy.deepcopy(parent)
bl.append(copy.deepcopy(self.data.blocks[ident].discoTimestamp))
parent = self.data.blocks[ident].parent
count = count-1
sk = skew(bl)
va = var(bl)
stdv = sqrt(va)
lam = (1.0/stdv)*(2.0/sk)**(1.0/3.0)
self.diff = self.diff*(lam/targetRate)
else:
print("Error, invalid difficulty mode entered.")
def propagate(self, t, blockIdent):
for edgeIdent in self.edges:
e = self.edges[edgeIdent]
l = e.data["length"]
toa = t + l
mIdent = e.getNeighbor(self.ident)
m = e.nodes[mIdent]
bc = m.data["blockchain"]
if blockIdent not in bc.blocks:
pB = e.data["pendingBlocks"]
pendingIdent = newIdent(len(pB))
mybc = self.data["blockchain"]
pendingDat = {"timeOfArrival":toa, "destIdent":mIdent, "block":mybc.blocks[blockIdent]}
pB.update({pendingIdent:pendingDat})
class Test_Node(unittest.TestCase):
def test_node(self):
verbose = True
nellyIdent = newIdent(0)
bill = Blockchain([], verbosity=verbose)
name = newIdent(0)
t = time.time()
s = t+1
diff = 1.0
params = {"ident":name, "disco":t, "arriv":s, "parent":None, "diff":diff}
genesis = Block(params)
bill.addBlock(genesis)
time.sleep(10)
name = newIdent(1)
t = time.time()
s = t+1
diff = 1.0
params = {"ident":name, "disco":t, "arriv":s, "parent":genesis.ident, "diff":diff}
blockA = Block(params)
bill.addBlock(blockA)
# Nodes need an identity and a blockchain object and verbosity and difficulty
nodeData = {"blockchain":bill, "intensity":random.random(), "offset":random.random()}
params = {"ident":name, "data":nodeData, "diff":diff, "verbose":verbose}
nelly = Node(params)
nelly.updateDifficulty(mode="Nakamoto")
time.sleep(9)
name = newIdent(len(nelly.data))
t = time.time()
s = t + 1
params = {"ident":name, "disco":t, "arriv":s, "parent":blockA.ident, "diff":diff}
blockB = Block(params)
nelly.updateBlockchain({blockB.ident:blockB})
time.sleep(8)
name = newIdent(len(nelly.data))
t = time.time()
s = t + 1
params = {"ident":name, "disco":t, "arriv":s, "parent":blockA.ident, "diff":diff}
blockC = Block(params)
nelly.updateBlockchain({blockC.ident:blockC})
time.sleep(1)
name = newIdent(len(nelly.data))
t = time.time()
s = t + 1
params = {"ident":name, "disco":t, "arriv":s, "parent":blockC.ident, "diff":diff}
blockD = Block(params)
nelly.updateBlockchain({blockD.ident:blockD})
time.sleep(7)
name = newIdent(len(nelly.data))
t = time.time()
s = t + 1
params = {"ident":name, "disco":t, "arriv":s, "parent":blockD.ident, "diff":diff}
blockE = Block(params)
nelly.updateBlockchain({blockE.ident:blockE})
time.sleep(6)
name = newIdent(len(nelly.data))
t = time.time()
s = t + 1
params = {"ident":name, "disco":t, "arriv":s, "parent":blockE.ident, "diff":diff}
blockF = Block(params)
nelly.updateBlockchain({blockF.ident:blockF})
suite = unittest.TestLoader().loadTestsFromTestCase(Test_Node)
unittest.TextTestRunner(verbosity=1).run(suite)

View file

@ -0,0 +1,53 @@
import unittest, random, time
class StochasticProcess(object):
'''
Stochastic processes have a clock and a state.
The clock moves forward, and then the state updates.
More detail requires knowledge of the underlying stochProc.
'''
def __init__(self, params=None):
# initialize with initial data
self.data = params
self.t = 0.0 # should always start at t=0.0
self.state = 0.0 # magic number
self.maxTime = 1000.0 # magic number
self.saveFile = "output.csv"
self.verbose = True
def go(self):
# Executes stochastic process.
assert self.maxTime > 0.0 # Check loop will eventually terminate.
t = self.t
while t <= self.maxTime:
deltaT = self.getNextTime() # Pick the next "time until event" and a description of the event.
self.updateState(t, deltaT) # Update state with deltaT input
t = self.t
if self.verbose:
print("Recording...")
self.record()
def getNextTime(self):
return 1 # Magic number right now
def updateState(self, t, deltaT):
# Update the state of the system. In this case,
# we are doing a random walk on the integers.
self.state += random.randrange(-1,2,1) # [-1, 0, 1]
self.t += deltaT
def record(self):
with open(self.saveFile,"w") as recordKeeper:
line = str(self.t) + ",\t" + str(self.state) + "\n"
recordKeeper.write(line)
class Test_StochasticProcess(unittest.TestCase):
def test_sp(self):
sally = StochasticProcess()
sally.verbose = False
sally.go()
suite = unittest.TestLoader().loadTestsFromTestCase(Test_StochasticProcess)
unittest.TextTestRunner(verbosity=1).run(suite)