#!/usr/bin/env ruby require 'pry' module NetMonte STANDARD_DEVIATION = 2.5 MINUTES = 365*24*60 class Simulate def initialize years, *networks years.times do |year| nets = init_networks networks roulette year, nets end end private def roulette year, nets MINUTES.times do |minute| nets.each do |net| net.run minute end if nets.all? { |net| net.down } puts "outage at year #{year}, minute #{minute}" end end end def init_networks networks nets = [] networks.each do |net| outage_count = RandomGaussian.new net[:outage_count], STANDARD_DEVIATION outage_length = RandomGaussian.new net[:outage_length], STANDARD_DEVIATION nets << Network.new(outage_count.rand, outage_length) end nets end end class Network attr_reader :down def initialize outage_count, outage_length @down = false @outage_end = 0 @outage_probability = (1 / ((outage_count/365)/1440)).to_i @outage_length = outage_length end def run minute if @down and minute > @outage_end @down = false end if rand(@outage_probability) == 42 @down = true @outage_end = minute + @outage_length.rand end end end #stole from SE class RandomGaussian def initialize(mean = 0.0, sd = 1.0, rng = lambda { Kernel.rand }) @mean, @sd, @rng = mean, sd, rng @compute_next_pair = false end def rand if (@compute_next_pair = !@compute_next_pair) # Compute a pair of random values with normal distribution. # See http://en.wikipedia.org/wiki/Box-Muller_transform theta = 2 * Math::PI * @rng.call scale = @sd * Math.sqrt(-2 * Math.log(1 - @rng.call)) @g1 = @mean + scale * Math.sin(theta) @g0 = @mean + scale * Math.cos(theta) else @g1 end end end end begin if __FILE__ == $0 netA = { outage_count: 3.0, outage_length: 180.0 } netO = { outage_count: 5.0, outage_length: 60.0 } NetMonte::Simulate.new 10000, netA, netO binding.pry end end