From 4e62b6cd120ae784e13c68419c23e29f0ccf13da Mon Sep 17 00:00:00 2001 From: elecbug Date: Thu, 2 Oct 2025 10:28:48 +0900 Subject: [PATCH 1/5] temp: start p2p --- p2p/network.go | 54 ++++++++++++++++++++++++++++ p2p/node.go | 93 +++++++++++++++++++++++++++++++++++++++++++++++++ p2p/p2p_test.go | 19 ++++++++++ 3 files changed, 166 insertions(+) create mode 100644 p2p/network.go create mode 100644 p2p/node.go create mode 100644 p2p/p2p_test.go diff --git a/p2p/network.go b/p2p/network.go new file mode 100644 index 0000000..14ff5fe --- /dev/null +++ b/p2p/network.go @@ -0,0 +1,54 @@ +package p2p + +import ( + "github.com/elecbug/netkit/network-graph/graph" + "github.com/elecbug/netkit/network-graph/node" +) + +// GenerateNetwork creates a P2P network from the given graph. +// nodeLatency and edgeLatency are functions that generate latencies for nodes and edges respectively. +func GenerateNetwork(g *graph.Graph, nodeLatency, edgeLatency func() float64) map[ID]*Node { + nodes := make(map[ID]*Node) + maps := make(map[node.ID]ID) + + // create nodes + for i, gn := range g.Nodes() { + n := &Node{ + ID: ID(i), + Latency: nodeLatency(), + Edges: make(map[ID]Edge), + } + + nodes[n.ID] = n + maps[gn] = n.ID + } + + for i, gn := range g.Nodes() { + n := nodes[ID(i)] + + for _, neighbor := range g.Neighbors(gn) { + j := maps[neighbor] + + edge := Edge{ + TargetID: ID(j), + Latency: edgeLatency(), + } + + n.Edges[edge.TargetID] = edge + } + } + + return nodes +} + +// RunNetworkSimulation starts the message handling routines for all nodes in the network. +func RunNetworkSimulation(nodes map[ID]*Node) { + for _, n := range nodes { + n.eachRun(nodes) + } +} + +// Publish sends a message to the specified node's message queue. +func Publish(node *Node, msg Message) { + node.msgQueue <- msg +} diff --git a/p2p/node.go b/p2p/node.go new file mode 100644 index 0000000..0dc759b --- /dev/null +++ b/p2p/node.go @@ -0,0 +1,93 @@ +package p2p + +import ( + "math" + "math/rand" + "sync" + "time" +) + +// ID represents a unique identifier for a node in the P2P network. +type ID uint64 + +// Edge represents a connection from one node to another in the P2P network. +type Edge struct { + TargetID ID + Latency float64 // in milliseconds +} + +// Node represents a node in the P2P network. +type Node struct { + ID ID + Latency float64 // in milliseconds + Edges map[ID]Edge + msgQueue chan Message +} + +// Degree returns the number of edges connected to the node. +func (n *Node) Degree() int { + return len(n.Edges) +} + +// Message represents a message sent between nodes in the P2P network. +type Message string + +// eachRun starts the message handling routine for the node. +func (n *Node) eachRun(network map[ID]*Node) { + n.msgQueue = make(chan Message, 100) + receivedMap := make(map[Message][]ID) + mu := sync.Mutex{} + + go func() { + for { + for msg := range n.msgQueue { + if _, ok := receivedMap[msg]; !ok { + mu.Lock() + receivedMap[msg] = []ID{} + mu.Unlock() + + go func() { + time.Sleep(time.Duration(n.Latency) * time.Millisecond) + publish(n.Edges, receivedMap[msg], network, msg) + }() + } + mu.Lock() + receivedMap[msg] = append(receivedMap[msg], n.ID) + mu.Unlock() + } + } + }() +} + +// publish sends the message to all connected nodes except those in the ids slice. +func publish(edges map[ID]Edge, ids []ID, network map[ID]*Node, msg Message) { + for i, edge := range edges { + found := false + + for _, id := range ids { + if id == i { + found = true + break + } + } + + if found { + continue + } + + time.Sleep(time.Duration(edge.Latency) * time.Millisecond) + network[edge.TargetID].msgQueue <- msg + } +} + +// LogNormalRand generates a log-normally distributed random number +// with given mu and sigma parameters. +func LogNormalRand(mu, sigma float64, src rand.Source) float64 { + r := rand.New(src) + + u1 := r.Float64() + u2 := r.Float64() + z := math.Sqrt(-2.0*math.Log(u1)) * math.Cos(2*math.Pi*u2) + + return math.Exp(mu + sigma*z) +} diff --git a/p2p/p2p_test.go b/p2p/p2p_test.go new file mode 100644 index 0000000..77468df --- /dev/null +++ b/p2p/p2p_test.go @@ -0,0 +1,19 @@ +package p2p_test + +import ( + "testing" + + "github.com/elecbug/netkit/network-graph/graph/standard_graph" + "github.com/elecbug/netkit/p2p" +) + +func TestGenerateNetwork(t *testing.T) { + g := standard_graph.ErdosRenyiGraph(1000, 0.01, true) + + nodeLatency := func() float64 { return p2p.LogNormalRand(3, 0.5, nil) } + edgeLatency := func() float64 { return p2p.LogNormalRand(2, 0.3, nil) } + + nw := p2p.GenerateNetwork(g, nodeLatency, edgeLatency) + p2p.RunNetworkSimulation(nw) + p2p.Publish(nw[0], "Hello, P2P Network!") +} From 121c6b13ef4b36947b49796dcbe265bd24ae8e2e Mon Sep 17 00:00:00 2001 From: elecbug Date: Thu, 2 Oct 2025 14:36:18 +0900 Subject: [PATCH 2/5] fix: er graph bug --- .../graph/standard_graph/erdos_reyni.go | 4 ++ p2p/network.go | 4 +- p2p/node.go | 68 +++++++++++------- p2p/p2p_test.go | 24 ++++++- temp | Bin 0 -> 432966 bytes 5 files changed, 69 insertions(+), 31 deletions(-) create mode 100644 temp diff --git a/network-graph/graph/standard_graph/erdos_reyni.go b/network-graph/graph/standard_graph/erdos_reyni.go index 6102046..a4db56e 100644 --- a/network-graph/graph/standard_graph/erdos_reyni.go +++ b/network-graph/graph/standard_graph/erdos_reyni.go @@ -11,6 +11,10 @@ func ErdosRenyiGraph(n int, p float64, isUndirected bool) *graph.Graph { g := graph.New(isUndirected) + for i := 0; i < n; i++ { + g.AddNode(node.ID(toString(i))) + } + if isUndirected { for i := 0; i < n; i++ { for j := i + 1; j < n; j++ { diff --git a/p2p/network.go b/p2p/network.go index 14ff5fe..c73fbce 100644 --- a/p2p/network.go +++ b/p2p/network.go @@ -49,6 +49,6 @@ func RunNetworkSimulation(nodes map[ID]*Node) { } // Publish sends a message to the specified node's message queue. -func Publish(node *Node, msg Message) { - node.msgQueue <- msg +func Publish(node *Node, msg string) { + node.msgQueue <- Message{From: node.ID, Content: msg} } diff --git a/p2p/node.go b/p2p/node.go index 0dc759b..517a9a5 100644 --- a/p2p/node.go +++ b/p2p/node.go @@ -18,10 +18,13 @@ type Edge struct { // Node represents a node in the P2P network. type Node struct { - ID ID - Latency float64 // in milliseconds - Edges map[ID]Edge - msgQueue chan Message + ID ID + Latency float64 // in milliseconds + Edges map[ID]Edge + Received map[string][]ID + ReceivedTime map[string]time.Time + msgQueue chan Message + mu sync.Mutex } // Degree returns the number of edges connected to the node. @@ -30,42 +33,52 @@ func (n *Node) Degree() int { } // Message represents a message sent between nodes in the P2P network. -type Message string +type Message struct { + From ID + Content string +} // eachRun starts the message handling routine for the node. func (n *Node) eachRun(network map[ID]*Node) { - n.msgQueue = make(chan Message, 100) - receivedMap := make(map[Message][]ID) - mu := sync.Mutex{} - go func() { + n.msgQueue = make(chan Message, 1000) + n.Received = make(map[string][]ID) + n.ReceivedTime = make(map[string]time.Time) + + n.mu = sync.Mutex{} + for { for msg := range n.msgQueue { - if _, ok := receivedMap[msg]; !ok { - mu.Lock() - receivedMap[msg] = []ID{} - mu.Unlock() - - go func() { + if _, ok := n.ReceivedTime[msg.Content]; !ok { + n.mu.Lock() + n.ReceivedTime[msg.Content] = time.Now() + n.Received[msg.Content] = []ID{} + n.Received[msg.Content] = append(n.Received[msg.Content], msg.From) + n.mu.Unlock() + + go func(msg Message) { time.Sleep(time.Duration(n.Latency) * time.Millisecond) - publish(n.Edges, receivedMap[msg], network, msg) - }() + n.mu.Lock() + n.publish(network, msg.Content) + n.mu.Unlock() + }(msg) + } else { + n.mu.Lock() + n.Received[msg.Content] = append(n.Received[msg.Content], msg.From) + n.mu.Unlock() } - mu.Lock() - receivedMap[msg] = append(receivedMap[msg], n.ID) - mu.Unlock() } } }() } // publish sends the message to all connected nodes except those in the ids slice. -func publish(edges map[ID]Edge, ids []ID, network map[ID]*Node, msg Message) { - for i, edge := range edges { +func (n *Node) publish(network map[ID]*Node, msg string) { + for _, edge := range n.Edges { found := false - for _, id := range ids { - if id == i { + for _, id := range n.Received[msg] { + if id == edge.TargetID { found = true break } @@ -75,8 +88,11 @@ func publish(edges map[ID]Edge, ids []ID, network map[ID]*Node, msg Message) { continue } - time.Sleep(time.Duration(edge.Latency) * time.Millisecond) - network[edge.TargetID].msgQueue <- msg + go func(edge Edge) { + time.Sleep(time.Duration(edge.Latency) * time.Millisecond) + msg := Message{From: n.ID, Content: msg} + network[edge.TargetID].msgQueue <- msg + }(edge) } } diff --git a/p2p/p2p_test.go b/p2p/p2p_test.go index 77468df..96adb4c 100644 --- a/p2p/p2p_test.go +++ b/p2p/p2p_test.go @@ -1,19 +1,37 @@ package p2p_test import ( + "math/rand" "testing" + "time" "github.com/elecbug/netkit/network-graph/graph/standard_graph" "github.com/elecbug/netkit/p2p" ) func TestGenerateNetwork(t *testing.T) { - g := standard_graph.ErdosRenyiGraph(1000, 0.01, true) + g := standard_graph.ErdosRenyiGraph(1000, 0.005, true) + t.Logf("Generated graph with %d nodes and %d edges\n", len(g.Nodes()), g.EdgeCount()) + src := rand.NewSource(time.Now().UnixNano()) - nodeLatency := func() float64 { return p2p.LogNormalRand(3, 0.5, nil) } - edgeLatency := func() float64 { return p2p.LogNormalRand(2, 0.3, nil) } + nodeLatency := func() float64 { return p2p.LogNormalRand(5.704, 0.5, src) } + edgeLatency := func() float64 { return p2p.LogNormalRand(5.704, 0.3, src) } nw := p2p.GenerateNetwork(g, nodeLatency, edgeLatency) + t.Logf("Generated network with %d nodes\n", len(nw)) + p2p.RunNetworkSimulation(nw) p2p.Publish(nw[0], "Hello, P2P Network!") + + time.Sleep(5 * time.Second) + + count := 0 + for id, node := range nw { + c := len(node.Received["Hello, P2P Network!"]) + t.Logf("Node %d received %d/%d\n", id, c, len(node.Edges)) + t.Logf("Node %d received messages: %+v, %+v\n", id, node.ReceivedTime, node.Received) + count += c + } + + t.Logf("Total received count: %d\n", count) } diff --git a/temp b/temp new file mode 100644 index 0000000000000000000000000000000000000000..a4948df96d3e8b88050bb8f909db782ae1715899 GIT binary patch literal 432966 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 A?EnA( literal 0 HcmV?d00001 From 98f674b20a8b330ca7689829b3532b4db217b1c5 Mon Sep 17 00:00:00 2001 From: elecbug Date: Thu, 2 Oct 2025 17:36:07 +0900 Subject: [PATCH 3/5] remove: temp --- p2p/node.go | 111 +++++++++++++++++++++++++++--------------------- p2p/p2p_test.go | 9 +++- temp | Bin 432966 -> 0 bytes 3 files changed, 70 insertions(+), 50 deletions(-) delete mode 100644 temp diff --git a/p2p/node.go b/p2p/node.go index 517a9a5..b981187 100644 --- a/p2p/node.go +++ b/p2p/node.go @@ -18,13 +18,16 @@ type Edge struct { // Node represents a node in the P2P network. type Node struct { - ID ID - Latency float64 // in milliseconds - Edges map[ID]Edge - Received map[string][]ID - ReceivedTime map[string]time.Time - msgQueue chan Message - mu sync.Mutex + ID ID + Latency float64 + Edges map[ID]Edge + + RecvFrom map[string]map[ID]struct{} // content -> set of senders + SentTo map[string]map[ID]struct{} // content -> set of targets + SeenAt map[string]time.Time // content -> first arrival time + + msgQueue chan Message + mu sync.Mutex } // Degree returns the number of edges connected to the node. @@ -42,58 +45,70 @@ type Message struct { func (n *Node) eachRun(network map[ID]*Node) { go func() { n.msgQueue = make(chan Message, 1000) - n.Received = make(map[string][]ID) - n.ReceivedTime = make(map[string]time.Time) - - n.mu = sync.Mutex{} - - for { - for msg := range n.msgQueue { - if _, ok := n.ReceivedTime[msg.Content]; !ok { - n.mu.Lock() - n.ReceivedTime[msg.Content] = time.Now() - n.Received[msg.Content] = []ID{} - n.Received[msg.Content] = append(n.Received[msg.Content], msg.From) - n.mu.Unlock() - - go func(msg Message) { - time.Sleep(time.Duration(n.Latency) * time.Millisecond) - n.mu.Lock() - n.publish(network, msg.Content) - n.mu.Unlock() - }(msg) - } else { - n.mu.Lock() - n.Received[msg.Content] = append(n.Received[msg.Content], msg.From) - n.mu.Unlock() - } + n.RecvFrom = make(map[string]map[ID]struct{}) + n.SentTo = make(map[string]map[ID]struct{}) + n.SeenAt = make(map[string]time.Time) + + for msg := range n.msgQueue { + first := false + var excludeSnapshot map[ID]struct{} + + n.mu.Lock() + if _, ok := n.RecvFrom[msg.Content]; !ok { + n.RecvFrom[msg.Content] = make(map[ID]struct{}) + } + n.RecvFrom[msg.Content][msg.From] = struct{}{} + + if _, ok := n.SeenAt[msg.Content]; !ok { + n.SeenAt[msg.Content] = time.Now() + first = true + excludeSnapshot = copyIDSet(n.RecvFrom[msg.Content]) + } + n.mu.Unlock() + + if first { + go func(content string, exclude map[ID]struct{}) { + time.Sleep(time.Duration(n.Latency) * time.Millisecond) + n.publish(network, content, exclude) + }(msg.Content, excludeSnapshot) } } }() } -// publish sends the message to all connected nodes except those in the ids slice. -func (n *Node) publish(network map[ID]*Node, msg string) { - for _, edge := range n.Edges { - found := false +// copyIDSet creates a shallow copy of a set of IDs. +func copyIDSet(src map[ID]struct{}) map[ID]struct{} { + dst := make(map[ID]struct{}, len(src)) + for k := range src { + dst[k] = struct{}{} + } + return dst +} - for _, id := range n.Received[msg] { - if id == edge.TargetID { - found = true - break - } - } +// publish sends the message to neighbors, excluding 'exclude' and already-sent targets. +func (n *Node) publish(network map[ID]*Node, content string, exclude map[ID]struct{}) { + n.mu.Lock() + if _, ok := n.SentTo[content]; !ok { + n.SentTo[content] = make(map[ID]struct{}) + } - if found { + for _, edge := range n.Edges { + if _, wasSender := exclude[edge.TargetID]; wasSender { + continue + } + if _, already := n.SentTo[content][edge.TargetID]; already { continue } - go func(edge Edge) { - time.Sleep(time.Duration(edge.Latency) * time.Millisecond) - msg := Message{From: n.ID, Content: msg} - network[edge.TargetID].msgQueue <- msg - }(edge) + n.SentTo[content][edge.TargetID] = struct{}{} + + edgeCopy := edge + go func(e Edge) { + time.Sleep(time.Duration(e.Latency) * time.Millisecond) + network[e.TargetID].msgQueue <- Message{From: n.ID, Content: content} + }(edgeCopy) } + n.mu.Unlock() } // LogNormalRand generates a log-normally distributed random number diff --git a/p2p/p2p_test.go b/p2p/p2p_test.go index 96adb4c..053e192 100644 --- a/p2p/p2p_test.go +++ b/p2p/p2p_test.go @@ -27,9 +27,14 @@ func TestGenerateNetwork(t *testing.T) { count := 0 for id, node := range nw { - c := len(node.Received["Hello, P2P Network!"]) + c := len(node.RecvFrom["Hello, P2P Network!"]) t.Logf("Node %d received %d/%d\n", id, c, len(node.Edges)) - t.Logf("Node %d received messages: %+v, %+v\n", id, node.ReceivedTime, node.Received) + t.Logf("Node %d received messages: %+v, %+v, %+v\n", + id, + node.RecvFrom["Hello, P2P Network!"], + node.SentTo["Hello, P2P Network!"], + node.SeenAt["Hello, P2P Network!"], + ) count += c } diff --git a/temp b/temp deleted file mode 100644 index a4948df96d3e8b88050bb8f909db782ae1715899..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 432966 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 A?EnA( From f39d3f619ee09c90b9aede5b8149f4196f1b48e1 Mon Sep 17 00:00:00 2001 From: elecbug Date: Thu, 2 Oct 2025 17:53:51 +0900 Subject: [PATCH 4/5] feat: update p2p tester --- .gitignore | 3 ++- p2p/network.go | 26 ++++++++++++++++++++------ p2p/node.go | 4 +++- p2p/p2p_test.go | 11 +++++++---- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 6adb588..fb99b84 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,5 @@ log venv/ __pycache__/ -*.pyc \ No newline at end of file +*.pyc +temp \ No newline at end of file diff --git a/p2p/network.go b/p2p/network.go index c73fbce..8700e63 100644 --- a/p2p/network.go +++ b/p2p/network.go @@ -1,20 +1,28 @@ package p2p import ( + "strconv" + "github.com/elecbug/netkit/network-graph/graph" "github.com/elecbug/netkit/network-graph/node" ) // GenerateNetwork creates a P2P network from the given graph. // nodeLatency and edgeLatency are functions that generate latencies for nodes and edges respectively. -func GenerateNetwork(g *graph.Graph, nodeLatency, edgeLatency func() float64) map[ID]*Node { +func GenerateNetwork(g *graph.Graph, nodeLatency, edgeLatency func() float64) (map[ID]*Node, error) { nodes := make(map[ID]*Node) maps := make(map[node.ID]ID) // create nodes - for i, gn := range g.Nodes() { + for _, gn := range g.Nodes() { + num, err := strconv.Atoi(gn.String()) + + if err != nil { + return nil, err + } + n := &Node{ - ID: ID(i), + ID: ID(num), Latency: nodeLatency(), Edges: make(map[ID]Edge), } @@ -23,8 +31,14 @@ func GenerateNetwork(g *graph.Graph, nodeLatency, edgeLatency func() float64) ma maps[gn] = n.ID } - for i, gn := range g.Nodes() { - n := nodes[ID(i)] + for _, gn := range g.Nodes() { + num, err := strconv.Atoi(gn.String()) + + if err != nil { + return nil, err + } + + n := nodes[ID(num)] for _, neighbor := range g.Neighbors(gn) { j := maps[neighbor] @@ -38,7 +52,7 @@ func GenerateNetwork(g *graph.Graph, nodeLatency, edgeLatency func() float64) ma } } - return nodes + return nodes, nil } // RunNetworkSimulation starts the message handling routines for all nodes in the network. diff --git a/p2p/node.go b/p2p/node.go index b981187..c35b65a 100644 --- a/p2p/node.go +++ b/p2p/node.go @@ -99,7 +99,9 @@ func (n *Node) publish(network map[ID]*Node, content string, exclude map[ID]stru if _, already := n.SentTo[content][edge.TargetID]; already { continue } - + if _, received := n.RecvFrom[content][edge.TargetID]; received { + continue + } n.SentTo[content][edge.TargetID] = struct{}{} edgeCopy := edge diff --git a/p2p/p2p_test.go b/p2p/p2p_test.go index 053e192..99f44ad 100644 --- a/p2p/p2p_test.go +++ b/p2p/p2p_test.go @@ -17,8 +17,11 @@ func TestGenerateNetwork(t *testing.T) { nodeLatency := func() float64 { return p2p.LogNormalRand(5.704, 0.5, src) } edgeLatency := func() float64 { return p2p.LogNormalRand(5.704, 0.3, src) } - nw := p2p.GenerateNetwork(g, nodeLatency, edgeLatency) + nw, _ := p2p.GenerateNetwork(g, nodeLatency, edgeLatency) t.Logf("Generated network with %d nodes\n", len(nw)) + for id, node := range nw { + t.Logf("Node %d: latency=%.2fms, edges=%v\n", id, node.Latency, node.Edges) + } p2p.RunNetworkSimulation(nw) p2p.Publish(nw[0], "Hello, P2P Network!") @@ -27,9 +30,9 @@ func TestGenerateNetwork(t *testing.T) { count := 0 for id, node := range nw { - c := len(node.RecvFrom["Hello, P2P Network!"]) - t.Logf("Node %d received %d/%d\n", id, c, len(node.Edges)) - t.Logf("Node %d received messages: %+v, %+v, %+v\n", + c := len(node.SentTo["Hello, P2P Network!"]) + t.Logf("Node %d sent %d/%d\n", id, c, len(node.Edges)) + t.Logf("Node %d data: recv: %v, sent: %v, seen: %v\n", id, node.RecvFrom["Hello, P2P Network!"], node.SentTo["Hello, P2P Network!"], From 0547c6a419361d1363cda0d5f7e2d619c2ae8174 Mon Sep 17 00:00:00 2001 From: elecbug Date: Thu, 2 Oct 2025 17:55:08 +0900 Subject: [PATCH 5/5] chore: split helper --- p2p/helper.go | 18 ++++++++++++++++++ p2p/node.go | 26 ++++++-------------------- 2 files changed, 24 insertions(+), 20 deletions(-) create mode 100644 p2p/helper.go diff --git a/p2p/helper.go b/p2p/helper.go new file mode 100644 index 0000000..a3f733f --- /dev/null +++ b/p2p/helper.go @@ -0,0 +1,18 @@ +package p2p + +import ( + "math" + "math/rand" +) + +// LogNormalRand generates a log-normally distributed random number +// with given mu and sigma parameters. +func LogNormalRand(mu, sigma float64, src rand.Source) float64 { + r := rand.New(src) + + u1 := r.Float64() + u2 := r.Float64() + z := math.Sqrt(-2.0*math.Log(u1)) * math.Cos(2*math.Pi*u2) + + return math.Exp(mu + sigma*z) +} diff --git a/p2p/node.go b/p2p/node.go index c35b65a..e8ab8fd 100644 --- a/p2p/node.go +++ b/p2p/node.go @@ -1,8 +1,6 @@ package p2p import ( - "math" - "math/rand" "sync" "time" ) @@ -10,6 +8,12 @@ import ( // ID represents a unique identifier for a node in the P2P network. type ID uint64 +// Message represents a message sent between nodes in the P2P network. +type Message struct { + From ID + Content string +} + // Edge represents a connection from one node to another in the P2P network. type Edge struct { TargetID ID @@ -35,12 +39,6 @@ func (n *Node) Degree() int { return len(n.Edges) } -// Message represents a message sent between nodes in the P2P network. -type Message struct { - From ID - Content string -} - // eachRun starts the message handling routine for the node. func (n *Node) eachRun(network map[ID]*Node) { go func() { @@ -112,15 +110,3 @@ func (n *Node) publish(network map[ID]*Node, content string, exclude map[ID]stru } n.mu.Unlock() } - -// LogNormalRand generates a log-normally distributed random number -// with given mu and sigma parameters. -func LogNormalRand(mu, sigma float64, src rand.Source) float64 { - r := rand.New(src) - - u1 := r.Float64() - u2 := r.Float64() - z := math.Sqrt(-2.0*math.Log(u1)) * math.Cos(2*math.Pi*u2) - - return math.Exp(mu + sigma*z) -}