def _build_results(records: Records) -> Results: """ Create results from the records. Parameters ---------- records : Records """ ret = Results() # # 1) Build system size, queue size and busy (server size) # distributions. To do this, we need PMFs. Queue size # PMF and busy PMF can be computed from system size PMF. # system_size_pmf = list(records.system_size.pmf) num_states = len(system_size_pmf) p0 = system_size_pmf[0] p1 = system_size_pmf[1] if num_states > 1 else 0.0 queue_size_pmf = [p0 + p1] + system_size_pmf[2:] server_size_pmf = [p0, sum(system_size_pmf[1:])] ret.system_size = CountableDistribution(system_size_pmf) ret.queue_size = CountableDistribution(queue_size_pmf) ret.busy = CountableDistribution(server_size_pmf) # # 2) For future estimations, we need packets and some filters. # Group all of them here. # all_packets = records.packets served_packets = [packet for packet in all_packets if packet.served] dropped_packets = [packet for packet in all_packets if packet.dropped] # # 3) Build scalar statistics. # ret.loss_prob = len(dropped_packets) / len(all_packets) # # 4) Build various intervals statistics: departures, waiting times, # response times. # departure_intervals = _get_departure_intervals(served_packets) ret.departures = build_statistics(np.asarray(departure_intervals)) ret.response_time = build_statistics([ pkt.departed_at - pkt.created_at for pkt in served_packets ]) ret.wait_time = build_statistics([ pkt.service_started_at - pkt.created_at for pkt in served_packets ]) return ret
def test_countable_distribution_with_pmf_props(pmf, string): """ Validate that CountableDistribution can be defined with PMF. """ dist = CountableDistribution(pmf) assert dist.truncated_at == len(pmf) - 1 assert dist.max_value == len(pmf) - 1 # Validate values: for i, prob in enumerate(pmf): assert_allclose(dist.pmf(i), prob, err_msg=f"PMF mismatch at i={i} (PMF: {pmf})") # Validate CDF values: cdf = np.cumsum(pmf) for i, prob in enumerate(cdf): assert_allclose(dist.cdf(i), prob, err_msg=f"CDF mismatch at i={i} (PMF: {pmf})") # Validate points outside the PMF: assert_allclose(dist.pmf(-1), 0.0) assert_allclose(dist.pmf(len(pmf) + 1), 0.0) assert_allclose(dist.cdf(-1), 0.0) assert_allclose(dist.cdf(len(pmf) + 1), 1.0) # Validate converting to string: assert str(dist) == string
def test_countable_distribution_with_fn_props(fn, precision, truncated_at): """ Validate that CountableDistribution stops when tail probability is less then precision. """ dist = CountableDistribution(fn, precision=precision) assert dist.truncated_at == truncated_at, str(dist)
def system_size(self) -> CountableDistribution: props = self._get_system_size_props() m1, var = props.get('avg', None), props.get('var', None) moments = [] if m1 is not None: moments = [m1, var + m1**2] if var is not None else [m1] return CountableDistribution(self.get_system_size_prob, precision=self._precision, moments=moments)
def test_countable_distribution_with_fn_and_max_value_ignores_large_values(): """ Validate that if probability is given in functional form, but max_value is provided, then any value above it is ignored. """ pmf = [0.2, 0.3, 0.5] touched = [False] * 10 # elements after 2 should not be touched! def prob(x: int) -> float: touched[x] = True return pmf[x] if 0 <= x < len(pmf) else 0.0 dist = CountableDistribution(prob, max_value=2) # probs given to 0, 1, 2 assert_allclose(dist.pmf(0), pmf[0]) assert_allclose(dist.pmf(1), pmf[1]) assert_allclose(dist.pmf(2), pmf[2]) assert all(touched[:3]) assert not any(touched[3:]) # Now ask for cdf at, say, 4, and make sure no elements above 3 touched: assert_allclose(dist.cdf(4), 1.0) assert not any(touched[3:]), "touched items >= 3 in CDF call" # Also ask for PMF at even larger element, and make sure no items touched: assert_allclose(dist.pmf(9), 0.0) assert not any(touched[3:]), "touched items >= 3 in PMF call"
def queue_size(self) -> CountableDistribution: props = self._get_queue_size_props() m1, var = props.get('avg', None), props.get('var', None) moments = [] if m1 is not None: moments = [m1, var + m1**2] if var is not None else [m1] def fn(x: int) -> float: if x > 0: return self.get_system_size_prob(x + 1) if x == 0: return self.get_system_size_prob(0) + \ self.get_system_size_prob(1) return 0.0 return CountableDistribution(fn, precision=self._precision, moments=moments)
def __init__(self, records: Optional[Records] = None, num_stations: Optional[int] = None): """ Create results. Parameters ---------- records : Records, optional num_stations: int, optional """ self.real_time = 0.0 self.system_size: List[CountableDistribution] = [] self.queue_size: List[CountableDistribution] = [] self.busy: List[CountableDistribution] = [] self.drop_prob: List[float] = [] self.delivery_prob: List[float] = [] self.departures: List[Statistics] = [] self.arrivals: List[Statistics] = [] self.wait_time: List[Statistics] = [] self.response_time: List[Statistics] = [] self.delivery_delays: List[Statistics] = [] self._num_stations = records.num_stations if records is not None \ else num_stations if records is None: return def idiv(x: int, y: int, default: float = 0.0) -> float: return x / y if y != 0 else default for i in range(records.num_stations): # # 1) Build system size, queue size and busy (server size) # distributions for each node. To do this, we need PMFs. # Queue size PMF and busy PMF can be computed from system size PMF. # system_size_pmf = list(records.get_system_size(i).pmf) num_states = len(system_size_pmf) p0 = system_size_pmf[0] p1 = system_size_pmf[1] if num_states > 1 else 0.0 queue_size_pmf = [p0 + p1] + system_size_pmf[2:] server_size_pmf = [p0, sum(system_size_pmf[1:])] self.system_size.append(CountableDistribution(system_size_pmf)) self.queue_size.append(CountableDistribution(queue_size_pmf)) self.busy.append(CountableDistribution(server_size_pmf)) # # 2) For future estimations, we need packets and some filters. # Group all of them here. # packets = records.packets arrived_packets = [p for p in packets if p.arrived[i] is not None] served_packets = [p for p in arrived_packets if p.was_served[i]] dropped_here_packets = [ p for p in arrived_packets if p.was_dropped and p.drop_node == i] source_packets = [p for p in packets if p.source == i] delivered_packets = [p for p in source_packets if p.was_delivered] dropped_from_packets = [p for p in source_packets if p.was_dropped] # # 3) Build scalar statistics. # num_arrived = len(arrived_packets) num_dropped_here = len(dropped_here_packets) num_dropped_from = len(dropped_from_packets) num_delivered = len(delivered_packets) # Drop - packet arrived here (at i), but was not queued and dropped p_drop = idiv(num_dropped_here, num_arrived) # Delivery - packet was generated here (at i) and was delivered # (at some another station j). # Here we ignore packets those were generated here, but didn't # finish transmission till the simulation end. p_delivery = idiv(num_delivered, num_delivered + num_dropped_from, default=1.0) # Record 'em! self.drop_prob.append(p_drop) self.delivery_prob.append(p_delivery) # # 4) Build various intervals statistics: departures, waiting times, # response times. # departures = _get_packet_intervals( served_packets, i, lambda pkt, node: pkt.service_finished[node] ) arrivals = _get_packet_intervals( arrived_packets, i, lambda pkt, node: pkt.arrived[node] ) self.departures.append(build_statistics(departures)) self.arrivals.append(build_statistics(arrivals)) self.response_time.append(build_statistics([ p.service_finished[i] - p.arrived[i] for p in served_packets])) self.wait_time.append(build_statistics([ p.service_started[i] - p.arrived[i] for p in served_packets])) self.delivery_delays.append(build_statistics([ p.delivery_time - p.arrived[i] for p in delivered_packets]))
states=[Const(3), Const(7), Const(5)], weights=[1, 4, 5]), 5.6, 33.0, 202.4, 1281.0, '(Mixture: ' 'states=[(Const: value=3), (Const: value=7), (Const: value=5)], ' 'probs=[0.1, 0.4, 0.5])', 1e-2, 2e-2, ), # Countable discrete distributions: ( # Geom(0.25) (number of attempts to get 1 success), CountableDistribution( lambda k: 0.25 * 0.75**(k - 1) if k > 0 else 0, precision=1e-9 # to get 1e-2 precision for m4: 1e-2^4 = 1e-8 ), 4, 28, 292, 4060, '(Countable: p=[0, 0.25, 0.188, 0.141, 0.105, ...], precision=1e-09)', 0.01, 0.01), ( # Geom(0.25) (number of attempts to get 1 success), explicit moments CountableDistribution(lambda k: 0.25 * 0.75**(k - 1) if k > 0 else 0, precision=0.001, moments=[4, 28, 292, 4060]), 4,