From bbf4e3e360c11606523f42428a42dcc230e29359 Mon Sep 17 00:00:00 2001 From: hajk1 Date: Mon, 18 May 2026 17:05:15 +0400 Subject: [PATCH] apply ticket changes --- simulator/__main__.py | 44 ++++++-- .../__pycache__/__init__.cpython-311.pyc | Bin 184 -> 0 bytes simulator/config/default.yaml | 2 + .../core/__pycache__/__init__.cpython-311.pyc | Bin 189 -> 0 bytes .../core/__pycache__/carrier.cpython-311.pyc | Bin 2196 -> 0 bytes .../__pycache__/greenhouse.cpython-311.pyc | Bin 3246 -> 0 bytes .../core/__pycache__/spider.cpython-311.pyc | Bin 2766 -> 0 bytes .../core/__pycache__/world.cpython-311.pyc | Bin 4679 -> 0 bytes simulator/core/world.py | 5 +- .../dtn/__pycache__/__init__.cpython-311.pyc | Bin 188 -> 0 bytes .../dtn/__pycache__/base.cpython-311.pyc | Bin 1039 -> 0 bytes .../dtn/__pycache__/epidemic.cpython-311.pyc | Bin 1834 -> 0 bytes .../dtn/__pycache__/prophet.cpython-311.pyc | Bin 3110 -> 0 bytes .../spray_and_wait.cpython-311.pyc | Bin 1978 -> 0 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 192 -> 0 bytes .../__pycache__/collector.cpython-311.pyc | Bin 3033 -> 0 bytes simulator/metrics/collector.py | 25 ++++- storage/__pycache__/__init__.cpython-311.pyc | Bin 182 -> 0 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 191 -> 0 bytes .../eviction/__pycache__/base.cpython-311.pyc | Bin 897 -> 0 bytes .../eviction/__pycache__/lru.cpython-311.pyc | Bin 1243 -> 0 bytes .../__pycache__/priority.cpython-311.pyc | Bin 1839 -> 0 bytes tests/__pycache__/__init__.cpython-311.pyc | Bin 180 -> 0 bytes .../test_dtn.cpython-311-pytest-9.0.3.pyc | Bin 11487 -> 0 bytes .../test_spider.cpython-311-pytest-9.0.3.pyc | Bin 6856 -> 0 bytes .../test_storage.cpython-311-pytest-9.0.3.pyc | Bin 5631 -> 0 bytes .../test_world.cpython-311-pytest-9.0.3.pyc | Bin 5861 -> 0 bytes tests/test_summary.py | 103 ++++++++++++++++++ 28 files changed, 163 insertions(+), 16 deletions(-) delete mode 100644 simulator/__pycache__/__init__.cpython-311.pyc delete mode 100644 simulator/core/__pycache__/__init__.cpython-311.pyc delete mode 100644 simulator/core/__pycache__/carrier.cpython-311.pyc delete mode 100644 simulator/core/__pycache__/greenhouse.cpython-311.pyc delete mode 100644 simulator/core/__pycache__/spider.cpython-311.pyc delete mode 100644 simulator/core/__pycache__/world.cpython-311.pyc delete mode 100644 simulator/dtn/__pycache__/__init__.cpython-311.pyc delete mode 100644 simulator/dtn/__pycache__/base.cpython-311.pyc delete mode 100644 simulator/dtn/__pycache__/epidemic.cpython-311.pyc delete mode 100644 simulator/dtn/__pycache__/prophet.cpython-311.pyc delete mode 100644 simulator/dtn/__pycache__/spray_and_wait.cpython-311.pyc delete mode 100644 simulator/metrics/__pycache__/__init__.cpython-311.pyc delete mode 100644 simulator/metrics/__pycache__/collector.cpython-311.pyc delete mode 100644 storage/__pycache__/__init__.cpython-311.pyc delete mode 100644 storage/eviction/__pycache__/__init__.cpython-311.pyc delete mode 100644 storage/eviction/__pycache__/base.cpython-311.pyc delete mode 100644 storage/eviction/__pycache__/lru.cpython-311.pyc delete mode 100644 storage/eviction/__pycache__/priority.cpython-311.pyc delete mode 100644 tests/__pycache__/__init__.cpython-311.pyc delete mode 100644 tests/__pycache__/test_dtn.cpython-311-pytest-9.0.3.pyc delete mode 100644 tests/__pycache__/test_spider.cpython-311-pytest-9.0.3.pyc delete mode 100644 tests/__pycache__/test_storage.cpython-311-pytest-9.0.3.pyc delete mode 100644 tests/__pycache__/test_world.cpython-311-pytest-9.0.3.pyc create mode 100644 tests/test_summary.py diff --git a/simulator/__main__.py b/simulator/__main__.py index 8b840c1..df7a7d6 100644 --- a/simulator/__main__.py +++ b/simulator/__main__.py @@ -1,7 +1,9 @@ -"""Entry point: python -m simulator [--config path/to/config.yaml] [--headless]""" +"""Entry point: python -m simulator [--config FILE] [--headless] [--output FILE]""" from __future__ import annotations import argparse +import json +import logging import math import random @@ -24,12 +26,11 @@ ), } +log = logging.getLogger(__name__) + def _build_greenhouse(cfg: dict) -> Greenhouse: gh = cfg["greenhouse"] - rng = random.Random(42) - - # Place nodes on a rough grid with jitter cols, rows = 5, 4 nodes = [] for r in range(rows): @@ -37,10 +38,9 @@ def _build_greenhouse(cfg: dict) -> Greenhouse: nid = f"n{r * cols + c}" x = c * (gh["width"] / (cols - 1)) y = r * (gh["height"] / (rows - 1)) - hotspot = rng.random() if len(nodes) < gh["hotspot_count"] else 0.0 + hotspot = random.random() if len(nodes) < gh["hotspot_count"] else 0.0 nodes.append(Node(id=nid, x=x, y=y, hotspot=hotspot)) - # Connect nearby nodes edges = [] for i, a in enumerate(nodes): for b in nodes[i + 1:]: @@ -82,30 +82,52 @@ def _build_world(cfg: dict) -> World: def main() -> None: - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser(description="RoboGreeno swarm simulator") parser.add_argument("--config", default="simulator/config/default.yaml") parser.add_argument("--headless", action="store_true", help="Run without visualization") + parser.add_argument("--output", metavar="FILE", help="Write run summary JSON to FILE") args = parser.parse_args() with open(args.config) as f: cfg = yaml.safe_load(f) + sim = cfg["simulation"] + + log_level = getattr(logging, sim.get("log_level", "INFO").upper(), logging.INFO) + logging.basicConfig(level=log_level, format="%(asctime)s %(levelname)s %(name)s — %(message)s") + + seed = sim.get("seed") + random.seed(seed) + log.info("seed=%s", seed if seed is not None else "random") + world = _build_world(cfg) - ticks = cfg["simulation"]["ticks"] + ticks = sim["ticks"] + log.info("starting: ticks=%d spiders=%d carriers=%d algorithm=%s", + ticks, len(world.spiders), len(world.carriers), cfg["dtn"]["algorithm"]) if args.headless: for _ in range(ticks): world.step() else: - renderer = Renderer(world, fps=cfg["simulation"]["fps"]) + renderer = Renderer(world, fps=sim["fps"]) for _ in range(ticks): world.step() if not renderer.draw(world): break summary = world.metrics.summary() - print(f"Done — {summary['total_transfers']} transfers, " - f"{summary['total_bytes'] / 1024:.1f} KB delivered in {world.tick} ticks") + log.info( + "done: ticks=%d total_bytes=%.0f delivery_ratio=%.2f avg_battery=%.3f", + summary["ticks"], + summary["total_bytes"], + summary["delivery_ratio"], + summary["avg_battery_remaining"], + ) + + if args.output: + with open(args.output, "w") as f: + json.dump(summary, f, indent=2) + log.info("summary written to %s", args.output) if __name__ == "__main__": diff --git a/simulator/__pycache__/__init__.cpython-311.pyc b/simulator/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 89c0db8ed9ca03e33c970bf9b0cf30e6018a0a41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 184 zcmZ3^%ge<81nXnIXM*U*AOZ#$p^VRLK*n^26oz01O-8?!3`I;p{%4TnFK_+O;?$yI z{fxw{Y(xEmqAdOF#N5P^)Z`5Pp!}qK_oCF)ynOv4ATu4z)J@LGFHO-e&de>%Ni4}P x(vOeN%*!l^kJl@x{Ka9Do1apelWJGQ3N#yJdoe$d_`uA_$oPQ)Miemv#Q^DmF75yT diff --git a/simulator/config/default.yaml b/simulator/config/default.yaml index 83dbc44..4f920ff 100644 --- a/simulator/config/default.yaml +++ b/simulator/config/default.yaml @@ -21,3 +21,5 @@ dtn: simulation: ticks: 500 fps: 10 # visualization frame rate (0 = headless / max speed) + seed: null # integer → reproducible run; null → random each run + log_level: INFO # DEBUG | INFO | WARNING | ERROR diff --git a/simulator/core/__pycache__/__init__.cpython-311.pyc b/simulator/core/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index b3ab69c84d73884d7223395d25fdd3eb0151450f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 189 zcmZ3^%ge<81RG+$XM*U*AOZ#$p^VRLK*n^26oz01O-8?!3`I;p{%4TnuK@kf;?$yI z{fxw{Y(xEmqAdOF#N5P^)Z`5Pp!}qK_oCF)ynOv4ATu4z)J@LGFHO-e&de>%Ni4}P z(ofDWO4W~#&&?t_e2|s1%h${}dbfV69XX!~yl@Do98@@n+UBabP_A_PsaHGw=Q0Z+Cx6 zCSwT3?_d3Gr&NUg;Ksp-go?Dk0m=>{h)52qNv0%mU3TP}Vk$M&RE4cLk(y>|5~3Q@ zh)N>POied+uoW4V5z)2~nRzH9^cb#iJcQfh0|S{+qTfV?=risqNI@Le>#mP|+pT*A zIfxSMJSJ?e)`4rtJakduU@(K(Q5gb6#h|NbqTFZ_60==4Qc-gC_9KE zQzFQeZ$U*=A@W7=5(P9>Xc5pfq0N|32UxnWqGm)`F=53`O;`zGCCwRO&2mddRHxBj zQV$`a$2=)9%ov%2y^e|a9GTxz%{a-E1-K_@lAIu!E%gW@vm^_MECn-HFk?1lf#Ufr zkZsb|PVpj71!^5`0EpvaKbZ4;m*I74Rd54WY`?W0o;-Rtm|4TVPg(2FaWW`K90WH# zO2&b&UJl~l*j3wNnAlBk>`h+|Vrvc^I|23-p;g>;e5;BpaAGS^9ozGR*&6jce*9Xq zT7_gaTyvXsKhPNUo2(u{U+T0n={{t_83w(P_Ai67gZv3r&=0ADZcprv)CQhTv!{cs z$vQzd(a-WKI|lmzSCksQ%jQB|^{7*288AG4v|+sXq)^sq`TdcT@7As*(&NW&=k}z!7tue&y`Ej?- zO}XcJ1}}r?qNj_w-M4;Fe@r~u>>KC%i>1M0X=qb>B6{AQ|o^UkL%E#Q4lU`+;qEKo@wp&lMDwf^^wSAfR=2y`VA4PR5_6ioGeI$A@~Nr)LLxcK6G^emYc#`fN|n5A^)* zw|)Igs18%Po}OdNJO_2nb)CwDmf+%?6BOrXPj`<Jn&YKu&l3}*cq>+EbGua*|J2wqHXLrKO)TqCm>tk0tkbc!Nn;M zf!<(lgEGIx&Vczd5{Zh|F`zCIf-Bx{Ai9TsduqH}?w0SDhw5B+rMq%}WvJ$RV)oVi za6Z#5_tZ={`)X#GTk5X#)TMCt)umx}se5h8k;!*I4mSYF_sAQ&=bxl|`BFb$8stme z)t*`kXJ0LiR4MZI2u%x3i{wXWTFfDBb|g!YlRP*S&lT`(eSX9nmd=Y^EyOtB)h4_l z0dU5@R6%_5siPj_6cL~Xn%`>J^>wE4n3#AsZMg9EW;zcM7{0+Z&P0Lh3;kl)$#>X! z@Nq_Y--8$_k|Yh$$=>wbL#KQHH;2gRP0u}L4oZ}khpN$=o+Ay(@sYeJLHYj`&$;g} PE&vxfzLh-^DN_Fj68IJ_ diff --git a/simulator/core/__pycache__/greenhouse.cpython-311.pyc b/simulator/core/__pycache__/greenhouse.cpython-311.pyc deleted file mode 100644 index c41db8135aaa8abaac29bb1e8595da57f915877e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3246 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 Ae*gdg diff --git a/simulator/core/__pycache__/world.cpython-311.pyc b/simulator/core/__pycache__/world.cpython-311.pyc deleted file mode 100644 index c227d322d69ccbec73eab883a04a4a6cda765fa9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4679 zcmb7IO>EoP5hf){qW&WP$dTlqSoW@yDA~20HgQpGes&x0s++(@_pfb~O$l10ZP}FQ zK2q7la1a+r0WaW#QMgFZ=AZ(LY=RVp4=Iqt9`?{f4n2^9022fXr06L(r76&0(L-k* zCEB#zL!X}K4Ik&tBWJ#uH}u!>c!YuW_h09VZI1Kx01DLyv#7L~dl-UBy zBJWdt1s{xl#b4$MTscq(z&s}flwdhj2vI($@a1qJ417r9l}I^Sh*CbR#LDqPobnOn zP&rXZu#C+4m^>pz?=VvAfsbJx!=KY0!0@(I-i6W6GXtyJC?prO#l#-@jbzbb69n$C^An=d*h(MSjt1WDV72}zz6tBNknifF={gva}#vSV6;cDMbdif(gyskjIWWxYx& zHaD$SKlary%-&^Im>Nv%Z(bmc0suf{Ikp=liabB#<$(o&@b6=EZOM+A_FKKp4kce2S zP*r76B+Q69gvm{kgiX+GMU_b8Fz(3OoF*%?8rnVk-PG7;noP8@d2wO!)YuYP7+Vy} zuy=8O>|=FCrSYSV5nw6k)VGU@T9wAM5`9FKjDcc#>L`Br=+qXRzL zZ3j0iKCkQwE4W(L-J=>%d2M5T0HCJE%j6}dDwKcf=O<|?Wr0|dm1 zq?YYqab7JI<(!{noizwMatwG4Un85%k|7vLFOcOf$B(-KiZ`_xAmGoQ%;x8{$*-o& zo>NUG5T2l9?R~2|z4=a)VdELABWa4#4<8>A|=PGrM>oRnX3#^b5>4zhKIBUkn8?kXCHcscW^;ovn zW5$j(V#f^HoALHG`w@XwriieC@gibx_#!$0B|pcY`@cgN5CE&(0DJ&@K88Q1-SV&a zLGy`Q+zPj&nGC@bxJSmpcv$hRa3F<;E^S`DEVF)c{R-lPjKJ+&cqiBi#RCoXgmZET zko_UX=VUvAX9W?ZQdJ{eu+EOC6``nBpr+EDBOu@ee?(N2*11DYq9E_;2&noz91Hlu zkP_NSp&u@n(=Bl9JC(i%ceURF@ur5gh}AQQD4Ap(x;I0l`ZjNGrzYyDiLL6>g=Zs; zO9eA^wUN5I_G2qE)MR{dT#0le(9SKLldP3YZ(iO`o~S2JJUp{C@-+EP#!OypBrh7t zi?5pjx+=W0>wfH@)hTu^LC)}E2fX>a{n-RO<)(g+SffaC^9>0BLR?Hh_UQz92xX_$ zTNJDy-JEXFveYfYDn`CK8qaiAdM-R(J2_xpu;{dXL8^h-}E?cSL+DE^+y>S~4)!26WbUl4~>!(ky zd>i^pDzn__~>(j}zK?==~vp?xP{_DKOp;09U)vZdE@i42hZ> z0yuRw2!e`!I#HC;Buj>I4$CNlXDY$mNf2!W-|i7ZH+O2SLpX~B z3qkFlKvsACo^!nh?Izc2aT$Yllgn6KpFz9H^;ukxLA%NISlN+Uz?k^J$mY##zLCwZ z<_)gZO)hVBbgoXWPT}uYQ%x=uNC9F!^gaTkzFX`d~n3(s`WHf+bu#fio zP{;oujycc>xlXkFY!yB*1Sxm3t?ad(Li#C1dSTOH?@)GC%h2*6l None: for spider in self.spiders: if not spider.alive: continue - # Hotspot areas yield more data (exponential burst) burst = 1.0 + spider.node.hotspot * random.expovariate(1.0) - spider.accumulate_data(1024 * burst) # ~1 KB/tick baseline + bytes_generated = 1024 * burst # ~1 KB/tick baseline + spider.accumulate_data(bytes_generated) + self.metrics.record_accumulation(bytes_generated) spider.drain_battery(0.001) def _run_ble_exchanges(self) -> None: diff --git a/simulator/dtn/__pycache__/__init__.cpython-311.pyc b/simulator/dtn/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 75f06aa32780f16de867435a37229fb06a0deaf8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 188 zcmZ3^%ge<81RG+$XM*U*AOZ#$p^VRLK*n^26oz01O-8?!3`I;p{%4TnFMs{e;?$yI z{fxw{Y(xEmqAdOF#N5P^)Z`5Pp!}qK_oCF)ynOv4ATu4z)J@LGFHO-e&de>%Ni4}P z(oZSL(~pnO%*!l^kJl@x{Ka9Do1apelWJGQ3N#<&f?|Fk@qw9M^ diff --git a/simulator/dtn/__pycache__/base.cpython-311.pyc b/simulator/dtn/__pycache__/base.cpython-311.pyc deleted file mode 100644 index a5f20a28baa165a61e7425db164669076f329b1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1039 zcmZuwzi-n(6uyg{A5DKqKqsaXECi{s*ij`)QH4MeDrNCd>teseZtS~ocP6A$hYpNv z{SS&z{}qOg$Wsv$TdE)-b>f}VrYiJofBx>>z4t!9cmA@x+$1=D?EXoPC?P-X2!8vvaIl*T({AyHvOIo#GcG{xF zA{Rm$ZW1YUt5P`6Hl7zNysM4kp~=9+G72wU%hzQfI(>}OBLc*y5%DV#^&N1dMpQd= zk8$N>?t=;Im~f$3*xc!C%iMt4|JGzU-lTcI&$}rwh2})`naygTKW06t*e0ZW>}@7m zq7N(st|!aU$5p>lX|&ql4@*;9<-p?osX1t#C~e z4hkm`rl=_}xe|rjkpe<@UIaq0nFMP`r!^$Dc~}$L`AS-D;c7^GMZMG(@GH_)4cw`1 z3|p*=SKd1fN_#Qi8$9ubYR?<+467B!-nQ(@Ed?N?r;zqbx)!D~k35}ZdCHAcUSx#V z<nya5+Chz|te0bVP?@r)xNvwkRJOh|MP>GziY^*YXCJe! s?&Ch&Lv8(D}q;SkB$O=fD_&ZW$iD*tmJ@sbfN}!y0vunEvO_BI`9{>Di-n=(&e)DGh z-S~I`f&BXU@9tv_p~tE-6f;$D{|+dJh#`hO2?GqPj&a$7m3QF^%0Yh@RkdEWh$e`>cqDQr%r>= zH6Fx7aEH5_JR*FPi!I{wC;|g<<0K3V!2&rG>IqKS7HNbYE0GWBSFZ0yU}M|^Z*UZ9 zTZG0WZg5yAib+^cLeyIIIv0cnjMMe^>+-W!3^zhW|)`#S8wlR+8L%_AH`^!n^@eu z7~@Et)^idMDnuFDnPZr9GMaa}htdB0#ds^|idBtd)eC$3KUQT3s>G-`qr@OrMenqt z^bJrBQ3JKnYGDVrp~Rk%@U^x-b{}=H)Lhm<9ZhPbw%n$qRir;%m<}gYHpq9mm(fJ zq2FBIzBR1gw4_U6U=K>SwB&^j^`hm{X!Zo;(|;Xc7oA#*Y5Vr$gQeqvKn{=;Gg9?soY*wFrz?qi@KZ`Ts%9yE;2)#O&&05FxQETS!qPv ztIKS1XKgxEY9a^j(xkS_B5Br=AKj1EqAZN7cK{?wR2H6*xPASF{EmZL^b^1?I!nkr zy4jsw02&`!i(PB+fqC`lqv;#n>0%FQ`NaetynibnltNbrVV}MI)g_` z?T4)8DgBylf6=1ez>>9X7CN>qW|d=d=*c-Hr>$5O3e=+%y$i5A`t&rz%=M6hYXuW{ zcmbrxu7Xq`B}k;+f|m~x5$b{N6`-eMjL*>Q e>E-qay>@!J^)gc!a(f;QKKo6LKE1+}Wc(-FX1%fi diff --git a/simulator/dtn/__pycache__/prophet.cpython-311.pyc b/simulator/dtn/__pycache__/prophet.cpython-311.pyc deleted file mode 100644 index c9c2ba2650e9032ba88fce3ffb791a16ba315fdd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3110 zcmb7GO>7&-6`mz``6ttoL&jDbxmeguW2P<{B8C+!vINyKRHra4yRwZEb{ET?6}i%K zmzf>PwLqaf_`nnbsssYWgAP0ht_u6$0xsZ7fucZ<0Yrm9hy?Rsc&}q zw@L)K!{ysI``*0w&3iL%=6BI(1VQ`TFaN9Y0fhd+I^8C?4EN#&Fk1*C%;~7eWjT)V zKHXRJXZ^tYF|YGQAu9kcWCJ*m4dP%n6PO+xYuQ*pFJiCy&VV0>%@~(}4PIs8S>HMAF{^jd4cm6f4 zxFVPO+!yyk1ZE4VD9d4#^k9i6{n&j0lCg6pL`42~lr zw9e-VQI(Z~XqZ?PDUp?BOT?<7PNc;*aPrkC{qPc7S#+VD@n(_!gA4V zESd1YYQQkFR8VQMbpK^&QkRQ4EKjYT@vM@hBx#05B`G!4-5 z0i=q)NKNd0a1)+p>Q*attH!SfYw&a2*gVygW!tYA)D94pmWg5W8PiY|7P4-GUGFAx z3bL?;D%?_6f-1f~zb35l6|~e#V})k`+*T3#Jmy|SYZa;v9YC=PwPqEJ9X@vQpmf0J zAeICE57Bxh|DLhTKMlI`Wz>UoK(jG0tG@O_U?26m-pw(K?%uist9&i67OVs+{4(1O zi2RHW^#*vo4MslDkGu!bpw0lgN{~gG9_d?V{fFTFQE#6eYA3*Auzs6WPgohfSj-~t z?AAiCh6bc}$y&G)ZiMqGX!VXPOp=AD?+V*{5won0bc{j4gRp2!0BmdO5 zKE&#o?b9$}*-7|pU-Dg>D_SfsqFWhWh9@nRDrT`XwfZA(X)q0`b5abHqOS5=V8R*$ zQbl_s7u=_QedE~1xy=*351o?1sy<=B5<||5i~w&3rFrQ}=E{Q2>zYMvL8EHXvi(K* zSGFMi$w;w=noZJpJxa~+Iy&c^>w!857_LJ+6v(LE4cbkccKjoWeE;mOnv!c4etw%P} zA4l7xWB=!fuLEdwtUkLv*O=M<@bj^=&&SR-$C9nFWb;I-bt1KoeBtYyOFy2g&D3an zcw}SdH!HQ3FT~T0*`2xFnVk=JKiawaYdU|uBj$5(Q4ZM@zm3Nk Kc+ z|2%enFLu5if3p>T=ks{_c|85cA3U-Cka(JC#wT0x$-VgGH~ZnPKWBwy61=|-Vl_WbNNktQvb;ci&q>tc|)R6=w37d)}k=NM36eDc6IF_9iL0Fk_ z2s>!4Ss>NkZ{H^fp?$>2QBJrGQ6Hdz*8~X5A;fjSY-ixA2>2GuaGU}@!iu#$q?o#{ zD$WJgy^ruY8Ezo#Y$a^plT%DBAkEEOy*lG!^x}pqhCAgc@;<1tW7ety+4plC*G8xI zyw8{D^q%+m61}wmsO%jJl!)L+daY2U2^VWZow&glo^pw1Y0`r=_s7{5Jh=R zP+lN7A0yqQhe+EJXzwM-Z80yAF0j%U3@(mfY})QP)d{DjpZ8tvI$-QO3DgMPan4n|8!zJA^9F zz(LUNfDVP2jDmeaEilO{k%|RM$_6;VXvs2-FrSJ8vA26a`q_m$feB96!f+A@l1)WW!Ckg?op$VS#Xw}EThzRG7Qkw5FIltr%W=gSu9(+ z=dcX%?2OB+sX-bwo{)5@5+pQ@8`rdr;mf5?w~2k+kpTc(sBv?2N317?YvDeHI~0hX zX{-k_qh6WWftYh_YVgFgmkdalhCcv-uE*QR7uMQTG=~lWKkA$}MIWE1F6B2RUn0Vd z4u>y3#VqGb2fX7iEfVw5TBi($fE{&Co3bwxaXs{ojR7ug0BtjV-k*ckq+C>NB42ue zL3iB5FYqB)KI&M<$d~>22FyM98JhvY-Ot1F$~mLM1h?Gm5buYZfb5|H^3hV)CiY5eO#v0YNhe>AL|gwigy5-KM zI}(GSz#Rdwg=zy+(cb^8daXM7<@L@5ephX=v-G`)>1vupfXTGmYB2C<^M8YH8UHSN_v44;Ao zaPUWluQ2AI>vdEcZ~br3T)X=%0BF}R!h?6}w?2T_jt8dd15-QFtD!gRLuwPr$>|X6 z+;8*`Rws8W2U4y7Htfd8+uvlr&K`XFahC1=`;jubZy}#dkoVxQ* zv$t(I%rm?JjK`}A;m9-o|DlRul0uyEk}+r;q{@6^ZM{e}E$Gs;qC?6S$9pvGNm;jA zl(?o5$Ivv^&js&raGis&sJww#ybbtlx}N~p>U^4lBqf@NgFY0Gct3aRc3|UEHtG ZaP8u5$|(%KT?YF%{^YlRT|o*x{uhNx{P6$) diff --git a/simulator/metrics/__pycache__/__init__.cpython-311.pyc b/simulator/metrics/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index e738d3e8b53acda24a4a386bca76145f9f38e145..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 192 zcmZ3^%ge<81RG<%XM*U*AOZ#$p^VRLK*n^26oz01O-8?!3`I;p{%4TnuVDSq;?$yI z{fxw{Y(xEmqAdOF#N5P^)Z`5Pp!}qK_oCF)ynOv4ATu4z)J@LGFHO-e&de>%Ni4}P z($7sTDauSP){l?R%*!l^kJl@x{Ka9Do1apelWJGQ3bX^{iei2s@qw9xv^uxpB&l0JQCnjTcrWwWz(T2`aPRuyuY zFm;_U?Y=_DL%4%>A07|u9g@|Veubn%&u}D7W!x+jT+j4!Zb76K8D*wtT8=4%)E9EL z!>q;-qwU@yfEF?9yIFVP758X9V_<1D?U&sj+aAwZ;vLsw7@uJFf?0ArW5L9c<&~I|6W-@6 zXL;9T)Z=DBEZAJgs3@34al`e53~}2l@q*Om-GXg3NP_)CaKi!V{x=|OkX5n-&!BBJ zj#MAP=PF%l%x82R`!#|HxkOrNXNvzG92~S4-m9BxD|zT*IJh=%HE$k)>BrH?A_yB~ z5zM@H;cE(8*A^zkYZ%dHB593_Hoe9y)~3hkruv<7`yv>lJVULtDwQ0Q;p?X7*?d`c z3gF&cv<;47l-77RC`p~Wr2^wUAoF85OOeXtSxJRJ(#FOPKh0bf;GE12bLr-}Opz~T zZkl=1v#lGMPu%P7Wp3LAH^V_(^u;rlaARsbUJt8KH%ZITU3<1l{ z7iU*aw};Fmw=I{m$r;DBOh?RuP*?5(qr??}HBu$oU{@1_NMCUod8^Jva zxMvLA!xOL^JkS9?^Jz7ZVXxYN!h1oM)T+6G_v5Ppfd51nE+}RJkS=uy=&HkRZ(mhC zf~@WC8MvxyUC*Dv2Vn__C(X-QH)U89a}4t}Vws%hKuYTztD_8=#i9+-Gz=X+f-s5` z6{!i^S&;gdE_Yb-i7?O6)hh!piGeuG7Xo07R3aU>yMB#~{t_9j^bXzmpsF;y7b35u zHb<*r7}&$8IPgOwRzJh;P;xU_)uFqG=RfdCNW@dIDgoFd@o4-20lLlLpBjpie_8`l zNfjQKvD%?=Aas4%vxNaQ*l|tIKekkJ1<$H|5x{OjFo+-@>HZ}U{#C)!R_Us;szS(U zM;04p9AY0tth8l=69#FE^FQ zx-z<0pc#L{Z;fmx=iqu^-o3Rou(=ZSnz5eR2@n2WI1R@N84m{hc#>V1_`(R{!)9qs zs*YXogAB5wl=ss=P5np{$YU*{!ZkZr249Gz+M97S_cFj5`8_qUb@ATpPU?I)b^hsa zGBESW#h+$D+DXop0f*$@Sq0*J=^^UMRKSUGqHKspx0 z{;N2E>~LT5vjBMts?|x9p3kYxKLexbxQrM^!OYu+A!CM-cUj3nxx+ASl}smi!!hop zh99bcmV{w6{bw@_pCXROOoiEtJHi)&Edu~cw`5ZnU8ey!=ZJ2HOv-6touN2Yd0 zrphBzYwvGsQ^B>PO;!H!ZB^^gA@5pYB!pwF(5>s3u17;)DjGPS4j~r{CHR*HTyRVP z8E^b6wFTE;#3#i+P}@llBQpVwv;1w)!-x@i0QfdjS|JnLt=BF&wSCl84G|??RZ;Pd;1- None: self.snapshots.append({ @@ -30,9 +31,27 @@ def record_transfer(self, spider_id: str, carrier_id: str, bytes_: float, tick: "bytes": bytes_, }) + def record_accumulation(self, bytes_: float) -> None: + """Track bytes generated by spiders (denominator for delivery_ratio).""" + self.total_bytes_accumulated += bytes_ + def summary(self) -> dict: + total_bytes = sum(t["bytes"] for t in self.transfers) + delivery_ratio = ( + min(1.0, total_bytes / self.total_bytes_accumulated) + if self.total_bytes_accumulated > 0 else 0.0 + ) + + avg_battery = 0.0 + if self.snapshots: + batteries = [s["battery"] for s in self.snapshots[-1]["spiders"]] + avg_battery = sum(batteries) / len(batteries) if batteries else 0.0 + + ticks = self.snapshots[-1]["tick"] + 1 if self.snapshots else 0 + return { - "total_transfers": len(self.transfers), - "total_bytes": sum(t["bytes"] for t in self.transfers), - "ticks_recorded": len(self.snapshots), + "ticks": ticks, + "total_bytes": total_bytes, + "delivery_ratio": round(delivery_ratio, 4), + "avg_battery_remaining": round(avg_battery, 4), } diff --git a/storage/__pycache__/__init__.cpython-311.pyc b/storage/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 6e042ff5dc865ed2bad9ee12c2d3c1aa0a53674c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 182 zcmZ3^%ge<81Y2UhXM*U*AOZ#$p^VRLK*n^26oz01O-8?!3`I;p{%4TnFHil@;?$yI z{fxw{Y(xEmqAdOF#N5P^)Z`5Pp!}qK_oCF)ynOv4ATu4z)J@LGFHO-eF3B%SOi$I1 vkI&4@EQycTE2#X%VUwGmQks)$SHuc58Dw`cKalvq%*e?2fdNJoF$2W_bjmIU diff --git a/storage/eviction/__pycache__/__init__.cpython-311.pyc b/storage/eviction/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index a14d7dfcee4e863374d1c0316e62ad7bf44bf57f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 191 zcmZ3^%ge<81Y2XiXM*U*AOZ#$p^VRLK*n^26oz01O-8?!3`I;p{%4TnuOR)<;?$yI z{fxw{Y(xEmqAdOF#N5P^)Z`5Pp!}qK_oCF)ynOv4ATu4z)J@LGFHO-eF3B%SOi$HM zEz3+U$;{8wkB`sH%PfhH*DI*}#bJ}1pHiBWYFESxv;yRaVtyd;ftit!@dE>lC}IYR F0RVlrF`xhd diff --git a/storage/eviction/__pycache__/base.cpython-311.pyc b/storage/eviction/__pycache__/base.cpython-311.pyc deleted file mode 100644 index bc8d268df460cb15fa83f20e76ffd19f6779e4d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 897 zcmZuvzi-n(6n^JlP13aDM+YWkfrONSSae2}3PRl?)QZK3S{L7?No(iI-36(gI%HsE z>wi#$`p^*5geDQUb_*&sbB{DGCZnKiP3vF`;=eJC0|EoIU@vCBxv^zbNwz!R zpyd{xky%gIu(YOgFr*xY;j9zAGg6zV&kv7YM92CtI^sFEGVMpZs;hRe>QF@*=|M@K zrCJB)Jl!jCeCiK8|=+&1m|I^nHSLf(Hcm2uQaC>a5vBI9puT z{=1&fI8nT4WBj<(;AYtwps?-Y%+?4g2?q|1 zA(0zBdcqhy_@@*@Vl#<}#*?=iOiVcWW)~_%eck=_&70r6_nUdMZ~OW(fcEv-ukt4X z@J$U4%}gA!cL$wKpg>6g6;dJus7CcbtLP=2fChJg8f!pJrf=y0+xR9{GN|{aW3?Et*BYOGSnfa9RmHh0Z3_u`PcVB3&!W zzQ7Za>-jztg3)*CoXJa}lH~U9J5L1T!kP2t7j8O>eBN2`Dqh6=Ip<+G9p1&ms)i0n zcQ$r!_(52sj)+3;%`%4_w95&2ZFF&IKRp(Bm1*jYFJC;;htZfeIu?Y!7l?6m5>gr^ zh+c$M_;B-syL+p!3VS$I3iSoHG^>1NG9${%2VKi+(h?z$7?qZ{xX7wBpOS`P!3Hw;{sDPpGqSRr;wOSjH|2gSieEJy&9AOtkw4o0*UOBdz|C_Q|0Qvu&S#U3^t+-rliu zyLPT+=i2tb#^dJo-J#1n_T@&QQE2xMH%{fU;(VzdAiWUC`wqq{s diff --git a/storage/eviction/__pycache__/priority.cpython-311.pyc b/storage/eviction/__pycache__/priority.cpython-311.pyc deleted file mode 100644 index fe333a81c41ad2374605af3f50c3582af5e3100a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1839 zcmZ`(&2Jk;6rc5e_#@k-By~QhsFnaq3`wj;IW_@7LvN;Pic}eu)n;dGZ?g89nN2HO zHp-zAP8Er$60+e!PN66WapZ{9f51jLSR)}7aq7(|AW%-c*>wz|&FuR5&D;0+oA>6M z3l|ay)~_G`<&0FM$wsh@6<01QSw%t-bOT}a z0m2$l4#3wT{KA!0abyjpqR+%OCGjDvRPubwcf1nIJd;A<@ajFs7NVP;>(~`LG!1Da z{F9Df2BwJ!%1RhPbSW$2IF3Gm_&5d;68z##>Uh-gE2rMJz6X?Gkj z%>M`jDtCDzM+q_4D?VXd&AFcCbBz*zi(QE4i);q%LL(9? zy;@WE`$ski5-_#s0pxQy$VozA8O8Cbfm-zfLUnETt(pqQIh5gwfPyOm1ge7;9Rgwx z9Kc3|9TO!}V7`!=Xh*K0ec+#8SlW>fAhE+ia#iU5x^%8S^!Hi)dZZ>{xfT(2Rfr5p zDj6R4og$pBRV?%2vkj6Dljyw-T`%W}pHk>Bh`0>B#kH_gxLoq?GX=!Zq}wOA(jPKH zS-N0tY+g;5=|+0fDq23V3+Y?lx_1NmpyZ_~u=#+Uw_R@wr#LvZZ_-`zp47`LW%ZTs)dw?2Ilp z-s>i(ns*-EXoBVh6GDW_zv2w=I!Jn9q7HLF}EC7g0s?^iGCaEL>}>9CHsqcn z$7g%!9P(!ZOtoum{q(-W|ErO8h)B z-5#0lUQF((U488H%)?A;siPZ5y3y8+uKv>A?bh5;a;BrtG*%m{-I4LeFcp)NNogS# zqOuSWF(o0ALY7i_%rr|@k(ee=m}b$#Tdv@B)BI%1a{FJRrineoGk*`9z4B>qQ;;MAo_?p(L?8u(4BCS(nJ!uEx5fP2`cUz`U()PtXt6fVxYr_ zDZz;fgDg&B*TTeC=p4wz9l&xxda@)*T{PW3`#wcO+rje`8SS%gPaBsY(dWtDr@ss7 KA20Y6zlPXbN@WM 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 dK!HO#iQ02daY-zs?!KqEq&7hc4#fr0%DU)1c zcI8?M2^c{RLJ%OnS%(y0fyRaE+rjm{DKk4q8q7Q`^J|PG&fSFuKK`LHyPq~m1n4X{(kW{w#n?`U1R;;^jiQJw(ksqA)i@=c|o zwEUk^;*g=_2`t@HxqK=h*QMpAN~6t4Lv2Lv@!n0Xq1k_R26`seaaR1Iv4*y)(m0}5 zBTooeg^#C8I2oT;ly#gJzrudsSM2j4+X)}nP{LlaFi!f@kE^LP5wh4rs$q-Apdw)u z9r#?^iYZEy&?HSYNLl5rs59()swg2#DPP`*hAo9#AHjxtpf*+bXt3VOdfM;TR2!=O zfWmh9Utd9*`8>i`BX)|_h=u1LZsBUclc&Iz&BB&l^0(}77j`vQqh@NXCD%k*q8}6rbR;-y%}u08Ej13 z7$tJzVuNUYh)+1Q4(tXfa8uiVgb)USHFNHf^TouUCjUJ7_dD&(?M~))H*@>py9Xrs z6}h-aF7C{9NUlqAZIU}ko$aQ^+hnpe1EifAZ_VtbCXwa$KAG(LyeC8mupM6`AhHQ6 zA=$nuWk&=O1Op0`fV9A|-hjY+WBQDc09L@I8^K$SIM+N4D z4TueSffuhtxEc?ID>$R-Ce}w1zfrj>=ndI{)CwE@BgR7 z@YM7Y_a9eEoL2a&$d52F#@B?V<|W5{F_g_fG+}rM@E{6lZ}Eg6x?(tv$u?gaKew)W zX>H9C`R*~Ptx+l8HUkVm^{uLmppny z>#G&ZVr&jP{Rw>RFJ2yiNA}3b&aljEAOL?l4@uz_ft$MK?F2p z?kNzm2`VAM=8o}edsy`0PN<(8t&E7b|L>J+PEmHT|QXwm|uwu9uA)&ufXCLRHmU$5?zIoDJ_Vy2Ubm{ zw}Pbtn6=Je$Mn4DbhndL*RqGB;J2{$+s>;*&AP8F7NRQIJQWBpX9ro5ltTg-yL5gAhIbt;qmEp7Lb5I zf?z;_5|Gwq*+&ckGy9DQ5nG zl^v+UNs$}(Wc)F9FW6;X3$)l&$ztXd|Gxv2@}LBXHFkHnPcN#Lf!AIvlkxXWp7St# zAQ@KVxsUw8eV%x$YSE2~InUk)1w5#b76CcbR8>8|+S-f%+R7V!_knVy9lj4^syeyt k94heDPZC7U9V(J!HTBA&B1xL4uc6i>*(e$~O87MX16Wdh{r~^~ diff --git a/tests/__pycache__/test_storage.cpython-311-pytest-9.0.3.pyc b/tests/__pycache__/test_storage.cpython-311-pytest-9.0.3.pyc deleted file mode 100644 index 1e71c384e0309ce6cf9900f7c2c627fb6caab6c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5631 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 diff --git a/tests/test_summary.py b/tests/test_summary.py new file mode 100644 index 0000000..0c08222 --- /dev/null +++ b/tests/test_summary.py @@ -0,0 +1,103 @@ +"""Tests for issue #02 — Logging + reproducible run summary.""" +from __future__ import annotations + +import json +import random +import subprocess +import sys +from pathlib import Path + +from simulator.core.carrier import Carrier +from simulator.core.greenhouse import Greenhouse, Node +from simulator.core.spider import Spider +from simulator.core.world import World +from simulator.dtn.epidemic import EpidemicRouter + +_PROJECT_ROOT = Path(__file__).parent.parent + + +def _run_world(seed: int | None, ticks: int = 30) -> dict: + """Build and run a minimal world with the given seed; return summary.""" + random.seed(seed) + n0 = Node(id="n0", x=0.0, y=0.0, hotspot=0.5) + n1 = Node(id="n1", x=1.0, y=0.0, hotspot=0.0) + gh = Greenhouse(width=5, height=5, nodes=[n0, n1], edges=[("n0", "n1")]) + spiders = [Spider(id="S0", node=random.choice([n0, n1]), storage_capacity=1_000_000)] + carriers = [Carrier(id="C0", node=random.choice([n0, n1]), storage_capacity=10_000_000)] + world = World(greenhouse=gh, spiders=spiders, carriers=carriers, router=EpidemicRouter()) + for _ in range(ticks): + world.step() + return world.metrics.summary() + + +# ── summary shape ───────────────────────────────────────────────────────────── + +def test_summary_has_required_keys(): + summary = _run_world(seed=1) + assert set(summary) == {"ticks", "total_bytes", "delivery_ratio", "avg_battery_remaining"} + + +def test_summary_ticks_matches_run_length(): + summary = _run_world(seed=1, ticks=30) + assert summary["ticks"] == 30 + + +def test_delivery_ratio_in_valid_range(): + summary = _run_world(seed=1, ticks=50) + assert 0.0 <= summary["delivery_ratio"] <= 1.0 + + +def test_avg_battery_remaining_in_valid_range(): + summary = _run_world(seed=1, ticks=50) + assert 0.0 <= summary["avg_battery_remaining"] <= 1.0 + + +def test_total_bytes_positive_after_run(): + summary = _run_world(seed=1, ticks=50) + assert summary["total_bytes"] >= 0.0 + + +# ── reproducibility ─────────────────────────────────────────────────────────── + +def test_same_seed_produces_identical_summary(): + s1 = _run_world(seed=42) + s2 = _run_world(seed=42) + assert s1 == s2 + + +def test_different_seeds_produce_different_accumulated_bytes(): + # expovariate bursts are seeded → different seeds produce different totals + totals = [] + for seed in range(5): + random.seed(seed) + n0 = Node(id="n0", x=0.0, y=0.0, hotspot=0.5) + n1 = Node(id="n1", x=1.0, y=0.0, hotspot=0.0) + gh = Greenhouse(width=5, height=5, nodes=[n0, n1], edges=[("n0", "n1")]) + spiders = [Spider(id="S0", node=random.choice([n0, n1]), storage_capacity=1_000_000)] + carriers = [Carrier(id="C0", node=random.choice([n0, n1]), storage_capacity=10_000_000)] + world = World(greenhouse=gh, spiders=spiders, carriers=carriers, router=EpidemicRouter()) + for _ in range(50): + world.step() + totals.append(world.metrics.total_bytes_accumulated) + assert len(set(totals)) > 1 + + +# ── --output flag (CLI) ─────────────────────────────────────────────────────── + +def test_output_flag_writes_valid_json(tmp_path): + out = tmp_path / "summary.json" + result = subprocess.run( + [ + sys.executable, "-m", "simulator", + "--headless", + "--config", str(_PROJECT_ROOT / "simulator" / "config" / "default.yaml"), + "--output", str(out), + ], + cwd=_PROJECT_ROOT, + capture_output=True, + timeout=60, + ) + assert result.returncode == 0, result.stderr.decode() + assert out.exists() + data = json.loads(out.read_text()) + assert set(data) == {"ticks", "total_bytes", "delivery_ratio", "avg_battery_remaining"}