From 5b584e2a8c451c1ef84cccd2dfbf657e766c0dab Mon Sep 17 00:00:00 2001 From: Axel Date: Mon, 2 Mar 2026 15:57:09 +0100 Subject: [PATCH] Axel's exams solustions --- exams/AxelRubini/2025-01-09/1/Makefile | 10 + exams/AxelRubini/2025-01-09/1/include/DES.hpp | 212 +++++++++ .../2025-01-09/1/include/Decision.hpp | 266 +++++++++++ .../2025-01-09/1/include/Dynamics.hpp | 214 +++++++++ .../2025-01-09/1/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-01-09/1/include/IO.hpp | 150 +++++++ .../2025-01-09/1/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-01-09/1/include/Queue.hpp | 70 +++ .../2025-01-09/1/include/Random.hpp | 77 ++++ .../AxelRubini/2025-01-09/1/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-01-09/1/main | Bin 0 -> 44208 bytes exams/AxelRubini/2025-01-09/1/main.cpp | 42 ++ exams/AxelRubini/2025-01-09/1/parameters.txt | 11 + exams/AxelRubini/2025-01-09/1/results.txt | 2 + exams/AxelRubini/2025-01-09/2/Makefile | 10 + exams/AxelRubini/2025-01-09/2/include/DES.hpp | 212 +++++++++ .../2025-01-09/2/include/Decision.hpp | 266 +++++++++++ .../2025-01-09/2/include/Dynamics.hpp | 214 +++++++++ .../2025-01-09/2/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-01-09/2/include/IO.hpp | 150 +++++++ .../2025-01-09/2/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-01-09/2/include/Queue.hpp | 70 +++ .../2025-01-09/2/include/Random.hpp | 77 ++++ .../AxelRubini/2025-01-09/2/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-01-09/2/main | Bin 0 -> 44376 bytes exams/AxelRubini/2025-01-09/2/main.cpp | 44 ++ exams/AxelRubini/2025-01-09/2/parameters.txt | 12 + exams/AxelRubini/2025-01-09/2/results.txt | 2 + exams/AxelRubini/2025-01-09/3/Makefile | 10 + exams/AxelRubini/2025-01-09/3/include/DES.hpp | 212 +++++++++ .../2025-01-09/3/include/Decision.hpp | 266 +++++++++++ .../2025-01-09/3/include/Dynamics.hpp | 214 +++++++++ .../2025-01-09/3/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-01-09/3/include/IO.hpp | 150 +++++++ .../2025-01-09/3/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-01-09/3/include/Queue.hpp | 70 +++ .../2025-01-09/3/include/Random.hpp | 77 ++++ .../AxelRubini/2025-01-09/3/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-01-09/3/main | Bin 0 -> 73624 bytes exams/AxelRubini/2025-01-09/3/main.cpp | 133 ++++++ exams/AxelRubini/2025-01-09/3/parameters.txt | 2 + exams/AxelRubini/2025-01-09/3/results.txt | 3 + exams/AxelRubini/2025-01-09/4/Makefile | 10 + exams/AxelRubini/2025-01-09/4/include/DES.hpp | 212 +++++++++ .../2025-01-09/4/include/Decision.hpp | 266 +++++++++++ .../2025-01-09/4/include/Dynamics.hpp | 214 +++++++++ .../2025-01-09/4/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-01-09/4/include/IO.hpp | 150 +++++++ .../2025-01-09/4/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-01-09/4/include/Queue.hpp | 70 +++ .../2025-01-09/4/include/Random.hpp | 77 ++++ .../AxelRubini/2025-01-09/4/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-01-09/4/main | Bin 0 -> 78288 bytes exams/AxelRubini/2025-01-09/4/main.cpp | 191 ++++++++ exams/AxelRubini/2025-01-09/4/parameters.txt | 3 + exams/AxelRubini/2025-01-09/4/results.txt | 9 + exams/AxelRubini/2025-01-09/5/Makefile | 10 + exams/AxelRubini/2025-01-09/5/include/DES.hpp | 212 +++++++++ .../2025-01-09/5/include/Decision.hpp | 266 +++++++++++ .../2025-01-09/5/include/Dynamics.hpp | 214 +++++++++ .../2025-01-09/5/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-01-09/5/include/IO.hpp | 150 +++++++ .../2025-01-09/5/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-01-09/5/include/Queue.hpp | 70 +++ .../2025-01-09/5/include/Random.hpp | 77 ++++ .../AxelRubini/2025-01-09/5/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-01-09/5/main | Bin 0 -> 87256 bytes exams/AxelRubini/2025-01-09/5/main.cpp | 235 ++++++++++ exams/AxelRubini/2025-01-09/5/parameters.txt | 3 + exams/AxelRubini/2025-01-09/5/results.txt | 12 + exams/AxelRubini/2025-02-05/1/Makefile | 10 + exams/AxelRubini/2025-02-05/1/include/DES.hpp | 212 +++++++++ .../2025-02-05/1/include/Decision.hpp | 266 +++++++++++ .../2025-02-05/1/include/Dynamics.hpp | 214 +++++++++ .../2025-02-05/1/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-02-05/1/include/IO.hpp | 150 +++++++ .../2025-02-05/1/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-02-05/1/include/Queue.hpp | 70 +++ .../2025-02-05/1/include/Random.hpp | 77 ++++ .../AxelRubini/2025-02-05/1/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-02-05/1/main | Bin 0 -> 76608 bytes exams/AxelRubini/2025-02-05/1/main.cpp | 165 +++++++ exams/AxelRubini/2025-02-05/1/parameters.txt | 9 + exams/AxelRubini/2025-02-05/1/results.txt | 6 + exams/AxelRubini/2025-02-05/2/Makefile | 10 + exams/AxelRubini/2025-02-05/2/include/DES.hpp | 212 +++++++++ .../2025-02-05/2/include/Decision.hpp | 266 +++++++++++ .../2025-02-05/2/include/Dynamics.hpp | 214 +++++++++ .../2025-02-05/2/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-02-05/2/include/IO.hpp | 150 +++++++ .../2025-02-05/2/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-02-05/2/include/Queue.hpp | 70 +++ .../2025-02-05/2/include/Random.hpp | 77 ++++ .../AxelRubini/2025-02-05/2/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-02-05/2/main | Bin 0 -> 44016 bytes exams/AxelRubini/2025-02-05/2/main.cpp | 187 ++++++++ exams/AxelRubini/2025-02-05/2/parameters.txt | 10 + exams/AxelRubini/2025-02-05/2/results.txt | 2 + exams/AxelRubini/2025-02-05/3/Makefile | 10 + exams/AxelRubini/2025-02-05/3/include/DES.hpp | 212 +++++++++ .../2025-02-05/3/include/Decision.hpp | 266 +++++++++++ .../2025-02-05/3/include/Dynamics.hpp | 214 +++++++++ .../2025-02-05/3/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-02-05/3/include/IO.hpp | 150 +++++++ .../2025-02-05/3/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-02-05/3/include/Queue.hpp | 70 +++ .../2025-02-05/3/include/Random.hpp | 77 ++++ .../AxelRubini/2025-02-05/3/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-02-05/3/main | Bin 0 -> 43304 bytes exams/AxelRubini/2025-02-05/3/main.cpp | 188 ++++++++ exams/AxelRubini/2025-02-05/3/parameters.txt | 11 + exams/AxelRubini/2025-02-05/3/results.txt | 2 + exams/AxelRubini/2025-02-05/4/Makefile | 10 + exams/AxelRubini/2025-02-05/4/include/DES.hpp | 212 +++++++++ .../2025-02-05/4/include/Decision.hpp | 266 +++++++++++ .../2025-02-05/4/include/Dynamics.hpp | 214 +++++++++ .../2025-02-05/4/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-02-05/4/include/IO.hpp | 150 +++++++ .../2025-02-05/4/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-02-05/4/include/Queue.hpp | 70 +++ .../2025-02-05/4/include/Random.hpp | 77 ++++ .../AxelRubini/2025-02-05/4/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-02-05/4/main | Bin 0 -> 43440 bytes exams/AxelRubini/2025-02-05/4/main.cpp | 150 +++++++ exams/AxelRubini/2025-02-05/4/parameters.txt | 11 + exams/AxelRubini/2025-02-05/4/results.txt | 2 + exams/AxelRubini/2025-02-05/5/Makefile | 10 + exams/AxelRubini/2025-02-05/5/include/DES.hpp | 212 +++++++++ .../2025-02-05/5/include/Decision.hpp | 266 +++++++++++ .../2025-02-05/5/include/Dynamics.hpp | 214 +++++++++ .../2025-02-05/5/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-02-05/5/include/IO.hpp | 150 +++++++ .../2025-02-05/5/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-02-05/5/include/Queue.hpp | 70 +++ .../2025-02-05/5/include/Random.hpp | 77 ++++ .../AxelRubini/2025-02-05/5/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-02-05/5/main | Bin 0 -> 43448 bytes exams/AxelRubini/2025-02-05/5/main.cpp | 152 +++++++ exams/AxelRubini/2025-02-05/5/parameters.txt | 12 + exams/AxelRubini/2025-02-05/5/results.txt | 2 + exams/AxelRubini/2025-03-21/1/Makefile | 10 + exams/AxelRubini/2025-03-21/1/include/DES.hpp | 212 +++++++++ .../2025-03-21/1/include/Decision.hpp | 266 +++++++++++ .../2025-03-21/1/include/Dynamics.hpp | 214 +++++++++ .../2025-03-21/1/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-03-21/1/include/IO.hpp | 150 +++++++ .../2025-03-21/1/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-03-21/1/include/Queue.hpp | 70 +++ .../2025-03-21/1/include/Random.hpp | 77 ++++ .../AxelRubini/2025-03-21/1/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-03-21/1/main | Bin 0 -> 43512 bytes exams/AxelRubini/2025-03-21/1/main.cpp | 98 ++++ exams/AxelRubini/2025-03-21/1/parameters.txt | 5 + exams/AxelRubini/2025-03-21/1/results.txt | 4 + exams/AxelRubini/2025-03-21/2/Makefile | 10 + exams/AxelRubini/2025-03-21/2/include/DES.hpp | 212 +++++++++ .../2025-03-21/2/include/Decision.hpp | 266 +++++++++++ .../2025-03-21/2/include/Dynamics.hpp | 214 +++++++++ .../2025-03-21/2/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-03-21/2/include/IO.hpp | 150 +++++++ .../2025-03-21/2/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-03-21/2/include/Queue.hpp | 70 +++ .../2025-03-21/2/include/Random.hpp | 77 ++++ .../AxelRubini/2025-03-21/2/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-03-21/2/main | Bin 0 -> 43504 bytes exams/AxelRubini/2025-03-21/2/main.cpp | 124 +++++ exams/AxelRubini/2025-03-21/2/parameters.txt | 5 + exams/AxelRubini/2025-03-21/2/results.txt | 3 + exams/AxelRubini/2025-03-21/3/Makefile | 10 + exams/AxelRubini/2025-03-21/3/include/DES.hpp | 212 +++++++++ .../2025-03-21/3/include/Decision.hpp | 266 +++++++++++ .../2025-03-21/3/include/Dynamics.hpp | 214 +++++++++ .../2025-03-21/3/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-03-21/3/include/IO.hpp | 150 +++++++ .../2025-03-21/3/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-03-21/3/include/Queue.hpp | 70 +++ .../2025-03-21/3/include/Random.hpp | 77 ++++ .../AxelRubini/2025-03-21/3/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-03-21/3/main | Bin 0 -> 47248 bytes exams/AxelRubini/2025-03-21/3/main.cpp | 104 +++++ exams/AxelRubini/2025-03-21/3/parameters.txt | 6 + exams/AxelRubini/2025-03-21/3/results.txt | 3 + exams/AxelRubini/2025-03-21/4/Makefile | 10 + exams/AxelRubini/2025-03-21/4/include/DES.hpp | 212 +++++++++ .../2025-03-21/4/include/Decision.hpp | 266 +++++++++++ .../2025-03-21/4/include/Dynamics.hpp | 214 +++++++++ .../2025-03-21/4/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-03-21/4/include/IO.hpp | 150 +++++++ .../2025-03-21/4/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-03-21/4/include/Queue.hpp | 70 +++ .../2025-03-21/4/include/Random.hpp | 77 ++++ .../AxelRubini/2025-03-21/4/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-03-21/4/main | Bin 0 -> 47248 bytes exams/AxelRubini/2025-03-21/4/main.cpp | 102 +++++ exams/AxelRubini/2025-03-21/4/parameters.txt | 6 + exams/AxelRubini/2025-03-21/4/results.txt | 3 + exams/AxelRubini/2025-03-21/5/Makefile | 10 + exams/AxelRubini/2025-03-21/5/include/DES.hpp | 212 +++++++++ .../2025-03-21/5/include/Decision.hpp | 266 +++++++++++ .../2025-03-21/5/include/Dynamics.hpp | 214 +++++++++ .../2025-03-21/5/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-03-21/5/include/IO.hpp | 150 +++++++ .../2025-03-21/5/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-03-21/5/include/Queue.hpp | 70 +++ .../2025-03-21/5/include/Random.hpp | 77 ++++ .../AxelRubini/2025-03-21/5/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-03-21/5/main | Bin 0 -> 47432 bytes exams/AxelRubini/2025-03-21/5/main.cpp | 124 +++++ exams/AxelRubini/2025-03-21/5/parameters.txt | 5 + exams/AxelRubini/2025-03-21/5/results.txt | 5 + exams/AxelRubini/2025-06-12/1/Makefile | 10 + exams/AxelRubini/2025-06-12/1/include/DES.hpp | 212 +++++++++ .../2025-06-12/1/include/Decision.hpp | 266 +++++++++++ .../2025-06-12/1/include/Dynamics.hpp | 214 +++++++++ .../2025-06-12/1/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-06-12/1/include/IO.hpp | 150 +++++++ .../2025-06-12/1/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-06-12/1/include/Queue.hpp | 70 +++ .../2025-06-12/1/include/Random.hpp | 77 ++++ .../AxelRubini/2025-06-12/1/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-06-12/1/main | Bin 0 -> 38904 bytes exams/AxelRubini/2025-06-12/1/main.cpp | 51 +++ exams/AxelRubini/2025-06-12/1/parameters.txt | 4 + exams/AxelRubini/2025-06-12/1/results.txt | 7 + exams/AxelRubini/2025-06-12/2/Makefile | 7 + exams/AxelRubini/2025-06-12/2/include/DES.hpp | 212 +++++++++ .../2025-06-12/2/include/Decision.hpp | 266 +++++++++++ .../2025-06-12/2/include/Dynamics.hpp | 214 +++++++++ .../2025-06-12/2/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-06-12/2/include/IO.hpp | 150 +++++++ .../2025-06-12/2/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-06-12/2/include/Queue.hpp | 70 +++ .../2025-06-12/2/include/Random.hpp | 77 ++++ .../AxelRubini/2025-06-12/2/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-06-12/2/main | Bin 0 -> 43352 bytes exams/AxelRubini/2025-06-12/2/main.cpp | 103 +++++ exams/AxelRubini/2025-06-12/2/parameters.txt | 6 + exams/AxelRubini/2025-06-12/2/results.txt | 2 + exams/AxelRubini/2025-06-12/3/Makefile | 10 + exams/AxelRubini/2025-06-12/3/include/DES.hpp | 212 +++++++++ .../2025-06-12/3/include/Decision.hpp | 266 +++++++++++ .../2025-06-12/3/include/Dynamics.hpp | 214 +++++++++ .../2025-06-12/3/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-06-12/3/include/IO.hpp | 150 +++++++ .../2025-06-12/3/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-06-12/3/include/Queue.hpp | 70 +++ .../2025-06-12/3/include/Random.hpp | 77 ++++ .../AxelRubini/2025-06-12/3/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-06-12/3/main | Bin 0 -> 39088 bytes exams/AxelRubini/2025-06-12/3/main.cpp | 33 ++ exams/AxelRubini/2025-06-12/3/parameters.txt | 5 + exams/AxelRubini/2025-06-12/3/results.txt | 12 + exams/AxelRubini/2025-06-12/4/Makefile | 7 + exams/AxelRubini/2025-06-12/4/include/DES.hpp | 212 +++++++++ .../2025-06-12/4/include/Decision.hpp | 266 +++++++++++ .../2025-06-12/4/include/Dynamics.hpp | 214 +++++++++ .../2025-06-12/4/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-06-12/4/include/IO.hpp | 150 +++++++ .../2025-06-12/4/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-06-12/4/include/Queue.hpp | 70 +++ .../2025-06-12/4/include/Random.hpp | 77 ++++ .../AxelRubini/2025-06-12/4/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-06-12/4/main | Bin 0 -> 44192 bytes exams/AxelRubini/2025-06-12/4/main.cpp | 100 +++++ exams/AxelRubini/2025-06-12/4/parameters.txt | 11 + exams/AxelRubini/2025-06-12/4/results.txt | 2 + exams/AxelRubini/2025-06-12/5/Makefile | 7 + exams/AxelRubini/2025-06-12/5/include/DES.hpp | 212 +++++++++ .../2025-06-12/5/include/Decision.hpp | 266 +++++++++++ .../2025-06-12/5/include/Dynamics.hpp | 214 +++++++++ .../2025-06-12/5/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-06-12/5/include/IO.hpp | 150 +++++++ .../2025-06-12/5/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-06-12/5/include/Queue.hpp | 70 +++ .../2025-06-12/5/include/Random.hpp | 77 ++++ .../AxelRubini/2025-06-12/5/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-06-12/5/main | Bin 0 -> 48288 bytes exams/AxelRubini/2025-06-12/5/main.cpp | 116 +++++ exams/AxelRubini/2025-06-12/5/parameters.txt | 11 + exams/AxelRubini/2025-06-12/5/results.txt | 2 + exams/AxelRubini/2025-07-08/1/Makefile | 10 + exams/AxelRubini/2025-07-08/1/include/DES.hpp | 212 +++++++++ .../2025-07-08/1/include/Decision.hpp | 266 +++++++++++ .../2025-07-08/1/include/Dynamics.hpp | 214 +++++++++ .../2025-07-08/1/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-07-08/1/include/IO.hpp | 150 +++++++ .../2025-07-08/1/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-07-08/1/include/Queue.hpp | 70 +++ .../2025-07-08/1/include/Random.hpp | 77 ++++ .../AxelRubini/2025-07-08/1/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-07-08/1/main | Bin 0 -> 43808 bytes exams/AxelRubini/2025-07-08/1/main.cpp | 149 ++++++ exams/AxelRubini/2025-07-08/1/parameters.txt | 7 + exams/AxelRubini/2025-07-08/1/results.txt | 2 + exams/AxelRubini/2025-07-08/2/Makefile | 10 + exams/AxelRubini/2025-07-08/2/include/DES.hpp | 212 +++++++++ .../2025-07-08/2/include/Decision.hpp | 266 +++++++++++ .../2025-07-08/2/include/Dynamics.hpp | 214 +++++++++ .../2025-07-08/2/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-07-08/2/include/IO.hpp | 150 +++++++ .../2025-07-08/2/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-07-08/2/include/Queue.hpp | 70 +++ .../2025-07-08/2/include/Random.hpp | 77 ++++ .../AxelRubini/2025-07-08/2/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-07-08/2/main | Bin 0 -> 43664 bytes exams/AxelRubini/2025-07-08/2/main.cpp | 123 +++++ exams/AxelRubini/2025-07-08/2/parameters.txt | 7 + exams/AxelRubini/2025-07-08/2/results.txt | 3 + exams/AxelRubini/2025-07-08/3/Makefile | 10 + exams/AxelRubini/2025-07-08/3/include/DES.hpp | 212 +++++++++ .../2025-07-08/3/include/Decision.hpp | 266 +++++++++++ .../2025-07-08/3/include/Dynamics.hpp | 214 +++++++++ .../2025-07-08/3/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-07-08/3/include/IO.hpp | 150 +++++++ .../2025-07-08/3/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-07-08/3/include/Queue.hpp | 70 +++ .../2025-07-08/3/include/Random.hpp | 77 ++++ .../AxelRubini/2025-07-08/3/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-07-08/3/main | Bin 0 -> 44192 bytes exams/AxelRubini/2025-07-08/3/main.cpp | 127 ++++++ exams/AxelRubini/2025-07-08/3/parameters.txt | 12 + exams/AxelRubini/2025-07-08/3/results.txt | 2 + exams/AxelRubini/2025-07-08/4/Makefile | 10 + exams/AxelRubini/2025-07-08/4/include/DES.hpp | 212 +++++++++ .../2025-07-08/4/include/Decision.hpp | 266 +++++++++++ .../2025-07-08/4/include/Dynamics.hpp | 214 +++++++++ .../2025-07-08/4/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-07-08/4/include/IO.hpp | 150 +++++++ .../2025-07-08/4/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-07-08/4/include/Queue.hpp | 70 +++ .../2025-07-08/4/include/Random.hpp | 77 ++++ .../AxelRubini/2025-07-08/4/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-07-08/4/main | Bin 0 -> 48536 bytes exams/AxelRubini/2025-07-08/4/main.cpp | 137 ++++++ exams/AxelRubini/2025-07-08/4/parameters.txt | 12 + exams/AxelRubini/2025-07-08/4/results.txt | 3 + exams/AxelRubini/2025-07-08/5/Makefile | 10 + exams/AxelRubini/2025-07-08/5/include/DES.hpp | 212 +++++++++ .../2025-07-08/5/include/Decision.hpp | 266 +++++++++++ .../2025-07-08/5/include/Dynamics.hpp | 214 +++++++++ .../2025-07-08/5/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-07-08/5/include/IO.hpp | 150 +++++++ .../2025-07-08/5/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-07-08/5/include/Queue.hpp | 70 +++ .../2025-07-08/5/include/Random.hpp | 77 ++++ .../AxelRubini/2025-07-08/5/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-07-08/5/main | Bin 0 -> 48288 bytes exams/AxelRubini/2025-07-08/5/main.cpp | 135 ++++++ exams/AxelRubini/2025-07-08/5/parameters.txt | 12 + exams/AxelRubini/2025-07-08/5/results.txt | 4 + exams/AxelRubini/2025-09-08/1/Makefile | 10 + exams/AxelRubini/2025-09-08/1/include/DES.hpp | 212 +++++++++ .../2025-09-08/1/include/Decision.hpp | 266 +++++++++++ .../2025-09-08/1/include/Dynamics.hpp | 214 +++++++++ .../2025-09-08/1/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-09-08/1/include/IO.hpp | 150 +++++++ .../2025-09-08/1/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-09-08/1/include/Queue.hpp | 70 +++ .../2025-09-08/1/include/Random.hpp | 77 ++++ .../AxelRubini/2025-09-08/1/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-09-08/1/main | Bin 0 -> 43968 bytes exams/AxelRubini/2025-09-08/1/main.cpp | 93 ++++ exams/AxelRubini/2025-09-08/1/parameters.txt | 8 + exams/AxelRubini/2025-09-08/1/results.txt | 2 + exams/AxelRubini/2025-09-08/2/Makefile | 10 + exams/AxelRubini/2025-09-08/2/include/DES.hpp | 212 +++++++++ .../2025-09-08/2/include/Decision.hpp | 266 +++++++++++ .../2025-09-08/2/include/Dynamics.hpp | 214 +++++++++ .../2025-09-08/2/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-09-08/2/include/IO.hpp | 150 +++++++ .../2025-09-08/2/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-09-08/2/include/Queue.hpp | 70 +++ .../2025-09-08/2/include/Random.hpp | 77 ++++ .../AxelRubini/2025-09-08/2/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-09-08/2/main | Bin 0 -> 43840 bytes exams/AxelRubini/2025-09-08/2/main.cpp | 120 +++++ exams/AxelRubini/2025-09-08/2/parameters.txt | 8 + exams/AxelRubini/2025-09-08/2/results.txt | 2 + exams/AxelRubini/2025-09-08/3/Makefile | 10 + exams/AxelRubini/2025-09-08/3/include/DES.hpp | 212 +++++++++ .../2025-09-08/3/include/Decision.hpp | 266 +++++++++++ .../2025-09-08/3/include/Dynamics.hpp | 214 +++++++++ .../2025-09-08/3/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-09-08/3/include/IO.hpp | 150 +++++++ .../2025-09-08/3/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-09-08/3/include/Queue.hpp | 70 +++ .../2025-09-08/3/include/Random.hpp | 77 ++++ .../AxelRubini/2025-09-08/3/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-09-08/3/main | Bin 0 -> 47896 bytes exams/AxelRubini/2025-09-08/3/main.cpp | 105 +++++ exams/AxelRubini/2025-09-08/3/parameters.txt | 12 + exams/AxelRubini/2025-09-08/3/results.txt | 2 + exams/AxelRubini/2025-09-08/4/Makefile | 10 + exams/AxelRubini/2025-09-08/4/include/DES.hpp | 212 +++++++++ .../2025-09-08/4/include/Decision.hpp | 266 +++++++++++ .../2025-09-08/4/include/Dynamics.hpp | 214 +++++++++ .../2025-09-08/4/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-09-08/4/include/IO.hpp | 150 +++++++ .../2025-09-08/4/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-09-08/4/include/Queue.hpp | 70 +++ .../2025-09-08/4/include/Random.hpp | 77 ++++ .../AxelRubini/2025-09-08/4/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-09-08/4/main | Bin 0 -> 47952 bytes exams/AxelRubini/2025-09-08/4/main.cpp | 119 +++++ exams/AxelRubini/2025-09-08/4/parameters.txt | 12 + exams/AxelRubini/2025-09-08/4/results.txt | 4 + exams/AxelRubini/2025-09-08/5/Makefile | 10 + exams/AxelRubini/2025-09-08/5/include/DES.hpp | 212 +++++++++ .../2025-09-08/5/include/Decision.hpp | 266 +++++++++++ .../2025-09-08/5/include/Dynamics.hpp | 214 +++++++++ .../2025-09-08/5/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-09-08/5/include/IO.hpp | 150 +++++++ .../2025-09-08/5/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-09-08/5/include/Queue.hpp | 70 +++ .../2025-09-08/5/include/Random.hpp | 77 ++++ .../AxelRubini/2025-09-08/5/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-09-08/5/main | Bin 0 -> 47896 bytes exams/AxelRubini/2025-09-08/5/main.cpp | 120 +++++ exams/AxelRubini/2025-09-08/5/parameters.txt | 11 + exams/AxelRubini/2025-09-08/5/results.txt | 5 + exams/AxelRubini/2025-11-03/1/Makefile | 10 + exams/AxelRubini/2025-11-03/1/include/DES.hpp | 212 +++++++++ .../2025-11-03/1/include/Decision.hpp | 266 +++++++++++ .../2025-11-03/1/include/Dynamics.hpp | 214 +++++++++ .../2025-11-03/1/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-11-03/1/include/IO.hpp | 150 +++++++ .../2025-11-03/1/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-11-03/1/include/Queue.hpp | 70 +++ .../2025-11-03/1/include/Random.hpp | 77 ++++ .../AxelRubini/2025-11-03/1/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-11-03/1/main | Bin 0 -> 43848 bytes exams/AxelRubini/2025-11-03/1/main.cpp | 39 ++ exams/AxelRubini/2025-11-03/1/parameters.txt | 18 + exams/AxelRubini/2025-11-03/1/results.txt | 2 + exams/AxelRubini/2025-11-03/2/Makefile | 10 + exams/AxelRubini/2025-11-03/2/include/DES.hpp | 212 +++++++++ .../2025-11-03/2/include/Decision.hpp | 266 +++++++++++ .../2025-11-03/2/include/Dynamics.hpp | 214 +++++++++ .../2025-11-03/2/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-11-03/2/include/IO.hpp | 150 +++++++ .../2025-11-03/2/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-11-03/2/include/Queue.hpp | 70 +++ .../2025-11-03/2/include/Random.hpp | 77 ++++ .../AxelRubini/2025-11-03/2/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-11-03/2/main | Bin 0 -> 44016 bytes exams/AxelRubini/2025-11-03/2/main.cpp | 42 ++ exams/AxelRubini/2025-11-03/2/parameters.txt | 18 + exams/AxelRubini/2025-11-03/2/results.txt | 2 + exams/AxelRubini/2025-11-03/3/Makefile | 10 + exams/AxelRubini/2025-11-03/3/include/DES.hpp | 212 +++++++++ .../2025-11-03/3/include/Decision.hpp | 266 +++++++++++ .../2025-11-03/3/include/Dynamics.hpp | 214 +++++++++ .../2025-11-03/3/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-11-03/3/include/IO.hpp | 150 +++++++ .../2025-11-03/3/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-11-03/3/include/Queue.hpp | 70 +++ .../2025-11-03/3/include/Random.hpp | 77 ++++ .../AxelRubini/2025-11-03/3/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-11-03/3/main | Bin 0 -> 52528 bytes exams/AxelRubini/2025-11-03/3/main.cpp | 72 +++ exams/AxelRubini/2025-11-03/3/parameters.txt | 19 + exams/AxelRubini/2025-11-03/3/results.txt | 4 + exams/AxelRubini/2025-11-03/4/include/DES.hpp | 212 +++++++++ .../2025-11-03/4/include/Decision.hpp | 266 +++++++++++ .../2025-11-03/4/include/Dynamics.hpp | 214 +++++++++ .../2025-11-03/4/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-11-03/4/include/IO.hpp | 150 +++++++ .../2025-11-03/4/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-11-03/4/include/Queue.hpp | 70 +++ .../2025-11-03/4/include/Random.hpp | 77 ++++ .../AxelRubini/2025-11-03/4/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-11-03/4/main | Bin 0 -> 57600 bytes exams/AxelRubini/2025-11-03/4/main.cpp | 117 +++++ exams/AxelRubini/2025-11-03/4/parameters.txt | 10 + exams/AxelRubini/2025-11-03/4/results.txt | 2 + exams/AxelRubini/2025-11-03/5/include/DES.hpp | 212 +++++++++ .../2025-11-03/5/include/Decision.hpp | 266 +++++++++++ .../2025-11-03/5/include/Dynamics.hpp | 214 +++++++++ .../2025-11-03/5/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2025-11-03/5/include/IO.hpp | 150 +++++++ .../2025-11-03/5/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2025-11-03/5/include/Queue.hpp | 70 +++ .../2025-11-03/5/include/Random.hpp | 77 ++++ .../AxelRubini/2025-11-03/5/include/Stat.hpp | 72 +++ exams/AxelRubini/2025-11-03/5/main | Bin 0 -> 57760 bytes exams/AxelRubini/2025-11-03/5/main.cpp | 137 ++++++ exams/AxelRubini/2025-11-03/5/parameters.txt | 11 + exams/AxelRubini/2025-11-03/5/results.txt | 4 + exams/AxelRubini/2026-01-14/1/Makefile | 10 + exams/AxelRubini/2026-01-14/1/include/DES.hpp | 212 +++++++++ .../2026-01-14/1/include/Decision.hpp | 266 +++++++++++ .../2026-01-14/1/include/Dynamics.hpp | 214 +++++++++ .../2026-01-14/1/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2026-01-14/1/include/IO.hpp | 150 +++++++ .../2026-01-14/1/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2026-01-14/1/include/Queue.hpp | 70 +++ .../2026-01-14/1/include/Random.hpp | 77 ++++ .../AxelRubini/2026-01-14/1/include/Stat.hpp | 72 +++ exams/AxelRubini/2026-01-14/1/main | Bin 0 -> 43952 bytes exams/AxelRubini/2026-01-14/1/main.cpp | 89 ++++ exams/AxelRubini/2026-01-14/1/parameters.txt | 9 + exams/AxelRubini/2026-01-14/1/results.txt | 2 + exams/AxelRubini/2026-01-14/2/Makefile | 10 + exams/AxelRubini/2026-01-14/2/include/DES.hpp | 212 +++++++++ .../2026-01-14/2/include/Decision.hpp | 266 +++++++++++ .../2026-01-14/2/include/Dynamics.hpp | 214 +++++++++ .../2026-01-14/2/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2026-01-14/2/include/IO.hpp | 150 +++++++ .../2026-01-14/2/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2026-01-14/2/include/Queue.hpp | 70 +++ .../2026-01-14/2/include/Random.hpp | 77 ++++ .../AxelRubini/2026-01-14/2/include/Stat.hpp | 72 +++ exams/AxelRubini/2026-01-14/2/main | Bin 0 -> 43768 bytes exams/AxelRubini/2026-01-14/2/main.cpp | 109 +++++ exams/AxelRubini/2026-01-14/2/parameters.txt | 9 + exams/AxelRubini/2026-01-14/2/results.txt | 2 + exams/AxelRubini/2026-01-14/3/Makefile | 10 + exams/AxelRubini/2026-01-14/3/include/DES.hpp | 212 +++++++++ .../2026-01-14/3/include/Decision.hpp | 266 +++++++++++ .../2026-01-14/3/include/Dynamics.hpp | 214 +++++++++ .../2026-01-14/3/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2026-01-14/3/include/IO.hpp | 150 +++++++ .../2026-01-14/3/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2026-01-14/3/include/Queue.hpp | 70 +++ .../2026-01-14/3/include/Random.hpp | 77 ++++ .../AxelRubini/2026-01-14/3/include/Stat.hpp | 72 +++ exams/AxelRubini/2026-01-14/3/main | Bin 0 -> 81552 bytes exams/AxelRubini/2026-01-14/3/main.cpp | 174 +++++++ exams/AxelRubini/2026-01-14/3/out.log | 0 exams/AxelRubini/2026-01-14/3/parameters.txt | 14 + exams/AxelRubini/2026-01-14/3/results.txt | 2 + exams/AxelRubini/2026-01-14/4/Makefile | 10 + exams/AxelRubini/2026-01-14/4/include/DES.hpp | 212 +++++++++ .../2026-01-14/4/include/Decision.hpp | 266 +++++++++++ .../2026-01-14/4/include/Dynamics.hpp | 214 +++++++++ .../2026-01-14/4/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2026-01-14/4/include/IO.hpp | 150 +++++++ .../2026-01-14/4/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2026-01-14/4/include/Queue.hpp | 70 +++ .../2026-01-14/4/include/Random.hpp | 77 ++++ .../AxelRubini/2026-01-14/4/include/Stat.hpp | 72 +++ exams/AxelRubini/2026-01-14/4/main | Bin 0 -> 81768 bytes exams/AxelRubini/2026-01-14/4/main.cpp | 202 +++++++++ exams/AxelRubini/2026-01-14/4/parameters.txt | 17 + exams/AxelRubini/2026-01-14/4/results.txt | 4 + exams/AxelRubini/2026-01-14/5/Makefile | 10 + exams/AxelRubini/2026-01-14/5/include/DES.hpp | 212 +++++++++ .../2026-01-14/5/include/Decision.hpp | 266 +++++++++++ .../2026-01-14/5/include/Dynamics.hpp | 214 +++++++++ .../2026-01-14/5/include/Geometry.hpp | 111 +++++ exams/AxelRubini/2026-01-14/5/include/IO.hpp | 150 +++++++ .../2026-01-14/5/include/Inventory.hpp | 423 ++++++++++++++++++ .../AxelRubini/2026-01-14/5/include/Queue.hpp | 70 +++ .../2026-01-14/5/include/Random.hpp | 77 ++++ .../AxelRubini/2026-01-14/5/include/Stat.hpp | 72 +++ exams/AxelRubini/2026-01-14/5/main | Bin 0 -> 81712 bytes exams/AxelRubini/2026-01-14/5/main.cpp | 110 +++++ exams/AxelRubini/2026-01-14/5/parameters.txt | 17 + exams/AxelRubini/2026-01-14/5/results.txt | 5 + 559 files changed, 69466 insertions(+) create mode 100644 exams/AxelRubini/2025-01-09/1/Makefile create mode 100644 exams/AxelRubini/2025-01-09/1/include/DES.hpp create mode 100644 exams/AxelRubini/2025-01-09/1/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-01-09/1/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-01-09/1/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-01-09/1/include/IO.hpp create mode 100644 exams/AxelRubini/2025-01-09/1/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-01-09/1/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-01-09/1/include/Random.hpp create mode 100644 exams/AxelRubini/2025-01-09/1/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-01-09/1/main create mode 100644 exams/AxelRubini/2025-01-09/1/main.cpp create mode 100644 exams/AxelRubini/2025-01-09/1/parameters.txt create mode 100644 exams/AxelRubini/2025-01-09/1/results.txt create mode 100644 exams/AxelRubini/2025-01-09/2/Makefile create mode 100644 exams/AxelRubini/2025-01-09/2/include/DES.hpp create mode 100644 exams/AxelRubini/2025-01-09/2/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-01-09/2/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-01-09/2/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-01-09/2/include/IO.hpp create mode 100644 exams/AxelRubini/2025-01-09/2/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-01-09/2/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-01-09/2/include/Random.hpp create mode 100644 exams/AxelRubini/2025-01-09/2/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-01-09/2/main create mode 100644 exams/AxelRubini/2025-01-09/2/main.cpp create mode 100644 exams/AxelRubini/2025-01-09/2/parameters.txt create mode 100644 exams/AxelRubini/2025-01-09/2/results.txt create mode 100644 exams/AxelRubini/2025-01-09/3/Makefile create mode 100644 exams/AxelRubini/2025-01-09/3/include/DES.hpp create mode 100644 exams/AxelRubini/2025-01-09/3/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-01-09/3/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-01-09/3/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-01-09/3/include/IO.hpp create mode 100644 exams/AxelRubini/2025-01-09/3/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-01-09/3/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-01-09/3/include/Random.hpp create mode 100644 exams/AxelRubini/2025-01-09/3/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-01-09/3/main create mode 100644 exams/AxelRubini/2025-01-09/3/main.cpp create mode 100644 exams/AxelRubini/2025-01-09/3/parameters.txt create mode 100644 exams/AxelRubini/2025-01-09/3/results.txt create mode 100644 exams/AxelRubini/2025-01-09/4/Makefile create mode 100644 exams/AxelRubini/2025-01-09/4/include/DES.hpp create mode 100644 exams/AxelRubini/2025-01-09/4/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-01-09/4/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-01-09/4/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-01-09/4/include/IO.hpp create mode 100644 exams/AxelRubini/2025-01-09/4/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-01-09/4/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-01-09/4/include/Random.hpp create mode 100644 exams/AxelRubini/2025-01-09/4/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-01-09/4/main create mode 100644 exams/AxelRubini/2025-01-09/4/main.cpp create mode 100644 exams/AxelRubini/2025-01-09/4/parameters.txt create mode 100644 exams/AxelRubini/2025-01-09/4/results.txt create mode 100644 exams/AxelRubini/2025-01-09/5/Makefile create mode 100644 exams/AxelRubini/2025-01-09/5/include/DES.hpp create mode 100644 exams/AxelRubini/2025-01-09/5/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-01-09/5/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-01-09/5/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-01-09/5/include/IO.hpp create mode 100644 exams/AxelRubini/2025-01-09/5/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-01-09/5/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-01-09/5/include/Random.hpp create mode 100644 exams/AxelRubini/2025-01-09/5/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-01-09/5/main create mode 100644 exams/AxelRubini/2025-01-09/5/main.cpp create mode 100644 exams/AxelRubini/2025-01-09/5/parameters.txt create mode 100644 exams/AxelRubini/2025-01-09/5/results.txt create mode 100644 exams/AxelRubini/2025-02-05/1/Makefile create mode 100644 exams/AxelRubini/2025-02-05/1/include/DES.hpp create mode 100644 exams/AxelRubini/2025-02-05/1/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-02-05/1/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-02-05/1/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-02-05/1/include/IO.hpp create mode 100644 exams/AxelRubini/2025-02-05/1/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-02-05/1/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-02-05/1/include/Random.hpp create mode 100644 exams/AxelRubini/2025-02-05/1/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-02-05/1/main create mode 100644 exams/AxelRubini/2025-02-05/1/main.cpp create mode 100644 exams/AxelRubini/2025-02-05/1/parameters.txt create mode 100644 exams/AxelRubini/2025-02-05/1/results.txt create mode 100644 exams/AxelRubini/2025-02-05/2/Makefile create mode 100644 exams/AxelRubini/2025-02-05/2/include/DES.hpp create mode 100644 exams/AxelRubini/2025-02-05/2/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-02-05/2/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-02-05/2/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-02-05/2/include/IO.hpp create mode 100644 exams/AxelRubini/2025-02-05/2/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-02-05/2/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-02-05/2/include/Random.hpp create mode 100644 exams/AxelRubini/2025-02-05/2/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-02-05/2/main create mode 100644 exams/AxelRubini/2025-02-05/2/main.cpp create mode 100644 exams/AxelRubini/2025-02-05/2/parameters.txt create mode 100644 exams/AxelRubini/2025-02-05/2/results.txt create mode 100644 exams/AxelRubini/2025-02-05/3/Makefile create mode 100644 exams/AxelRubini/2025-02-05/3/include/DES.hpp create mode 100644 exams/AxelRubini/2025-02-05/3/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-02-05/3/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-02-05/3/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-02-05/3/include/IO.hpp create mode 100644 exams/AxelRubini/2025-02-05/3/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-02-05/3/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-02-05/3/include/Random.hpp create mode 100644 exams/AxelRubini/2025-02-05/3/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-02-05/3/main create mode 100644 exams/AxelRubini/2025-02-05/3/main.cpp create mode 100644 exams/AxelRubini/2025-02-05/3/parameters.txt create mode 100644 exams/AxelRubini/2025-02-05/3/results.txt create mode 100644 exams/AxelRubini/2025-02-05/4/Makefile create mode 100644 exams/AxelRubini/2025-02-05/4/include/DES.hpp create mode 100644 exams/AxelRubini/2025-02-05/4/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-02-05/4/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-02-05/4/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-02-05/4/include/IO.hpp create mode 100644 exams/AxelRubini/2025-02-05/4/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-02-05/4/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-02-05/4/include/Random.hpp create mode 100644 exams/AxelRubini/2025-02-05/4/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-02-05/4/main create mode 100644 exams/AxelRubini/2025-02-05/4/main.cpp create mode 100644 exams/AxelRubini/2025-02-05/4/parameters.txt create mode 100644 exams/AxelRubini/2025-02-05/4/results.txt create mode 100644 exams/AxelRubini/2025-02-05/5/Makefile create mode 100644 exams/AxelRubini/2025-02-05/5/include/DES.hpp create mode 100644 exams/AxelRubini/2025-02-05/5/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-02-05/5/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-02-05/5/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-02-05/5/include/IO.hpp create mode 100644 exams/AxelRubini/2025-02-05/5/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-02-05/5/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-02-05/5/include/Random.hpp create mode 100644 exams/AxelRubini/2025-02-05/5/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-02-05/5/main create mode 100644 exams/AxelRubini/2025-02-05/5/main.cpp create mode 100644 exams/AxelRubini/2025-02-05/5/parameters.txt create mode 100644 exams/AxelRubini/2025-02-05/5/results.txt create mode 100644 exams/AxelRubini/2025-03-21/1/Makefile create mode 100644 exams/AxelRubini/2025-03-21/1/include/DES.hpp create mode 100644 exams/AxelRubini/2025-03-21/1/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-03-21/1/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-03-21/1/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-03-21/1/include/IO.hpp create mode 100644 exams/AxelRubini/2025-03-21/1/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-03-21/1/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-03-21/1/include/Random.hpp create mode 100644 exams/AxelRubini/2025-03-21/1/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-03-21/1/main create mode 100644 exams/AxelRubini/2025-03-21/1/main.cpp create mode 100644 exams/AxelRubini/2025-03-21/1/parameters.txt create mode 100644 exams/AxelRubini/2025-03-21/1/results.txt create mode 100644 exams/AxelRubini/2025-03-21/2/Makefile create mode 100644 exams/AxelRubini/2025-03-21/2/include/DES.hpp create mode 100644 exams/AxelRubini/2025-03-21/2/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-03-21/2/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-03-21/2/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-03-21/2/include/IO.hpp create mode 100644 exams/AxelRubini/2025-03-21/2/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-03-21/2/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-03-21/2/include/Random.hpp create mode 100644 exams/AxelRubini/2025-03-21/2/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-03-21/2/main create mode 100644 exams/AxelRubini/2025-03-21/2/main.cpp create mode 100644 exams/AxelRubini/2025-03-21/2/parameters.txt create mode 100644 exams/AxelRubini/2025-03-21/2/results.txt create mode 100644 exams/AxelRubini/2025-03-21/3/Makefile create mode 100644 exams/AxelRubini/2025-03-21/3/include/DES.hpp create mode 100644 exams/AxelRubini/2025-03-21/3/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-03-21/3/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-03-21/3/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-03-21/3/include/IO.hpp create mode 100644 exams/AxelRubini/2025-03-21/3/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-03-21/3/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-03-21/3/include/Random.hpp create mode 100644 exams/AxelRubini/2025-03-21/3/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-03-21/3/main create mode 100644 exams/AxelRubini/2025-03-21/3/main.cpp create mode 100644 exams/AxelRubini/2025-03-21/3/parameters.txt create mode 100644 exams/AxelRubini/2025-03-21/3/results.txt create mode 100644 exams/AxelRubini/2025-03-21/4/Makefile create mode 100644 exams/AxelRubini/2025-03-21/4/include/DES.hpp create mode 100644 exams/AxelRubini/2025-03-21/4/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-03-21/4/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-03-21/4/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-03-21/4/include/IO.hpp create mode 100644 exams/AxelRubini/2025-03-21/4/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-03-21/4/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-03-21/4/include/Random.hpp create mode 100644 exams/AxelRubini/2025-03-21/4/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-03-21/4/main create mode 100644 exams/AxelRubini/2025-03-21/4/main.cpp create mode 100644 exams/AxelRubini/2025-03-21/4/parameters.txt create mode 100644 exams/AxelRubini/2025-03-21/4/results.txt create mode 100644 exams/AxelRubini/2025-03-21/5/Makefile create mode 100644 exams/AxelRubini/2025-03-21/5/include/DES.hpp create mode 100644 exams/AxelRubini/2025-03-21/5/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-03-21/5/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-03-21/5/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-03-21/5/include/IO.hpp create mode 100644 exams/AxelRubini/2025-03-21/5/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-03-21/5/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-03-21/5/include/Random.hpp create mode 100644 exams/AxelRubini/2025-03-21/5/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-03-21/5/main create mode 100644 exams/AxelRubini/2025-03-21/5/main.cpp create mode 100644 exams/AxelRubini/2025-03-21/5/parameters.txt create mode 100644 exams/AxelRubini/2025-03-21/5/results.txt create mode 100644 exams/AxelRubini/2025-06-12/1/Makefile create mode 100644 exams/AxelRubini/2025-06-12/1/include/DES.hpp create mode 100644 exams/AxelRubini/2025-06-12/1/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-06-12/1/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-06-12/1/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-06-12/1/include/IO.hpp create mode 100644 exams/AxelRubini/2025-06-12/1/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-06-12/1/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-06-12/1/include/Random.hpp create mode 100644 exams/AxelRubini/2025-06-12/1/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-06-12/1/main create mode 100644 exams/AxelRubini/2025-06-12/1/main.cpp create mode 100644 exams/AxelRubini/2025-06-12/1/parameters.txt create mode 100644 exams/AxelRubini/2025-06-12/1/results.txt create mode 100644 exams/AxelRubini/2025-06-12/2/Makefile create mode 100644 exams/AxelRubini/2025-06-12/2/include/DES.hpp create mode 100644 exams/AxelRubini/2025-06-12/2/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-06-12/2/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-06-12/2/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-06-12/2/include/IO.hpp create mode 100644 exams/AxelRubini/2025-06-12/2/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-06-12/2/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-06-12/2/include/Random.hpp create mode 100644 exams/AxelRubini/2025-06-12/2/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-06-12/2/main create mode 100644 exams/AxelRubini/2025-06-12/2/main.cpp create mode 100644 exams/AxelRubini/2025-06-12/2/parameters.txt create mode 100644 exams/AxelRubini/2025-06-12/2/results.txt create mode 100644 exams/AxelRubini/2025-06-12/3/Makefile create mode 100644 exams/AxelRubini/2025-06-12/3/include/DES.hpp create mode 100644 exams/AxelRubini/2025-06-12/3/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-06-12/3/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-06-12/3/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-06-12/3/include/IO.hpp create mode 100644 exams/AxelRubini/2025-06-12/3/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-06-12/3/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-06-12/3/include/Random.hpp create mode 100644 exams/AxelRubini/2025-06-12/3/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-06-12/3/main create mode 100644 exams/AxelRubini/2025-06-12/3/main.cpp create mode 100644 exams/AxelRubini/2025-06-12/3/parameters.txt create mode 100644 exams/AxelRubini/2025-06-12/3/results.txt create mode 100644 exams/AxelRubini/2025-06-12/4/Makefile create mode 100644 exams/AxelRubini/2025-06-12/4/include/DES.hpp create mode 100644 exams/AxelRubini/2025-06-12/4/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-06-12/4/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-06-12/4/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-06-12/4/include/IO.hpp create mode 100644 exams/AxelRubini/2025-06-12/4/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-06-12/4/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-06-12/4/include/Random.hpp create mode 100644 exams/AxelRubini/2025-06-12/4/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-06-12/4/main create mode 100644 exams/AxelRubini/2025-06-12/4/main.cpp create mode 100644 exams/AxelRubini/2025-06-12/4/parameters.txt create mode 100644 exams/AxelRubini/2025-06-12/4/results.txt create mode 100644 exams/AxelRubini/2025-06-12/5/Makefile create mode 100644 exams/AxelRubini/2025-06-12/5/include/DES.hpp create mode 100644 exams/AxelRubini/2025-06-12/5/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-06-12/5/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-06-12/5/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-06-12/5/include/IO.hpp create mode 100644 exams/AxelRubini/2025-06-12/5/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-06-12/5/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-06-12/5/include/Random.hpp create mode 100644 exams/AxelRubini/2025-06-12/5/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-06-12/5/main create mode 100644 exams/AxelRubini/2025-06-12/5/main.cpp create mode 100644 exams/AxelRubini/2025-06-12/5/parameters.txt create mode 100644 exams/AxelRubini/2025-06-12/5/results.txt create mode 100644 exams/AxelRubini/2025-07-08/1/Makefile create mode 100644 exams/AxelRubini/2025-07-08/1/include/DES.hpp create mode 100644 exams/AxelRubini/2025-07-08/1/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-07-08/1/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-07-08/1/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-07-08/1/include/IO.hpp create mode 100644 exams/AxelRubini/2025-07-08/1/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-07-08/1/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-07-08/1/include/Random.hpp create mode 100644 exams/AxelRubini/2025-07-08/1/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-07-08/1/main create mode 100644 exams/AxelRubini/2025-07-08/1/main.cpp create mode 100644 exams/AxelRubini/2025-07-08/1/parameters.txt create mode 100644 exams/AxelRubini/2025-07-08/1/results.txt create mode 100644 exams/AxelRubini/2025-07-08/2/Makefile create mode 100644 exams/AxelRubini/2025-07-08/2/include/DES.hpp create mode 100644 exams/AxelRubini/2025-07-08/2/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-07-08/2/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-07-08/2/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-07-08/2/include/IO.hpp create mode 100644 exams/AxelRubini/2025-07-08/2/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-07-08/2/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-07-08/2/include/Random.hpp create mode 100644 exams/AxelRubini/2025-07-08/2/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-07-08/2/main create mode 100644 exams/AxelRubini/2025-07-08/2/main.cpp create mode 100644 exams/AxelRubini/2025-07-08/2/parameters.txt create mode 100644 exams/AxelRubini/2025-07-08/2/results.txt create mode 100644 exams/AxelRubini/2025-07-08/3/Makefile create mode 100644 exams/AxelRubini/2025-07-08/3/include/DES.hpp create mode 100644 exams/AxelRubini/2025-07-08/3/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-07-08/3/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-07-08/3/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-07-08/3/include/IO.hpp create mode 100644 exams/AxelRubini/2025-07-08/3/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-07-08/3/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-07-08/3/include/Random.hpp create mode 100644 exams/AxelRubini/2025-07-08/3/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-07-08/3/main create mode 100644 exams/AxelRubini/2025-07-08/3/main.cpp create mode 100644 exams/AxelRubini/2025-07-08/3/parameters.txt create mode 100644 exams/AxelRubini/2025-07-08/3/results.txt create mode 100644 exams/AxelRubini/2025-07-08/4/Makefile create mode 100644 exams/AxelRubini/2025-07-08/4/include/DES.hpp create mode 100644 exams/AxelRubini/2025-07-08/4/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-07-08/4/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-07-08/4/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-07-08/4/include/IO.hpp create mode 100644 exams/AxelRubini/2025-07-08/4/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-07-08/4/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-07-08/4/include/Random.hpp create mode 100644 exams/AxelRubini/2025-07-08/4/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-07-08/4/main create mode 100644 exams/AxelRubini/2025-07-08/4/main.cpp create mode 100644 exams/AxelRubini/2025-07-08/4/parameters.txt create mode 100644 exams/AxelRubini/2025-07-08/4/results.txt create mode 100644 exams/AxelRubini/2025-07-08/5/Makefile create mode 100644 exams/AxelRubini/2025-07-08/5/include/DES.hpp create mode 100644 exams/AxelRubini/2025-07-08/5/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-07-08/5/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-07-08/5/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-07-08/5/include/IO.hpp create mode 100644 exams/AxelRubini/2025-07-08/5/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-07-08/5/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-07-08/5/include/Random.hpp create mode 100644 exams/AxelRubini/2025-07-08/5/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-07-08/5/main create mode 100644 exams/AxelRubini/2025-07-08/5/main.cpp create mode 100644 exams/AxelRubini/2025-07-08/5/parameters.txt create mode 100644 exams/AxelRubini/2025-07-08/5/results.txt create mode 100644 exams/AxelRubini/2025-09-08/1/Makefile create mode 100644 exams/AxelRubini/2025-09-08/1/include/DES.hpp create mode 100644 exams/AxelRubini/2025-09-08/1/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-09-08/1/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-09-08/1/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-09-08/1/include/IO.hpp create mode 100644 exams/AxelRubini/2025-09-08/1/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-09-08/1/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-09-08/1/include/Random.hpp create mode 100644 exams/AxelRubini/2025-09-08/1/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-09-08/1/main create mode 100644 exams/AxelRubini/2025-09-08/1/main.cpp create mode 100644 exams/AxelRubini/2025-09-08/1/parameters.txt create mode 100644 exams/AxelRubini/2025-09-08/1/results.txt create mode 100644 exams/AxelRubini/2025-09-08/2/Makefile create mode 100644 exams/AxelRubini/2025-09-08/2/include/DES.hpp create mode 100644 exams/AxelRubini/2025-09-08/2/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-09-08/2/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-09-08/2/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-09-08/2/include/IO.hpp create mode 100644 exams/AxelRubini/2025-09-08/2/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-09-08/2/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-09-08/2/include/Random.hpp create mode 100644 exams/AxelRubini/2025-09-08/2/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-09-08/2/main create mode 100644 exams/AxelRubini/2025-09-08/2/main.cpp create mode 100644 exams/AxelRubini/2025-09-08/2/parameters.txt create mode 100644 exams/AxelRubini/2025-09-08/2/results.txt create mode 100644 exams/AxelRubini/2025-09-08/3/Makefile create mode 100644 exams/AxelRubini/2025-09-08/3/include/DES.hpp create mode 100644 exams/AxelRubini/2025-09-08/3/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-09-08/3/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-09-08/3/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-09-08/3/include/IO.hpp create mode 100644 exams/AxelRubini/2025-09-08/3/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-09-08/3/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-09-08/3/include/Random.hpp create mode 100644 exams/AxelRubini/2025-09-08/3/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-09-08/3/main create mode 100644 exams/AxelRubini/2025-09-08/3/main.cpp create mode 100644 exams/AxelRubini/2025-09-08/3/parameters.txt create mode 100644 exams/AxelRubini/2025-09-08/3/results.txt create mode 100644 exams/AxelRubini/2025-09-08/4/Makefile create mode 100644 exams/AxelRubini/2025-09-08/4/include/DES.hpp create mode 100644 exams/AxelRubini/2025-09-08/4/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-09-08/4/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-09-08/4/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-09-08/4/include/IO.hpp create mode 100644 exams/AxelRubini/2025-09-08/4/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-09-08/4/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-09-08/4/include/Random.hpp create mode 100644 exams/AxelRubini/2025-09-08/4/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-09-08/4/main create mode 100644 exams/AxelRubini/2025-09-08/4/main.cpp create mode 100644 exams/AxelRubini/2025-09-08/4/parameters.txt create mode 100644 exams/AxelRubini/2025-09-08/4/results.txt create mode 100644 exams/AxelRubini/2025-09-08/5/Makefile create mode 100644 exams/AxelRubini/2025-09-08/5/include/DES.hpp create mode 100644 exams/AxelRubini/2025-09-08/5/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-09-08/5/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-09-08/5/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-09-08/5/include/IO.hpp create mode 100644 exams/AxelRubini/2025-09-08/5/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-09-08/5/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-09-08/5/include/Random.hpp create mode 100644 exams/AxelRubini/2025-09-08/5/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-09-08/5/main create mode 100644 exams/AxelRubini/2025-09-08/5/main.cpp create mode 100644 exams/AxelRubini/2025-09-08/5/parameters.txt create mode 100644 exams/AxelRubini/2025-09-08/5/results.txt create mode 100644 exams/AxelRubini/2025-11-03/1/Makefile create mode 100644 exams/AxelRubini/2025-11-03/1/include/DES.hpp create mode 100644 exams/AxelRubini/2025-11-03/1/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-11-03/1/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-11-03/1/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-11-03/1/include/IO.hpp create mode 100644 exams/AxelRubini/2025-11-03/1/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-11-03/1/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-11-03/1/include/Random.hpp create mode 100644 exams/AxelRubini/2025-11-03/1/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-11-03/1/main create mode 100644 exams/AxelRubini/2025-11-03/1/main.cpp create mode 100644 exams/AxelRubini/2025-11-03/1/parameters.txt create mode 100644 exams/AxelRubini/2025-11-03/1/results.txt create mode 100644 exams/AxelRubini/2025-11-03/2/Makefile create mode 100644 exams/AxelRubini/2025-11-03/2/include/DES.hpp create mode 100644 exams/AxelRubini/2025-11-03/2/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-11-03/2/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-11-03/2/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-11-03/2/include/IO.hpp create mode 100644 exams/AxelRubini/2025-11-03/2/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-11-03/2/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-11-03/2/include/Random.hpp create mode 100644 exams/AxelRubini/2025-11-03/2/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-11-03/2/main create mode 100644 exams/AxelRubini/2025-11-03/2/main.cpp create mode 100644 exams/AxelRubini/2025-11-03/2/parameters.txt create mode 100644 exams/AxelRubini/2025-11-03/2/results.txt create mode 100644 exams/AxelRubini/2025-11-03/3/Makefile create mode 100644 exams/AxelRubini/2025-11-03/3/include/DES.hpp create mode 100644 exams/AxelRubini/2025-11-03/3/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-11-03/3/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-11-03/3/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-11-03/3/include/IO.hpp create mode 100644 exams/AxelRubini/2025-11-03/3/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-11-03/3/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-11-03/3/include/Random.hpp create mode 100644 exams/AxelRubini/2025-11-03/3/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-11-03/3/main create mode 100644 exams/AxelRubini/2025-11-03/3/main.cpp create mode 100644 exams/AxelRubini/2025-11-03/3/parameters.txt create mode 100644 exams/AxelRubini/2025-11-03/3/results.txt create mode 100644 exams/AxelRubini/2025-11-03/4/include/DES.hpp create mode 100644 exams/AxelRubini/2025-11-03/4/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-11-03/4/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-11-03/4/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-11-03/4/include/IO.hpp create mode 100644 exams/AxelRubini/2025-11-03/4/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-11-03/4/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-11-03/4/include/Random.hpp create mode 100644 exams/AxelRubini/2025-11-03/4/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-11-03/4/main create mode 100644 exams/AxelRubini/2025-11-03/4/main.cpp create mode 100644 exams/AxelRubini/2025-11-03/4/parameters.txt create mode 100644 exams/AxelRubini/2025-11-03/4/results.txt create mode 100644 exams/AxelRubini/2025-11-03/5/include/DES.hpp create mode 100644 exams/AxelRubini/2025-11-03/5/include/Decision.hpp create mode 100644 exams/AxelRubini/2025-11-03/5/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2025-11-03/5/include/Geometry.hpp create mode 100644 exams/AxelRubini/2025-11-03/5/include/IO.hpp create mode 100644 exams/AxelRubini/2025-11-03/5/include/Inventory.hpp create mode 100644 exams/AxelRubini/2025-11-03/5/include/Queue.hpp create mode 100644 exams/AxelRubini/2025-11-03/5/include/Random.hpp create mode 100644 exams/AxelRubini/2025-11-03/5/include/Stat.hpp create mode 100755 exams/AxelRubini/2025-11-03/5/main create mode 100644 exams/AxelRubini/2025-11-03/5/main.cpp create mode 100644 exams/AxelRubini/2025-11-03/5/parameters.txt create mode 100644 exams/AxelRubini/2025-11-03/5/results.txt create mode 100644 exams/AxelRubini/2026-01-14/1/Makefile create mode 100644 exams/AxelRubini/2026-01-14/1/include/DES.hpp create mode 100644 exams/AxelRubini/2026-01-14/1/include/Decision.hpp create mode 100644 exams/AxelRubini/2026-01-14/1/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2026-01-14/1/include/Geometry.hpp create mode 100644 exams/AxelRubini/2026-01-14/1/include/IO.hpp create mode 100644 exams/AxelRubini/2026-01-14/1/include/Inventory.hpp create mode 100644 exams/AxelRubini/2026-01-14/1/include/Queue.hpp create mode 100644 exams/AxelRubini/2026-01-14/1/include/Random.hpp create mode 100644 exams/AxelRubini/2026-01-14/1/include/Stat.hpp create mode 100755 exams/AxelRubini/2026-01-14/1/main create mode 100644 exams/AxelRubini/2026-01-14/1/main.cpp create mode 100644 exams/AxelRubini/2026-01-14/1/parameters.txt create mode 100644 exams/AxelRubini/2026-01-14/1/results.txt create mode 100644 exams/AxelRubini/2026-01-14/2/Makefile create mode 100644 exams/AxelRubini/2026-01-14/2/include/DES.hpp create mode 100644 exams/AxelRubini/2026-01-14/2/include/Decision.hpp create mode 100644 exams/AxelRubini/2026-01-14/2/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2026-01-14/2/include/Geometry.hpp create mode 100644 exams/AxelRubini/2026-01-14/2/include/IO.hpp create mode 100644 exams/AxelRubini/2026-01-14/2/include/Inventory.hpp create mode 100644 exams/AxelRubini/2026-01-14/2/include/Queue.hpp create mode 100644 exams/AxelRubini/2026-01-14/2/include/Random.hpp create mode 100644 exams/AxelRubini/2026-01-14/2/include/Stat.hpp create mode 100755 exams/AxelRubini/2026-01-14/2/main create mode 100644 exams/AxelRubini/2026-01-14/2/main.cpp create mode 100644 exams/AxelRubini/2026-01-14/2/parameters.txt create mode 100644 exams/AxelRubini/2026-01-14/2/results.txt create mode 100644 exams/AxelRubini/2026-01-14/3/Makefile create mode 100644 exams/AxelRubini/2026-01-14/3/include/DES.hpp create mode 100644 exams/AxelRubini/2026-01-14/3/include/Decision.hpp create mode 100644 exams/AxelRubini/2026-01-14/3/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2026-01-14/3/include/Geometry.hpp create mode 100644 exams/AxelRubini/2026-01-14/3/include/IO.hpp create mode 100644 exams/AxelRubini/2026-01-14/3/include/Inventory.hpp create mode 100644 exams/AxelRubini/2026-01-14/3/include/Queue.hpp create mode 100644 exams/AxelRubini/2026-01-14/3/include/Random.hpp create mode 100644 exams/AxelRubini/2026-01-14/3/include/Stat.hpp create mode 100755 exams/AxelRubini/2026-01-14/3/main create mode 100644 exams/AxelRubini/2026-01-14/3/main.cpp create mode 100644 exams/AxelRubini/2026-01-14/3/out.log create mode 100644 exams/AxelRubini/2026-01-14/3/parameters.txt create mode 100644 exams/AxelRubini/2026-01-14/3/results.txt create mode 100644 exams/AxelRubini/2026-01-14/4/Makefile create mode 100644 exams/AxelRubini/2026-01-14/4/include/DES.hpp create mode 100644 exams/AxelRubini/2026-01-14/4/include/Decision.hpp create mode 100644 exams/AxelRubini/2026-01-14/4/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2026-01-14/4/include/Geometry.hpp create mode 100644 exams/AxelRubini/2026-01-14/4/include/IO.hpp create mode 100644 exams/AxelRubini/2026-01-14/4/include/Inventory.hpp create mode 100644 exams/AxelRubini/2026-01-14/4/include/Queue.hpp create mode 100644 exams/AxelRubini/2026-01-14/4/include/Random.hpp create mode 100644 exams/AxelRubini/2026-01-14/4/include/Stat.hpp create mode 100755 exams/AxelRubini/2026-01-14/4/main create mode 100644 exams/AxelRubini/2026-01-14/4/main.cpp create mode 100644 exams/AxelRubini/2026-01-14/4/parameters.txt create mode 100644 exams/AxelRubini/2026-01-14/4/results.txt create mode 100644 exams/AxelRubini/2026-01-14/5/Makefile create mode 100644 exams/AxelRubini/2026-01-14/5/include/DES.hpp create mode 100644 exams/AxelRubini/2026-01-14/5/include/Decision.hpp create mode 100644 exams/AxelRubini/2026-01-14/5/include/Dynamics.hpp create mode 100644 exams/AxelRubini/2026-01-14/5/include/Geometry.hpp create mode 100644 exams/AxelRubini/2026-01-14/5/include/IO.hpp create mode 100644 exams/AxelRubini/2026-01-14/5/include/Inventory.hpp create mode 100644 exams/AxelRubini/2026-01-14/5/include/Queue.hpp create mode 100644 exams/AxelRubini/2026-01-14/5/include/Random.hpp create mode 100644 exams/AxelRubini/2026-01-14/5/include/Stat.hpp create mode 100755 exams/AxelRubini/2026-01-14/5/main create mode 100644 exams/AxelRubini/2026-01-14/5/main.cpp create mode 100644 exams/AxelRubini/2026-01-14/5/parameters.txt create mode 100644 exams/AxelRubini/2026-01-14/5/results.txt diff --git a/exams/AxelRubini/2025-01-09/1/Makefile b/exams/AxelRubini/2025-01-09/1/Makefile new file mode 100644 index 0000000..6910fc5 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/1/Makefile @@ -0,0 +1,10 @@ +CC=g++ +CFLAGS=-std=c++23 -O3 -Wall + +all: main + +main: main.cpp + $(CC) $(CFLAGS) main.cpp -o main + +clean: + rm -f main *.o diff --git a/exams/AxelRubini/2025-01-09/1/include/DES.hpp b/exams/AxelRubini/2025-01-09/1/include/DES.hpp new file mode 100644 index 0000000..1706b8f --- /dev/null +++ b/exams/AxelRubini/2025-01-09/1/include/DES.hpp @@ -0,0 +1,212 @@ + +#pragma once + +#include "Random.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// --------------------------------------------------------------------------- +// Messaggio +// --------------------------------------------------------------------------- +struct Message { + double time{0.0}; + int sender{-1}; + int receiver{-1}; + int item{0}; + double quantity{0.0}; +}; + +// --------------------------------------------------------------------------- +// Bus di messaggi: process -> network, network -> process +// --------------------------------------------------------------------------- +class MessageBus { +public: + explicit MessageBus(std::size_t n = 0) { resize(n); } + + void resize(std::size_t n) { + procToNet_.assign(n, {}); + netToProc_.assign(n, {}); + } + + std::size_t size() const { return procToNet_.size(); } + + std::queue &procToNet(int pid) { return procToNet_.at(pid); } + std::queue &netToProc(int pid) { return netToProc_.at(pid); } + const std::queue &procToNet(int pid) const { + return procToNet_.at(pid); + } + const std::queue &netToProc(int pid) const { + return netToProc_.at(pid); + } + +private: + std::vector> procToNet_; + std::vector> netToProc_; +}; + +// --------------------------------------------------------------------------- +// Processo base a eventi discreti +// --------------------------------------------------------------------------- +class DiscreteEventProcess { +public: + virtual ~DiscreteEventProcess() = default; + virtual void initialize(double startTime) = 0; + virtual double nextEventTime() const = 0; + virtual void handleEvent(double currentTime) = 0; +}; + +// --------------------------------------------------------------------------- +// NetworkRouter: inoltra i messaggi P->N->P +// --------------------------------------------------------------------------- +class NetworkRouter : public DiscreteEventProcess { +public: + NetworkRouter(MessageBus &bus, RandomGenerator &rng, double scanPeriod = 0.1) + : bus_(bus), rng_(rng), scanPeriod_(scanPeriod), time_(0.0), + nextScanTime_(0.0), scanned_(0) {} + + void initialize(double startTime) override { + time_ = startTime; + nextScanTime_ = startTime + scanPeriod_; + + const int n = static_cast(bus_.size()); + if (n <= 0) { + throw std::runtime_error( + "NetworkRouter: no processes configured in MessageBus"); + } + + scanner_.resize(n); + for (int i = 0; i < n; ++i) { + scanner_[i] = i; + } + reshuffleScanner(); + scanned_ = 0; + } + + double nextEventTime() const override { return nextScanTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextScanTime_) + return; + + const int n = static_cast(scanner_.size()); + if (n == 0) + return; + + if (scanned_ >= n) { + scanned_ = 0; + reshuffleScanner(); + } + + const int channel = scanner_[scanned_++]; + auto &fromQueue = bus_.procToNet(channel); + + if (!fromQueue.empty()) { + Message m = fromQueue.front(); + fromQueue.pop(); + + if (m.receiver < 0 || m.receiver >= static_cast(bus_.size())) { + throw std::runtime_error("NetworkRouter: invalid receiver index"); + } + + bus_.netToProc(m.receiver).push(m); + } + + nextScanTime_ = currentTime + scanPeriod_; + } + +private: + void reshuffleScanner() { + auto &eng = rng_.engine(); // serve engine() in RandomGenerator + std::shuffle(scanner_.begin(), scanner_.end(), eng); + } + + MessageBus &bus_; + RandomGenerator &rng_; + + double scanPeriod_; + double time_; + double nextScanTime_; + std::vector scanner_; + int scanned_; +}; + +// --------------------------------------------------------------------------- +// Sistema DES: collezione di processi +// --------------------------------------------------------------------------- +class DiscreteEventSystem { +public: + template + Proc &emplaceProcess(Args &&...args) { + auto ptr = std::make_unique(std::forward(args)...); + Proc &ref = *ptr; + processes_.push_back(std::move(ptr)); + return ref; + } + + void initialize(double startTime = 0.0) { + for (auto &p : processes_) { + p->initialize(startTime); + } + } + + double nextEventTime() const { + if (processes_.empty()) { + return std::numeric_limits::infinity(); + } + + double tMin = std::numeric_limits::infinity(); + for (const auto &p : processes_) { + double t = p->nextEventTime(); + if (t < tMin) + tMin = t; + } + return tMin; + } + + void handleEvent(double currentTime) { + for (auto &p : processes_) { + if (p->nextEventTime() <= currentTime) { + p->handleEvent(currentTime); + } + } + } + +private: + std::vector> processes_; +}; + +// --------------------------------------------------------------------------- +// Simulatore DES +// --------------------------------------------------------------------------- +class DiscreteEventSimulator { +public: + explicit DiscreteEventSimulator(DiscreteEventSystem &system) + : system_(system), currentTime_(0.0) {} + + void run(double horizon) { + system_.initialize(0.0); + currentTime_ = 0.0; + + while (currentTime_ < horizon) { + double nextTime = system_.nextEventTime(); + if (nextTime == std::numeric_limits::infinity()) + break; + currentTime_ = nextTime; + system_.handleEvent(currentTime_); + } + } + + double currentTime() const { return currentTime_; } + +private: + DiscreteEventSystem &system_; + double currentTime_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/1/include/Decision.hpp b/exams/AxelRubini/2025-01-09/1/include/Decision.hpp new file mode 100644 index 0000000..4d37cf0 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/1/include/Decision.hpp @@ -0,0 +1,266 @@ +#pragma once + +#include "Random.hpp" +#include "Stat.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 7. MARKOV CHAIN +// ============================================================================ + +// MarkovChain: Implements a discrete-time Markov chain. +// Purpose: +// - Simulates stochastic processes with defined states and transition +// probabilities. +// - Useful in modeling random systems and computing related probabilities. +// Usage: +// MarkovChain mc(3, rng); +// mc.setTransition(0, 1, 0.5); +// mc.setTransition(0, 2, 0.5); +// mc.setState(0); +// int nextState = mc.step(); +class MarkovChain { +private: + int numStates; + std::vector> transitionMatrix; + int currentState; + RandomGenerator &rng; + +public: + MarkovChain(int states, RandomGenerator &generator) + : numStates(states), currentState(0), rng(generator) { + transitionMatrix.resize(states, std::vector(states, 0.0)); + } + + void setTransition(int from, int to, double probability) { + if (from >= 0 && from < numStates && to >= 0 && to < numStates) { + transitionMatrix[from][to] = probability; + } + } + + void setState(int state) { + if (state >= 0 && state < numStates) { + currentState = state; + } + } + + int getState() const { return currentState; } + + int step() { + currentState = rng.discrete(transitionMatrix[currentState]); + return currentState; + } + + double getTransitionProb(int from, int to) const { + return transitionMatrix[from][to]; + } + + double expectedSojournTime(int state, double timeStep = 1.0) const { + double pii = transitionMatrix[state][state]; + if (pii >= 1.0) + return std::numeric_limits::infinity(); + return timeStep / (1.0 - pii); + } +}; + +// ============================================================================ +// 8. MDP +// ============================================================================ + +// MDP: Implements a Markov Decision Process (MDP) model. +// Purpose: +// - Simulate decision-making processes with states, transitions, and +// associated costs. +// - Calculate the total cost to reach a target state from a starting state. +// Usage: +// - Add transitions between states with associated probabilities and costs +// using `addTransition`. +// - Use `step` to simulate a single transition and calculate its cost. +// - Use `simulate` to estimate the total cost from a start to an end state. +class MDP { +private: + int numStates; + std::map>>> + transitions; + RandomGenerator &rng; + +public: + MDP(int states, RandomGenerator &generator) + : numStates(states), rng(generator) {} + + void addTransition(int from, int to, double probability, double cost) { + transitions[from].push_back({to, {probability, cost}}); + } + + std::pair step(int currentState) { + if (transitions.find(currentState) == transitions.end()) { + return {currentState, 0.0}; + } + + std::vector probs; + for (const auto &t : transitions[currentState]) { + probs.push_back(t.second.first); + } + + int idx = rng.discrete(probs); + int nextState = transitions[currentState][idx].first; + double cost = transitions[currentState][idx].second.second; + + return {nextState, cost}; + } + + double simulate(int startState, int endState) { + int state = startState; + double totalCost = 0.0; + int maxSteps = 100000; + + for (int step = 0; step < maxSteps && state != endState; step++) { + auto [nextState, cost] = this->step(state); + totalCost += cost; + state = nextState; + } + return totalCost; + } +}; + +// ============================================================================ +// 10. MONTE CARLO SIMULATOR +// ============================================================================ + +// MonteCarloSimulator: A utility for running Monte Carlo simulations. +// Purpose: +// - Estimate statistical metrics through repeated random sampling. +// - Provides methods to calculate mean results and probabilities of events. +// Usage: +// MonteCarloSimulator simulator(1000); +// double estimate = simulator.estimate([](int) { return some_random_value; }); +// double probability = simulator.estimateProbability([](int) { return +// some_event_occurs; }); +class MonteCarloSimulator { +private: + Statistics stats; + int numSimulations; + +public: + MonteCarloSimulator(int simulations = 1000) : numSimulations(simulations) {} + + template double estimate(SimFunc simulation) { + stats.clear(); + for (int i = 0; i < numSimulations; i++) { + double result = simulation(i); + stats.addSample(result); + } + return stats.mean(); + } + + template double estimateProbability(SimFunc simulation) { + int successes = 0; + for (int i = 0; i < numSimulations; i++) { + if (simulation(i)) + successes++; + } + return static_cast(successes) / numSimulations; + } + + const Statistics &getStatistics() const { return stats; } +}; + +// ============================================================================ +// 13. OPTIMIZATION UTILITIES +// ============================================================================ + +// Optimizer: Utility class offering optimization techniques such as random +// search and grid search. Purpose: +// - Explore parameter spaces to maximize or minimize an objective function. +// - Supports both continuous and discrete search spaces. +// Usage: +// - Use `randomSearch` for stochastic optimization over a defined range. +// - Use `gridSearch` for systematic exploration in evenly spaced steps. +// Example: +// auto [bestParam, bestValue] = Optimizer::randomSearch(objectiveFunction, +// 0.0, 10.0, 100, rng); auto [bestInt, bestIntValue] = +// Optimizer::randomSearchInt(objectiveFunction, 0, 10, 50, rng); +class Optimizer { +public: + template + static std::pair + randomSearch(ObjFunc objective, double minValue, double maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + for (int i = 0; i < budget; i++) { + double param = rng.uniform(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair + randomSearchInt(ObjFunc objective, int minValue, int maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + int bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + if (budget <= 0) { + throw std::invalid_argument("randomSearchInt: budget must be positive."); + } + + if (minValue >= maxValue) { + throw std::invalid_argument( + "randomSearchInt: minValue must be less than maxValue."); + } + + for (int i = 0; i < budget; i++) { + int param = rng.uniformInt(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair gridSearch(ObjFunc objective, + double minValue, double maxValue, + int steps, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + double stepSize = (maxValue - minValue) / steps; + + if (steps <= 0) { + throw std::invalid_argument("gridSearch: steps must be positive."); + } + + for (int i = 0; i <= steps; i++) { + double param = minValue + i * stepSize; + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/1/include/Dynamics.hpp b/exams/AxelRubini/2025-01-09/1/include/Dynamics.hpp new file mode 100644 index 0000000..044176a --- /dev/null +++ b/exams/AxelRubini/2025-01-09/1/include/Dynamics.hpp @@ -0,0 +1,214 @@ +#pragma once + +#include +#include +namespace SELib { +// ============================================================================ +// 3. ODE SOLVER (Runge-Kutta 4th order) +// ============================================================================ + +using StateVector = std::vector; +using DerivativeFunction = std::function; + +// ODESolver: Implements numerical methods for solving Ordinary Differential +// Equations (ODEs). Purpose: +// - Provides tools for simulating dynamic systems modeled by ODEs. +// - Includes methods such as Runge-Kutta 4th order and Euler's method for +// integration. +// Usage: +// - Pass a derivative function that defines the ODE system. +// - Use `rk4Step` for higher accuracy or `eulerStep` for simpler systems. +// Example: +// StateVector nextState = ODESolver::rk4Step(0.0, currentState, controlInputs, +// dt, dynamicsFunction); +class ODESolver { +public: + static StateVector rk4Step(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector k1 = f(t, x, u); + StateVector x2 = addScaled(x, k1, dt / 2.0); + StateVector k2 = f(t + dt / 2.0, x2, u); + StateVector x3 = addScaled(x, k2, dt / 2.0); + StateVector k3 = f(t + dt / 2.0, x3, u); + StateVector x4 = addScaled(x, k3, dt); + StateVector k4 = f(t + dt, x4, u); + + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += (dt / 6.0) * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]); + } + return xNew; + } + + static StateVector eulerStep(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector dx = f(t, x, u); + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += dx[i] * dt; + } + return xNew; + } + +private: + static StateVector addScaled(const StateVector &x, const StateVector &dx, + double scale) { + StateVector result = x; + for (size_t i = 0; i < x.size(); i++) { + result[i] += dx[i] * scale; + } + return result; + } +}; + +// ============================================================================ +// 4. PID CONTROLLER +// ============================================================================ + +// PIDController: Implements a Proportional-Integral-Derivative (PID) +// controller. Purpose: +// - Automatically adjusts a control signal to minimize the difference between +// a desired setpoint and a measured value. +// - Used in control systems to regulate processes like temperature, speed, or +// position. +// Usage: +// - Create an instance with proportional (Kp), integral (Ki), and derivative +// (Kd) gains, along with a time step. +// - Use `compute` to calculate the control signal based on the setpoint and +// measurement. +// Example: +// PIDController pid(1.0, 0.1, 0.01, 0.1); +// double controlSignal = pid.compute(desiredValue, actualValue); +class PIDController { +private: + double kp, ki, kd; + double integral; + double prevError; + double dt; + +public: + PIDController(double Kp, double Ki, double Kd, double timeStep) + : kp(Kp), ki(Ki), kd(Kd), integral(0), prevError(0), dt(timeStep) {} + + double compute(double setpoint, double measurement) { + double error = setpoint - measurement; + integral += error * dt; + double derivative = (error - prevError) / dt; + prevError = error; + return kp * error + ki * integral + kd * derivative; + } + + void reset() { + integral = 0; + prevError = 0; + } + + void setGains(double Kp, double Ki, double Kd) { + kp = Kp; + ki = Ki; + kd = Kd; + } +}; + +// ============================================================================ +// 5. CONTINUOUS TIME SIMULATION +// ============================================================================ + +// ContinuousTimeSimulation: A base class for simulating continuous-time dynamic +// systems. Purpose: +// - Provides a framework for simulating systems characterized by continuous +// dynamics over time. +// - Integrates system dynamics using a Runge-Kutta 4th order solver. +// Usage: +// - Derive from this class and implement the `dynamics`, `computeControl`, +// `initialize`, and `finalize` methods. +// - Customize the `onStep` method for per-step side effects, if required. +// Example: +// class MySimulation : public ContinuousTimeSimulation { +// // Implement the required virtual methods. +// }; +// MySimulation sim(dt, horizon, initialState); +// sim.run(); +class ContinuousTimeSimulation { +protected: + double currentTime; + double timeStep; + double horizon; + StateVector state; + StateVector control; + +public: + ContinuousTimeSimulation(double dt, double H, const StateVector &initialState) + : currentTime(0.0), timeStep(dt), horizon(H), state(initialState) { + control.resize(initialState.size(), 0.0); + } + + virtual ~ContinuousTimeSimulation() = default; + + virtual StateVector dynamics(double t, const StateVector &x, + const StateVector &u) = 0; + virtual StateVector computeControl(double t, const StateVector &x) = 0; + virtual void initialize() = 0; + virtual void onStep() {} + virtual void finalize() = 0; + + void run() { + initialize(); + while (currentTime < horizon) { + control = computeControl(currentTime, state); + state = ODESolver::rk4Step( + currentTime, state, control, timeStep, + [this](double t, const StateVector &x, const StateVector &u) { + return this->dynamics(t, x, u); + }); + currentTime += timeStep; + onStep(); + } + finalize(); + } + + double getCurrentTime() const { return currentTime; } + const StateVector &getState() const { return state; } + const StateVector &getControl() const { return control; } +}; + +// ============================================================================ +// 6. SAMPLE AND HOLD +// ============================================================================ + +// SampleAndHold: A utility to store a value and update it periodically. +// Purpose: +// - Maintain a value that gets updated periodically based on a time interval. +// - Useful for sampling data and ensuring updates happen at fixed intervals. +// Usage: +// SampleAndHold sampler(5.0, 0); +// if (sampler.shouldUpdate(currentTime)) { +// sampler.update(currentTime, newValue); +// } +// int currentValue = sampler.getValue(); +template class SampleAndHold { +private: + T value; + double lastUpdateTime; + double samplePeriod; + +public: + SampleAndHold(double period, const T &initialValue = T()) + : value(initialValue), lastUpdateTime(0), samplePeriod(period) {} + + bool shouldUpdate(double currentTime) const { + return (currentTime - lastUpdateTime) >= samplePeriod; + } + + void update(double currentTime, const T &newValue) { + value = newValue; + lastUpdateTime = currentTime; + } + + const T &getValue() const { return value; } + void reset() { lastUpdateTime = 0; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/1/include/Geometry.hpp b/exams/AxelRubini/2025-01-09/1/include/Geometry.hpp new file mode 100644 index 0000000..525a91d --- /dev/null +++ b/exams/AxelRubini/2025-01-09/1/include/Geometry.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 14. GEOMETRIC UTILITIES +// ============================================================================ + +// Point2D: Represents a point or vector in 2D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point2D p1(1.0, 2.0), p2(2.0, 3.0); +// double dist = p1.distanceTo(p2); +// Point2D sum = p1 + p2; +struct Point2D { + double x, y; + + Point2D(double x_ = 0, double y_ = 0) : x(x_), y(y_) {} + + double distanceTo(const Point2D &other) const { + double dx = x - other.x; + double dy = y - other.y; + return std::sqrt(dx * dx + dy * dy); + } + + Point2D operator+(const Point2D &other) const { + return Point2D(x + other.x, y + other.y); + } + + Point2D operator-(const Point2D &other) const { + return Point2D(x - other.x, y - other.y); + } +}; + +// Point3D: Represents a point or vector in 3D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point3D p1(1.0, 2.0, 3.0), p2(2.0, 3.0, 4.0); +// double dist = p1.distanceTo(p2); +// Point3D sum = p1 + p2; +struct Point3D { + double x, y, z; + + Point3D(double x_ = 0, double y_ = 0, double z_ = 0) : x(x_), y(y_), z(z_) {} + + double distanceTo(const Point3D &other) const { + double dx = x - other.x; + double dy = y - other.y; + double dz = z - other.z; + return std::sqrt(dx * dx + dy * dy + dz * dz); + } + + Point3D operator+(const Point3D &other) const { + return Point3D(x + other.x, y + other.y, z + other.z); + } + + Point3D operator-(const Point3D &other) const { + return Point3D(x - other.x, y - other.y, z - other.z); + } +}; + +// ============================================================================ +// 19. COLLISION DETECTOR +// ============================================================================ + +// CollisionDetector: A utility class for detecting collisions between points +// in space. Purpose: +// - Counts the number of collisions within a given distance threshold. +// - Retrieves pairs of colliding points. +// Usage: +// std::vector points = {Point2D(0, 0), Point2D(1, 1)}; +// int numCollisions = CollisionDetector::countCollisions(points, 1.5); +// auto collidingPairs = CollisionDetector::getCollidingPairs(points, 1.5); +class CollisionDetector { +public: + template + static int countCollisions(const std::vector &positions, + double threshold) { + int collisions = 0; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + collisions++; + } + } + } + return collisions; + } + + template + static std::vector> + getCollidingPairs(const std::vector &positions, double threshold) { + std::vector> pairs; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + pairs.push_back({i, j}); + } + } + } + return pairs; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/1/include/IO.hpp b/exams/AxelRubini/2025-01-09/1/include/IO.hpp new file mode 100644 index 0000000..1a79cc6 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/1/include/IO.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 11. PARAMETER PARSER +// ============================================================================ + +// ParameterParser: A utility to parse and retrieve parameters from +// configuration files. Purpose: +// - Reads a file and stores key-value pairs for parameters. +// - Allows retrieval of parameters as integers, doubles, or strings. +// - Provides structured data for advanced parsing. +// Usage: +// ParameterParser parser; +// if (parser.parseFile("config.txt")) { +// int param = parser.getInt("example_key", 42); +// } +class ParameterParser { +private: + std::map params; + std::vector> structuredData; + +public: + bool parseFile(const std::string &filename) { + std::ifstream file(filename); + if (!file.is_open()) + return false; + + std::string line; + while (std::getline(file, line)) { + if (line.empty()) + continue; + + std::istringstream iss(line); + std::string key; + iss >> key; + + std::string value; + std::getline(iss, value); + if (!value.empty() && value[0] == ' ') { + value = value.substr(1); + } + + params[key] = value; + + std::vector tokens; + std::istringstream tokenStream(line); + std::string token; + while (tokenStream >> token) { + tokens.push_back(token); + } + structuredData.push_back(tokens); + } + return true; + } + + int getInt(const std::string &key, int defaultValue = 0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stoi(it->second); + } + return defaultValue; + } + + double getDouble(const std::string &key, double defaultValue = 0.0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stod(it->second); + } + return defaultValue; + } + + std::string getString(const std::string &key, + const std::string &defaultValue = "") const { + auto it = params.find(key); + if (it != params.end()) { + return it->second; + } + return defaultValue; + } + + const std::vector> &getStructuredData() const { + return structuredData; + } + + bool hasKey(const std::string &key) const { + return params.find(key) != params.end(); + } +}; + +// ============================================================================ +// 12. OUTPUT WRITER +// ============================================================================ + +// OutputWriter: A utility class for writing data to output files. +// Purpose: +// - Simplifies the process of writing data to files. +// - Supports writing lines, key-value pairs, and managing file headers. +// Usage: +// OutputWriter writer; +// if (writer.open("output.txt", "Header Info")) { +// writer.writeLine("Example Line"); +// writer.writeKeyValue("Key", 42.0); +// writer.close(); +// } +class OutputWriter { +private: + std::ofstream file; + +public: + bool open(const std::string &filename, const std::string &header) { + file.open(filename); + if (!file.is_open()) + return false; + file << header << std::endl; + return true; + } + + template void writeLine(const T &value) { + file << value << std::endl; + } + + template + void writeLine(const T &first, const Args &...args) { + file << first << " "; + writeLine(args...); + } + + void writeKeyValue(const std::string &key, double value, int precision = 2) { + file << key << " " << std::fixed << std::setprecision(precision) << value + << std::endl; + } + + void close() { + if (file.is_open()) { + file.close(); + } + } + + ~OutputWriter() { close(); } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/1/include/Inventory.hpp b/exams/AxelRubini/2025-01-09/1/include/Inventory.hpp new file mode 100644 index 0000000..c82d776 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/1/include/Inventory.hpp @@ -0,0 +1,423 @@ +#pragma once + +#include "DES.hpp" +#include "Random.hpp" +#include +#include + +namespace SELib { +// ============================================================================ +// 16. CUSTOMER MODEL +// ============================================================================ + +// Customer: Represents a customer model in a simulation. +// Purpose: +// - Simulates customer requests over time. +// - Generates random requests for products at specific intervals. +// Usage: +// Customer customer(5.0, 10.0, 3, rng); +// int productId = customer.step(0.1); // Simulates a time step of 0.1 seconds. +class Customer { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer(double A, double B, int N, RandomGenerator &generator) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator) { + nextRequestTime = rng.uniform(A, B); + } + + int step(double dt) { + nextRequestTime -= dt; + if (nextRequestTime <= 0) { + nextRequestTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } +}; +class Customer_Event_Driven { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer_Event_Driven(double A, double B, int N, RandomGenerator &generator, + double nextRequestTime) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator), + nextRequestTime(nextRequestTime) {} + + int step_event_driven(double *current_time) { + if (*current_time >= nextRequestTime) { + nextRequestTime = *current_time + rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + double getNextRequestTime() { return nextRequestTime; }; +}; + +// ============================================================================ +// 17. SERVER MODEL +// ============================================================================ + +// Server: Represents a server in a simulated environment. +// Purpose: +// - Handles product requests and manages inventory. +// - Simulates server busy time during request processing. +// Usage: +// Server server(numProducts, maxInventory, processingTimeNormal, +// processingTimeSupplier, rng); int result = +// server.processRequest(productId, needsSupply); +// server.addInventory(productId, quantityAdded); +class Server { +private: + std::vector inventory; + int busyTime; + int processingTimeNormal; + int processingTimeSupplier; + RandomGenerator *rng; + +public: + Server(int numProducts, int K, int F, int G, RandomGenerator &rng) + : inventory(numProducts + 1, 0), busyTime(0), processingTimeNormal(F), + processingTimeSupplier(G), rng(&rng) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + + bool isBusy() const { return busyTime > 0; } + + void decrementBusyTime() { + if (busyTime > 0) + busyTime--; + } + + int processRequest(int productId, bool needsSupply) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return productId; + } else { + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return -productId; + } + } + + int processRequest2(int productId, double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = processingTimeNormal; + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + busyTime = processingTimeSupplier; + return -productId; + } + } + + void addInventory(int productId, int quantity) { + inventory[productId] += quantity; + } + + int getInventory(int productId) const { return inventory[productId]; } +}; +// ============================================================================ +// 17.5 SERVER-EVENT-DRIVEN +// ============================================================================ +class Server_Event_Driven { +private: + std::vector inventory; + RandomGenerator *rng; + double next_step_time; + double min_interval; + double max_interval; + +public: + Server_Event_Driven(int numProducts, int K, double min_interval, + double max_interval, RandomGenerator &rng, + double nextStepTime) + : inventory(numProducts + 1, 0), rng(&rng), min_interval(min_interval), + max_interval(max_interval), next_step_time(nextStepTime) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + int processRequestEventDriven(double current_time, int productId, + double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (current_time >= next_step_time) { + if (inventory[productId] > 0) { + inventory[productId]--; + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + + return -productId; + } + } else { + return 0; + } + } + double getNextStepTime() { return next_step_time; } +}; + +// ============================================================================ +// 18. SUPPLIER MODEL +// ============================================================================ + +// Supplier: Represents a supplier in a simulation. +// Purpose: +// - Simulates the supply of products at specific intervals. +// - Generates random supply requests periodically based on the defined +// intervals. +// Usage: +// Supplier supplier(minInterval, maxInterval, numProducts, rng); +// int suppliedProduct = supplier.step(timeStep); +// supplier.reset(); +class Supplier { +private: + double nextSupplyTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Supplier(double V, double Q, int N, RandomGenerator &generator) + : minInterval(V), maxInterval(Q), numProducts(N), rng(generator) { + nextSupplyTime = rng.uniform(V, Q); + } + + int step(double dt) { + nextSupplyTime -= dt; + if (nextSupplyTime <= 0) { + nextSupplyTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + void reset() { nextSupplyTime = rng.uniform(minInterval, maxInterval); } +}; + +// Server di inventario con messaggi (customer/server network-style) +class InventoryServerProcess : public DiscreteEventProcess { +public: + InventoryServerProcess(int pid, int numItems, int initialStockPerItem, + double serviceTime, MessageBus &bus, + RandomGenerator &rng) + : pid_(pid), serviceTime_(serviceTime), bus_(bus), rng_(rng), + nextEventTime_(0.0), + storage_(static_cast(numItems), initialStockPerItem), + totalRequests_(0), missedRequests_(0) {} + + void initialize(double startTime) override { + nextEventTime_ = startTime + serviceTime_; + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // processa TUTTI i messaggi arrivati a questo server + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + totalRequests_++; + + // sicurezza sugli indici prodotto + if (m.item < 0 || static_cast(m.item) >= storage_.size()) { + continue; // scarta richieste invalide + } + + double servedQty = 0.0; + int &stock = storage_[static_cast(m.item)]; + + if (stock >= static_cast(m.quantity)) { + // richiesta soddisfatta + stock -= static_cast(m.quantity); + servedQty = m.quantity; + } else { + // non abbastanza stock + servedQty = static_cast(stock); + missedRequests_++; + stock = 0; + } + + // risposta al cliente + Message reply; + reply.time = currentTime; + reply.sender = pid_; + reply.receiver = m.sender; // rimanda al mittente + reply.item = m.item; + reply.quantity = servedQty; + + bus_.procToNet(pid_).push(reply); + + // eventuale logica di refill: esempio semplice + if (stock == 0) { + int refill = rng_.uniformInt(1, 10); // batch di rifornimento random + stock += refill; + } + } + + nextEventTime_ = currentTime + serviceTime_; + } + + const std::vector &storage() const { return storage_; } + + int totalRequests() const { return totalRequests_; } + int missedRequests() const { return missedRequests_; } + +private: + int pid_; + double serviceTime_; + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + std::vector storage_; + + int totalRequests_; + int missedRequests_; +}; + +// ============================================================================ +// 19. INVENTORY CUSTOMER PROCESS (DES-compatible) +// ============================================================================ +// +// InventoryCustomerProcess: +// - Processo a eventi discreti che rappresenta un cliente. +// - Invia richieste al server tramite MessageBus (procToNet -> NetworkRouter). +// - Legge le risposte dal canale netToProc. +// - Statistiche: quante richieste ha mandato e quante risposte ha ricevuto. +// +class InventoryCustomerProcess : public DiscreteEventProcess { +public: + // pid : id di questo processo (indice nei canali del MessageBus) + // serverPid : pid del server a cui mandare le richieste + // numItems : numero di prodotti gestiti (item in [0, numItems-1]) + // minInterval/maxInterval : intervallo di tempo tra una richiesta e la + // successiva + InventoryCustomerProcess(int pid, int serverPid, int numItems, + double minInterval, double maxInterval, + MessageBus &bus, RandomGenerator &rng) + : pid_(pid), serverPid_(serverPid), numItems_(numItems), + minInterval_(minInterval), maxInterval_(maxInterval), bus_(bus), + rng_(rng), nextEventTime_(0.0), issuedRequests_(0), receivedReplies_(0), + totalRequestedQty_(0.0), totalServedQty_(0.0) {} + + // inizializza il prossimo evento (prima richiesta) + void initialize(double startTime) override { + nextEventTime_ = startTime + rng_.uniform(minInterval_, maxInterval_); + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // 1) genera una nuova richiesta al server + const int item = rng_.uniformInt( + 0, numItems_ - 1); // coerente con InventoryServerProcess + const int qty = rng_.uniformInt(1, 3); // quantità richiesta (1..3) + + Message req; + req.time = currentTime; + req.sender = pid_; + req.receiver = serverPid_; + req.item = item; + req.quantity = static_cast(qty); + + bus_.procToNet(pid_).push(req); + issuedRequests_++; + totalRequestedQty_ += req.quantity; + + // 2) processa tutte le risposte arrivate su questo cliente + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + // opzionale: possiamo filtrare solo messaggi dal serverPid_ + // if (m.sender != serverPid_) continue; + + receivedReplies_++; + totalServedQty_ += m.quantity; + } + + // 3) pianifica la prossima richiesta + nextEventTime_ = currentTime + rng_.uniform(minInterval_, maxInterval_); + } + + // --- metriche accessibili dall’esterno --- + + int pid() const { return pid_; } + int serverPid() const { return serverPid_; } + + int issuedRequests() const { return issuedRequests_; } + int receivedReplies() const { return receivedReplies_; } + + double totalRequestedQty() const { return totalRequestedQty_; } + double totalServedQty() const { return totalServedQty_; } + + // fill-rate lato customer + double serviceLevel() const { + if (totalRequestedQty_ <= 0.0) + return 1.0; + return totalServedQty_ / totalRequestedQty_; + } + +private: + int pid_; + int serverPid_; + int numItems_; + double minInterval_; + double maxInterval_; + + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + + int issuedRequests_; + int receivedReplies_; + double totalRequestedQty_; + double totalServedQty_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/1/include/Queue.hpp b/exams/AxelRubini/2025-01-09/1/include/Queue.hpp new file mode 100644 index 0000000..0c12ce8 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/1/include/Queue.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 9. FIFO QUEUE +// ============================================================================ + +// FIFOQueue: Implements a first-in-first-out (FIFO) queue with a fixed maximum +// size. Purpose: +// - Enqueue and dequeue items in FIFO order. +// - Track statistics such as total items enqueued and dequeued. +// - Support dynamic size constraints. +// Usage: +// FIFOQueue queue(10); +// queue.enqueue(5); +// int item; +// if (queue.dequeue(item)) { +// // Process item. +// } +template class FIFOQueue { +private: + std::queue queue; + size_t maxSize; + size_t totalEnqueued; + size_t totalDequeued; + +public: + FIFOQueue(size_t max = SIZE_MAX) + : maxSize(max), totalEnqueued(0), totalDequeued(0) {} + + bool enqueue(const T &item) { + if (queue.size() >= maxSize) + return false; + queue.push(item); + totalEnqueued++; + return true; + } + + bool dequeue(T &item) { + if (queue.empty()) + return false; + item = queue.front(); + queue.pop(); + totalDequeued++; + return true; + } + + bool isEmpty() const { return queue.empty(); } + bool isFull() const { return queue.size() >= maxSize; } + size_t size() const { return queue.size(); } + size_t getTotalEnqueued() const { return totalEnqueued; } + size_t getTotalDequeued() const { return totalDequeued; } + std::queue getQueue() const { return queue; } + T getFirst() const { + if (queue.empty()) + throw std::runtime_error("Queue is empty"); + return queue.front(); + } + + void clear() { + while (!queue.empty()) + queue.pop(); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/1/include/Random.hpp b/exams/AxelRubini/2025-01-09/1/include/Random.hpp new file mode 100644 index 0000000..52356f9 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/1/include/Random.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include // std::isfinite +#include // std::accumulate +#include +#include +#include + +namespace SELib { + +class RandomGenerator { +private: + std::mt19937 gen; + +public: + explicit RandomGenerator(unsigned seed = std::random_device{}()) + : gen(seed) {} + + double uniform(double min, double max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_real_distribution dis(lo, high); + return dis(gen); + } + + int uniformInt(int min, int max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_int_distribution dis(lo, high); + return dis(gen); + } + + double gaussian(double mean, double stddev) { + if (stddev < 0.0) { + throw std::invalid_argument("gaussian: stddev must be >= 0"); + } + std::normal_distribution dis(mean, stddev); + return dis(gen); + } + + bool bernoulli(double p) { + if (p < 0.0 || p > 1.0 || !std::isfinite(p)) { + throw std::invalid_argument("bernoulli: p must be in [0,1]"); + } + std::bernoulli_distribution dis(p); + return dis(gen); + } + + int discrete(const std::vector &probabilities) { + if (probabilities.empty()) { + throw std::invalid_argument("discrete: empty probability vector"); + } + + for (double w : probabilities) { + if (w < 0.0 || !std::isfinite(w)) { + throw std::invalid_argument( + "discrete: weights must be non-negative and finite"); + } + } + + double sum = + std::accumulate(probabilities.begin(), probabilities.end(), 0.0); + if (sum <= 0.0) { + throw std::invalid_argument("discrete: sum of weights must be > 0"); + } + + std::discrete_distribution dis(probabilities.begin(), + probabilities.end()); + return dis(gen); + } + + void setSeed(unsigned seed) { gen.seed(seed); } + + // (opzionale) accesso al motore se ti serve per altre distribuzioni + std::mt19937 &engine() { return gen; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/1/include/Stat.hpp b/exams/AxelRubini/2025-01-09/1/include/Stat.hpp new file mode 100644 index 0000000..27b4656 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/1/include/Stat.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 2. STATISTICS +// ============================================================================ + +// Statistics: A utility class for managing and analyzing numerical data +// samples. Purpose: +// - Compute statistical metrics such as mean and standard deviation. +// - Estimate probabilities of custom conditions. +// - Check convergence of data over iterations. +// Usage: +// Statistics stats; +// stats.addSample(5.0); +// double meanValue = stats.mean(); +class Statistics { +private: + std::vector samples; + +public: + void addSample(double value) { samples.push_back(value); } + + void clear() { samples.clear(); } + + double mean() const { + if (samples.empty()) + return 0.0; + double sum = 0.0; + for (double s : samples) + sum += s; + return sum / samples.size(); + } + + double stddev() const { + if (samples.size() < 2) + return 0.0; + double m = mean(); + double variance = 0.0; + for (double s : samples) { + variance += (s - m) * (s - m); + } + return std::sqrt(variance / (samples.size() - 1)); + } + + double probability(std::function condition) const { + if (samples.empty()) + return 0.0; + int count = 0; + for (double s : samples) { + if (condition(s)) + count++; + } + return static_cast(count) / samples.size(); + } + + size_t size() const { return samples.size(); } + const std::vector &getSamples() const { return samples; } + + bool hasConverged(double tolerance = 0.1) const { + if (samples.size() < 30) + return false; + double m = mean(); + double sd = stddev(); + return sd <= tolerance * std::abs(m); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/1/main b/exams/AxelRubini/2025-01-09/1/main new file mode 100755 index 0000000000000000000000000000000000000000..ac457245bd8e39b756d260c3daf28696801211d6 GIT binary patch literal 44208 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 E1HB7&VgLXD literal 0 HcmV?d00001 diff --git a/exams/AxelRubini/2025-01-09/1/main.cpp b/exams/AxelRubini/2025-01-09/1/main.cpp new file mode 100644 index 0000000..a8c4fb4 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/1/main.cpp @@ -0,0 +1,42 @@ +#include "include/Decision.hpp" +#include "include/IO.hpp" +#include "include/Random.hpp" +#include +#include +#include +#include + +int main() { + SELib::RandomGenerator rng; + SELib::ParameterParser parser; + if (!parser.parseFile("parameters.txt")) { + return 1; + } + + int numStates = parser.getInt("N", 0); + if (numStates <= 0) + return 1; + + SELib::MDP mdp(numStates, rng); + const auto &data = parser.getStructuredData(); + for (const auto &line : data) { + if (!line.empty() && line[0] == "A") { + int from = std::stoi(line[1]); + int to = std::stoi(line[2]); + double prob = std::stod(line[3]); + double cost = std::stod(line[4]); + mdp.addTransition(from, to, prob, cost); + } + } + + SELib::MonteCarloSimulator simulator(1000); + double expectedCost = + simulator.estimate([&](int) { return mdp.simulate(0, numStates - 1); }); + + std::ofstream outFile("results.txt"); + outFile << "2025-01-09-Axel-Rubini-2158099" << std::endl; + outFile << "C " << expectedCost << std::endl; + outFile.close(); + + return 0; +} diff --git a/exams/AxelRubini/2025-01-09/1/parameters.txt b/exams/AxelRubini/2025-01-09/1/parameters.txt new file mode 100644 index 0000000..7f56793 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/1/parameters.txt @@ -0,0 +1,11 @@ +N 5 +A 0 1 1 100 +A 1 2 0.3 100 +A 1 3 0.7 150 +A 2 3 1 100 +A 3 0 0.6 10 +A 3 1 0.1 10 +A 3 2 0.1 10 +A 3 3 0.1 10 +A 3 4 0.1 10 +A 4 4 1 0 diff --git a/exams/AxelRubini/2025-01-09/1/results.txt b/exams/AxelRubini/2025-01-09/1/results.txt new file mode 100644 index 0000000..4ddcdff --- /dev/null +++ b/exams/AxelRubini/2025-01-09/1/results.txt @@ -0,0 +1,2 @@ +2025-01-09-Axel-Rubini-2158099 +C 2333.51 diff --git a/exams/AxelRubini/2025-01-09/2/Makefile b/exams/AxelRubini/2025-01-09/2/Makefile new file mode 100644 index 0000000..6910fc5 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/2/Makefile @@ -0,0 +1,10 @@ +CC=g++ +CFLAGS=-std=c++23 -O3 -Wall + +all: main + +main: main.cpp + $(CC) $(CFLAGS) main.cpp -o main + +clean: + rm -f main *.o diff --git a/exams/AxelRubini/2025-01-09/2/include/DES.hpp b/exams/AxelRubini/2025-01-09/2/include/DES.hpp new file mode 100644 index 0000000..1706b8f --- /dev/null +++ b/exams/AxelRubini/2025-01-09/2/include/DES.hpp @@ -0,0 +1,212 @@ + +#pragma once + +#include "Random.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// --------------------------------------------------------------------------- +// Messaggio +// --------------------------------------------------------------------------- +struct Message { + double time{0.0}; + int sender{-1}; + int receiver{-1}; + int item{0}; + double quantity{0.0}; +}; + +// --------------------------------------------------------------------------- +// Bus di messaggi: process -> network, network -> process +// --------------------------------------------------------------------------- +class MessageBus { +public: + explicit MessageBus(std::size_t n = 0) { resize(n); } + + void resize(std::size_t n) { + procToNet_.assign(n, {}); + netToProc_.assign(n, {}); + } + + std::size_t size() const { return procToNet_.size(); } + + std::queue &procToNet(int pid) { return procToNet_.at(pid); } + std::queue &netToProc(int pid) { return netToProc_.at(pid); } + const std::queue &procToNet(int pid) const { + return procToNet_.at(pid); + } + const std::queue &netToProc(int pid) const { + return netToProc_.at(pid); + } + +private: + std::vector> procToNet_; + std::vector> netToProc_; +}; + +// --------------------------------------------------------------------------- +// Processo base a eventi discreti +// --------------------------------------------------------------------------- +class DiscreteEventProcess { +public: + virtual ~DiscreteEventProcess() = default; + virtual void initialize(double startTime) = 0; + virtual double nextEventTime() const = 0; + virtual void handleEvent(double currentTime) = 0; +}; + +// --------------------------------------------------------------------------- +// NetworkRouter: inoltra i messaggi P->N->P +// --------------------------------------------------------------------------- +class NetworkRouter : public DiscreteEventProcess { +public: + NetworkRouter(MessageBus &bus, RandomGenerator &rng, double scanPeriod = 0.1) + : bus_(bus), rng_(rng), scanPeriod_(scanPeriod), time_(0.0), + nextScanTime_(0.0), scanned_(0) {} + + void initialize(double startTime) override { + time_ = startTime; + nextScanTime_ = startTime + scanPeriod_; + + const int n = static_cast(bus_.size()); + if (n <= 0) { + throw std::runtime_error( + "NetworkRouter: no processes configured in MessageBus"); + } + + scanner_.resize(n); + for (int i = 0; i < n; ++i) { + scanner_[i] = i; + } + reshuffleScanner(); + scanned_ = 0; + } + + double nextEventTime() const override { return nextScanTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextScanTime_) + return; + + const int n = static_cast(scanner_.size()); + if (n == 0) + return; + + if (scanned_ >= n) { + scanned_ = 0; + reshuffleScanner(); + } + + const int channel = scanner_[scanned_++]; + auto &fromQueue = bus_.procToNet(channel); + + if (!fromQueue.empty()) { + Message m = fromQueue.front(); + fromQueue.pop(); + + if (m.receiver < 0 || m.receiver >= static_cast(bus_.size())) { + throw std::runtime_error("NetworkRouter: invalid receiver index"); + } + + bus_.netToProc(m.receiver).push(m); + } + + nextScanTime_ = currentTime + scanPeriod_; + } + +private: + void reshuffleScanner() { + auto &eng = rng_.engine(); // serve engine() in RandomGenerator + std::shuffle(scanner_.begin(), scanner_.end(), eng); + } + + MessageBus &bus_; + RandomGenerator &rng_; + + double scanPeriod_; + double time_; + double nextScanTime_; + std::vector scanner_; + int scanned_; +}; + +// --------------------------------------------------------------------------- +// Sistema DES: collezione di processi +// --------------------------------------------------------------------------- +class DiscreteEventSystem { +public: + template + Proc &emplaceProcess(Args &&...args) { + auto ptr = std::make_unique(std::forward(args)...); + Proc &ref = *ptr; + processes_.push_back(std::move(ptr)); + return ref; + } + + void initialize(double startTime = 0.0) { + for (auto &p : processes_) { + p->initialize(startTime); + } + } + + double nextEventTime() const { + if (processes_.empty()) { + return std::numeric_limits::infinity(); + } + + double tMin = std::numeric_limits::infinity(); + for (const auto &p : processes_) { + double t = p->nextEventTime(); + if (t < tMin) + tMin = t; + } + return tMin; + } + + void handleEvent(double currentTime) { + for (auto &p : processes_) { + if (p->nextEventTime() <= currentTime) { + p->handleEvent(currentTime); + } + } + } + +private: + std::vector> processes_; +}; + +// --------------------------------------------------------------------------- +// Simulatore DES +// --------------------------------------------------------------------------- +class DiscreteEventSimulator { +public: + explicit DiscreteEventSimulator(DiscreteEventSystem &system) + : system_(system), currentTime_(0.0) {} + + void run(double horizon) { + system_.initialize(0.0); + currentTime_ = 0.0; + + while (currentTime_ < horizon) { + double nextTime = system_.nextEventTime(); + if (nextTime == std::numeric_limits::infinity()) + break; + currentTime_ = nextTime; + system_.handleEvent(currentTime_); + } + } + + double currentTime() const { return currentTime_; } + +private: + DiscreteEventSystem &system_; + double currentTime_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/2/include/Decision.hpp b/exams/AxelRubini/2025-01-09/2/include/Decision.hpp new file mode 100644 index 0000000..4d37cf0 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/2/include/Decision.hpp @@ -0,0 +1,266 @@ +#pragma once + +#include "Random.hpp" +#include "Stat.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 7. MARKOV CHAIN +// ============================================================================ + +// MarkovChain: Implements a discrete-time Markov chain. +// Purpose: +// - Simulates stochastic processes with defined states and transition +// probabilities. +// - Useful in modeling random systems and computing related probabilities. +// Usage: +// MarkovChain mc(3, rng); +// mc.setTransition(0, 1, 0.5); +// mc.setTransition(0, 2, 0.5); +// mc.setState(0); +// int nextState = mc.step(); +class MarkovChain { +private: + int numStates; + std::vector> transitionMatrix; + int currentState; + RandomGenerator &rng; + +public: + MarkovChain(int states, RandomGenerator &generator) + : numStates(states), currentState(0), rng(generator) { + transitionMatrix.resize(states, std::vector(states, 0.0)); + } + + void setTransition(int from, int to, double probability) { + if (from >= 0 && from < numStates && to >= 0 && to < numStates) { + transitionMatrix[from][to] = probability; + } + } + + void setState(int state) { + if (state >= 0 && state < numStates) { + currentState = state; + } + } + + int getState() const { return currentState; } + + int step() { + currentState = rng.discrete(transitionMatrix[currentState]); + return currentState; + } + + double getTransitionProb(int from, int to) const { + return transitionMatrix[from][to]; + } + + double expectedSojournTime(int state, double timeStep = 1.0) const { + double pii = transitionMatrix[state][state]; + if (pii >= 1.0) + return std::numeric_limits::infinity(); + return timeStep / (1.0 - pii); + } +}; + +// ============================================================================ +// 8. MDP +// ============================================================================ + +// MDP: Implements a Markov Decision Process (MDP) model. +// Purpose: +// - Simulate decision-making processes with states, transitions, and +// associated costs. +// - Calculate the total cost to reach a target state from a starting state. +// Usage: +// - Add transitions between states with associated probabilities and costs +// using `addTransition`. +// - Use `step` to simulate a single transition and calculate its cost. +// - Use `simulate` to estimate the total cost from a start to an end state. +class MDP { +private: + int numStates; + std::map>>> + transitions; + RandomGenerator &rng; + +public: + MDP(int states, RandomGenerator &generator) + : numStates(states), rng(generator) {} + + void addTransition(int from, int to, double probability, double cost) { + transitions[from].push_back({to, {probability, cost}}); + } + + std::pair step(int currentState) { + if (transitions.find(currentState) == transitions.end()) { + return {currentState, 0.0}; + } + + std::vector probs; + for (const auto &t : transitions[currentState]) { + probs.push_back(t.second.first); + } + + int idx = rng.discrete(probs); + int nextState = transitions[currentState][idx].first; + double cost = transitions[currentState][idx].second.second; + + return {nextState, cost}; + } + + double simulate(int startState, int endState) { + int state = startState; + double totalCost = 0.0; + int maxSteps = 100000; + + for (int step = 0; step < maxSteps && state != endState; step++) { + auto [nextState, cost] = this->step(state); + totalCost += cost; + state = nextState; + } + return totalCost; + } +}; + +// ============================================================================ +// 10. MONTE CARLO SIMULATOR +// ============================================================================ + +// MonteCarloSimulator: A utility for running Monte Carlo simulations. +// Purpose: +// - Estimate statistical metrics through repeated random sampling. +// - Provides methods to calculate mean results and probabilities of events. +// Usage: +// MonteCarloSimulator simulator(1000); +// double estimate = simulator.estimate([](int) { return some_random_value; }); +// double probability = simulator.estimateProbability([](int) { return +// some_event_occurs; }); +class MonteCarloSimulator { +private: + Statistics stats; + int numSimulations; + +public: + MonteCarloSimulator(int simulations = 1000) : numSimulations(simulations) {} + + template double estimate(SimFunc simulation) { + stats.clear(); + for (int i = 0; i < numSimulations; i++) { + double result = simulation(i); + stats.addSample(result); + } + return stats.mean(); + } + + template double estimateProbability(SimFunc simulation) { + int successes = 0; + for (int i = 0; i < numSimulations; i++) { + if (simulation(i)) + successes++; + } + return static_cast(successes) / numSimulations; + } + + const Statistics &getStatistics() const { return stats; } +}; + +// ============================================================================ +// 13. OPTIMIZATION UTILITIES +// ============================================================================ + +// Optimizer: Utility class offering optimization techniques such as random +// search and grid search. Purpose: +// - Explore parameter spaces to maximize or minimize an objective function. +// - Supports both continuous and discrete search spaces. +// Usage: +// - Use `randomSearch` for stochastic optimization over a defined range. +// - Use `gridSearch` for systematic exploration in evenly spaced steps. +// Example: +// auto [bestParam, bestValue] = Optimizer::randomSearch(objectiveFunction, +// 0.0, 10.0, 100, rng); auto [bestInt, bestIntValue] = +// Optimizer::randomSearchInt(objectiveFunction, 0, 10, 50, rng); +class Optimizer { +public: + template + static std::pair + randomSearch(ObjFunc objective, double minValue, double maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + for (int i = 0; i < budget; i++) { + double param = rng.uniform(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair + randomSearchInt(ObjFunc objective, int minValue, int maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + int bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + if (budget <= 0) { + throw std::invalid_argument("randomSearchInt: budget must be positive."); + } + + if (minValue >= maxValue) { + throw std::invalid_argument( + "randomSearchInt: minValue must be less than maxValue."); + } + + for (int i = 0; i < budget; i++) { + int param = rng.uniformInt(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair gridSearch(ObjFunc objective, + double minValue, double maxValue, + int steps, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + double stepSize = (maxValue - minValue) / steps; + + if (steps <= 0) { + throw std::invalid_argument("gridSearch: steps must be positive."); + } + + for (int i = 0; i <= steps; i++) { + double param = minValue + i * stepSize; + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/2/include/Dynamics.hpp b/exams/AxelRubini/2025-01-09/2/include/Dynamics.hpp new file mode 100644 index 0000000..044176a --- /dev/null +++ b/exams/AxelRubini/2025-01-09/2/include/Dynamics.hpp @@ -0,0 +1,214 @@ +#pragma once + +#include +#include +namespace SELib { +// ============================================================================ +// 3. ODE SOLVER (Runge-Kutta 4th order) +// ============================================================================ + +using StateVector = std::vector; +using DerivativeFunction = std::function; + +// ODESolver: Implements numerical methods for solving Ordinary Differential +// Equations (ODEs). Purpose: +// - Provides tools for simulating dynamic systems modeled by ODEs. +// - Includes methods such as Runge-Kutta 4th order and Euler's method for +// integration. +// Usage: +// - Pass a derivative function that defines the ODE system. +// - Use `rk4Step` for higher accuracy or `eulerStep` for simpler systems. +// Example: +// StateVector nextState = ODESolver::rk4Step(0.0, currentState, controlInputs, +// dt, dynamicsFunction); +class ODESolver { +public: + static StateVector rk4Step(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector k1 = f(t, x, u); + StateVector x2 = addScaled(x, k1, dt / 2.0); + StateVector k2 = f(t + dt / 2.0, x2, u); + StateVector x3 = addScaled(x, k2, dt / 2.0); + StateVector k3 = f(t + dt / 2.0, x3, u); + StateVector x4 = addScaled(x, k3, dt); + StateVector k4 = f(t + dt, x4, u); + + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += (dt / 6.0) * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]); + } + return xNew; + } + + static StateVector eulerStep(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector dx = f(t, x, u); + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += dx[i] * dt; + } + return xNew; + } + +private: + static StateVector addScaled(const StateVector &x, const StateVector &dx, + double scale) { + StateVector result = x; + for (size_t i = 0; i < x.size(); i++) { + result[i] += dx[i] * scale; + } + return result; + } +}; + +// ============================================================================ +// 4. PID CONTROLLER +// ============================================================================ + +// PIDController: Implements a Proportional-Integral-Derivative (PID) +// controller. Purpose: +// - Automatically adjusts a control signal to minimize the difference between +// a desired setpoint and a measured value. +// - Used in control systems to regulate processes like temperature, speed, or +// position. +// Usage: +// - Create an instance with proportional (Kp), integral (Ki), and derivative +// (Kd) gains, along with a time step. +// - Use `compute` to calculate the control signal based on the setpoint and +// measurement. +// Example: +// PIDController pid(1.0, 0.1, 0.01, 0.1); +// double controlSignal = pid.compute(desiredValue, actualValue); +class PIDController { +private: + double kp, ki, kd; + double integral; + double prevError; + double dt; + +public: + PIDController(double Kp, double Ki, double Kd, double timeStep) + : kp(Kp), ki(Ki), kd(Kd), integral(0), prevError(0), dt(timeStep) {} + + double compute(double setpoint, double measurement) { + double error = setpoint - measurement; + integral += error * dt; + double derivative = (error - prevError) / dt; + prevError = error; + return kp * error + ki * integral + kd * derivative; + } + + void reset() { + integral = 0; + prevError = 0; + } + + void setGains(double Kp, double Ki, double Kd) { + kp = Kp; + ki = Ki; + kd = Kd; + } +}; + +// ============================================================================ +// 5. CONTINUOUS TIME SIMULATION +// ============================================================================ + +// ContinuousTimeSimulation: A base class for simulating continuous-time dynamic +// systems. Purpose: +// - Provides a framework for simulating systems characterized by continuous +// dynamics over time. +// - Integrates system dynamics using a Runge-Kutta 4th order solver. +// Usage: +// - Derive from this class and implement the `dynamics`, `computeControl`, +// `initialize`, and `finalize` methods. +// - Customize the `onStep` method for per-step side effects, if required. +// Example: +// class MySimulation : public ContinuousTimeSimulation { +// // Implement the required virtual methods. +// }; +// MySimulation sim(dt, horizon, initialState); +// sim.run(); +class ContinuousTimeSimulation { +protected: + double currentTime; + double timeStep; + double horizon; + StateVector state; + StateVector control; + +public: + ContinuousTimeSimulation(double dt, double H, const StateVector &initialState) + : currentTime(0.0), timeStep(dt), horizon(H), state(initialState) { + control.resize(initialState.size(), 0.0); + } + + virtual ~ContinuousTimeSimulation() = default; + + virtual StateVector dynamics(double t, const StateVector &x, + const StateVector &u) = 0; + virtual StateVector computeControl(double t, const StateVector &x) = 0; + virtual void initialize() = 0; + virtual void onStep() {} + virtual void finalize() = 0; + + void run() { + initialize(); + while (currentTime < horizon) { + control = computeControl(currentTime, state); + state = ODESolver::rk4Step( + currentTime, state, control, timeStep, + [this](double t, const StateVector &x, const StateVector &u) { + return this->dynamics(t, x, u); + }); + currentTime += timeStep; + onStep(); + } + finalize(); + } + + double getCurrentTime() const { return currentTime; } + const StateVector &getState() const { return state; } + const StateVector &getControl() const { return control; } +}; + +// ============================================================================ +// 6. SAMPLE AND HOLD +// ============================================================================ + +// SampleAndHold: A utility to store a value and update it periodically. +// Purpose: +// - Maintain a value that gets updated periodically based on a time interval. +// - Useful for sampling data and ensuring updates happen at fixed intervals. +// Usage: +// SampleAndHold sampler(5.0, 0); +// if (sampler.shouldUpdate(currentTime)) { +// sampler.update(currentTime, newValue); +// } +// int currentValue = sampler.getValue(); +template class SampleAndHold { +private: + T value; + double lastUpdateTime; + double samplePeriod; + +public: + SampleAndHold(double period, const T &initialValue = T()) + : value(initialValue), lastUpdateTime(0), samplePeriod(period) {} + + bool shouldUpdate(double currentTime) const { + return (currentTime - lastUpdateTime) >= samplePeriod; + } + + void update(double currentTime, const T &newValue) { + value = newValue; + lastUpdateTime = currentTime; + } + + const T &getValue() const { return value; } + void reset() { lastUpdateTime = 0; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/2/include/Geometry.hpp b/exams/AxelRubini/2025-01-09/2/include/Geometry.hpp new file mode 100644 index 0000000..525a91d --- /dev/null +++ b/exams/AxelRubini/2025-01-09/2/include/Geometry.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 14. GEOMETRIC UTILITIES +// ============================================================================ + +// Point2D: Represents a point or vector in 2D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point2D p1(1.0, 2.0), p2(2.0, 3.0); +// double dist = p1.distanceTo(p2); +// Point2D sum = p1 + p2; +struct Point2D { + double x, y; + + Point2D(double x_ = 0, double y_ = 0) : x(x_), y(y_) {} + + double distanceTo(const Point2D &other) const { + double dx = x - other.x; + double dy = y - other.y; + return std::sqrt(dx * dx + dy * dy); + } + + Point2D operator+(const Point2D &other) const { + return Point2D(x + other.x, y + other.y); + } + + Point2D operator-(const Point2D &other) const { + return Point2D(x - other.x, y - other.y); + } +}; + +// Point3D: Represents a point or vector in 3D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point3D p1(1.0, 2.0, 3.0), p2(2.0, 3.0, 4.0); +// double dist = p1.distanceTo(p2); +// Point3D sum = p1 + p2; +struct Point3D { + double x, y, z; + + Point3D(double x_ = 0, double y_ = 0, double z_ = 0) : x(x_), y(y_), z(z_) {} + + double distanceTo(const Point3D &other) const { + double dx = x - other.x; + double dy = y - other.y; + double dz = z - other.z; + return std::sqrt(dx * dx + dy * dy + dz * dz); + } + + Point3D operator+(const Point3D &other) const { + return Point3D(x + other.x, y + other.y, z + other.z); + } + + Point3D operator-(const Point3D &other) const { + return Point3D(x - other.x, y - other.y, z - other.z); + } +}; + +// ============================================================================ +// 19. COLLISION DETECTOR +// ============================================================================ + +// CollisionDetector: A utility class for detecting collisions between points +// in space. Purpose: +// - Counts the number of collisions within a given distance threshold. +// - Retrieves pairs of colliding points. +// Usage: +// std::vector points = {Point2D(0, 0), Point2D(1, 1)}; +// int numCollisions = CollisionDetector::countCollisions(points, 1.5); +// auto collidingPairs = CollisionDetector::getCollidingPairs(points, 1.5); +class CollisionDetector { +public: + template + static int countCollisions(const std::vector &positions, + double threshold) { + int collisions = 0; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + collisions++; + } + } + } + return collisions; + } + + template + static std::vector> + getCollidingPairs(const std::vector &positions, double threshold) { + std::vector> pairs; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + pairs.push_back({i, j}); + } + } + } + return pairs; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/2/include/IO.hpp b/exams/AxelRubini/2025-01-09/2/include/IO.hpp new file mode 100644 index 0000000..1a79cc6 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/2/include/IO.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 11. PARAMETER PARSER +// ============================================================================ + +// ParameterParser: A utility to parse and retrieve parameters from +// configuration files. Purpose: +// - Reads a file and stores key-value pairs for parameters. +// - Allows retrieval of parameters as integers, doubles, or strings. +// - Provides structured data for advanced parsing. +// Usage: +// ParameterParser parser; +// if (parser.parseFile("config.txt")) { +// int param = parser.getInt("example_key", 42); +// } +class ParameterParser { +private: + std::map params; + std::vector> structuredData; + +public: + bool parseFile(const std::string &filename) { + std::ifstream file(filename); + if (!file.is_open()) + return false; + + std::string line; + while (std::getline(file, line)) { + if (line.empty()) + continue; + + std::istringstream iss(line); + std::string key; + iss >> key; + + std::string value; + std::getline(iss, value); + if (!value.empty() && value[0] == ' ') { + value = value.substr(1); + } + + params[key] = value; + + std::vector tokens; + std::istringstream tokenStream(line); + std::string token; + while (tokenStream >> token) { + tokens.push_back(token); + } + structuredData.push_back(tokens); + } + return true; + } + + int getInt(const std::string &key, int defaultValue = 0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stoi(it->second); + } + return defaultValue; + } + + double getDouble(const std::string &key, double defaultValue = 0.0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stod(it->second); + } + return defaultValue; + } + + std::string getString(const std::string &key, + const std::string &defaultValue = "") const { + auto it = params.find(key); + if (it != params.end()) { + return it->second; + } + return defaultValue; + } + + const std::vector> &getStructuredData() const { + return structuredData; + } + + bool hasKey(const std::string &key) const { + return params.find(key) != params.end(); + } +}; + +// ============================================================================ +// 12. OUTPUT WRITER +// ============================================================================ + +// OutputWriter: A utility class for writing data to output files. +// Purpose: +// - Simplifies the process of writing data to files. +// - Supports writing lines, key-value pairs, and managing file headers. +// Usage: +// OutputWriter writer; +// if (writer.open("output.txt", "Header Info")) { +// writer.writeLine("Example Line"); +// writer.writeKeyValue("Key", 42.0); +// writer.close(); +// } +class OutputWriter { +private: + std::ofstream file; + +public: + bool open(const std::string &filename, const std::string &header) { + file.open(filename); + if (!file.is_open()) + return false; + file << header << std::endl; + return true; + } + + template void writeLine(const T &value) { + file << value << std::endl; + } + + template + void writeLine(const T &first, const Args &...args) { + file << first << " "; + writeLine(args...); + } + + void writeKeyValue(const std::string &key, double value, int precision = 2) { + file << key << " " << std::fixed << std::setprecision(precision) << value + << std::endl; + } + + void close() { + if (file.is_open()) { + file.close(); + } + } + + ~OutputWriter() { close(); } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/2/include/Inventory.hpp b/exams/AxelRubini/2025-01-09/2/include/Inventory.hpp new file mode 100644 index 0000000..c82d776 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/2/include/Inventory.hpp @@ -0,0 +1,423 @@ +#pragma once + +#include "DES.hpp" +#include "Random.hpp" +#include +#include + +namespace SELib { +// ============================================================================ +// 16. CUSTOMER MODEL +// ============================================================================ + +// Customer: Represents a customer model in a simulation. +// Purpose: +// - Simulates customer requests over time. +// - Generates random requests for products at specific intervals. +// Usage: +// Customer customer(5.0, 10.0, 3, rng); +// int productId = customer.step(0.1); // Simulates a time step of 0.1 seconds. +class Customer { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer(double A, double B, int N, RandomGenerator &generator) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator) { + nextRequestTime = rng.uniform(A, B); + } + + int step(double dt) { + nextRequestTime -= dt; + if (nextRequestTime <= 0) { + nextRequestTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } +}; +class Customer_Event_Driven { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer_Event_Driven(double A, double B, int N, RandomGenerator &generator, + double nextRequestTime) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator), + nextRequestTime(nextRequestTime) {} + + int step_event_driven(double *current_time) { + if (*current_time >= nextRequestTime) { + nextRequestTime = *current_time + rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + double getNextRequestTime() { return nextRequestTime; }; +}; + +// ============================================================================ +// 17. SERVER MODEL +// ============================================================================ + +// Server: Represents a server in a simulated environment. +// Purpose: +// - Handles product requests and manages inventory. +// - Simulates server busy time during request processing. +// Usage: +// Server server(numProducts, maxInventory, processingTimeNormal, +// processingTimeSupplier, rng); int result = +// server.processRequest(productId, needsSupply); +// server.addInventory(productId, quantityAdded); +class Server { +private: + std::vector inventory; + int busyTime; + int processingTimeNormal; + int processingTimeSupplier; + RandomGenerator *rng; + +public: + Server(int numProducts, int K, int F, int G, RandomGenerator &rng) + : inventory(numProducts + 1, 0), busyTime(0), processingTimeNormal(F), + processingTimeSupplier(G), rng(&rng) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + + bool isBusy() const { return busyTime > 0; } + + void decrementBusyTime() { + if (busyTime > 0) + busyTime--; + } + + int processRequest(int productId, bool needsSupply) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return productId; + } else { + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return -productId; + } + } + + int processRequest2(int productId, double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = processingTimeNormal; + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + busyTime = processingTimeSupplier; + return -productId; + } + } + + void addInventory(int productId, int quantity) { + inventory[productId] += quantity; + } + + int getInventory(int productId) const { return inventory[productId]; } +}; +// ============================================================================ +// 17.5 SERVER-EVENT-DRIVEN +// ============================================================================ +class Server_Event_Driven { +private: + std::vector inventory; + RandomGenerator *rng; + double next_step_time; + double min_interval; + double max_interval; + +public: + Server_Event_Driven(int numProducts, int K, double min_interval, + double max_interval, RandomGenerator &rng, + double nextStepTime) + : inventory(numProducts + 1, 0), rng(&rng), min_interval(min_interval), + max_interval(max_interval), next_step_time(nextStepTime) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + int processRequestEventDriven(double current_time, int productId, + double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (current_time >= next_step_time) { + if (inventory[productId] > 0) { + inventory[productId]--; + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + + return -productId; + } + } else { + return 0; + } + } + double getNextStepTime() { return next_step_time; } +}; + +// ============================================================================ +// 18. SUPPLIER MODEL +// ============================================================================ + +// Supplier: Represents a supplier in a simulation. +// Purpose: +// - Simulates the supply of products at specific intervals. +// - Generates random supply requests periodically based on the defined +// intervals. +// Usage: +// Supplier supplier(minInterval, maxInterval, numProducts, rng); +// int suppliedProduct = supplier.step(timeStep); +// supplier.reset(); +class Supplier { +private: + double nextSupplyTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Supplier(double V, double Q, int N, RandomGenerator &generator) + : minInterval(V), maxInterval(Q), numProducts(N), rng(generator) { + nextSupplyTime = rng.uniform(V, Q); + } + + int step(double dt) { + nextSupplyTime -= dt; + if (nextSupplyTime <= 0) { + nextSupplyTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + void reset() { nextSupplyTime = rng.uniform(minInterval, maxInterval); } +}; + +// Server di inventario con messaggi (customer/server network-style) +class InventoryServerProcess : public DiscreteEventProcess { +public: + InventoryServerProcess(int pid, int numItems, int initialStockPerItem, + double serviceTime, MessageBus &bus, + RandomGenerator &rng) + : pid_(pid), serviceTime_(serviceTime), bus_(bus), rng_(rng), + nextEventTime_(0.0), + storage_(static_cast(numItems), initialStockPerItem), + totalRequests_(0), missedRequests_(0) {} + + void initialize(double startTime) override { + nextEventTime_ = startTime + serviceTime_; + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // processa TUTTI i messaggi arrivati a questo server + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + totalRequests_++; + + // sicurezza sugli indici prodotto + if (m.item < 0 || static_cast(m.item) >= storage_.size()) { + continue; // scarta richieste invalide + } + + double servedQty = 0.0; + int &stock = storage_[static_cast(m.item)]; + + if (stock >= static_cast(m.quantity)) { + // richiesta soddisfatta + stock -= static_cast(m.quantity); + servedQty = m.quantity; + } else { + // non abbastanza stock + servedQty = static_cast(stock); + missedRequests_++; + stock = 0; + } + + // risposta al cliente + Message reply; + reply.time = currentTime; + reply.sender = pid_; + reply.receiver = m.sender; // rimanda al mittente + reply.item = m.item; + reply.quantity = servedQty; + + bus_.procToNet(pid_).push(reply); + + // eventuale logica di refill: esempio semplice + if (stock == 0) { + int refill = rng_.uniformInt(1, 10); // batch di rifornimento random + stock += refill; + } + } + + nextEventTime_ = currentTime + serviceTime_; + } + + const std::vector &storage() const { return storage_; } + + int totalRequests() const { return totalRequests_; } + int missedRequests() const { return missedRequests_; } + +private: + int pid_; + double serviceTime_; + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + std::vector storage_; + + int totalRequests_; + int missedRequests_; +}; + +// ============================================================================ +// 19. INVENTORY CUSTOMER PROCESS (DES-compatible) +// ============================================================================ +// +// InventoryCustomerProcess: +// - Processo a eventi discreti che rappresenta un cliente. +// - Invia richieste al server tramite MessageBus (procToNet -> NetworkRouter). +// - Legge le risposte dal canale netToProc. +// - Statistiche: quante richieste ha mandato e quante risposte ha ricevuto. +// +class InventoryCustomerProcess : public DiscreteEventProcess { +public: + // pid : id di questo processo (indice nei canali del MessageBus) + // serverPid : pid del server a cui mandare le richieste + // numItems : numero di prodotti gestiti (item in [0, numItems-1]) + // minInterval/maxInterval : intervallo di tempo tra una richiesta e la + // successiva + InventoryCustomerProcess(int pid, int serverPid, int numItems, + double minInterval, double maxInterval, + MessageBus &bus, RandomGenerator &rng) + : pid_(pid), serverPid_(serverPid), numItems_(numItems), + minInterval_(minInterval), maxInterval_(maxInterval), bus_(bus), + rng_(rng), nextEventTime_(0.0), issuedRequests_(0), receivedReplies_(0), + totalRequestedQty_(0.0), totalServedQty_(0.0) {} + + // inizializza il prossimo evento (prima richiesta) + void initialize(double startTime) override { + nextEventTime_ = startTime + rng_.uniform(minInterval_, maxInterval_); + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // 1) genera una nuova richiesta al server + const int item = rng_.uniformInt( + 0, numItems_ - 1); // coerente con InventoryServerProcess + const int qty = rng_.uniformInt(1, 3); // quantità richiesta (1..3) + + Message req; + req.time = currentTime; + req.sender = pid_; + req.receiver = serverPid_; + req.item = item; + req.quantity = static_cast(qty); + + bus_.procToNet(pid_).push(req); + issuedRequests_++; + totalRequestedQty_ += req.quantity; + + // 2) processa tutte le risposte arrivate su questo cliente + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + // opzionale: possiamo filtrare solo messaggi dal serverPid_ + // if (m.sender != serverPid_) continue; + + receivedReplies_++; + totalServedQty_ += m.quantity; + } + + // 3) pianifica la prossima richiesta + nextEventTime_ = currentTime + rng_.uniform(minInterval_, maxInterval_); + } + + // --- metriche accessibili dall’esterno --- + + int pid() const { return pid_; } + int serverPid() const { return serverPid_; } + + int issuedRequests() const { return issuedRequests_; } + int receivedReplies() const { return receivedReplies_; } + + double totalRequestedQty() const { return totalRequestedQty_; } + double totalServedQty() const { return totalServedQty_; } + + // fill-rate lato customer + double serviceLevel() const { + if (totalRequestedQty_ <= 0.0) + return 1.0; + return totalServedQty_ / totalRequestedQty_; + } + +private: + int pid_; + int serverPid_; + int numItems_; + double minInterval_; + double maxInterval_; + + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + + int issuedRequests_; + int receivedReplies_; + double totalRequestedQty_; + double totalServedQty_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/2/include/Queue.hpp b/exams/AxelRubini/2025-01-09/2/include/Queue.hpp new file mode 100644 index 0000000..0c12ce8 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/2/include/Queue.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 9. FIFO QUEUE +// ============================================================================ + +// FIFOQueue: Implements a first-in-first-out (FIFO) queue with a fixed maximum +// size. Purpose: +// - Enqueue and dequeue items in FIFO order. +// - Track statistics such as total items enqueued and dequeued. +// - Support dynamic size constraints. +// Usage: +// FIFOQueue queue(10); +// queue.enqueue(5); +// int item; +// if (queue.dequeue(item)) { +// // Process item. +// } +template class FIFOQueue { +private: + std::queue queue; + size_t maxSize; + size_t totalEnqueued; + size_t totalDequeued; + +public: + FIFOQueue(size_t max = SIZE_MAX) + : maxSize(max), totalEnqueued(0), totalDequeued(0) {} + + bool enqueue(const T &item) { + if (queue.size() >= maxSize) + return false; + queue.push(item); + totalEnqueued++; + return true; + } + + bool dequeue(T &item) { + if (queue.empty()) + return false; + item = queue.front(); + queue.pop(); + totalDequeued++; + return true; + } + + bool isEmpty() const { return queue.empty(); } + bool isFull() const { return queue.size() >= maxSize; } + size_t size() const { return queue.size(); } + size_t getTotalEnqueued() const { return totalEnqueued; } + size_t getTotalDequeued() const { return totalDequeued; } + std::queue getQueue() const { return queue; } + T getFirst() const { + if (queue.empty()) + throw std::runtime_error("Queue is empty"); + return queue.front(); + } + + void clear() { + while (!queue.empty()) + queue.pop(); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/2/include/Random.hpp b/exams/AxelRubini/2025-01-09/2/include/Random.hpp new file mode 100644 index 0000000..52356f9 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/2/include/Random.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include // std::isfinite +#include // std::accumulate +#include +#include +#include + +namespace SELib { + +class RandomGenerator { +private: + std::mt19937 gen; + +public: + explicit RandomGenerator(unsigned seed = std::random_device{}()) + : gen(seed) {} + + double uniform(double min, double max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_real_distribution dis(lo, high); + return dis(gen); + } + + int uniformInt(int min, int max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_int_distribution dis(lo, high); + return dis(gen); + } + + double gaussian(double mean, double stddev) { + if (stddev < 0.0) { + throw std::invalid_argument("gaussian: stddev must be >= 0"); + } + std::normal_distribution dis(mean, stddev); + return dis(gen); + } + + bool bernoulli(double p) { + if (p < 0.0 || p > 1.0 || !std::isfinite(p)) { + throw std::invalid_argument("bernoulli: p must be in [0,1]"); + } + std::bernoulli_distribution dis(p); + return dis(gen); + } + + int discrete(const std::vector &probabilities) { + if (probabilities.empty()) { + throw std::invalid_argument("discrete: empty probability vector"); + } + + for (double w : probabilities) { + if (w < 0.0 || !std::isfinite(w)) { + throw std::invalid_argument( + "discrete: weights must be non-negative and finite"); + } + } + + double sum = + std::accumulate(probabilities.begin(), probabilities.end(), 0.0); + if (sum <= 0.0) { + throw std::invalid_argument("discrete: sum of weights must be > 0"); + } + + std::discrete_distribution dis(probabilities.begin(), + probabilities.end()); + return dis(gen); + } + + void setSeed(unsigned seed) { gen.seed(seed); } + + // (opzionale) accesso al motore se ti serve per altre distribuzioni + std::mt19937 &engine() { return gen; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/2/include/Stat.hpp b/exams/AxelRubini/2025-01-09/2/include/Stat.hpp new file mode 100644 index 0000000..27b4656 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/2/include/Stat.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 2. STATISTICS +// ============================================================================ + +// Statistics: A utility class for managing and analyzing numerical data +// samples. Purpose: +// - Compute statistical metrics such as mean and standard deviation. +// - Estimate probabilities of custom conditions. +// - Check convergence of data over iterations. +// Usage: +// Statistics stats; +// stats.addSample(5.0); +// double meanValue = stats.mean(); +class Statistics { +private: + std::vector samples; + +public: + void addSample(double value) { samples.push_back(value); } + + void clear() { samples.clear(); } + + double mean() const { + if (samples.empty()) + return 0.0; + double sum = 0.0; + for (double s : samples) + sum += s; + return sum / samples.size(); + } + + double stddev() const { + if (samples.size() < 2) + return 0.0; + double m = mean(); + double variance = 0.0; + for (double s : samples) { + variance += (s - m) * (s - m); + } + return std::sqrt(variance / (samples.size() - 1)); + } + + double probability(std::function condition) const { + if (samples.empty()) + return 0.0; + int count = 0; + for (double s : samples) { + if (condition(s)) + count++; + } + return static_cast(count) / samples.size(); + } + + size_t size() const { return samples.size(); } + const std::vector &getSamples() const { return samples; } + + bool hasConverged(double tolerance = 0.1) const { + if (samples.size() < 30) + return false; + double m = mean(); + double sd = stddev(); + return sd <= tolerance * std::abs(m); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/2/main b/exams/AxelRubini/2025-01-09/2/main new file mode 100755 index 0000000000000000000000000000000000000000..5d928a6e7ff834a12972a177abe9942df812c0d9 GIT binary patch literal 44376 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 Nxrs47p+G`X`QOC!0A~OI literal 0 HcmV?d00001 diff --git a/exams/AxelRubini/2025-01-09/2/main.cpp b/exams/AxelRubini/2025-01-09/2/main.cpp new file mode 100644 index 0000000..a9953f3 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/2/main.cpp @@ -0,0 +1,44 @@ +#include "include/Decision.hpp" +#include "include/IO.hpp" +#include "include/Random.hpp" +#include +#include +#include +#include + +int main() { + SELib::RandomGenerator rng; + SELib::ParameterParser parser; + if (!parser.parseFile("parameters.txt")) { + return 1; + } + + double maxCost = parser.getDouble("C", 0.0); + int numStates = parser.getInt("N", 0); + if (numStates <= 0) + return 1; + + SELib::MDP mdp(numStates, rng); + const auto &data = parser.getStructuredData(); + for (const auto &line : data) { + if (!line.empty() && line[0] == "A") { + int from = std::stoi(line[1]); + int to = std::stoi(line[2]); + double prob = std::stod(line[3]); + double cost = std::stod(line[4]); + mdp.addTransition(from, to, prob, cost); + } + } + + SELib::MonteCarloSimulator simulator(1000); + double prob = simulator.estimateProbability([&](int) { + return mdp.simulate(0, numStates - 1) <= maxCost; + }); + + std::ofstream outFile("results.txt"); + outFile << "2025-01-09-Axel-Rubini-2158099" << std::endl; + outFile << "P " << prob << std::endl; + outFile.close(); + + return 0; +} diff --git a/exams/AxelRubini/2025-01-09/2/parameters.txt b/exams/AxelRubini/2025-01-09/2/parameters.txt new file mode 100644 index 0000000..541d237 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/2/parameters.txt @@ -0,0 +1,12 @@ +C 200 +N 5 +A 0 1 0.7 100 +A 0 2 0.3 50 +A 1 3 1.0 100 +A 2 3 1.0 100 +A 3 3 1.0 70 +A 4 0 0.2 5 +A 4 1 0.2 10 +A 4 2 0.2 20 +A 4 3 0.2 30 +A 4 4 0.2 40 diff --git a/exams/AxelRubini/2025-01-09/2/results.txt b/exams/AxelRubini/2025-01-09/2/results.txt new file mode 100644 index 0000000..f185dcc --- /dev/null +++ b/exams/AxelRubini/2025-01-09/2/results.txt @@ -0,0 +1,2 @@ +2025-01-09-Axel-Rubini-2158099 +P 0 diff --git a/exams/AxelRubini/2025-01-09/3/Makefile b/exams/AxelRubini/2025-01-09/3/Makefile new file mode 100644 index 0000000..6910fc5 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/3/Makefile @@ -0,0 +1,10 @@ +CC=g++ +CFLAGS=-std=c++23 -O3 -Wall + +all: main + +main: main.cpp + $(CC) $(CFLAGS) main.cpp -o main + +clean: + rm -f main *.o diff --git a/exams/AxelRubini/2025-01-09/3/include/DES.hpp b/exams/AxelRubini/2025-01-09/3/include/DES.hpp new file mode 100644 index 0000000..1706b8f --- /dev/null +++ b/exams/AxelRubini/2025-01-09/3/include/DES.hpp @@ -0,0 +1,212 @@ + +#pragma once + +#include "Random.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// --------------------------------------------------------------------------- +// Messaggio +// --------------------------------------------------------------------------- +struct Message { + double time{0.0}; + int sender{-1}; + int receiver{-1}; + int item{0}; + double quantity{0.0}; +}; + +// --------------------------------------------------------------------------- +// Bus di messaggi: process -> network, network -> process +// --------------------------------------------------------------------------- +class MessageBus { +public: + explicit MessageBus(std::size_t n = 0) { resize(n); } + + void resize(std::size_t n) { + procToNet_.assign(n, {}); + netToProc_.assign(n, {}); + } + + std::size_t size() const { return procToNet_.size(); } + + std::queue &procToNet(int pid) { return procToNet_.at(pid); } + std::queue &netToProc(int pid) { return netToProc_.at(pid); } + const std::queue &procToNet(int pid) const { + return procToNet_.at(pid); + } + const std::queue &netToProc(int pid) const { + return netToProc_.at(pid); + } + +private: + std::vector> procToNet_; + std::vector> netToProc_; +}; + +// --------------------------------------------------------------------------- +// Processo base a eventi discreti +// --------------------------------------------------------------------------- +class DiscreteEventProcess { +public: + virtual ~DiscreteEventProcess() = default; + virtual void initialize(double startTime) = 0; + virtual double nextEventTime() const = 0; + virtual void handleEvent(double currentTime) = 0; +}; + +// --------------------------------------------------------------------------- +// NetworkRouter: inoltra i messaggi P->N->P +// --------------------------------------------------------------------------- +class NetworkRouter : public DiscreteEventProcess { +public: + NetworkRouter(MessageBus &bus, RandomGenerator &rng, double scanPeriod = 0.1) + : bus_(bus), rng_(rng), scanPeriod_(scanPeriod), time_(0.0), + nextScanTime_(0.0), scanned_(0) {} + + void initialize(double startTime) override { + time_ = startTime; + nextScanTime_ = startTime + scanPeriod_; + + const int n = static_cast(bus_.size()); + if (n <= 0) { + throw std::runtime_error( + "NetworkRouter: no processes configured in MessageBus"); + } + + scanner_.resize(n); + for (int i = 0; i < n; ++i) { + scanner_[i] = i; + } + reshuffleScanner(); + scanned_ = 0; + } + + double nextEventTime() const override { return nextScanTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextScanTime_) + return; + + const int n = static_cast(scanner_.size()); + if (n == 0) + return; + + if (scanned_ >= n) { + scanned_ = 0; + reshuffleScanner(); + } + + const int channel = scanner_[scanned_++]; + auto &fromQueue = bus_.procToNet(channel); + + if (!fromQueue.empty()) { + Message m = fromQueue.front(); + fromQueue.pop(); + + if (m.receiver < 0 || m.receiver >= static_cast(bus_.size())) { + throw std::runtime_error("NetworkRouter: invalid receiver index"); + } + + bus_.netToProc(m.receiver).push(m); + } + + nextScanTime_ = currentTime + scanPeriod_; + } + +private: + void reshuffleScanner() { + auto &eng = rng_.engine(); // serve engine() in RandomGenerator + std::shuffle(scanner_.begin(), scanner_.end(), eng); + } + + MessageBus &bus_; + RandomGenerator &rng_; + + double scanPeriod_; + double time_; + double nextScanTime_; + std::vector scanner_; + int scanned_; +}; + +// --------------------------------------------------------------------------- +// Sistema DES: collezione di processi +// --------------------------------------------------------------------------- +class DiscreteEventSystem { +public: + template + Proc &emplaceProcess(Args &&...args) { + auto ptr = std::make_unique(std::forward(args)...); + Proc &ref = *ptr; + processes_.push_back(std::move(ptr)); + return ref; + } + + void initialize(double startTime = 0.0) { + for (auto &p : processes_) { + p->initialize(startTime); + } + } + + double nextEventTime() const { + if (processes_.empty()) { + return std::numeric_limits::infinity(); + } + + double tMin = std::numeric_limits::infinity(); + for (const auto &p : processes_) { + double t = p->nextEventTime(); + if (t < tMin) + tMin = t; + } + return tMin; + } + + void handleEvent(double currentTime) { + for (auto &p : processes_) { + if (p->nextEventTime() <= currentTime) { + p->handleEvent(currentTime); + } + } + } + +private: + std::vector> processes_; +}; + +// --------------------------------------------------------------------------- +// Simulatore DES +// --------------------------------------------------------------------------- +class DiscreteEventSimulator { +public: + explicit DiscreteEventSimulator(DiscreteEventSystem &system) + : system_(system), currentTime_(0.0) {} + + void run(double horizon) { + system_.initialize(0.0); + currentTime_ = 0.0; + + while (currentTime_ < horizon) { + double nextTime = system_.nextEventTime(); + if (nextTime == std::numeric_limits::infinity()) + break; + currentTime_ = nextTime; + system_.handleEvent(currentTime_); + } + } + + double currentTime() const { return currentTime_; } + +private: + DiscreteEventSystem &system_; + double currentTime_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/3/include/Decision.hpp b/exams/AxelRubini/2025-01-09/3/include/Decision.hpp new file mode 100644 index 0000000..4d37cf0 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/3/include/Decision.hpp @@ -0,0 +1,266 @@ +#pragma once + +#include "Random.hpp" +#include "Stat.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 7. MARKOV CHAIN +// ============================================================================ + +// MarkovChain: Implements a discrete-time Markov chain. +// Purpose: +// - Simulates stochastic processes with defined states and transition +// probabilities. +// - Useful in modeling random systems and computing related probabilities. +// Usage: +// MarkovChain mc(3, rng); +// mc.setTransition(0, 1, 0.5); +// mc.setTransition(0, 2, 0.5); +// mc.setState(0); +// int nextState = mc.step(); +class MarkovChain { +private: + int numStates; + std::vector> transitionMatrix; + int currentState; + RandomGenerator &rng; + +public: + MarkovChain(int states, RandomGenerator &generator) + : numStates(states), currentState(0), rng(generator) { + transitionMatrix.resize(states, std::vector(states, 0.0)); + } + + void setTransition(int from, int to, double probability) { + if (from >= 0 && from < numStates && to >= 0 && to < numStates) { + transitionMatrix[from][to] = probability; + } + } + + void setState(int state) { + if (state >= 0 && state < numStates) { + currentState = state; + } + } + + int getState() const { return currentState; } + + int step() { + currentState = rng.discrete(transitionMatrix[currentState]); + return currentState; + } + + double getTransitionProb(int from, int to) const { + return transitionMatrix[from][to]; + } + + double expectedSojournTime(int state, double timeStep = 1.0) const { + double pii = transitionMatrix[state][state]; + if (pii >= 1.0) + return std::numeric_limits::infinity(); + return timeStep / (1.0 - pii); + } +}; + +// ============================================================================ +// 8. MDP +// ============================================================================ + +// MDP: Implements a Markov Decision Process (MDP) model. +// Purpose: +// - Simulate decision-making processes with states, transitions, and +// associated costs. +// - Calculate the total cost to reach a target state from a starting state. +// Usage: +// - Add transitions between states with associated probabilities and costs +// using `addTransition`. +// - Use `step` to simulate a single transition and calculate its cost. +// - Use `simulate` to estimate the total cost from a start to an end state. +class MDP { +private: + int numStates; + std::map>>> + transitions; + RandomGenerator &rng; + +public: + MDP(int states, RandomGenerator &generator) + : numStates(states), rng(generator) {} + + void addTransition(int from, int to, double probability, double cost) { + transitions[from].push_back({to, {probability, cost}}); + } + + std::pair step(int currentState) { + if (transitions.find(currentState) == transitions.end()) { + return {currentState, 0.0}; + } + + std::vector probs; + for (const auto &t : transitions[currentState]) { + probs.push_back(t.second.first); + } + + int idx = rng.discrete(probs); + int nextState = transitions[currentState][idx].first; + double cost = transitions[currentState][idx].second.second; + + return {nextState, cost}; + } + + double simulate(int startState, int endState) { + int state = startState; + double totalCost = 0.0; + int maxSteps = 100000; + + for (int step = 0; step < maxSteps && state != endState; step++) { + auto [nextState, cost] = this->step(state); + totalCost += cost; + state = nextState; + } + return totalCost; + } +}; + +// ============================================================================ +// 10. MONTE CARLO SIMULATOR +// ============================================================================ + +// MonteCarloSimulator: A utility for running Monte Carlo simulations. +// Purpose: +// - Estimate statistical metrics through repeated random sampling. +// - Provides methods to calculate mean results and probabilities of events. +// Usage: +// MonteCarloSimulator simulator(1000); +// double estimate = simulator.estimate([](int) { return some_random_value; }); +// double probability = simulator.estimateProbability([](int) { return +// some_event_occurs; }); +class MonteCarloSimulator { +private: + Statistics stats; + int numSimulations; + +public: + MonteCarloSimulator(int simulations = 1000) : numSimulations(simulations) {} + + template double estimate(SimFunc simulation) { + stats.clear(); + for (int i = 0; i < numSimulations; i++) { + double result = simulation(i); + stats.addSample(result); + } + return stats.mean(); + } + + template double estimateProbability(SimFunc simulation) { + int successes = 0; + for (int i = 0; i < numSimulations; i++) { + if (simulation(i)) + successes++; + } + return static_cast(successes) / numSimulations; + } + + const Statistics &getStatistics() const { return stats; } +}; + +// ============================================================================ +// 13. OPTIMIZATION UTILITIES +// ============================================================================ + +// Optimizer: Utility class offering optimization techniques such as random +// search and grid search. Purpose: +// - Explore parameter spaces to maximize or minimize an objective function. +// - Supports both continuous and discrete search spaces. +// Usage: +// - Use `randomSearch` for stochastic optimization over a defined range. +// - Use `gridSearch` for systematic exploration in evenly spaced steps. +// Example: +// auto [bestParam, bestValue] = Optimizer::randomSearch(objectiveFunction, +// 0.0, 10.0, 100, rng); auto [bestInt, bestIntValue] = +// Optimizer::randomSearchInt(objectiveFunction, 0, 10, 50, rng); +class Optimizer { +public: + template + static std::pair + randomSearch(ObjFunc objective, double minValue, double maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + for (int i = 0; i < budget; i++) { + double param = rng.uniform(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair + randomSearchInt(ObjFunc objective, int minValue, int maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + int bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + if (budget <= 0) { + throw std::invalid_argument("randomSearchInt: budget must be positive."); + } + + if (minValue >= maxValue) { + throw std::invalid_argument( + "randomSearchInt: minValue must be less than maxValue."); + } + + for (int i = 0; i < budget; i++) { + int param = rng.uniformInt(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair gridSearch(ObjFunc objective, + double minValue, double maxValue, + int steps, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + double stepSize = (maxValue - minValue) / steps; + + if (steps <= 0) { + throw std::invalid_argument("gridSearch: steps must be positive."); + } + + for (int i = 0; i <= steps; i++) { + double param = minValue + i * stepSize; + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/3/include/Dynamics.hpp b/exams/AxelRubini/2025-01-09/3/include/Dynamics.hpp new file mode 100644 index 0000000..044176a --- /dev/null +++ b/exams/AxelRubini/2025-01-09/3/include/Dynamics.hpp @@ -0,0 +1,214 @@ +#pragma once + +#include +#include +namespace SELib { +// ============================================================================ +// 3. ODE SOLVER (Runge-Kutta 4th order) +// ============================================================================ + +using StateVector = std::vector; +using DerivativeFunction = std::function; + +// ODESolver: Implements numerical methods for solving Ordinary Differential +// Equations (ODEs). Purpose: +// - Provides tools for simulating dynamic systems modeled by ODEs. +// - Includes methods such as Runge-Kutta 4th order and Euler's method for +// integration. +// Usage: +// - Pass a derivative function that defines the ODE system. +// - Use `rk4Step` for higher accuracy or `eulerStep` for simpler systems. +// Example: +// StateVector nextState = ODESolver::rk4Step(0.0, currentState, controlInputs, +// dt, dynamicsFunction); +class ODESolver { +public: + static StateVector rk4Step(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector k1 = f(t, x, u); + StateVector x2 = addScaled(x, k1, dt / 2.0); + StateVector k2 = f(t + dt / 2.0, x2, u); + StateVector x3 = addScaled(x, k2, dt / 2.0); + StateVector k3 = f(t + dt / 2.0, x3, u); + StateVector x4 = addScaled(x, k3, dt); + StateVector k4 = f(t + dt, x4, u); + + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += (dt / 6.0) * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]); + } + return xNew; + } + + static StateVector eulerStep(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector dx = f(t, x, u); + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += dx[i] * dt; + } + return xNew; + } + +private: + static StateVector addScaled(const StateVector &x, const StateVector &dx, + double scale) { + StateVector result = x; + for (size_t i = 0; i < x.size(); i++) { + result[i] += dx[i] * scale; + } + return result; + } +}; + +// ============================================================================ +// 4. PID CONTROLLER +// ============================================================================ + +// PIDController: Implements a Proportional-Integral-Derivative (PID) +// controller. Purpose: +// - Automatically adjusts a control signal to minimize the difference between +// a desired setpoint and a measured value. +// - Used in control systems to regulate processes like temperature, speed, or +// position. +// Usage: +// - Create an instance with proportional (Kp), integral (Ki), and derivative +// (Kd) gains, along with a time step. +// - Use `compute` to calculate the control signal based on the setpoint and +// measurement. +// Example: +// PIDController pid(1.0, 0.1, 0.01, 0.1); +// double controlSignal = pid.compute(desiredValue, actualValue); +class PIDController { +private: + double kp, ki, kd; + double integral; + double prevError; + double dt; + +public: + PIDController(double Kp, double Ki, double Kd, double timeStep) + : kp(Kp), ki(Ki), kd(Kd), integral(0), prevError(0), dt(timeStep) {} + + double compute(double setpoint, double measurement) { + double error = setpoint - measurement; + integral += error * dt; + double derivative = (error - prevError) / dt; + prevError = error; + return kp * error + ki * integral + kd * derivative; + } + + void reset() { + integral = 0; + prevError = 0; + } + + void setGains(double Kp, double Ki, double Kd) { + kp = Kp; + ki = Ki; + kd = Kd; + } +}; + +// ============================================================================ +// 5. CONTINUOUS TIME SIMULATION +// ============================================================================ + +// ContinuousTimeSimulation: A base class for simulating continuous-time dynamic +// systems. Purpose: +// - Provides a framework for simulating systems characterized by continuous +// dynamics over time. +// - Integrates system dynamics using a Runge-Kutta 4th order solver. +// Usage: +// - Derive from this class and implement the `dynamics`, `computeControl`, +// `initialize`, and `finalize` methods. +// - Customize the `onStep` method for per-step side effects, if required. +// Example: +// class MySimulation : public ContinuousTimeSimulation { +// // Implement the required virtual methods. +// }; +// MySimulation sim(dt, horizon, initialState); +// sim.run(); +class ContinuousTimeSimulation { +protected: + double currentTime; + double timeStep; + double horizon; + StateVector state; + StateVector control; + +public: + ContinuousTimeSimulation(double dt, double H, const StateVector &initialState) + : currentTime(0.0), timeStep(dt), horizon(H), state(initialState) { + control.resize(initialState.size(), 0.0); + } + + virtual ~ContinuousTimeSimulation() = default; + + virtual StateVector dynamics(double t, const StateVector &x, + const StateVector &u) = 0; + virtual StateVector computeControl(double t, const StateVector &x) = 0; + virtual void initialize() = 0; + virtual void onStep() {} + virtual void finalize() = 0; + + void run() { + initialize(); + while (currentTime < horizon) { + control = computeControl(currentTime, state); + state = ODESolver::rk4Step( + currentTime, state, control, timeStep, + [this](double t, const StateVector &x, const StateVector &u) { + return this->dynamics(t, x, u); + }); + currentTime += timeStep; + onStep(); + } + finalize(); + } + + double getCurrentTime() const { return currentTime; } + const StateVector &getState() const { return state; } + const StateVector &getControl() const { return control; } +}; + +// ============================================================================ +// 6. SAMPLE AND HOLD +// ============================================================================ + +// SampleAndHold: A utility to store a value and update it periodically. +// Purpose: +// - Maintain a value that gets updated periodically based on a time interval. +// - Useful for sampling data and ensuring updates happen at fixed intervals. +// Usage: +// SampleAndHold sampler(5.0, 0); +// if (sampler.shouldUpdate(currentTime)) { +// sampler.update(currentTime, newValue); +// } +// int currentValue = sampler.getValue(); +template class SampleAndHold { +private: + T value; + double lastUpdateTime; + double samplePeriod; + +public: + SampleAndHold(double period, const T &initialValue = T()) + : value(initialValue), lastUpdateTime(0), samplePeriod(period) {} + + bool shouldUpdate(double currentTime) const { + return (currentTime - lastUpdateTime) >= samplePeriod; + } + + void update(double currentTime, const T &newValue) { + value = newValue; + lastUpdateTime = currentTime; + } + + const T &getValue() const { return value; } + void reset() { lastUpdateTime = 0; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/3/include/Geometry.hpp b/exams/AxelRubini/2025-01-09/3/include/Geometry.hpp new file mode 100644 index 0000000..525a91d --- /dev/null +++ b/exams/AxelRubini/2025-01-09/3/include/Geometry.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 14. GEOMETRIC UTILITIES +// ============================================================================ + +// Point2D: Represents a point or vector in 2D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point2D p1(1.0, 2.0), p2(2.0, 3.0); +// double dist = p1.distanceTo(p2); +// Point2D sum = p1 + p2; +struct Point2D { + double x, y; + + Point2D(double x_ = 0, double y_ = 0) : x(x_), y(y_) {} + + double distanceTo(const Point2D &other) const { + double dx = x - other.x; + double dy = y - other.y; + return std::sqrt(dx * dx + dy * dy); + } + + Point2D operator+(const Point2D &other) const { + return Point2D(x + other.x, y + other.y); + } + + Point2D operator-(const Point2D &other) const { + return Point2D(x - other.x, y - other.y); + } +}; + +// Point3D: Represents a point or vector in 3D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point3D p1(1.0, 2.0, 3.0), p2(2.0, 3.0, 4.0); +// double dist = p1.distanceTo(p2); +// Point3D sum = p1 + p2; +struct Point3D { + double x, y, z; + + Point3D(double x_ = 0, double y_ = 0, double z_ = 0) : x(x_), y(y_), z(z_) {} + + double distanceTo(const Point3D &other) const { + double dx = x - other.x; + double dy = y - other.y; + double dz = z - other.z; + return std::sqrt(dx * dx + dy * dy + dz * dz); + } + + Point3D operator+(const Point3D &other) const { + return Point3D(x + other.x, y + other.y, z + other.z); + } + + Point3D operator-(const Point3D &other) const { + return Point3D(x - other.x, y - other.y, z - other.z); + } +}; + +// ============================================================================ +// 19. COLLISION DETECTOR +// ============================================================================ + +// CollisionDetector: A utility class for detecting collisions between points +// in space. Purpose: +// - Counts the number of collisions within a given distance threshold. +// - Retrieves pairs of colliding points. +// Usage: +// std::vector points = {Point2D(0, 0), Point2D(1, 1)}; +// int numCollisions = CollisionDetector::countCollisions(points, 1.5); +// auto collidingPairs = CollisionDetector::getCollidingPairs(points, 1.5); +class CollisionDetector { +public: + template + static int countCollisions(const std::vector &positions, + double threshold) { + int collisions = 0; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + collisions++; + } + } + } + return collisions; + } + + template + static std::vector> + getCollidingPairs(const std::vector &positions, double threshold) { + std::vector> pairs; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + pairs.push_back({i, j}); + } + } + } + return pairs; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/3/include/IO.hpp b/exams/AxelRubini/2025-01-09/3/include/IO.hpp new file mode 100644 index 0000000..1a79cc6 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/3/include/IO.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 11. PARAMETER PARSER +// ============================================================================ + +// ParameterParser: A utility to parse and retrieve parameters from +// configuration files. Purpose: +// - Reads a file and stores key-value pairs for parameters. +// - Allows retrieval of parameters as integers, doubles, or strings. +// - Provides structured data for advanced parsing. +// Usage: +// ParameterParser parser; +// if (parser.parseFile("config.txt")) { +// int param = parser.getInt("example_key", 42); +// } +class ParameterParser { +private: + std::map params; + std::vector> structuredData; + +public: + bool parseFile(const std::string &filename) { + std::ifstream file(filename); + if (!file.is_open()) + return false; + + std::string line; + while (std::getline(file, line)) { + if (line.empty()) + continue; + + std::istringstream iss(line); + std::string key; + iss >> key; + + std::string value; + std::getline(iss, value); + if (!value.empty() && value[0] == ' ') { + value = value.substr(1); + } + + params[key] = value; + + std::vector tokens; + std::istringstream tokenStream(line); + std::string token; + while (tokenStream >> token) { + tokens.push_back(token); + } + structuredData.push_back(tokens); + } + return true; + } + + int getInt(const std::string &key, int defaultValue = 0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stoi(it->second); + } + return defaultValue; + } + + double getDouble(const std::string &key, double defaultValue = 0.0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stod(it->second); + } + return defaultValue; + } + + std::string getString(const std::string &key, + const std::string &defaultValue = "") const { + auto it = params.find(key); + if (it != params.end()) { + return it->second; + } + return defaultValue; + } + + const std::vector> &getStructuredData() const { + return structuredData; + } + + bool hasKey(const std::string &key) const { + return params.find(key) != params.end(); + } +}; + +// ============================================================================ +// 12. OUTPUT WRITER +// ============================================================================ + +// OutputWriter: A utility class for writing data to output files. +// Purpose: +// - Simplifies the process of writing data to files. +// - Supports writing lines, key-value pairs, and managing file headers. +// Usage: +// OutputWriter writer; +// if (writer.open("output.txt", "Header Info")) { +// writer.writeLine("Example Line"); +// writer.writeKeyValue("Key", 42.0); +// writer.close(); +// } +class OutputWriter { +private: + std::ofstream file; + +public: + bool open(const std::string &filename, const std::string &header) { + file.open(filename); + if (!file.is_open()) + return false; + file << header << std::endl; + return true; + } + + template void writeLine(const T &value) { + file << value << std::endl; + } + + template + void writeLine(const T &first, const Args &...args) { + file << first << " "; + writeLine(args...); + } + + void writeKeyValue(const std::string &key, double value, int precision = 2) { + file << key << " " << std::fixed << std::setprecision(precision) << value + << std::endl; + } + + void close() { + if (file.is_open()) { + file.close(); + } + } + + ~OutputWriter() { close(); } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/3/include/Inventory.hpp b/exams/AxelRubini/2025-01-09/3/include/Inventory.hpp new file mode 100644 index 0000000..c82d776 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/3/include/Inventory.hpp @@ -0,0 +1,423 @@ +#pragma once + +#include "DES.hpp" +#include "Random.hpp" +#include +#include + +namespace SELib { +// ============================================================================ +// 16. CUSTOMER MODEL +// ============================================================================ + +// Customer: Represents a customer model in a simulation. +// Purpose: +// - Simulates customer requests over time. +// - Generates random requests for products at specific intervals. +// Usage: +// Customer customer(5.0, 10.0, 3, rng); +// int productId = customer.step(0.1); // Simulates a time step of 0.1 seconds. +class Customer { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer(double A, double B, int N, RandomGenerator &generator) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator) { + nextRequestTime = rng.uniform(A, B); + } + + int step(double dt) { + nextRequestTime -= dt; + if (nextRequestTime <= 0) { + nextRequestTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } +}; +class Customer_Event_Driven { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer_Event_Driven(double A, double B, int N, RandomGenerator &generator, + double nextRequestTime) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator), + nextRequestTime(nextRequestTime) {} + + int step_event_driven(double *current_time) { + if (*current_time >= nextRequestTime) { + nextRequestTime = *current_time + rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + double getNextRequestTime() { return nextRequestTime; }; +}; + +// ============================================================================ +// 17. SERVER MODEL +// ============================================================================ + +// Server: Represents a server in a simulated environment. +// Purpose: +// - Handles product requests and manages inventory. +// - Simulates server busy time during request processing. +// Usage: +// Server server(numProducts, maxInventory, processingTimeNormal, +// processingTimeSupplier, rng); int result = +// server.processRequest(productId, needsSupply); +// server.addInventory(productId, quantityAdded); +class Server { +private: + std::vector inventory; + int busyTime; + int processingTimeNormal; + int processingTimeSupplier; + RandomGenerator *rng; + +public: + Server(int numProducts, int K, int F, int G, RandomGenerator &rng) + : inventory(numProducts + 1, 0), busyTime(0), processingTimeNormal(F), + processingTimeSupplier(G), rng(&rng) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + + bool isBusy() const { return busyTime > 0; } + + void decrementBusyTime() { + if (busyTime > 0) + busyTime--; + } + + int processRequest(int productId, bool needsSupply) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return productId; + } else { + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return -productId; + } + } + + int processRequest2(int productId, double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = processingTimeNormal; + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + busyTime = processingTimeSupplier; + return -productId; + } + } + + void addInventory(int productId, int quantity) { + inventory[productId] += quantity; + } + + int getInventory(int productId) const { return inventory[productId]; } +}; +// ============================================================================ +// 17.5 SERVER-EVENT-DRIVEN +// ============================================================================ +class Server_Event_Driven { +private: + std::vector inventory; + RandomGenerator *rng; + double next_step_time; + double min_interval; + double max_interval; + +public: + Server_Event_Driven(int numProducts, int K, double min_interval, + double max_interval, RandomGenerator &rng, + double nextStepTime) + : inventory(numProducts + 1, 0), rng(&rng), min_interval(min_interval), + max_interval(max_interval), next_step_time(nextStepTime) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + int processRequestEventDriven(double current_time, int productId, + double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (current_time >= next_step_time) { + if (inventory[productId] > 0) { + inventory[productId]--; + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + + return -productId; + } + } else { + return 0; + } + } + double getNextStepTime() { return next_step_time; } +}; + +// ============================================================================ +// 18. SUPPLIER MODEL +// ============================================================================ + +// Supplier: Represents a supplier in a simulation. +// Purpose: +// - Simulates the supply of products at specific intervals. +// - Generates random supply requests periodically based on the defined +// intervals. +// Usage: +// Supplier supplier(minInterval, maxInterval, numProducts, rng); +// int suppliedProduct = supplier.step(timeStep); +// supplier.reset(); +class Supplier { +private: + double nextSupplyTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Supplier(double V, double Q, int N, RandomGenerator &generator) + : minInterval(V), maxInterval(Q), numProducts(N), rng(generator) { + nextSupplyTime = rng.uniform(V, Q); + } + + int step(double dt) { + nextSupplyTime -= dt; + if (nextSupplyTime <= 0) { + nextSupplyTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + void reset() { nextSupplyTime = rng.uniform(minInterval, maxInterval); } +}; + +// Server di inventario con messaggi (customer/server network-style) +class InventoryServerProcess : public DiscreteEventProcess { +public: + InventoryServerProcess(int pid, int numItems, int initialStockPerItem, + double serviceTime, MessageBus &bus, + RandomGenerator &rng) + : pid_(pid), serviceTime_(serviceTime), bus_(bus), rng_(rng), + nextEventTime_(0.0), + storage_(static_cast(numItems), initialStockPerItem), + totalRequests_(0), missedRequests_(0) {} + + void initialize(double startTime) override { + nextEventTime_ = startTime + serviceTime_; + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // processa TUTTI i messaggi arrivati a questo server + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + totalRequests_++; + + // sicurezza sugli indici prodotto + if (m.item < 0 || static_cast(m.item) >= storage_.size()) { + continue; // scarta richieste invalide + } + + double servedQty = 0.0; + int &stock = storage_[static_cast(m.item)]; + + if (stock >= static_cast(m.quantity)) { + // richiesta soddisfatta + stock -= static_cast(m.quantity); + servedQty = m.quantity; + } else { + // non abbastanza stock + servedQty = static_cast(stock); + missedRequests_++; + stock = 0; + } + + // risposta al cliente + Message reply; + reply.time = currentTime; + reply.sender = pid_; + reply.receiver = m.sender; // rimanda al mittente + reply.item = m.item; + reply.quantity = servedQty; + + bus_.procToNet(pid_).push(reply); + + // eventuale logica di refill: esempio semplice + if (stock == 0) { + int refill = rng_.uniformInt(1, 10); // batch di rifornimento random + stock += refill; + } + } + + nextEventTime_ = currentTime + serviceTime_; + } + + const std::vector &storage() const { return storage_; } + + int totalRequests() const { return totalRequests_; } + int missedRequests() const { return missedRequests_; } + +private: + int pid_; + double serviceTime_; + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + std::vector storage_; + + int totalRequests_; + int missedRequests_; +}; + +// ============================================================================ +// 19. INVENTORY CUSTOMER PROCESS (DES-compatible) +// ============================================================================ +// +// InventoryCustomerProcess: +// - Processo a eventi discreti che rappresenta un cliente. +// - Invia richieste al server tramite MessageBus (procToNet -> NetworkRouter). +// - Legge le risposte dal canale netToProc. +// - Statistiche: quante richieste ha mandato e quante risposte ha ricevuto. +// +class InventoryCustomerProcess : public DiscreteEventProcess { +public: + // pid : id di questo processo (indice nei canali del MessageBus) + // serverPid : pid del server a cui mandare le richieste + // numItems : numero di prodotti gestiti (item in [0, numItems-1]) + // minInterval/maxInterval : intervallo di tempo tra una richiesta e la + // successiva + InventoryCustomerProcess(int pid, int serverPid, int numItems, + double minInterval, double maxInterval, + MessageBus &bus, RandomGenerator &rng) + : pid_(pid), serverPid_(serverPid), numItems_(numItems), + minInterval_(minInterval), maxInterval_(maxInterval), bus_(bus), + rng_(rng), nextEventTime_(0.0), issuedRequests_(0), receivedReplies_(0), + totalRequestedQty_(0.0), totalServedQty_(0.0) {} + + // inizializza il prossimo evento (prima richiesta) + void initialize(double startTime) override { + nextEventTime_ = startTime + rng_.uniform(minInterval_, maxInterval_); + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // 1) genera una nuova richiesta al server + const int item = rng_.uniformInt( + 0, numItems_ - 1); // coerente con InventoryServerProcess + const int qty = rng_.uniformInt(1, 3); // quantità richiesta (1..3) + + Message req; + req.time = currentTime; + req.sender = pid_; + req.receiver = serverPid_; + req.item = item; + req.quantity = static_cast(qty); + + bus_.procToNet(pid_).push(req); + issuedRequests_++; + totalRequestedQty_ += req.quantity; + + // 2) processa tutte le risposte arrivate su questo cliente + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + // opzionale: possiamo filtrare solo messaggi dal serverPid_ + // if (m.sender != serverPid_) continue; + + receivedReplies_++; + totalServedQty_ += m.quantity; + } + + // 3) pianifica la prossima richiesta + nextEventTime_ = currentTime + rng_.uniform(minInterval_, maxInterval_); + } + + // --- metriche accessibili dall’esterno --- + + int pid() const { return pid_; } + int serverPid() const { return serverPid_; } + + int issuedRequests() const { return issuedRequests_; } + int receivedReplies() const { return receivedReplies_; } + + double totalRequestedQty() const { return totalRequestedQty_; } + double totalServedQty() const { return totalServedQty_; } + + // fill-rate lato customer + double serviceLevel() const { + if (totalRequestedQty_ <= 0.0) + return 1.0; + return totalServedQty_ / totalRequestedQty_; + } + +private: + int pid_; + int serverPid_; + int numItems_; + double minInterval_; + double maxInterval_; + + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + + int issuedRequests_; + int receivedReplies_; + double totalRequestedQty_; + double totalServedQty_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/3/include/Queue.hpp b/exams/AxelRubini/2025-01-09/3/include/Queue.hpp new file mode 100644 index 0000000..0c12ce8 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/3/include/Queue.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 9. FIFO QUEUE +// ============================================================================ + +// FIFOQueue: Implements a first-in-first-out (FIFO) queue with a fixed maximum +// size. Purpose: +// - Enqueue and dequeue items in FIFO order. +// - Track statistics such as total items enqueued and dequeued. +// - Support dynamic size constraints. +// Usage: +// FIFOQueue queue(10); +// queue.enqueue(5); +// int item; +// if (queue.dequeue(item)) { +// // Process item. +// } +template class FIFOQueue { +private: + std::queue queue; + size_t maxSize; + size_t totalEnqueued; + size_t totalDequeued; + +public: + FIFOQueue(size_t max = SIZE_MAX) + : maxSize(max), totalEnqueued(0), totalDequeued(0) {} + + bool enqueue(const T &item) { + if (queue.size() >= maxSize) + return false; + queue.push(item); + totalEnqueued++; + return true; + } + + bool dequeue(T &item) { + if (queue.empty()) + return false; + item = queue.front(); + queue.pop(); + totalDequeued++; + return true; + } + + bool isEmpty() const { return queue.empty(); } + bool isFull() const { return queue.size() >= maxSize; } + size_t size() const { return queue.size(); } + size_t getTotalEnqueued() const { return totalEnqueued; } + size_t getTotalDequeued() const { return totalDequeued; } + std::queue getQueue() const { return queue; } + T getFirst() const { + if (queue.empty()) + throw std::runtime_error("Queue is empty"); + return queue.front(); + } + + void clear() { + while (!queue.empty()) + queue.pop(); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/3/include/Random.hpp b/exams/AxelRubini/2025-01-09/3/include/Random.hpp new file mode 100644 index 0000000..52356f9 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/3/include/Random.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include // std::isfinite +#include // std::accumulate +#include +#include +#include + +namespace SELib { + +class RandomGenerator { +private: + std::mt19937 gen; + +public: + explicit RandomGenerator(unsigned seed = std::random_device{}()) + : gen(seed) {} + + double uniform(double min, double max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_real_distribution dis(lo, high); + return dis(gen); + } + + int uniformInt(int min, int max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_int_distribution dis(lo, high); + return dis(gen); + } + + double gaussian(double mean, double stddev) { + if (stddev < 0.0) { + throw std::invalid_argument("gaussian: stddev must be >= 0"); + } + std::normal_distribution dis(mean, stddev); + return dis(gen); + } + + bool bernoulli(double p) { + if (p < 0.0 || p > 1.0 || !std::isfinite(p)) { + throw std::invalid_argument("bernoulli: p must be in [0,1]"); + } + std::bernoulli_distribution dis(p); + return dis(gen); + } + + int discrete(const std::vector &probabilities) { + if (probabilities.empty()) { + throw std::invalid_argument("discrete: empty probability vector"); + } + + for (double w : probabilities) { + if (w < 0.0 || !std::isfinite(w)) { + throw std::invalid_argument( + "discrete: weights must be non-negative and finite"); + } + } + + double sum = + std::accumulate(probabilities.begin(), probabilities.end(), 0.0); + if (sum <= 0.0) { + throw std::invalid_argument("discrete: sum of weights must be > 0"); + } + + std::discrete_distribution dis(probabilities.begin(), + probabilities.end()); + return dis(gen); + } + + void setSeed(unsigned seed) { gen.seed(seed); } + + // (opzionale) accesso al motore se ti serve per altre distribuzioni + std::mt19937 &engine() { return gen; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/3/include/Stat.hpp b/exams/AxelRubini/2025-01-09/3/include/Stat.hpp new file mode 100644 index 0000000..27b4656 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/3/include/Stat.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 2. STATISTICS +// ============================================================================ + +// Statistics: A utility class for managing and analyzing numerical data +// samples. Purpose: +// - Compute statistical metrics such as mean and standard deviation. +// - Estimate probabilities of custom conditions. +// - Check convergence of data over iterations. +// Usage: +// Statistics stats; +// stats.addSample(5.0); +// double meanValue = stats.mean(); +class Statistics { +private: + std::vector samples; + +public: + void addSample(double value) { samples.push_back(value); } + + void clear() { samples.clear(); } + + double mean() const { + if (samples.empty()) + return 0.0; + double sum = 0.0; + for (double s : samples) + sum += s; + return sum / samples.size(); + } + + double stddev() const { + if (samples.size() < 2) + return 0.0; + double m = mean(); + double variance = 0.0; + for (double s : samples) { + variance += (s - m) * (s - m); + } + return std::sqrt(variance / (samples.size() - 1)); + } + + double probability(std::function condition) const { + if (samples.empty()) + return 0.0; + int count = 0; + for (double s : samples) { + if (condition(s)) + count++; + } + return static_cast(count) / samples.size(); + } + + size_t size() const { return samples.size(); } + const std::vector &getSamples() const { return samples; } + + bool hasConverged(double tolerance = 0.1) const { + if (samples.size() < 30) + return false; + double m = mean(); + double sd = stddev(); + return sd <= tolerance * std::abs(m); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/3/main b/exams/AxelRubini/2025-01-09/3/main new file mode 100755 index 0000000000000000000000000000000000000000..7dd71d7f17932a858fb85c77cb82f7487bf99060 GIT binary patch literal 73624 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 literal 0 HcmV?d00001 diff --git a/exams/AxelRubini/2025-01-09/3/main.cpp b/exams/AxelRubini/2025-01-09/3/main.cpp new file mode 100644 index 0000000..5f96206 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/3/main.cpp @@ -0,0 +1,133 @@ +#include "include/DES.hpp" +#include "include/IO.hpp" +#include "include/Random.hpp" +#include "include/Stat.hpp" +#include +#include +#include +#include +#include + +namespace SELib { + +class EcommerceCustomer : public DiscreteEventProcess { + public: + EcommerceCustomer( + int pid, + MessageBus &bus, + RandomGenerator &rng, + double avg, + double stddev + ) + : pid_(pid), bus_(bus), rng_(rng), avg_(avg), stddev_(stddev) {} + + void initialize(double startTime) override { scheduleNext(startTime); } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + Message msg; + msg.time = currentTime; + msg.sender = pid_; + msg.receiver = 1; // Monitor PID + bus_.procToNet(pid_).push(msg); + + scheduleNext(currentTime); + } + + private: + void scheduleNext(double currentTime) { + // T soggiorno in idle ~ Gaussiana(Avg, StdDev) + double delay = rng_.gaussian(avg_, stddev_); + if (delay < 1.0) + delay = 1.0; // Time step minimo T=1 + nextEventTime_ = currentTime + delay; + } + + int pid_; + MessageBus &bus_; + RandomGenerator &rng_; + double avg_; + double stddev_; + double nextEventTime_; +}; + +class EcommerceMonitor : public DiscreteEventProcess { + public: + EcommerceMonitor(int pid, MessageBus &bus) + : pid_(pid), bus_(bus), lastRequestTime_(-1.0) {} + + void initialize(double startTime) override {} + + double nextEventTime() const override { + return std::numeric_limits::infinity(); + } + + void handleEvent(double currentTime) override {} + + void processInbox() { + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + if (lastRequestTime_ >= 0) { + stats_.addSample(m.time - lastRequestTime_); + } + lastRequestTime_ = m.time; + } + } + + double getAvg() const { return stats_.mean(); } + double getStdDev() const { return stats_.stddev(); } + + private: + int pid_; + MessageBus &bus_; + double lastRequestTime_; + Statistics stats_; +}; + +} // namespace SELib + +int main() { + SELib::RandomGenerator rng; + SELib::ParameterParser parser; + if (!parser.parseFile("parameters.txt")) { + return 1; + } + + double avg = parser.getDouble("Avg", 3600.0); + double stddev = parser.getDouble("StdDev", 500.0); + + SELib::MessageBus bus(2); + SELib::DiscreteEventSystem system; + + // Processo 0: Customer + system.emplaceProcess(0, bus, rng, avg, stddev); + // Processo 1: Monitor + auto &monitor = system.emplaceProcess(1, bus); + // Network Router + system.emplaceProcess(bus, rng, 0.1); + + double currentTime = 0.0; + double horizon = 1000000.0; + system.initialize(0.0); + + while (currentTime < horizon) { + double nextTime = system.nextEventTime(); + if (nextTime > horizon) + break; + currentTime = nextTime; + system.handleEvent(currentTime); + monitor.processInbox(); + } + + std::ofstream outFile("results.txt"); + outFile << "2025-01-09-Axel-Rubini-2158099" << std::endl; + outFile << "Avg " << monitor.getAvg() << std::endl; + outFile << "StdDev " << monitor.getStdDev() << std::endl; + outFile.close(); + + return 0; +} diff --git a/exams/AxelRubini/2025-01-09/3/parameters.txt b/exams/AxelRubini/2025-01-09/3/parameters.txt new file mode 100644 index 0000000..5b5ab4e --- /dev/null +++ b/exams/AxelRubini/2025-01-09/3/parameters.txt @@ -0,0 +1,2 @@ +Avg 100 +StdDev 5 diff --git a/exams/AxelRubini/2025-01-09/3/results.txt b/exams/AxelRubini/2025-01-09/3/results.txt new file mode 100644 index 0000000..78da49e --- /dev/null +++ b/exams/AxelRubini/2025-01-09/3/results.txt @@ -0,0 +1,3 @@ +2025-01-09-Axel-Rubini-2158099 +Avg 99.9165 +StdDev 4.9439 diff --git a/exams/AxelRubini/2025-01-09/4/Makefile b/exams/AxelRubini/2025-01-09/4/Makefile new file mode 100644 index 0000000..6910fc5 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/4/Makefile @@ -0,0 +1,10 @@ +CC=g++ +CFLAGS=-std=c++23 -O3 -Wall + +all: main + +main: main.cpp + $(CC) $(CFLAGS) main.cpp -o main + +clean: + rm -f main *.o diff --git a/exams/AxelRubini/2025-01-09/4/include/DES.hpp b/exams/AxelRubini/2025-01-09/4/include/DES.hpp new file mode 100644 index 0000000..1706b8f --- /dev/null +++ b/exams/AxelRubini/2025-01-09/4/include/DES.hpp @@ -0,0 +1,212 @@ + +#pragma once + +#include "Random.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// --------------------------------------------------------------------------- +// Messaggio +// --------------------------------------------------------------------------- +struct Message { + double time{0.0}; + int sender{-1}; + int receiver{-1}; + int item{0}; + double quantity{0.0}; +}; + +// --------------------------------------------------------------------------- +// Bus di messaggi: process -> network, network -> process +// --------------------------------------------------------------------------- +class MessageBus { +public: + explicit MessageBus(std::size_t n = 0) { resize(n); } + + void resize(std::size_t n) { + procToNet_.assign(n, {}); + netToProc_.assign(n, {}); + } + + std::size_t size() const { return procToNet_.size(); } + + std::queue &procToNet(int pid) { return procToNet_.at(pid); } + std::queue &netToProc(int pid) { return netToProc_.at(pid); } + const std::queue &procToNet(int pid) const { + return procToNet_.at(pid); + } + const std::queue &netToProc(int pid) const { + return netToProc_.at(pid); + } + +private: + std::vector> procToNet_; + std::vector> netToProc_; +}; + +// --------------------------------------------------------------------------- +// Processo base a eventi discreti +// --------------------------------------------------------------------------- +class DiscreteEventProcess { +public: + virtual ~DiscreteEventProcess() = default; + virtual void initialize(double startTime) = 0; + virtual double nextEventTime() const = 0; + virtual void handleEvent(double currentTime) = 0; +}; + +// --------------------------------------------------------------------------- +// NetworkRouter: inoltra i messaggi P->N->P +// --------------------------------------------------------------------------- +class NetworkRouter : public DiscreteEventProcess { +public: + NetworkRouter(MessageBus &bus, RandomGenerator &rng, double scanPeriod = 0.1) + : bus_(bus), rng_(rng), scanPeriod_(scanPeriod), time_(0.0), + nextScanTime_(0.0), scanned_(0) {} + + void initialize(double startTime) override { + time_ = startTime; + nextScanTime_ = startTime + scanPeriod_; + + const int n = static_cast(bus_.size()); + if (n <= 0) { + throw std::runtime_error( + "NetworkRouter: no processes configured in MessageBus"); + } + + scanner_.resize(n); + for (int i = 0; i < n; ++i) { + scanner_[i] = i; + } + reshuffleScanner(); + scanned_ = 0; + } + + double nextEventTime() const override { return nextScanTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextScanTime_) + return; + + const int n = static_cast(scanner_.size()); + if (n == 0) + return; + + if (scanned_ >= n) { + scanned_ = 0; + reshuffleScanner(); + } + + const int channel = scanner_[scanned_++]; + auto &fromQueue = bus_.procToNet(channel); + + if (!fromQueue.empty()) { + Message m = fromQueue.front(); + fromQueue.pop(); + + if (m.receiver < 0 || m.receiver >= static_cast(bus_.size())) { + throw std::runtime_error("NetworkRouter: invalid receiver index"); + } + + bus_.netToProc(m.receiver).push(m); + } + + nextScanTime_ = currentTime + scanPeriod_; + } + +private: + void reshuffleScanner() { + auto &eng = rng_.engine(); // serve engine() in RandomGenerator + std::shuffle(scanner_.begin(), scanner_.end(), eng); + } + + MessageBus &bus_; + RandomGenerator &rng_; + + double scanPeriod_; + double time_; + double nextScanTime_; + std::vector scanner_; + int scanned_; +}; + +// --------------------------------------------------------------------------- +// Sistema DES: collezione di processi +// --------------------------------------------------------------------------- +class DiscreteEventSystem { +public: + template + Proc &emplaceProcess(Args &&...args) { + auto ptr = std::make_unique(std::forward(args)...); + Proc &ref = *ptr; + processes_.push_back(std::move(ptr)); + return ref; + } + + void initialize(double startTime = 0.0) { + for (auto &p : processes_) { + p->initialize(startTime); + } + } + + double nextEventTime() const { + if (processes_.empty()) { + return std::numeric_limits::infinity(); + } + + double tMin = std::numeric_limits::infinity(); + for (const auto &p : processes_) { + double t = p->nextEventTime(); + if (t < tMin) + tMin = t; + } + return tMin; + } + + void handleEvent(double currentTime) { + for (auto &p : processes_) { + if (p->nextEventTime() <= currentTime) { + p->handleEvent(currentTime); + } + } + } + +private: + std::vector> processes_; +}; + +// --------------------------------------------------------------------------- +// Simulatore DES +// --------------------------------------------------------------------------- +class DiscreteEventSimulator { +public: + explicit DiscreteEventSimulator(DiscreteEventSystem &system) + : system_(system), currentTime_(0.0) {} + + void run(double horizon) { + system_.initialize(0.0); + currentTime_ = 0.0; + + while (currentTime_ < horizon) { + double nextTime = system_.nextEventTime(); + if (nextTime == std::numeric_limits::infinity()) + break; + currentTime_ = nextTime; + system_.handleEvent(currentTime_); + } + } + + double currentTime() const { return currentTime_; } + +private: + DiscreteEventSystem &system_; + double currentTime_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/4/include/Decision.hpp b/exams/AxelRubini/2025-01-09/4/include/Decision.hpp new file mode 100644 index 0000000..4d37cf0 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/4/include/Decision.hpp @@ -0,0 +1,266 @@ +#pragma once + +#include "Random.hpp" +#include "Stat.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 7. MARKOV CHAIN +// ============================================================================ + +// MarkovChain: Implements a discrete-time Markov chain. +// Purpose: +// - Simulates stochastic processes with defined states and transition +// probabilities. +// - Useful in modeling random systems and computing related probabilities. +// Usage: +// MarkovChain mc(3, rng); +// mc.setTransition(0, 1, 0.5); +// mc.setTransition(0, 2, 0.5); +// mc.setState(0); +// int nextState = mc.step(); +class MarkovChain { +private: + int numStates; + std::vector> transitionMatrix; + int currentState; + RandomGenerator &rng; + +public: + MarkovChain(int states, RandomGenerator &generator) + : numStates(states), currentState(0), rng(generator) { + transitionMatrix.resize(states, std::vector(states, 0.0)); + } + + void setTransition(int from, int to, double probability) { + if (from >= 0 && from < numStates && to >= 0 && to < numStates) { + transitionMatrix[from][to] = probability; + } + } + + void setState(int state) { + if (state >= 0 && state < numStates) { + currentState = state; + } + } + + int getState() const { return currentState; } + + int step() { + currentState = rng.discrete(transitionMatrix[currentState]); + return currentState; + } + + double getTransitionProb(int from, int to) const { + return transitionMatrix[from][to]; + } + + double expectedSojournTime(int state, double timeStep = 1.0) const { + double pii = transitionMatrix[state][state]; + if (pii >= 1.0) + return std::numeric_limits::infinity(); + return timeStep / (1.0 - pii); + } +}; + +// ============================================================================ +// 8. MDP +// ============================================================================ + +// MDP: Implements a Markov Decision Process (MDP) model. +// Purpose: +// - Simulate decision-making processes with states, transitions, and +// associated costs. +// - Calculate the total cost to reach a target state from a starting state. +// Usage: +// - Add transitions between states with associated probabilities and costs +// using `addTransition`. +// - Use `step` to simulate a single transition and calculate its cost. +// - Use `simulate` to estimate the total cost from a start to an end state. +class MDP { +private: + int numStates; + std::map>>> + transitions; + RandomGenerator &rng; + +public: + MDP(int states, RandomGenerator &generator) + : numStates(states), rng(generator) {} + + void addTransition(int from, int to, double probability, double cost) { + transitions[from].push_back({to, {probability, cost}}); + } + + std::pair step(int currentState) { + if (transitions.find(currentState) == transitions.end()) { + return {currentState, 0.0}; + } + + std::vector probs; + for (const auto &t : transitions[currentState]) { + probs.push_back(t.second.first); + } + + int idx = rng.discrete(probs); + int nextState = transitions[currentState][idx].first; + double cost = transitions[currentState][idx].second.second; + + return {nextState, cost}; + } + + double simulate(int startState, int endState) { + int state = startState; + double totalCost = 0.0; + int maxSteps = 100000; + + for (int step = 0; step < maxSteps && state != endState; step++) { + auto [nextState, cost] = this->step(state); + totalCost += cost; + state = nextState; + } + return totalCost; + } +}; + +// ============================================================================ +// 10. MONTE CARLO SIMULATOR +// ============================================================================ + +// MonteCarloSimulator: A utility for running Monte Carlo simulations. +// Purpose: +// - Estimate statistical metrics through repeated random sampling. +// - Provides methods to calculate mean results and probabilities of events. +// Usage: +// MonteCarloSimulator simulator(1000); +// double estimate = simulator.estimate([](int) { return some_random_value; }); +// double probability = simulator.estimateProbability([](int) { return +// some_event_occurs; }); +class MonteCarloSimulator { +private: + Statistics stats; + int numSimulations; + +public: + MonteCarloSimulator(int simulations = 1000) : numSimulations(simulations) {} + + template double estimate(SimFunc simulation) { + stats.clear(); + for (int i = 0; i < numSimulations; i++) { + double result = simulation(i); + stats.addSample(result); + } + return stats.mean(); + } + + template double estimateProbability(SimFunc simulation) { + int successes = 0; + for (int i = 0; i < numSimulations; i++) { + if (simulation(i)) + successes++; + } + return static_cast(successes) / numSimulations; + } + + const Statistics &getStatistics() const { return stats; } +}; + +// ============================================================================ +// 13. OPTIMIZATION UTILITIES +// ============================================================================ + +// Optimizer: Utility class offering optimization techniques such as random +// search and grid search. Purpose: +// - Explore parameter spaces to maximize or minimize an objective function. +// - Supports both continuous and discrete search spaces. +// Usage: +// - Use `randomSearch` for stochastic optimization over a defined range. +// - Use `gridSearch` for systematic exploration in evenly spaced steps. +// Example: +// auto [bestParam, bestValue] = Optimizer::randomSearch(objectiveFunction, +// 0.0, 10.0, 100, rng); auto [bestInt, bestIntValue] = +// Optimizer::randomSearchInt(objectiveFunction, 0, 10, 50, rng); +class Optimizer { +public: + template + static std::pair + randomSearch(ObjFunc objective, double minValue, double maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + for (int i = 0; i < budget; i++) { + double param = rng.uniform(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair + randomSearchInt(ObjFunc objective, int minValue, int maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + int bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + if (budget <= 0) { + throw std::invalid_argument("randomSearchInt: budget must be positive."); + } + + if (minValue >= maxValue) { + throw std::invalid_argument( + "randomSearchInt: minValue must be less than maxValue."); + } + + for (int i = 0; i < budget; i++) { + int param = rng.uniformInt(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair gridSearch(ObjFunc objective, + double minValue, double maxValue, + int steps, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + double stepSize = (maxValue - minValue) / steps; + + if (steps <= 0) { + throw std::invalid_argument("gridSearch: steps must be positive."); + } + + for (int i = 0; i <= steps; i++) { + double param = minValue + i * stepSize; + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/4/include/Dynamics.hpp b/exams/AxelRubini/2025-01-09/4/include/Dynamics.hpp new file mode 100644 index 0000000..044176a --- /dev/null +++ b/exams/AxelRubini/2025-01-09/4/include/Dynamics.hpp @@ -0,0 +1,214 @@ +#pragma once + +#include +#include +namespace SELib { +// ============================================================================ +// 3. ODE SOLVER (Runge-Kutta 4th order) +// ============================================================================ + +using StateVector = std::vector; +using DerivativeFunction = std::function; + +// ODESolver: Implements numerical methods for solving Ordinary Differential +// Equations (ODEs). Purpose: +// - Provides tools for simulating dynamic systems modeled by ODEs. +// - Includes methods such as Runge-Kutta 4th order and Euler's method for +// integration. +// Usage: +// - Pass a derivative function that defines the ODE system. +// - Use `rk4Step` for higher accuracy or `eulerStep` for simpler systems. +// Example: +// StateVector nextState = ODESolver::rk4Step(0.0, currentState, controlInputs, +// dt, dynamicsFunction); +class ODESolver { +public: + static StateVector rk4Step(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector k1 = f(t, x, u); + StateVector x2 = addScaled(x, k1, dt / 2.0); + StateVector k2 = f(t + dt / 2.0, x2, u); + StateVector x3 = addScaled(x, k2, dt / 2.0); + StateVector k3 = f(t + dt / 2.0, x3, u); + StateVector x4 = addScaled(x, k3, dt); + StateVector k4 = f(t + dt, x4, u); + + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += (dt / 6.0) * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]); + } + return xNew; + } + + static StateVector eulerStep(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector dx = f(t, x, u); + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += dx[i] * dt; + } + return xNew; + } + +private: + static StateVector addScaled(const StateVector &x, const StateVector &dx, + double scale) { + StateVector result = x; + for (size_t i = 0; i < x.size(); i++) { + result[i] += dx[i] * scale; + } + return result; + } +}; + +// ============================================================================ +// 4. PID CONTROLLER +// ============================================================================ + +// PIDController: Implements a Proportional-Integral-Derivative (PID) +// controller. Purpose: +// - Automatically adjusts a control signal to minimize the difference between +// a desired setpoint and a measured value. +// - Used in control systems to regulate processes like temperature, speed, or +// position. +// Usage: +// - Create an instance with proportional (Kp), integral (Ki), and derivative +// (Kd) gains, along with a time step. +// - Use `compute` to calculate the control signal based on the setpoint and +// measurement. +// Example: +// PIDController pid(1.0, 0.1, 0.01, 0.1); +// double controlSignal = pid.compute(desiredValue, actualValue); +class PIDController { +private: + double kp, ki, kd; + double integral; + double prevError; + double dt; + +public: + PIDController(double Kp, double Ki, double Kd, double timeStep) + : kp(Kp), ki(Ki), kd(Kd), integral(0), prevError(0), dt(timeStep) {} + + double compute(double setpoint, double measurement) { + double error = setpoint - measurement; + integral += error * dt; + double derivative = (error - prevError) / dt; + prevError = error; + return kp * error + ki * integral + kd * derivative; + } + + void reset() { + integral = 0; + prevError = 0; + } + + void setGains(double Kp, double Ki, double Kd) { + kp = Kp; + ki = Ki; + kd = Kd; + } +}; + +// ============================================================================ +// 5. CONTINUOUS TIME SIMULATION +// ============================================================================ + +// ContinuousTimeSimulation: A base class for simulating continuous-time dynamic +// systems. Purpose: +// - Provides a framework for simulating systems characterized by continuous +// dynamics over time. +// - Integrates system dynamics using a Runge-Kutta 4th order solver. +// Usage: +// - Derive from this class and implement the `dynamics`, `computeControl`, +// `initialize`, and `finalize` methods. +// - Customize the `onStep` method for per-step side effects, if required. +// Example: +// class MySimulation : public ContinuousTimeSimulation { +// // Implement the required virtual methods. +// }; +// MySimulation sim(dt, horizon, initialState); +// sim.run(); +class ContinuousTimeSimulation { +protected: + double currentTime; + double timeStep; + double horizon; + StateVector state; + StateVector control; + +public: + ContinuousTimeSimulation(double dt, double H, const StateVector &initialState) + : currentTime(0.0), timeStep(dt), horizon(H), state(initialState) { + control.resize(initialState.size(), 0.0); + } + + virtual ~ContinuousTimeSimulation() = default; + + virtual StateVector dynamics(double t, const StateVector &x, + const StateVector &u) = 0; + virtual StateVector computeControl(double t, const StateVector &x) = 0; + virtual void initialize() = 0; + virtual void onStep() {} + virtual void finalize() = 0; + + void run() { + initialize(); + while (currentTime < horizon) { + control = computeControl(currentTime, state); + state = ODESolver::rk4Step( + currentTime, state, control, timeStep, + [this](double t, const StateVector &x, const StateVector &u) { + return this->dynamics(t, x, u); + }); + currentTime += timeStep; + onStep(); + } + finalize(); + } + + double getCurrentTime() const { return currentTime; } + const StateVector &getState() const { return state; } + const StateVector &getControl() const { return control; } +}; + +// ============================================================================ +// 6. SAMPLE AND HOLD +// ============================================================================ + +// SampleAndHold: A utility to store a value and update it periodically. +// Purpose: +// - Maintain a value that gets updated periodically based on a time interval. +// - Useful for sampling data and ensuring updates happen at fixed intervals. +// Usage: +// SampleAndHold sampler(5.0, 0); +// if (sampler.shouldUpdate(currentTime)) { +// sampler.update(currentTime, newValue); +// } +// int currentValue = sampler.getValue(); +template class SampleAndHold { +private: + T value; + double lastUpdateTime; + double samplePeriod; + +public: + SampleAndHold(double period, const T &initialValue = T()) + : value(initialValue), lastUpdateTime(0), samplePeriod(period) {} + + bool shouldUpdate(double currentTime) const { + return (currentTime - lastUpdateTime) >= samplePeriod; + } + + void update(double currentTime, const T &newValue) { + value = newValue; + lastUpdateTime = currentTime; + } + + const T &getValue() const { return value; } + void reset() { lastUpdateTime = 0; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/4/include/Geometry.hpp b/exams/AxelRubini/2025-01-09/4/include/Geometry.hpp new file mode 100644 index 0000000..525a91d --- /dev/null +++ b/exams/AxelRubini/2025-01-09/4/include/Geometry.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 14. GEOMETRIC UTILITIES +// ============================================================================ + +// Point2D: Represents a point or vector in 2D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point2D p1(1.0, 2.0), p2(2.0, 3.0); +// double dist = p1.distanceTo(p2); +// Point2D sum = p1 + p2; +struct Point2D { + double x, y; + + Point2D(double x_ = 0, double y_ = 0) : x(x_), y(y_) {} + + double distanceTo(const Point2D &other) const { + double dx = x - other.x; + double dy = y - other.y; + return std::sqrt(dx * dx + dy * dy); + } + + Point2D operator+(const Point2D &other) const { + return Point2D(x + other.x, y + other.y); + } + + Point2D operator-(const Point2D &other) const { + return Point2D(x - other.x, y - other.y); + } +}; + +// Point3D: Represents a point or vector in 3D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point3D p1(1.0, 2.0, 3.0), p2(2.0, 3.0, 4.0); +// double dist = p1.distanceTo(p2); +// Point3D sum = p1 + p2; +struct Point3D { + double x, y, z; + + Point3D(double x_ = 0, double y_ = 0, double z_ = 0) : x(x_), y(y_), z(z_) {} + + double distanceTo(const Point3D &other) const { + double dx = x - other.x; + double dy = y - other.y; + double dz = z - other.z; + return std::sqrt(dx * dx + dy * dy + dz * dz); + } + + Point3D operator+(const Point3D &other) const { + return Point3D(x + other.x, y + other.y, z + other.z); + } + + Point3D operator-(const Point3D &other) const { + return Point3D(x - other.x, y - other.y, z - other.z); + } +}; + +// ============================================================================ +// 19. COLLISION DETECTOR +// ============================================================================ + +// CollisionDetector: A utility class for detecting collisions between points +// in space. Purpose: +// - Counts the number of collisions within a given distance threshold. +// - Retrieves pairs of colliding points. +// Usage: +// std::vector points = {Point2D(0, 0), Point2D(1, 1)}; +// int numCollisions = CollisionDetector::countCollisions(points, 1.5); +// auto collidingPairs = CollisionDetector::getCollidingPairs(points, 1.5); +class CollisionDetector { +public: + template + static int countCollisions(const std::vector &positions, + double threshold) { + int collisions = 0; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + collisions++; + } + } + } + return collisions; + } + + template + static std::vector> + getCollidingPairs(const std::vector &positions, double threshold) { + std::vector> pairs; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + pairs.push_back({i, j}); + } + } + } + return pairs; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/4/include/IO.hpp b/exams/AxelRubini/2025-01-09/4/include/IO.hpp new file mode 100644 index 0000000..1a79cc6 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/4/include/IO.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 11. PARAMETER PARSER +// ============================================================================ + +// ParameterParser: A utility to parse and retrieve parameters from +// configuration files. Purpose: +// - Reads a file and stores key-value pairs for parameters. +// - Allows retrieval of parameters as integers, doubles, or strings. +// - Provides structured data for advanced parsing. +// Usage: +// ParameterParser parser; +// if (parser.parseFile("config.txt")) { +// int param = parser.getInt("example_key", 42); +// } +class ParameterParser { +private: + std::map params; + std::vector> structuredData; + +public: + bool parseFile(const std::string &filename) { + std::ifstream file(filename); + if (!file.is_open()) + return false; + + std::string line; + while (std::getline(file, line)) { + if (line.empty()) + continue; + + std::istringstream iss(line); + std::string key; + iss >> key; + + std::string value; + std::getline(iss, value); + if (!value.empty() && value[0] == ' ') { + value = value.substr(1); + } + + params[key] = value; + + std::vector tokens; + std::istringstream tokenStream(line); + std::string token; + while (tokenStream >> token) { + tokens.push_back(token); + } + structuredData.push_back(tokens); + } + return true; + } + + int getInt(const std::string &key, int defaultValue = 0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stoi(it->second); + } + return defaultValue; + } + + double getDouble(const std::string &key, double defaultValue = 0.0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stod(it->second); + } + return defaultValue; + } + + std::string getString(const std::string &key, + const std::string &defaultValue = "") const { + auto it = params.find(key); + if (it != params.end()) { + return it->second; + } + return defaultValue; + } + + const std::vector> &getStructuredData() const { + return structuredData; + } + + bool hasKey(const std::string &key) const { + return params.find(key) != params.end(); + } +}; + +// ============================================================================ +// 12. OUTPUT WRITER +// ============================================================================ + +// OutputWriter: A utility class for writing data to output files. +// Purpose: +// - Simplifies the process of writing data to files. +// - Supports writing lines, key-value pairs, and managing file headers. +// Usage: +// OutputWriter writer; +// if (writer.open("output.txt", "Header Info")) { +// writer.writeLine("Example Line"); +// writer.writeKeyValue("Key", 42.0); +// writer.close(); +// } +class OutputWriter { +private: + std::ofstream file; + +public: + bool open(const std::string &filename, const std::string &header) { + file.open(filename); + if (!file.is_open()) + return false; + file << header << std::endl; + return true; + } + + template void writeLine(const T &value) { + file << value << std::endl; + } + + template + void writeLine(const T &first, const Args &...args) { + file << first << " "; + writeLine(args...); + } + + void writeKeyValue(const std::string &key, double value, int precision = 2) { + file << key << " " << std::fixed << std::setprecision(precision) << value + << std::endl; + } + + void close() { + if (file.is_open()) { + file.close(); + } + } + + ~OutputWriter() { close(); } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/4/include/Inventory.hpp b/exams/AxelRubini/2025-01-09/4/include/Inventory.hpp new file mode 100644 index 0000000..c82d776 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/4/include/Inventory.hpp @@ -0,0 +1,423 @@ +#pragma once + +#include "DES.hpp" +#include "Random.hpp" +#include +#include + +namespace SELib { +// ============================================================================ +// 16. CUSTOMER MODEL +// ============================================================================ + +// Customer: Represents a customer model in a simulation. +// Purpose: +// - Simulates customer requests over time. +// - Generates random requests for products at specific intervals. +// Usage: +// Customer customer(5.0, 10.0, 3, rng); +// int productId = customer.step(0.1); // Simulates a time step of 0.1 seconds. +class Customer { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer(double A, double B, int N, RandomGenerator &generator) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator) { + nextRequestTime = rng.uniform(A, B); + } + + int step(double dt) { + nextRequestTime -= dt; + if (nextRequestTime <= 0) { + nextRequestTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } +}; +class Customer_Event_Driven { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer_Event_Driven(double A, double B, int N, RandomGenerator &generator, + double nextRequestTime) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator), + nextRequestTime(nextRequestTime) {} + + int step_event_driven(double *current_time) { + if (*current_time >= nextRequestTime) { + nextRequestTime = *current_time + rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + double getNextRequestTime() { return nextRequestTime; }; +}; + +// ============================================================================ +// 17. SERVER MODEL +// ============================================================================ + +// Server: Represents a server in a simulated environment. +// Purpose: +// - Handles product requests and manages inventory. +// - Simulates server busy time during request processing. +// Usage: +// Server server(numProducts, maxInventory, processingTimeNormal, +// processingTimeSupplier, rng); int result = +// server.processRequest(productId, needsSupply); +// server.addInventory(productId, quantityAdded); +class Server { +private: + std::vector inventory; + int busyTime; + int processingTimeNormal; + int processingTimeSupplier; + RandomGenerator *rng; + +public: + Server(int numProducts, int K, int F, int G, RandomGenerator &rng) + : inventory(numProducts + 1, 0), busyTime(0), processingTimeNormal(F), + processingTimeSupplier(G), rng(&rng) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + + bool isBusy() const { return busyTime > 0; } + + void decrementBusyTime() { + if (busyTime > 0) + busyTime--; + } + + int processRequest(int productId, bool needsSupply) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return productId; + } else { + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return -productId; + } + } + + int processRequest2(int productId, double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = processingTimeNormal; + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + busyTime = processingTimeSupplier; + return -productId; + } + } + + void addInventory(int productId, int quantity) { + inventory[productId] += quantity; + } + + int getInventory(int productId) const { return inventory[productId]; } +}; +// ============================================================================ +// 17.5 SERVER-EVENT-DRIVEN +// ============================================================================ +class Server_Event_Driven { +private: + std::vector inventory; + RandomGenerator *rng; + double next_step_time; + double min_interval; + double max_interval; + +public: + Server_Event_Driven(int numProducts, int K, double min_interval, + double max_interval, RandomGenerator &rng, + double nextStepTime) + : inventory(numProducts + 1, 0), rng(&rng), min_interval(min_interval), + max_interval(max_interval), next_step_time(nextStepTime) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + int processRequestEventDriven(double current_time, int productId, + double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (current_time >= next_step_time) { + if (inventory[productId] > 0) { + inventory[productId]--; + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + + return -productId; + } + } else { + return 0; + } + } + double getNextStepTime() { return next_step_time; } +}; + +// ============================================================================ +// 18. SUPPLIER MODEL +// ============================================================================ + +// Supplier: Represents a supplier in a simulation. +// Purpose: +// - Simulates the supply of products at specific intervals. +// - Generates random supply requests periodically based on the defined +// intervals. +// Usage: +// Supplier supplier(minInterval, maxInterval, numProducts, rng); +// int suppliedProduct = supplier.step(timeStep); +// supplier.reset(); +class Supplier { +private: + double nextSupplyTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Supplier(double V, double Q, int N, RandomGenerator &generator) + : minInterval(V), maxInterval(Q), numProducts(N), rng(generator) { + nextSupplyTime = rng.uniform(V, Q); + } + + int step(double dt) { + nextSupplyTime -= dt; + if (nextSupplyTime <= 0) { + nextSupplyTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + void reset() { nextSupplyTime = rng.uniform(minInterval, maxInterval); } +}; + +// Server di inventario con messaggi (customer/server network-style) +class InventoryServerProcess : public DiscreteEventProcess { +public: + InventoryServerProcess(int pid, int numItems, int initialStockPerItem, + double serviceTime, MessageBus &bus, + RandomGenerator &rng) + : pid_(pid), serviceTime_(serviceTime), bus_(bus), rng_(rng), + nextEventTime_(0.0), + storage_(static_cast(numItems), initialStockPerItem), + totalRequests_(0), missedRequests_(0) {} + + void initialize(double startTime) override { + nextEventTime_ = startTime + serviceTime_; + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // processa TUTTI i messaggi arrivati a questo server + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + totalRequests_++; + + // sicurezza sugli indici prodotto + if (m.item < 0 || static_cast(m.item) >= storage_.size()) { + continue; // scarta richieste invalide + } + + double servedQty = 0.0; + int &stock = storage_[static_cast(m.item)]; + + if (stock >= static_cast(m.quantity)) { + // richiesta soddisfatta + stock -= static_cast(m.quantity); + servedQty = m.quantity; + } else { + // non abbastanza stock + servedQty = static_cast(stock); + missedRequests_++; + stock = 0; + } + + // risposta al cliente + Message reply; + reply.time = currentTime; + reply.sender = pid_; + reply.receiver = m.sender; // rimanda al mittente + reply.item = m.item; + reply.quantity = servedQty; + + bus_.procToNet(pid_).push(reply); + + // eventuale logica di refill: esempio semplice + if (stock == 0) { + int refill = rng_.uniformInt(1, 10); // batch di rifornimento random + stock += refill; + } + } + + nextEventTime_ = currentTime + serviceTime_; + } + + const std::vector &storage() const { return storage_; } + + int totalRequests() const { return totalRequests_; } + int missedRequests() const { return missedRequests_; } + +private: + int pid_; + double serviceTime_; + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + std::vector storage_; + + int totalRequests_; + int missedRequests_; +}; + +// ============================================================================ +// 19. INVENTORY CUSTOMER PROCESS (DES-compatible) +// ============================================================================ +// +// InventoryCustomerProcess: +// - Processo a eventi discreti che rappresenta un cliente. +// - Invia richieste al server tramite MessageBus (procToNet -> NetworkRouter). +// - Legge le risposte dal canale netToProc. +// - Statistiche: quante richieste ha mandato e quante risposte ha ricevuto. +// +class InventoryCustomerProcess : public DiscreteEventProcess { +public: + // pid : id di questo processo (indice nei canali del MessageBus) + // serverPid : pid del server a cui mandare le richieste + // numItems : numero di prodotti gestiti (item in [0, numItems-1]) + // minInterval/maxInterval : intervallo di tempo tra una richiesta e la + // successiva + InventoryCustomerProcess(int pid, int serverPid, int numItems, + double minInterval, double maxInterval, + MessageBus &bus, RandomGenerator &rng) + : pid_(pid), serverPid_(serverPid), numItems_(numItems), + minInterval_(minInterval), maxInterval_(maxInterval), bus_(bus), + rng_(rng), nextEventTime_(0.0), issuedRequests_(0), receivedReplies_(0), + totalRequestedQty_(0.0), totalServedQty_(0.0) {} + + // inizializza il prossimo evento (prima richiesta) + void initialize(double startTime) override { + nextEventTime_ = startTime + rng_.uniform(minInterval_, maxInterval_); + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // 1) genera una nuova richiesta al server + const int item = rng_.uniformInt( + 0, numItems_ - 1); // coerente con InventoryServerProcess + const int qty = rng_.uniformInt(1, 3); // quantità richiesta (1..3) + + Message req; + req.time = currentTime; + req.sender = pid_; + req.receiver = serverPid_; + req.item = item; + req.quantity = static_cast(qty); + + bus_.procToNet(pid_).push(req); + issuedRequests_++; + totalRequestedQty_ += req.quantity; + + // 2) processa tutte le risposte arrivate su questo cliente + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + // opzionale: possiamo filtrare solo messaggi dal serverPid_ + // if (m.sender != serverPid_) continue; + + receivedReplies_++; + totalServedQty_ += m.quantity; + } + + // 3) pianifica la prossima richiesta + nextEventTime_ = currentTime + rng_.uniform(minInterval_, maxInterval_); + } + + // --- metriche accessibili dall’esterno --- + + int pid() const { return pid_; } + int serverPid() const { return serverPid_; } + + int issuedRequests() const { return issuedRequests_; } + int receivedReplies() const { return receivedReplies_; } + + double totalRequestedQty() const { return totalRequestedQty_; } + double totalServedQty() const { return totalServedQty_; } + + // fill-rate lato customer + double serviceLevel() const { + if (totalRequestedQty_ <= 0.0) + return 1.0; + return totalServedQty_ / totalRequestedQty_; + } + +private: + int pid_; + int serverPid_; + int numItems_; + double minInterval_; + double maxInterval_; + + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + + int issuedRequests_; + int receivedReplies_; + double totalRequestedQty_; + double totalServedQty_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/4/include/Queue.hpp b/exams/AxelRubini/2025-01-09/4/include/Queue.hpp new file mode 100644 index 0000000..0c12ce8 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/4/include/Queue.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 9. FIFO QUEUE +// ============================================================================ + +// FIFOQueue: Implements a first-in-first-out (FIFO) queue with a fixed maximum +// size. Purpose: +// - Enqueue and dequeue items in FIFO order. +// - Track statistics such as total items enqueued and dequeued. +// - Support dynamic size constraints. +// Usage: +// FIFOQueue queue(10); +// queue.enqueue(5); +// int item; +// if (queue.dequeue(item)) { +// // Process item. +// } +template class FIFOQueue { +private: + std::queue queue; + size_t maxSize; + size_t totalEnqueued; + size_t totalDequeued; + +public: + FIFOQueue(size_t max = SIZE_MAX) + : maxSize(max), totalEnqueued(0), totalDequeued(0) {} + + bool enqueue(const T &item) { + if (queue.size() >= maxSize) + return false; + queue.push(item); + totalEnqueued++; + return true; + } + + bool dequeue(T &item) { + if (queue.empty()) + return false; + item = queue.front(); + queue.pop(); + totalDequeued++; + return true; + } + + bool isEmpty() const { return queue.empty(); } + bool isFull() const { return queue.size() >= maxSize; } + size_t size() const { return queue.size(); } + size_t getTotalEnqueued() const { return totalEnqueued; } + size_t getTotalDequeued() const { return totalDequeued; } + std::queue getQueue() const { return queue; } + T getFirst() const { + if (queue.empty()) + throw std::runtime_error("Queue is empty"); + return queue.front(); + } + + void clear() { + while (!queue.empty()) + queue.pop(); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/4/include/Random.hpp b/exams/AxelRubini/2025-01-09/4/include/Random.hpp new file mode 100644 index 0000000..52356f9 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/4/include/Random.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include // std::isfinite +#include // std::accumulate +#include +#include +#include + +namespace SELib { + +class RandomGenerator { +private: + std::mt19937 gen; + +public: + explicit RandomGenerator(unsigned seed = std::random_device{}()) + : gen(seed) {} + + double uniform(double min, double max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_real_distribution dis(lo, high); + return dis(gen); + } + + int uniformInt(int min, int max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_int_distribution dis(lo, high); + return dis(gen); + } + + double gaussian(double mean, double stddev) { + if (stddev < 0.0) { + throw std::invalid_argument("gaussian: stddev must be >= 0"); + } + std::normal_distribution dis(mean, stddev); + return dis(gen); + } + + bool bernoulli(double p) { + if (p < 0.0 || p > 1.0 || !std::isfinite(p)) { + throw std::invalid_argument("bernoulli: p must be in [0,1]"); + } + std::bernoulli_distribution dis(p); + return dis(gen); + } + + int discrete(const std::vector &probabilities) { + if (probabilities.empty()) { + throw std::invalid_argument("discrete: empty probability vector"); + } + + for (double w : probabilities) { + if (w < 0.0 || !std::isfinite(w)) { + throw std::invalid_argument( + "discrete: weights must be non-negative and finite"); + } + } + + double sum = + std::accumulate(probabilities.begin(), probabilities.end(), 0.0); + if (sum <= 0.0) { + throw std::invalid_argument("discrete: sum of weights must be > 0"); + } + + std::discrete_distribution dis(probabilities.begin(), + probabilities.end()); + return dis(gen); + } + + void setSeed(unsigned seed) { gen.seed(seed); } + + // (opzionale) accesso al motore se ti serve per altre distribuzioni + std::mt19937 &engine() { return gen; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/4/include/Stat.hpp b/exams/AxelRubini/2025-01-09/4/include/Stat.hpp new file mode 100644 index 0000000..27b4656 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/4/include/Stat.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 2. STATISTICS +// ============================================================================ + +// Statistics: A utility class for managing and analyzing numerical data +// samples. Purpose: +// - Compute statistical metrics such as mean and standard deviation. +// - Estimate probabilities of custom conditions. +// - Check convergence of data over iterations. +// Usage: +// Statistics stats; +// stats.addSample(5.0); +// double meanValue = stats.mean(); +class Statistics { +private: + std::vector samples; + +public: + void addSample(double value) { samples.push_back(value); } + + void clear() { samples.clear(); } + + double mean() const { + if (samples.empty()) + return 0.0; + double sum = 0.0; + for (double s : samples) + sum += s; + return sum / samples.size(); + } + + double stddev() const { + if (samples.size() < 2) + return 0.0; + double m = mean(); + double variance = 0.0; + for (double s : samples) { + variance += (s - m) * (s - m); + } + return std::sqrt(variance / (samples.size() - 1)); + } + + double probability(std::function condition) const { + if (samples.empty()) + return 0.0; + int count = 0; + for (double s : samples) { + if (condition(s)) + count++; + } + return static_cast(count) / samples.size(); + } + + size_t size() const { return samples.size(); } + const std::vector &getSamples() const { return samples; } + + bool hasConverged(double tolerance = 0.1) const { + if (samples.size() < 30) + return false; + double m = mean(); + double sd = stddev(); + return sd <= tolerance * std::abs(m); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/4/main b/exams/AxelRubini/2025-01-09/4/main new file mode 100755 index 0000000000000000000000000000000000000000..af8698c9a67bf5b836346fb499dcf0f9f7f41778 GIT binary patch literal 78288 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

l=VD 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 literal 0 HcmV?d00001 diff --git a/exams/AxelRubini/2025-01-09/4/main.cpp b/exams/AxelRubini/2025-01-09/4/main.cpp new file mode 100644 index 0000000..97de4ea --- /dev/null +++ b/exams/AxelRubini/2025-01-09/4/main.cpp @@ -0,0 +1,191 @@ +#include "include/DES.hpp" +#include "include/IO.hpp" +#include "include/Random.hpp" +#include "include/Stat.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace SELib { + +class DispatcherCustomer : public DiscreteEventProcess { + public: + DispatcherCustomer( + int pid, + int dispatcherPid, + MessageBus &bus, + RandomGenerator &rng, + double avg, + double stddev + ) + : pid_(pid), dispatcherPid_(dispatcherPid), bus_(bus), rng_(rng), + avg_(avg), stddev_(stddev) {} + + void initialize(double startTime) override { scheduleNext(startTime); } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + Message msg; + msg.time = currentTime; + msg.sender = pid_; + msg.receiver = dispatcherPid_; + bus_.procToNet(pid_).push(msg); + + scheduleNext(currentTime); + } + + private: + void scheduleNext(double currentTime) { + double delay = rng_.gaussian(avg_, stddev_); + if (delay < 1.0) + delay = 1.0; // T=1 is the minimum step + nextEventTime_ = currentTime + delay; + } + + int pid_; + int dispatcherPid_; + MessageBus &bus_; + RandomGenerator &rng_; + double avg_; + double stddev_; + double nextEventTime_; +}; + +class Dispatcher : public DiscreteEventProcess { + public: + Dispatcher(int pid, int monitorPid, int numCustomers, MessageBus &bus) + : pid_(pid), monitorPid_(monitorPid), numCustomers_(numCustomers), + bus_(bus) { + for (int i = 0; i < numCustomers; ++i) { + inputCounts_[i] = 0; + } + } + + void initialize(double startTime) override {} + + double nextEventTime() const override { + return std::numeric_limits::infinity(); + } + + void handleEvent(double currentTime) override {} + + void processInbox(double currentTime) { + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + if (m.sender >= 0 && m.sender < numCustomers_) { + inputCounts_[m.sender]++; + } + + Message forward = m; + forward.sender = pid_; + forward.receiver = monitorPid_; + bus_.procToNet(pid_).push(forward); + } + } + + int getCount(int customerId) const { + auto it = inputCounts_.find(customerId); + return it != inputCounts_.end() ? it->second : 0; + } + + private: + int pid_; + int monitorPid_; + int numCustomers_; + MessageBus &bus_; + std::map inputCounts_; +}; + +class DispatcherMonitor : public DiscreteEventProcess { + public: + DispatcherMonitor(int pid, MessageBus &bus) + : pid_(pid), bus_(bus), totalReceived_(0) {} + + void initialize(double startTime) override {} + + double nextEventTime() const override { + return std::numeric_limits::infinity(); + } + + void handleEvent(double currentTime) override {} + + void processInbox(double currentTime) { + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + inbox.pop(); + totalReceived_++; + } + } + + int getTotalReceived() const { return totalReceived_; } + + private: + int pid_; + MessageBus &bus_; + int totalReceived_; +}; + +} // namespace SELib + +int main() { + SELib::RandomGenerator rng; + SELib::ParameterParser parser; + if (!parser.parseFile("parameters.txt")) { + return 1; + } + + int N = parser.getInt("N", 0); + double avg = parser.getDouble("Avg", 0.0); + double stddev = parser.getDouble("StdDev", 0.0); + + int dispatcherPid = N; + int monitorPid = N + 1; + SELib::MessageBus bus(N + 2); + SELib::DiscreteEventSystem system; + + for (int i = 0; i < N; ++i) { + system.emplaceProcess( + i, dispatcherPid, bus, rng, avg, stddev + ); + } + auto &dispatcher = system.emplaceProcess( + dispatcherPid, monitorPid, N, bus + ); + auto &monitor = + system.emplaceProcess(monitorPid, bus); + system.emplaceProcess( + bus, rng, 0.1 + ); // Back to a sane default + + double currentTime = 0.0; + double horizon = 1000000.0; + system.initialize(0.0); + + while (currentTime < horizon) { + double nextTime = system.nextEventTime(); + if (nextTime > horizon) + break; + currentTime = nextTime; + system.handleEvent(currentTime); + dispatcher.processInbox(currentTime); + monitor.processInbox(currentTime); + } + + std::ofstream outFile("results.txt"); + outFile << "2025-01-09-Axel-Rubini-2158099" << std::endl; + for (int i = 0; i < N; ++i) { + outFile << (i + 1) << " " << dispatcher.getCount(i) << std::endl; + } + outFile << "M1 " << monitor.getTotalReceived() << std::endl; + outFile.close(); + + return 0; +} diff --git a/exams/AxelRubini/2025-01-09/4/parameters.txt b/exams/AxelRubini/2025-01-09/4/parameters.txt new file mode 100644 index 0000000..c1e8d88 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/4/parameters.txt @@ -0,0 +1,3 @@ +N 7 +Avg 1500 +StdDev 150 diff --git a/exams/AxelRubini/2025-01-09/4/results.txt b/exams/AxelRubini/2025-01-09/4/results.txt new file mode 100644 index 0000000..f991aaf --- /dev/null +++ b/exams/AxelRubini/2025-01-09/4/results.txt @@ -0,0 +1,9 @@ +2025-01-09-Axel-Rubini-2158099 +1 666 +2 672 +3 669 +4 669 +5 667 +6 667 +7 664 +M1 4674 diff --git a/exams/AxelRubini/2025-01-09/5/Makefile b/exams/AxelRubini/2025-01-09/5/Makefile new file mode 100644 index 0000000..6910fc5 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/5/Makefile @@ -0,0 +1,10 @@ +CC=g++ +CFLAGS=-std=c++23 -O3 -Wall + +all: main + +main: main.cpp + $(CC) $(CFLAGS) main.cpp -o main + +clean: + rm -f main *.o diff --git a/exams/AxelRubini/2025-01-09/5/include/DES.hpp b/exams/AxelRubini/2025-01-09/5/include/DES.hpp new file mode 100644 index 0000000..1706b8f --- /dev/null +++ b/exams/AxelRubini/2025-01-09/5/include/DES.hpp @@ -0,0 +1,212 @@ + +#pragma once + +#include "Random.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// --------------------------------------------------------------------------- +// Messaggio +// --------------------------------------------------------------------------- +struct Message { + double time{0.0}; + int sender{-1}; + int receiver{-1}; + int item{0}; + double quantity{0.0}; +}; + +// --------------------------------------------------------------------------- +// Bus di messaggi: process -> network, network -> process +// --------------------------------------------------------------------------- +class MessageBus { +public: + explicit MessageBus(std::size_t n = 0) { resize(n); } + + void resize(std::size_t n) { + procToNet_.assign(n, {}); + netToProc_.assign(n, {}); + } + + std::size_t size() const { return procToNet_.size(); } + + std::queue &procToNet(int pid) { return procToNet_.at(pid); } + std::queue &netToProc(int pid) { return netToProc_.at(pid); } + const std::queue &procToNet(int pid) const { + return procToNet_.at(pid); + } + const std::queue &netToProc(int pid) const { + return netToProc_.at(pid); + } + +private: + std::vector> procToNet_; + std::vector> netToProc_; +}; + +// --------------------------------------------------------------------------- +// Processo base a eventi discreti +// --------------------------------------------------------------------------- +class DiscreteEventProcess { +public: + virtual ~DiscreteEventProcess() = default; + virtual void initialize(double startTime) = 0; + virtual double nextEventTime() const = 0; + virtual void handleEvent(double currentTime) = 0; +}; + +// --------------------------------------------------------------------------- +// NetworkRouter: inoltra i messaggi P->N->P +// --------------------------------------------------------------------------- +class NetworkRouter : public DiscreteEventProcess { +public: + NetworkRouter(MessageBus &bus, RandomGenerator &rng, double scanPeriod = 0.1) + : bus_(bus), rng_(rng), scanPeriod_(scanPeriod), time_(0.0), + nextScanTime_(0.0), scanned_(0) {} + + void initialize(double startTime) override { + time_ = startTime; + nextScanTime_ = startTime + scanPeriod_; + + const int n = static_cast(bus_.size()); + if (n <= 0) { + throw std::runtime_error( + "NetworkRouter: no processes configured in MessageBus"); + } + + scanner_.resize(n); + for (int i = 0; i < n; ++i) { + scanner_[i] = i; + } + reshuffleScanner(); + scanned_ = 0; + } + + double nextEventTime() const override { return nextScanTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextScanTime_) + return; + + const int n = static_cast(scanner_.size()); + if (n == 0) + return; + + if (scanned_ >= n) { + scanned_ = 0; + reshuffleScanner(); + } + + const int channel = scanner_[scanned_++]; + auto &fromQueue = bus_.procToNet(channel); + + if (!fromQueue.empty()) { + Message m = fromQueue.front(); + fromQueue.pop(); + + if (m.receiver < 0 || m.receiver >= static_cast(bus_.size())) { + throw std::runtime_error("NetworkRouter: invalid receiver index"); + } + + bus_.netToProc(m.receiver).push(m); + } + + nextScanTime_ = currentTime + scanPeriod_; + } + +private: + void reshuffleScanner() { + auto &eng = rng_.engine(); // serve engine() in RandomGenerator + std::shuffle(scanner_.begin(), scanner_.end(), eng); + } + + MessageBus &bus_; + RandomGenerator &rng_; + + double scanPeriod_; + double time_; + double nextScanTime_; + std::vector scanner_; + int scanned_; +}; + +// --------------------------------------------------------------------------- +// Sistema DES: collezione di processi +// --------------------------------------------------------------------------- +class DiscreteEventSystem { +public: + template + Proc &emplaceProcess(Args &&...args) { + auto ptr = std::make_unique(std::forward(args)...); + Proc &ref = *ptr; + processes_.push_back(std::move(ptr)); + return ref; + } + + void initialize(double startTime = 0.0) { + for (auto &p : processes_) { + p->initialize(startTime); + } + } + + double nextEventTime() const { + if (processes_.empty()) { + return std::numeric_limits::infinity(); + } + + double tMin = std::numeric_limits::infinity(); + for (const auto &p : processes_) { + double t = p->nextEventTime(); + if (t < tMin) + tMin = t; + } + return tMin; + } + + void handleEvent(double currentTime) { + for (auto &p : processes_) { + if (p->nextEventTime() <= currentTime) { + p->handleEvent(currentTime); + } + } + } + +private: + std::vector> processes_; +}; + +// --------------------------------------------------------------------------- +// Simulatore DES +// --------------------------------------------------------------------------- +class DiscreteEventSimulator { +public: + explicit DiscreteEventSimulator(DiscreteEventSystem &system) + : system_(system), currentTime_(0.0) {} + + void run(double horizon) { + system_.initialize(0.0); + currentTime_ = 0.0; + + while (currentTime_ < horizon) { + double nextTime = system_.nextEventTime(); + if (nextTime == std::numeric_limits::infinity()) + break; + currentTime_ = nextTime; + system_.handleEvent(currentTime_); + } + } + + double currentTime() const { return currentTime_; } + +private: + DiscreteEventSystem &system_; + double currentTime_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/5/include/Decision.hpp b/exams/AxelRubini/2025-01-09/5/include/Decision.hpp new file mode 100644 index 0000000..4d37cf0 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/5/include/Decision.hpp @@ -0,0 +1,266 @@ +#pragma once + +#include "Random.hpp" +#include "Stat.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 7. MARKOV CHAIN +// ============================================================================ + +// MarkovChain: Implements a discrete-time Markov chain. +// Purpose: +// - Simulates stochastic processes with defined states and transition +// probabilities. +// - Useful in modeling random systems and computing related probabilities. +// Usage: +// MarkovChain mc(3, rng); +// mc.setTransition(0, 1, 0.5); +// mc.setTransition(0, 2, 0.5); +// mc.setState(0); +// int nextState = mc.step(); +class MarkovChain { +private: + int numStates; + std::vector> transitionMatrix; + int currentState; + RandomGenerator &rng; + +public: + MarkovChain(int states, RandomGenerator &generator) + : numStates(states), currentState(0), rng(generator) { + transitionMatrix.resize(states, std::vector(states, 0.0)); + } + + void setTransition(int from, int to, double probability) { + if (from >= 0 && from < numStates && to >= 0 && to < numStates) { + transitionMatrix[from][to] = probability; + } + } + + void setState(int state) { + if (state >= 0 && state < numStates) { + currentState = state; + } + } + + int getState() const { return currentState; } + + int step() { + currentState = rng.discrete(transitionMatrix[currentState]); + return currentState; + } + + double getTransitionProb(int from, int to) const { + return transitionMatrix[from][to]; + } + + double expectedSojournTime(int state, double timeStep = 1.0) const { + double pii = transitionMatrix[state][state]; + if (pii >= 1.0) + return std::numeric_limits::infinity(); + return timeStep / (1.0 - pii); + } +}; + +// ============================================================================ +// 8. MDP +// ============================================================================ + +// MDP: Implements a Markov Decision Process (MDP) model. +// Purpose: +// - Simulate decision-making processes with states, transitions, and +// associated costs. +// - Calculate the total cost to reach a target state from a starting state. +// Usage: +// - Add transitions between states with associated probabilities and costs +// using `addTransition`. +// - Use `step` to simulate a single transition and calculate its cost. +// - Use `simulate` to estimate the total cost from a start to an end state. +class MDP { +private: + int numStates; + std::map>>> + transitions; + RandomGenerator &rng; + +public: + MDP(int states, RandomGenerator &generator) + : numStates(states), rng(generator) {} + + void addTransition(int from, int to, double probability, double cost) { + transitions[from].push_back({to, {probability, cost}}); + } + + std::pair step(int currentState) { + if (transitions.find(currentState) == transitions.end()) { + return {currentState, 0.0}; + } + + std::vector probs; + for (const auto &t : transitions[currentState]) { + probs.push_back(t.second.first); + } + + int idx = rng.discrete(probs); + int nextState = transitions[currentState][idx].first; + double cost = transitions[currentState][idx].second.second; + + return {nextState, cost}; + } + + double simulate(int startState, int endState) { + int state = startState; + double totalCost = 0.0; + int maxSteps = 100000; + + for (int step = 0; step < maxSteps && state != endState; step++) { + auto [nextState, cost] = this->step(state); + totalCost += cost; + state = nextState; + } + return totalCost; + } +}; + +// ============================================================================ +// 10. MONTE CARLO SIMULATOR +// ============================================================================ + +// MonteCarloSimulator: A utility for running Monte Carlo simulations. +// Purpose: +// - Estimate statistical metrics through repeated random sampling. +// - Provides methods to calculate mean results and probabilities of events. +// Usage: +// MonteCarloSimulator simulator(1000); +// double estimate = simulator.estimate([](int) { return some_random_value; }); +// double probability = simulator.estimateProbability([](int) { return +// some_event_occurs; }); +class MonteCarloSimulator { +private: + Statistics stats; + int numSimulations; + +public: + MonteCarloSimulator(int simulations = 1000) : numSimulations(simulations) {} + + template double estimate(SimFunc simulation) { + stats.clear(); + for (int i = 0; i < numSimulations; i++) { + double result = simulation(i); + stats.addSample(result); + } + return stats.mean(); + } + + template double estimateProbability(SimFunc simulation) { + int successes = 0; + for (int i = 0; i < numSimulations; i++) { + if (simulation(i)) + successes++; + } + return static_cast(successes) / numSimulations; + } + + const Statistics &getStatistics() const { return stats; } +}; + +// ============================================================================ +// 13. OPTIMIZATION UTILITIES +// ============================================================================ + +// Optimizer: Utility class offering optimization techniques such as random +// search and grid search. Purpose: +// - Explore parameter spaces to maximize or minimize an objective function. +// - Supports both continuous and discrete search spaces. +// Usage: +// - Use `randomSearch` for stochastic optimization over a defined range. +// - Use `gridSearch` for systematic exploration in evenly spaced steps. +// Example: +// auto [bestParam, bestValue] = Optimizer::randomSearch(objectiveFunction, +// 0.0, 10.0, 100, rng); auto [bestInt, bestIntValue] = +// Optimizer::randomSearchInt(objectiveFunction, 0, 10, 50, rng); +class Optimizer { +public: + template + static std::pair + randomSearch(ObjFunc objective, double minValue, double maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + for (int i = 0; i < budget; i++) { + double param = rng.uniform(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair + randomSearchInt(ObjFunc objective, int minValue, int maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + int bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + if (budget <= 0) { + throw std::invalid_argument("randomSearchInt: budget must be positive."); + } + + if (minValue >= maxValue) { + throw std::invalid_argument( + "randomSearchInt: minValue must be less than maxValue."); + } + + for (int i = 0; i < budget; i++) { + int param = rng.uniformInt(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair gridSearch(ObjFunc objective, + double minValue, double maxValue, + int steps, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + double stepSize = (maxValue - minValue) / steps; + + if (steps <= 0) { + throw std::invalid_argument("gridSearch: steps must be positive."); + } + + for (int i = 0; i <= steps; i++) { + double param = minValue + i * stepSize; + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/5/include/Dynamics.hpp b/exams/AxelRubini/2025-01-09/5/include/Dynamics.hpp new file mode 100644 index 0000000..044176a --- /dev/null +++ b/exams/AxelRubini/2025-01-09/5/include/Dynamics.hpp @@ -0,0 +1,214 @@ +#pragma once + +#include +#include +namespace SELib { +// ============================================================================ +// 3. ODE SOLVER (Runge-Kutta 4th order) +// ============================================================================ + +using StateVector = std::vector; +using DerivativeFunction = std::function; + +// ODESolver: Implements numerical methods for solving Ordinary Differential +// Equations (ODEs). Purpose: +// - Provides tools for simulating dynamic systems modeled by ODEs. +// - Includes methods such as Runge-Kutta 4th order and Euler's method for +// integration. +// Usage: +// - Pass a derivative function that defines the ODE system. +// - Use `rk4Step` for higher accuracy or `eulerStep` for simpler systems. +// Example: +// StateVector nextState = ODESolver::rk4Step(0.0, currentState, controlInputs, +// dt, dynamicsFunction); +class ODESolver { +public: + static StateVector rk4Step(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector k1 = f(t, x, u); + StateVector x2 = addScaled(x, k1, dt / 2.0); + StateVector k2 = f(t + dt / 2.0, x2, u); + StateVector x3 = addScaled(x, k2, dt / 2.0); + StateVector k3 = f(t + dt / 2.0, x3, u); + StateVector x4 = addScaled(x, k3, dt); + StateVector k4 = f(t + dt, x4, u); + + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += (dt / 6.0) * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]); + } + return xNew; + } + + static StateVector eulerStep(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector dx = f(t, x, u); + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += dx[i] * dt; + } + return xNew; + } + +private: + static StateVector addScaled(const StateVector &x, const StateVector &dx, + double scale) { + StateVector result = x; + for (size_t i = 0; i < x.size(); i++) { + result[i] += dx[i] * scale; + } + return result; + } +}; + +// ============================================================================ +// 4. PID CONTROLLER +// ============================================================================ + +// PIDController: Implements a Proportional-Integral-Derivative (PID) +// controller. Purpose: +// - Automatically adjusts a control signal to minimize the difference between +// a desired setpoint and a measured value. +// - Used in control systems to regulate processes like temperature, speed, or +// position. +// Usage: +// - Create an instance with proportional (Kp), integral (Ki), and derivative +// (Kd) gains, along with a time step. +// - Use `compute` to calculate the control signal based on the setpoint and +// measurement. +// Example: +// PIDController pid(1.0, 0.1, 0.01, 0.1); +// double controlSignal = pid.compute(desiredValue, actualValue); +class PIDController { +private: + double kp, ki, kd; + double integral; + double prevError; + double dt; + +public: + PIDController(double Kp, double Ki, double Kd, double timeStep) + : kp(Kp), ki(Ki), kd(Kd), integral(0), prevError(0), dt(timeStep) {} + + double compute(double setpoint, double measurement) { + double error = setpoint - measurement; + integral += error * dt; + double derivative = (error - prevError) / dt; + prevError = error; + return kp * error + ki * integral + kd * derivative; + } + + void reset() { + integral = 0; + prevError = 0; + } + + void setGains(double Kp, double Ki, double Kd) { + kp = Kp; + ki = Ki; + kd = Kd; + } +}; + +// ============================================================================ +// 5. CONTINUOUS TIME SIMULATION +// ============================================================================ + +// ContinuousTimeSimulation: A base class for simulating continuous-time dynamic +// systems. Purpose: +// - Provides a framework for simulating systems characterized by continuous +// dynamics over time. +// - Integrates system dynamics using a Runge-Kutta 4th order solver. +// Usage: +// - Derive from this class and implement the `dynamics`, `computeControl`, +// `initialize`, and `finalize` methods. +// - Customize the `onStep` method for per-step side effects, if required. +// Example: +// class MySimulation : public ContinuousTimeSimulation { +// // Implement the required virtual methods. +// }; +// MySimulation sim(dt, horizon, initialState); +// sim.run(); +class ContinuousTimeSimulation { +protected: + double currentTime; + double timeStep; + double horizon; + StateVector state; + StateVector control; + +public: + ContinuousTimeSimulation(double dt, double H, const StateVector &initialState) + : currentTime(0.0), timeStep(dt), horizon(H), state(initialState) { + control.resize(initialState.size(), 0.0); + } + + virtual ~ContinuousTimeSimulation() = default; + + virtual StateVector dynamics(double t, const StateVector &x, + const StateVector &u) = 0; + virtual StateVector computeControl(double t, const StateVector &x) = 0; + virtual void initialize() = 0; + virtual void onStep() {} + virtual void finalize() = 0; + + void run() { + initialize(); + while (currentTime < horizon) { + control = computeControl(currentTime, state); + state = ODESolver::rk4Step( + currentTime, state, control, timeStep, + [this](double t, const StateVector &x, const StateVector &u) { + return this->dynamics(t, x, u); + }); + currentTime += timeStep; + onStep(); + } + finalize(); + } + + double getCurrentTime() const { return currentTime; } + const StateVector &getState() const { return state; } + const StateVector &getControl() const { return control; } +}; + +// ============================================================================ +// 6. SAMPLE AND HOLD +// ============================================================================ + +// SampleAndHold: A utility to store a value and update it periodically. +// Purpose: +// - Maintain a value that gets updated periodically based on a time interval. +// - Useful for sampling data and ensuring updates happen at fixed intervals. +// Usage: +// SampleAndHold sampler(5.0, 0); +// if (sampler.shouldUpdate(currentTime)) { +// sampler.update(currentTime, newValue); +// } +// int currentValue = sampler.getValue(); +template class SampleAndHold { +private: + T value; + double lastUpdateTime; + double samplePeriod; + +public: + SampleAndHold(double period, const T &initialValue = T()) + : value(initialValue), lastUpdateTime(0), samplePeriod(period) {} + + bool shouldUpdate(double currentTime) const { + return (currentTime - lastUpdateTime) >= samplePeriod; + } + + void update(double currentTime, const T &newValue) { + value = newValue; + lastUpdateTime = currentTime; + } + + const T &getValue() const { return value; } + void reset() { lastUpdateTime = 0; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/5/include/Geometry.hpp b/exams/AxelRubini/2025-01-09/5/include/Geometry.hpp new file mode 100644 index 0000000..525a91d --- /dev/null +++ b/exams/AxelRubini/2025-01-09/5/include/Geometry.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 14. GEOMETRIC UTILITIES +// ============================================================================ + +// Point2D: Represents a point or vector in 2D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point2D p1(1.0, 2.0), p2(2.0, 3.0); +// double dist = p1.distanceTo(p2); +// Point2D sum = p1 + p2; +struct Point2D { + double x, y; + + Point2D(double x_ = 0, double y_ = 0) : x(x_), y(y_) {} + + double distanceTo(const Point2D &other) const { + double dx = x - other.x; + double dy = y - other.y; + return std::sqrt(dx * dx + dy * dy); + } + + Point2D operator+(const Point2D &other) const { + return Point2D(x + other.x, y + other.y); + } + + Point2D operator-(const Point2D &other) const { + return Point2D(x - other.x, y - other.y); + } +}; + +// Point3D: Represents a point or vector in 3D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point3D p1(1.0, 2.0, 3.0), p2(2.0, 3.0, 4.0); +// double dist = p1.distanceTo(p2); +// Point3D sum = p1 + p2; +struct Point3D { + double x, y, z; + + Point3D(double x_ = 0, double y_ = 0, double z_ = 0) : x(x_), y(y_), z(z_) {} + + double distanceTo(const Point3D &other) const { + double dx = x - other.x; + double dy = y - other.y; + double dz = z - other.z; + return std::sqrt(dx * dx + dy * dy + dz * dz); + } + + Point3D operator+(const Point3D &other) const { + return Point3D(x + other.x, y + other.y, z + other.z); + } + + Point3D operator-(const Point3D &other) const { + return Point3D(x - other.x, y - other.y, z - other.z); + } +}; + +// ============================================================================ +// 19. COLLISION DETECTOR +// ============================================================================ + +// CollisionDetector: A utility class for detecting collisions between points +// in space. Purpose: +// - Counts the number of collisions within a given distance threshold. +// - Retrieves pairs of colliding points. +// Usage: +// std::vector points = {Point2D(0, 0), Point2D(1, 1)}; +// int numCollisions = CollisionDetector::countCollisions(points, 1.5); +// auto collidingPairs = CollisionDetector::getCollidingPairs(points, 1.5); +class CollisionDetector { +public: + template + static int countCollisions(const std::vector &positions, + double threshold) { + int collisions = 0; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + collisions++; + } + } + } + return collisions; + } + + template + static std::vector> + getCollidingPairs(const std::vector &positions, double threshold) { + std::vector> pairs; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + pairs.push_back({i, j}); + } + } + } + return pairs; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/5/include/IO.hpp b/exams/AxelRubini/2025-01-09/5/include/IO.hpp new file mode 100644 index 0000000..1a79cc6 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/5/include/IO.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 11. PARAMETER PARSER +// ============================================================================ + +// ParameterParser: A utility to parse and retrieve parameters from +// configuration files. Purpose: +// - Reads a file and stores key-value pairs for parameters. +// - Allows retrieval of parameters as integers, doubles, or strings. +// - Provides structured data for advanced parsing. +// Usage: +// ParameterParser parser; +// if (parser.parseFile("config.txt")) { +// int param = parser.getInt("example_key", 42); +// } +class ParameterParser { +private: + std::map params; + std::vector> structuredData; + +public: + bool parseFile(const std::string &filename) { + std::ifstream file(filename); + if (!file.is_open()) + return false; + + std::string line; + while (std::getline(file, line)) { + if (line.empty()) + continue; + + std::istringstream iss(line); + std::string key; + iss >> key; + + std::string value; + std::getline(iss, value); + if (!value.empty() && value[0] == ' ') { + value = value.substr(1); + } + + params[key] = value; + + std::vector tokens; + std::istringstream tokenStream(line); + std::string token; + while (tokenStream >> token) { + tokens.push_back(token); + } + structuredData.push_back(tokens); + } + return true; + } + + int getInt(const std::string &key, int defaultValue = 0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stoi(it->second); + } + return defaultValue; + } + + double getDouble(const std::string &key, double defaultValue = 0.0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stod(it->second); + } + return defaultValue; + } + + std::string getString(const std::string &key, + const std::string &defaultValue = "") const { + auto it = params.find(key); + if (it != params.end()) { + return it->second; + } + return defaultValue; + } + + const std::vector> &getStructuredData() const { + return structuredData; + } + + bool hasKey(const std::string &key) const { + return params.find(key) != params.end(); + } +}; + +// ============================================================================ +// 12. OUTPUT WRITER +// ============================================================================ + +// OutputWriter: A utility class for writing data to output files. +// Purpose: +// - Simplifies the process of writing data to files. +// - Supports writing lines, key-value pairs, and managing file headers. +// Usage: +// OutputWriter writer; +// if (writer.open("output.txt", "Header Info")) { +// writer.writeLine("Example Line"); +// writer.writeKeyValue("Key", 42.0); +// writer.close(); +// } +class OutputWriter { +private: + std::ofstream file; + +public: + bool open(const std::string &filename, const std::string &header) { + file.open(filename); + if (!file.is_open()) + return false; + file << header << std::endl; + return true; + } + + template void writeLine(const T &value) { + file << value << std::endl; + } + + template + void writeLine(const T &first, const Args &...args) { + file << first << " "; + writeLine(args...); + } + + void writeKeyValue(const std::string &key, double value, int precision = 2) { + file << key << " " << std::fixed << std::setprecision(precision) << value + << std::endl; + } + + void close() { + if (file.is_open()) { + file.close(); + } + } + + ~OutputWriter() { close(); } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/5/include/Inventory.hpp b/exams/AxelRubini/2025-01-09/5/include/Inventory.hpp new file mode 100644 index 0000000..c82d776 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/5/include/Inventory.hpp @@ -0,0 +1,423 @@ +#pragma once + +#include "DES.hpp" +#include "Random.hpp" +#include +#include + +namespace SELib { +// ============================================================================ +// 16. CUSTOMER MODEL +// ============================================================================ + +// Customer: Represents a customer model in a simulation. +// Purpose: +// - Simulates customer requests over time. +// - Generates random requests for products at specific intervals. +// Usage: +// Customer customer(5.0, 10.0, 3, rng); +// int productId = customer.step(0.1); // Simulates a time step of 0.1 seconds. +class Customer { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer(double A, double B, int N, RandomGenerator &generator) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator) { + nextRequestTime = rng.uniform(A, B); + } + + int step(double dt) { + nextRequestTime -= dt; + if (nextRequestTime <= 0) { + nextRequestTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } +}; +class Customer_Event_Driven { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer_Event_Driven(double A, double B, int N, RandomGenerator &generator, + double nextRequestTime) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator), + nextRequestTime(nextRequestTime) {} + + int step_event_driven(double *current_time) { + if (*current_time >= nextRequestTime) { + nextRequestTime = *current_time + rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + double getNextRequestTime() { return nextRequestTime; }; +}; + +// ============================================================================ +// 17. SERVER MODEL +// ============================================================================ + +// Server: Represents a server in a simulated environment. +// Purpose: +// - Handles product requests and manages inventory. +// - Simulates server busy time during request processing. +// Usage: +// Server server(numProducts, maxInventory, processingTimeNormal, +// processingTimeSupplier, rng); int result = +// server.processRequest(productId, needsSupply); +// server.addInventory(productId, quantityAdded); +class Server { +private: + std::vector inventory; + int busyTime; + int processingTimeNormal; + int processingTimeSupplier; + RandomGenerator *rng; + +public: + Server(int numProducts, int K, int F, int G, RandomGenerator &rng) + : inventory(numProducts + 1, 0), busyTime(0), processingTimeNormal(F), + processingTimeSupplier(G), rng(&rng) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + + bool isBusy() const { return busyTime > 0; } + + void decrementBusyTime() { + if (busyTime > 0) + busyTime--; + } + + int processRequest(int productId, bool needsSupply) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return productId; + } else { + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return -productId; + } + } + + int processRequest2(int productId, double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = processingTimeNormal; + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + busyTime = processingTimeSupplier; + return -productId; + } + } + + void addInventory(int productId, int quantity) { + inventory[productId] += quantity; + } + + int getInventory(int productId) const { return inventory[productId]; } +}; +// ============================================================================ +// 17.5 SERVER-EVENT-DRIVEN +// ============================================================================ +class Server_Event_Driven { +private: + std::vector inventory; + RandomGenerator *rng; + double next_step_time; + double min_interval; + double max_interval; + +public: + Server_Event_Driven(int numProducts, int K, double min_interval, + double max_interval, RandomGenerator &rng, + double nextStepTime) + : inventory(numProducts + 1, 0), rng(&rng), min_interval(min_interval), + max_interval(max_interval), next_step_time(nextStepTime) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + int processRequestEventDriven(double current_time, int productId, + double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (current_time >= next_step_time) { + if (inventory[productId] > 0) { + inventory[productId]--; + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + + return -productId; + } + } else { + return 0; + } + } + double getNextStepTime() { return next_step_time; } +}; + +// ============================================================================ +// 18. SUPPLIER MODEL +// ============================================================================ + +// Supplier: Represents a supplier in a simulation. +// Purpose: +// - Simulates the supply of products at specific intervals. +// - Generates random supply requests periodically based on the defined +// intervals. +// Usage: +// Supplier supplier(minInterval, maxInterval, numProducts, rng); +// int suppliedProduct = supplier.step(timeStep); +// supplier.reset(); +class Supplier { +private: + double nextSupplyTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Supplier(double V, double Q, int N, RandomGenerator &generator) + : minInterval(V), maxInterval(Q), numProducts(N), rng(generator) { + nextSupplyTime = rng.uniform(V, Q); + } + + int step(double dt) { + nextSupplyTime -= dt; + if (nextSupplyTime <= 0) { + nextSupplyTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + void reset() { nextSupplyTime = rng.uniform(minInterval, maxInterval); } +}; + +// Server di inventario con messaggi (customer/server network-style) +class InventoryServerProcess : public DiscreteEventProcess { +public: + InventoryServerProcess(int pid, int numItems, int initialStockPerItem, + double serviceTime, MessageBus &bus, + RandomGenerator &rng) + : pid_(pid), serviceTime_(serviceTime), bus_(bus), rng_(rng), + nextEventTime_(0.0), + storage_(static_cast(numItems), initialStockPerItem), + totalRequests_(0), missedRequests_(0) {} + + void initialize(double startTime) override { + nextEventTime_ = startTime + serviceTime_; + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // processa TUTTI i messaggi arrivati a questo server + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + totalRequests_++; + + // sicurezza sugli indici prodotto + if (m.item < 0 || static_cast(m.item) >= storage_.size()) { + continue; // scarta richieste invalide + } + + double servedQty = 0.0; + int &stock = storage_[static_cast(m.item)]; + + if (stock >= static_cast(m.quantity)) { + // richiesta soddisfatta + stock -= static_cast(m.quantity); + servedQty = m.quantity; + } else { + // non abbastanza stock + servedQty = static_cast(stock); + missedRequests_++; + stock = 0; + } + + // risposta al cliente + Message reply; + reply.time = currentTime; + reply.sender = pid_; + reply.receiver = m.sender; // rimanda al mittente + reply.item = m.item; + reply.quantity = servedQty; + + bus_.procToNet(pid_).push(reply); + + // eventuale logica di refill: esempio semplice + if (stock == 0) { + int refill = rng_.uniformInt(1, 10); // batch di rifornimento random + stock += refill; + } + } + + nextEventTime_ = currentTime + serviceTime_; + } + + const std::vector &storage() const { return storage_; } + + int totalRequests() const { return totalRequests_; } + int missedRequests() const { return missedRequests_; } + +private: + int pid_; + double serviceTime_; + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + std::vector storage_; + + int totalRequests_; + int missedRequests_; +}; + +// ============================================================================ +// 19. INVENTORY CUSTOMER PROCESS (DES-compatible) +// ============================================================================ +// +// InventoryCustomerProcess: +// - Processo a eventi discreti che rappresenta un cliente. +// - Invia richieste al server tramite MessageBus (procToNet -> NetworkRouter). +// - Legge le risposte dal canale netToProc. +// - Statistiche: quante richieste ha mandato e quante risposte ha ricevuto. +// +class InventoryCustomerProcess : public DiscreteEventProcess { +public: + // pid : id di questo processo (indice nei canali del MessageBus) + // serverPid : pid del server a cui mandare le richieste + // numItems : numero di prodotti gestiti (item in [0, numItems-1]) + // minInterval/maxInterval : intervallo di tempo tra una richiesta e la + // successiva + InventoryCustomerProcess(int pid, int serverPid, int numItems, + double minInterval, double maxInterval, + MessageBus &bus, RandomGenerator &rng) + : pid_(pid), serverPid_(serverPid), numItems_(numItems), + minInterval_(minInterval), maxInterval_(maxInterval), bus_(bus), + rng_(rng), nextEventTime_(0.0), issuedRequests_(0), receivedReplies_(0), + totalRequestedQty_(0.0), totalServedQty_(0.0) {} + + // inizializza il prossimo evento (prima richiesta) + void initialize(double startTime) override { + nextEventTime_ = startTime + rng_.uniform(minInterval_, maxInterval_); + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // 1) genera una nuova richiesta al server + const int item = rng_.uniformInt( + 0, numItems_ - 1); // coerente con InventoryServerProcess + const int qty = rng_.uniformInt(1, 3); // quantità richiesta (1..3) + + Message req; + req.time = currentTime; + req.sender = pid_; + req.receiver = serverPid_; + req.item = item; + req.quantity = static_cast(qty); + + bus_.procToNet(pid_).push(req); + issuedRequests_++; + totalRequestedQty_ += req.quantity; + + // 2) processa tutte le risposte arrivate su questo cliente + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + // opzionale: possiamo filtrare solo messaggi dal serverPid_ + // if (m.sender != serverPid_) continue; + + receivedReplies_++; + totalServedQty_ += m.quantity; + } + + // 3) pianifica la prossima richiesta + nextEventTime_ = currentTime + rng_.uniform(minInterval_, maxInterval_); + } + + // --- metriche accessibili dall’esterno --- + + int pid() const { return pid_; } + int serverPid() const { return serverPid_; } + + int issuedRequests() const { return issuedRequests_; } + int receivedReplies() const { return receivedReplies_; } + + double totalRequestedQty() const { return totalRequestedQty_; } + double totalServedQty() const { return totalServedQty_; } + + // fill-rate lato customer + double serviceLevel() const { + if (totalRequestedQty_ <= 0.0) + return 1.0; + return totalServedQty_ / totalRequestedQty_; + } + +private: + int pid_; + int serverPid_; + int numItems_; + double minInterval_; + double maxInterval_; + + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + + int issuedRequests_; + int receivedReplies_; + double totalRequestedQty_; + double totalServedQty_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/5/include/Queue.hpp b/exams/AxelRubini/2025-01-09/5/include/Queue.hpp new file mode 100644 index 0000000..0c12ce8 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/5/include/Queue.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 9. FIFO QUEUE +// ============================================================================ + +// FIFOQueue: Implements a first-in-first-out (FIFO) queue with a fixed maximum +// size. Purpose: +// - Enqueue and dequeue items in FIFO order. +// - Track statistics such as total items enqueued and dequeued. +// - Support dynamic size constraints. +// Usage: +// FIFOQueue queue(10); +// queue.enqueue(5); +// int item; +// if (queue.dequeue(item)) { +// // Process item. +// } +template class FIFOQueue { +private: + std::queue queue; + size_t maxSize; + size_t totalEnqueued; + size_t totalDequeued; + +public: + FIFOQueue(size_t max = SIZE_MAX) + : maxSize(max), totalEnqueued(0), totalDequeued(0) {} + + bool enqueue(const T &item) { + if (queue.size() >= maxSize) + return false; + queue.push(item); + totalEnqueued++; + return true; + } + + bool dequeue(T &item) { + if (queue.empty()) + return false; + item = queue.front(); + queue.pop(); + totalDequeued++; + return true; + } + + bool isEmpty() const { return queue.empty(); } + bool isFull() const { return queue.size() >= maxSize; } + size_t size() const { return queue.size(); } + size_t getTotalEnqueued() const { return totalEnqueued; } + size_t getTotalDequeued() const { return totalDequeued; } + std::queue getQueue() const { return queue; } + T getFirst() const { + if (queue.empty()) + throw std::runtime_error("Queue is empty"); + return queue.front(); + } + + void clear() { + while (!queue.empty()) + queue.pop(); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/5/include/Random.hpp b/exams/AxelRubini/2025-01-09/5/include/Random.hpp new file mode 100644 index 0000000..52356f9 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/5/include/Random.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include // std::isfinite +#include // std::accumulate +#include +#include +#include + +namespace SELib { + +class RandomGenerator { +private: + std::mt19937 gen; + +public: + explicit RandomGenerator(unsigned seed = std::random_device{}()) + : gen(seed) {} + + double uniform(double min, double max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_real_distribution dis(lo, high); + return dis(gen); + } + + int uniformInt(int min, int max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_int_distribution dis(lo, high); + return dis(gen); + } + + double gaussian(double mean, double stddev) { + if (stddev < 0.0) { + throw std::invalid_argument("gaussian: stddev must be >= 0"); + } + std::normal_distribution dis(mean, stddev); + return dis(gen); + } + + bool bernoulli(double p) { + if (p < 0.0 || p > 1.0 || !std::isfinite(p)) { + throw std::invalid_argument("bernoulli: p must be in [0,1]"); + } + std::bernoulli_distribution dis(p); + return dis(gen); + } + + int discrete(const std::vector &probabilities) { + if (probabilities.empty()) { + throw std::invalid_argument("discrete: empty probability vector"); + } + + for (double w : probabilities) { + if (w < 0.0 || !std::isfinite(w)) { + throw std::invalid_argument( + "discrete: weights must be non-negative and finite"); + } + } + + double sum = + std::accumulate(probabilities.begin(), probabilities.end(), 0.0); + if (sum <= 0.0) { + throw std::invalid_argument("discrete: sum of weights must be > 0"); + } + + std::discrete_distribution dis(probabilities.begin(), + probabilities.end()); + return dis(gen); + } + + void setSeed(unsigned seed) { gen.seed(seed); } + + // (opzionale) accesso al motore se ti serve per altre distribuzioni + std::mt19937 &engine() { return gen; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/5/include/Stat.hpp b/exams/AxelRubini/2025-01-09/5/include/Stat.hpp new file mode 100644 index 0000000..27b4656 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/5/include/Stat.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 2. STATISTICS +// ============================================================================ + +// Statistics: A utility class for managing and analyzing numerical data +// samples. Purpose: +// - Compute statistical metrics such as mean and standard deviation. +// - Estimate probabilities of custom conditions. +// - Check convergence of data over iterations. +// Usage: +// Statistics stats; +// stats.addSample(5.0); +// double meanValue = stats.mean(); +class Statistics { +private: + std::vector samples; + +public: + void addSample(double value) { samples.push_back(value); } + + void clear() { samples.clear(); } + + double mean() const { + if (samples.empty()) + return 0.0; + double sum = 0.0; + for (double s : samples) + sum += s; + return sum / samples.size(); + } + + double stddev() const { + if (samples.size() < 2) + return 0.0; + double m = mean(); + double variance = 0.0; + for (double s : samples) { + variance += (s - m) * (s - m); + } + return std::sqrt(variance / (samples.size() - 1)); + } + + double probability(std::function condition) const { + if (samples.empty()) + return 0.0; + int count = 0; + for (double s : samples) { + if (condition(s)) + count++; + } + return static_cast(count) / samples.size(); + } + + size_t size() const { return samples.size(); } + const std::vector &getSamples() const { return samples; } + + bool hasConverged(double tolerance = 0.1) const { + if (samples.size() < 30) + return false; + double m = mean(); + double sd = stddev(); + return sd <= tolerance * std::abs(m); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-01-09/5/main b/exams/AxelRubini/2025-01-09/5/main new file mode 100755 index 0000000000000000000000000000000000000000..8f3c33ad50b9059cf7b20cca9c4f51cc8e7cf2ea GIT binary patch literal 87256 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 SEL-}U=g63oZ3%2YjsFKnaq4IQ literal 0 HcmV?d00001 diff --git a/exams/AxelRubini/2025-01-09/5/main.cpp b/exams/AxelRubini/2025-01-09/5/main.cpp new file mode 100644 index 0000000..d68e7e3 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/5/main.cpp @@ -0,0 +1,235 @@ +#include "include/DES.hpp" +#include "include/IO.hpp" +#include "include/Random.hpp" +#include "include/Stat.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace SELib { + +class DispatcherCustomer : public DiscreteEventProcess { + public: + DispatcherCustomer( + int pid, + int dispatcherPid, + MessageBus &bus, + RandomGenerator &rng, + double avg, + double stddev + ) + : pid_(pid), dispatcherPid_(dispatcherPid), bus_(bus), rng_(rng), + avg_(avg), stddev_(stddev) {} + + void initialize(double startTime) override { scheduleNext(startTime); } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + Message msg; + msg.time = currentTime; + msg.sender = pid_; + msg.receiver = dispatcherPid_; + bus_.procToNet(pid_).push(msg); + + scheduleNext(currentTime); + } + + private: + void scheduleNext(double currentTime) { + double delay = rng_.gaussian(avg_, stddev_); + if (delay < 1.0) + delay = 1.0; + nextEventTime_ = currentTime + delay; + } + + int pid_; + int dispatcherPid_; + MessageBus &bus_; + RandomGenerator &rng_; + double avg_; + double stddev_; + double nextEventTime_; +}; + +class Dispatcher : public DiscreteEventProcess { + public: + Dispatcher(int pid, int monitorPid, int numCustomers, MessageBus &bus) + : pid_(pid), monitorPid_(monitorPid), numCustomers_(numCustomers), + bus_(bus) { + for (int i = 0; i < numCustomers; ++i) { + inputCounts_[i] = 0; + } + } + + void initialize(double startTime) override {} + + double nextEventTime() const override { + return std::numeric_limits::infinity(); + } + + void handleEvent(double currentTime) override {} + + void processInbox(double currentTime) { + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + buffer_.push_back(inbox.front()); + inbox.pop(); + } + + // Release messages older than currentTime - 1.0 + std::sort( + buffer_.begin(), + buffer_.end(), + [](const Message &a, const Message &b) { return a.time < b.time; } + ); + + auto it = std::lower_bound( + buffer_.begin(), + buffer_.end(), + currentTime - 1.0, + [](const Message &m, double t) { return m.time < t; } + ); + + std::vector toRelease(buffer_.begin(), it); + buffer_.erase(buffer_.begin(), it); + + for (const auto &m : toRelease) { + if (m.sender >= 0 && m.sender < numCustomers_) { + inputCounts_[m.sender]++; + } + + Message forward = m; + forward.sender = pid_; + forward.receiver = monitorPid_; + bus_.procToNet(pid_).push(forward); + } + } + + void flushAll() { + std::sort( + buffer_.begin(), + buffer_.end(), + [](const Message &a, const Message &b) { return a.time < b.time; } + ); + for (const auto &m : buffer_) { + if (m.sender >= 0 && m.sender < numCustomers_) { + inputCounts_[m.sender]++; + } + Message forward = m; + forward.sender = pid_; + forward.receiver = monitorPid_; + bus_.procToNet(pid_).push(forward); + } + buffer_.clear(); + } + + int getCount(int customerId) const { + auto it = inputCounts_.find(customerId); + return it != inputCounts_.end() ? it->second : 0; + } + + private: + int pid_; + int monitorPid_; + int numCustomers_; + MessageBus &bus_; + std::map inputCounts_; + std::vector buffer_; +}; + +class OrderMonitor : public DiscreteEventProcess { + public: + OrderMonitor(int pid, MessageBus &bus) + : pid_(pid), bus_(bus), lastTime_(-1.0), violated_(0) {} + + void initialize(double startTime) override {} + + double nextEventTime() const override { + return std::numeric_limits::infinity(); + } + + void handleEvent(double currentTime) override {} + + void processInbox(double currentTime) { + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + if (lastTime_ >= 0 && m.time < lastTime_) { + violated_ = 1; + } + lastTime_ = m.time; + } + } + + int getViolated() const { return violated_; } + + private: + int pid_; + MessageBus &bus_; + double lastTime_; + int violated_; +}; + +} // namespace SELib + +int main() { + SELib::RandomGenerator rng; + SELib::ParameterParser parser; + if (!parser.parseFile("parameters.txt")) { + return 1; + } + + int N = parser.getInt("N", 0); + double avg = parser.getDouble("Avg", 0.0); + double stddev = parser.getDouble("StdDev", 0.0); + + int dispatcherPid = N; + int monitorPid = N + 1; + SELib::MessageBus bus(N + 2); + SELib::DiscreteEventSystem system; + + for (int i = 0; i < N; ++i) { + system.emplaceProcess( + i, dispatcherPid, bus, rng, avg, stddev + ); + } + auto &dispatcher = system.emplaceProcess( + dispatcherPid, monitorPid, N, bus + ); + auto &monitor = system.emplaceProcess(monitorPid, bus); + system.emplaceProcess(bus, rng, 0.01); + + double currentTime = 0.0; + double horizon = 1000000.0; + system.initialize(0.0); + + while (currentTime < horizon) { + double nextTime = system.nextEventTime(); + if (nextTime > horizon) + break; + currentTime = nextTime; + system.handleEvent(currentTime); + dispatcher.processInbox(currentTime); + monitor.processInbox(currentTime); + } + dispatcher.flushAll(); + monitor.processInbox(currentTime); + + std::ofstream outFile("results.txt"); + outFile << "2025-01-09-Axel-Rubini-2158099" << std::endl; + for (int i = 0; i < N; ++i) { + outFile << (i + 1) << " " << dispatcher.getCount(i) << std::endl; + } + outFile << "M2 " << monitor.getViolated() << std::endl; + outFile.close(); + + return 0; +} diff --git a/exams/AxelRubini/2025-01-09/5/parameters.txt b/exams/AxelRubini/2025-01-09/5/parameters.txt new file mode 100644 index 0000000..ca68d86 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/5/parameters.txt @@ -0,0 +1,3 @@ +N 10 +Avg 100 +StdDev 1 diff --git a/exams/AxelRubini/2025-01-09/5/results.txt b/exams/AxelRubini/2025-01-09/5/results.txt new file mode 100644 index 0000000..34bfcd8 --- /dev/null +++ b/exams/AxelRubini/2025-01-09/5/results.txt @@ -0,0 +1,12 @@ +2025-01-09-Axel-Rubini-2158099 +1 10000 +2 9998 +3 10001 +4 9998 +5 10000 +6 10000 +7 9999 +8 9999 +9 9998 +10 9998 +M2 0 diff --git a/exams/AxelRubini/2025-02-05/1/Makefile b/exams/AxelRubini/2025-02-05/1/Makefile new file mode 100644 index 0000000..6910fc5 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/1/Makefile @@ -0,0 +1,10 @@ +CC=g++ +CFLAGS=-std=c++23 -O3 -Wall + +all: main + +main: main.cpp + $(CC) $(CFLAGS) main.cpp -o main + +clean: + rm -f main *.o diff --git a/exams/AxelRubini/2025-02-05/1/include/DES.hpp b/exams/AxelRubini/2025-02-05/1/include/DES.hpp new file mode 100644 index 0000000..1706b8f --- /dev/null +++ b/exams/AxelRubini/2025-02-05/1/include/DES.hpp @@ -0,0 +1,212 @@ + +#pragma once + +#include "Random.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// --------------------------------------------------------------------------- +// Messaggio +// --------------------------------------------------------------------------- +struct Message { + double time{0.0}; + int sender{-1}; + int receiver{-1}; + int item{0}; + double quantity{0.0}; +}; + +// --------------------------------------------------------------------------- +// Bus di messaggi: process -> network, network -> process +// --------------------------------------------------------------------------- +class MessageBus { +public: + explicit MessageBus(std::size_t n = 0) { resize(n); } + + void resize(std::size_t n) { + procToNet_.assign(n, {}); + netToProc_.assign(n, {}); + } + + std::size_t size() const { return procToNet_.size(); } + + std::queue &procToNet(int pid) { return procToNet_.at(pid); } + std::queue &netToProc(int pid) { return netToProc_.at(pid); } + const std::queue &procToNet(int pid) const { + return procToNet_.at(pid); + } + const std::queue &netToProc(int pid) const { + return netToProc_.at(pid); + } + +private: + std::vector> procToNet_; + std::vector> netToProc_; +}; + +// --------------------------------------------------------------------------- +// Processo base a eventi discreti +// --------------------------------------------------------------------------- +class DiscreteEventProcess { +public: + virtual ~DiscreteEventProcess() = default; + virtual void initialize(double startTime) = 0; + virtual double nextEventTime() const = 0; + virtual void handleEvent(double currentTime) = 0; +}; + +// --------------------------------------------------------------------------- +// NetworkRouter: inoltra i messaggi P->N->P +// --------------------------------------------------------------------------- +class NetworkRouter : public DiscreteEventProcess { +public: + NetworkRouter(MessageBus &bus, RandomGenerator &rng, double scanPeriod = 0.1) + : bus_(bus), rng_(rng), scanPeriod_(scanPeriod), time_(0.0), + nextScanTime_(0.0), scanned_(0) {} + + void initialize(double startTime) override { + time_ = startTime; + nextScanTime_ = startTime + scanPeriod_; + + const int n = static_cast(bus_.size()); + if (n <= 0) { + throw std::runtime_error( + "NetworkRouter: no processes configured in MessageBus"); + } + + scanner_.resize(n); + for (int i = 0; i < n; ++i) { + scanner_[i] = i; + } + reshuffleScanner(); + scanned_ = 0; + } + + double nextEventTime() const override { return nextScanTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextScanTime_) + return; + + const int n = static_cast(scanner_.size()); + if (n == 0) + return; + + if (scanned_ >= n) { + scanned_ = 0; + reshuffleScanner(); + } + + const int channel = scanner_[scanned_++]; + auto &fromQueue = bus_.procToNet(channel); + + if (!fromQueue.empty()) { + Message m = fromQueue.front(); + fromQueue.pop(); + + if (m.receiver < 0 || m.receiver >= static_cast(bus_.size())) { + throw std::runtime_error("NetworkRouter: invalid receiver index"); + } + + bus_.netToProc(m.receiver).push(m); + } + + nextScanTime_ = currentTime + scanPeriod_; + } + +private: + void reshuffleScanner() { + auto &eng = rng_.engine(); // serve engine() in RandomGenerator + std::shuffle(scanner_.begin(), scanner_.end(), eng); + } + + MessageBus &bus_; + RandomGenerator &rng_; + + double scanPeriod_; + double time_; + double nextScanTime_; + std::vector scanner_; + int scanned_; +}; + +// --------------------------------------------------------------------------- +// Sistema DES: collezione di processi +// --------------------------------------------------------------------------- +class DiscreteEventSystem { +public: + template + Proc &emplaceProcess(Args &&...args) { + auto ptr = std::make_unique(std::forward(args)...); + Proc &ref = *ptr; + processes_.push_back(std::move(ptr)); + return ref; + } + + void initialize(double startTime = 0.0) { + for (auto &p : processes_) { + p->initialize(startTime); + } + } + + double nextEventTime() const { + if (processes_.empty()) { + return std::numeric_limits::infinity(); + } + + double tMin = std::numeric_limits::infinity(); + for (const auto &p : processes_) { + double t = p->nextEventTime(); + if (t < tMin) + tMin = t; + } + return tMin; + } + + void handleEvent(double currentTime) { + for (auto &p : processes_) { + if (p->nextEventTime() <= currentTime) { + p->handleEvent(currentTime); + } + } + } + +private: + std::vector> processes_; +}; + +// --------------------------------------------------------------------------- +// Simulatore DES +// --------------------------------------------------------------------------- +class DiscreteEventSimulator { +public: + explicit DiscreteEventSimulator(DiscreteEventSystem &system) + : system_(system), currentTime_(0.0) {} + + void run(double horizon) { + system_.initialize(0.0); + currentTime_ = 0.0; + + while (currentTime_ < horizon) { + double nextTime = system_.nextEventTime(); + if (nextTime == std::numeric_limits::infinity()) + break; + currentTime_ = nextTime; + system_.handleEvent(currentTime_); + } + } + + double currentTime() const { return currentTime_; } + +private: + DiscreteEventSystem &system_; + double currentTime_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/1/include/Decision.hpp b/exams/AxelRubini/2025-02-05/1/include/Decision.hpp new file mode 100644 index 0000000..4d37cf0 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/1/include/Decision.hpp @@ -0,0 +1,266 @@ +#pragma once + +#include "Random.hpp" +#include "Stat.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 7. MARKOV CHAIN +// ============================================================================ + +// MarkovChain: Implements a discrete-time Markov chain. +// Purpose: +// - Simulates stochastic processes with defined states and transition +// probabilities. +// - Useful in modeling random systems and computing related probabilities. +// Usage: +// MarkovChain mc(3, rng); +// mc.setTransition(0, 1, 0.5); +// mc.setTransition(0, 2, 0.5); +// mc.setState(0); +// int nextState = mc.step(); +class MarkovChain { +private: + int numStates; + std::vector> transitionMatrix; + int currentState; + RandomGenerator &rng; + +public: + MarkovChain(int states, RandomGenerator &generator) + : numStates(states), currentState(0), rng(generator) { + transitionMatrix.resize(states, std::vector(states, 0.0)); + } + + void setTransition(int from, int to, double probability) { + if (from >= 0 && from < numStates && to >= 0 && to < numStates) { + transitionMatrix[from][to] = probability; + } + } + + void setState(int state) { + if (state >= 0 && state < numStates) { + currentState = state; + } + } + + int getState() const { return currentState; } + + int step() { + currentState = rng.discrete(transitionMatrix[currentState]); + return currentState; + } + + double getTransitionProb(int from, int to) const { + return transitionMatrix[from][to]; + } + + double expectedSojournTime(int state, double timeStep = 1.0) const { + double pii = transitionMatrix[state][state]; + if (pii >= 1.0) + return std::numeric_limits::infinity(); + return timeStep / (1.0 - pii); + } +}; + +// ============================================================================ +// 8. MDP +// ============================================================================ + +// MDP: Implements a Markov Decision Process (MDP) model. +// Purpose: +// - Simulate decision-making processes with states, transitions, and +// associated costs. +// - Calculate the total cost to reach a target state from a starting state. +// Usage: +// - Add transitions between states with associated probabilities and costs +// using `addTransition`. +// - Use `step` to simulate a single transition and calculate its cost. +// - Use `simulate` to estimate the total cost from a start to an end state. +class MDP { +private: + int numStates; + std::map>>> + transitions; + RandomGenerator &rng; + +public: + MDP(int states, RandomGenerator &generator) + : numStates(states), rng(generator) {} + + void addTransition(int from, int to, double probability, double cost) { + transitions[from].push_back({to, {probability, cost}}); + } + + std::pair step(int currentState) { + if (transitions.find(currentState) == transitions.end()) { + return {currentState, 0.0}; + } + + std::vector probs; + for (const auto &t : transitions[currentState]) { + probs.push_back(t.second.first); + } + + int idx = rng.discrete(probs); + int nextState = transitions[currentState][idx].first; + double cost = transitions[currentState][idx].second.second; + + return {nextState, cost}; + } + + double simulate(int startState, int endState) { + int state = startState; + double totalCost = 0.0; + int maxSteps = 100000; + + for (int step = 0; step < maxSteps && state != endState; step++) { + auto [nextState, cost] = this->step(state); + totalCost += cost; + state = nextState; + } + return totalCost; + } +}; + +// ============================================================================ +// 10. MONTE CARLO SIMULATOR +// ============================================================================ + +// MonteCarloSimulator: A utility for running Monte Carlo simulations. +// Purpose: +// - Estimate statistical metrics through repeated random sampling. +// - Provides methods to calculate mean results and probabilities of events. +// Usage: +// MonteCarloSimulator simulator(1000); +// double estimate = simulator.estimate([](int) { return some_random_value; }); +// double probability = simulator.estimateProbability([](int) { return +// some_event_occurs; }); +class MonteCarloSimulator { +private: + Statistics stats; + int numSimulations; + +public: + MonteCarloSimulator(int simulations = 1000) : numSimulations(simulations) {} + + template double estimate(SimFunc simulation) { + stats.clear(); + for (int i = 0; i < numSimulations; i++) { + double result = simulation(i); + stats.addSample(result); + } + return stats.mean(); + } + + template double estimateProbability(SimFunc simulation) { + int successes = 0; + for (int i = 0; i < numSimulations; i++) { + if (simulation(i)) + successes++; + } + return static_cast(successes) / numSimulations; + } + + const Statistics &getStatistics() const { return stats; } +}; + +// ============================================================================ +// 13. OPTIMIZATION UTILITIES +// ============================================================================ + +// Optimizer: Utility class offering optimization techniques such as random +// search and grid search. Purpose: +// - Explore parameter spaces to maximize or minimize an objective function. +// - Supports both continuous and discrete search spaces. +// Usage: +// - Use `randomSearch` for stochastic optimization over a defined range. +// - Use `gridSearch` for systematic exploration in evenly spaced steps. +// Example: +// auto [bestParam, bestValue] = Optimizer::randomSearch(objectiveFunction, +// 0.0, 10.0, 100, rng); auto [bestInt, bestIntValue] = +// Optimizer::randomSearchInt(objectiveFunction, 0, 10, 50, rng); +class Optimizer { +public: + template + static std::pair + randomSearch(ObjFunc objective, double minValue, double maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + for (int i = 0; i < budget; i++) { + double param = rng.uniform(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair + randomSearchInt(ObjFunc objective, int minValue, int maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + int bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + if (budget <= 0) { + throw std::invalid_argument("randomSearchInt: budget must be positive."); + } + + if (minValue >= maxValue) { + throw std::invalid_argument( + "randomSearchInt: minValue must be less than maxValue."); + } + + for (int i = 0; i < budget; i++) { + int param = rng.uniformInt(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair gridSearch(ObjFunc objective, + double minValue, double maxValue, + int steps, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + double stepSize = (maxValue - minValue) / steps; + + if (steps <= 0) { + throw std::invalid_argument("gridSearch: steps must be positive."); + } + + for (int i = 0; i <= steps; i++) { + double param = minValue + i * stepSize; + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/1/include/Dynamics.hpp b/exams/AxelRubini/2025-02-05/1/include/Dynamics.hpp new file mode 100644 index 0000000..044176a --- /dev/null +++ b/exams/AxelRubini/2025-02-05/1/include/Dynamics.hpp @@ -0,0 +1,214 @@ +#pragma once + +#include +#include +namespace SELib { +// ============================================================================ +// 3. ODE SOLVER (Runge-Kutta 4th order) +// ============================================================================ + +using StateVector = std::vector; +using DerivativeFunction = std::function; + +// ODESolver: Implements numerical methods for solving Ordinary Differential +// Equations (ODEs). Purpose: +// - Provides tools for simulating dynamic systems modeled by ODEs. +// - Includes methods such as Runge-Kutta 4th order and Euler's method for +// integration. +// Usage: +// - Pass a derivative function that defines the ODE system. +// - Use `rk4Step` for higher accuracy or `eulerStep` for simpler systems. +// Example: +// StateVector nextState = ODESolver::rk4Step(0.0, currentState, controlInputs, +// dt, dynamicsFunction); +class ODESolver { +public: + static StateVector rk4Step(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector k1 = f(t, x, u); + StateVector x2 = addScaled(x, k1, dt / 2.0); + StateVector k2 = f(t + dt / 2.0, x2, u); + StateVector x3 = addScaled(x, k2, dt / 2.0); + StateVector k3 = f(t + dt / 2.0, x3, u); + StateVector x4 = addScaled(x, k3, dt); + StateVector k4 = f(t + dt, x4, u); + + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += (dt / 6.0) * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]); + } + return xNew; + } + + static StateVector eulerStep(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector dx = f(t, x, u); + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += dx[i] * dt; + } + return xNew; + } + +private: + static StateVector addScaled(const StateVector &x, const StateVector &dx, + double scale) { + StateVector result = x; + for (size_t i = 0; i < x.size(); i++) { + result[i] += dx[i] * scale; + } + return result; + } +}; + +// ============================================================================ +// 4. PID CONTROLLER +// ============================================================================ + +// PIDController: Implements a Proportional-Integral-Derivative (PID) +// controller. Purpose: +// - Automatically adjusts a control signal to minimize the difference between +// a desired setpoint and a measured value. +// - Used in control systems to regulate processes like temperature, speed, or +// position. +// Usage: +// - Create an instance with proportional (Kp), integral (Ki), and derivative +// (Kd) gains, along with a time step. +// - Use `compute` to calculate the control signal based on the setpoint and +// measurement. +// Example: +// PIDController pid(1.0, 0.1, 0.01, 0.1); +// double controlSignal = pid.compute(desiredValue, actualValue); +class PIDController { +private: + double kp, ki, kd; + double integral; + double prevError; + double dt; + +public: + PIDController(double Kp, double Ki, double Kd, double timeStep) + : kp(Kp), ki(Ki), kd(Kd), integral(0), prevError(0), dt(timeStep) {} + + double compute(double setpoint, double measurement) { + double error = setpoint - measurement; + integral += error * dt; + double derivative = (error - prevError) / dt; + prevError = error; + return kp * error + ki * integral + kd * derivative; + } + + void reset() { + integral = 0; + prevError = 0; + } + + void setGains(double Kp, double Ki, double Kd) { + kp = Kp; + ki = Ki; + kd = Kd; + } +}; + +// ============================================================================ +// 5. CONTINUOUS TIME SIMULATION +// ============================================================================ + +// ContinuousTimeSimulation: A base class for simulating continuous-time dynamic +// systems. Purpose: +// - Provides a framework for simulating systems characterized by continuous +// dynamics over time. +// - Integrates system dynamics using a Runge-Kutta 4th order solver. +// Usage: +// - Derive from this class and implement the `dynamics`, `computeControl`, +// `initialize`, and `finalize` methods. +// - Customize the `onStep` method for per-step side effects, if required. +// Example: +// class MySimulation : public ContinuousTimeSimulation { +// // Implement the required virtual methods. +// }; +// MySimulation sim(dt, horizon, initialState); +// sim.run(); +class ContinuousTimeSimulation { +protected: + double currentTime; + double timeStep; + double horizon; + StateVector state; + StateVector control; + +public: + ContinuousTimeSimulation(double dt, double H, const StateVector &initialState) + : currentTime(0.0), timeStep(dt), horizon(H), state(initialState) { + control.resize(initialState.size(), 0.0); + } + + virtual ~ContinuousTimeSimulation() = default; + + virtual StateVector dynamics(double t, const StateVector &x, + const StateVector &u) = 0; + virtual StateVector computeControl(double t, const StateVector &x) = 0; + virtual void initialize() = 0; + virtual void onStep() {} + virtual void finalize() = 0; + + void run() { + initialize(); + while (currentTime < horizon) { + control = computeControl(currentTime, state); + state = ODESolver::rk4Step( + currentTime, state, control, timeStep, + [this](double t, const StateVector &x, const StateVector &u) { + return this->dynamics(t, x, u); + }); + currentTime += timeStep; + onStep(); + } + finalize(); + } + + double getCurrentTime() const { return currentTime; } + const StateVector &getState() const { return state; } + const StateVector &getControl() const { return control; } +}; + +// ============================================================================ +// 6. SAMPLE AND HOLD +// ============================================================================ + +// SampleAndHold: A utility to store a value and update it periodically. +// Purpose: +// - Maintain a value that gets updated periodically based on a time interval. +// - Useful for sampling data and ensuring updates happen at fixed intervals. +// Usage: +// SampleAndHold sampler(5.0, 0); +// if (sampler.shouldUpdate(currentTime)) { +// sampler.update(currentTime, newValue); +// } +// int currentValue = sampler.getValue(); +template class SampleAndHold { +private: + T value; + double lastUpdateTime; + double samplePeriod; + +public: + SampleAndHold(double period, const T &initialValue = T()) + : value(initialValue), lastUpdateTime(0), samplePeriod(period) {} + + bool shouldUpdate(double currentTime) const { + return (currentTime - lastUpdateTime) >= samplePeriod; + } + + void update(double currentTime, const T &newValue) { + value = newValue; + lastUpdateTime = currentTime; + } + + const T &getValue() const { return value; } + void reset() { lastUpdateTime = 0; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/1/include/Geometry.hpp b/exams/AxelRubini/2025-02-05/1/include/Geometry.hpp new file mode 100644 index 0000000..525a91d --- /dev/null +++ b/exams/AxelRubini/2025-02-05/1/include/Geometry.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 14. GEOMETRIC UTILITIES +// ============================================================================ + +// Point2D: Represents a point or vector in 2D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point2D p1(1.0, 2.0), p2(2.0, 3.0); +// double dist = p1.distanceTo(p2); +// Point2D sum = p1 + p2; +struct Point2D { + double x, y; + + Point2D(double x_ = 0, double y_ = 0) : x(x_), y(y_) {} + + double distanceTo(const Point2D &other) const { + double dx = x - other.x; + double dy = y - other.y; + return std::sqrt(dx * dx + dy * dy); + } + + Point2D operator+(const Point2D &other) const { + return Point2D(x + other.x, y + other.y); + } + + Point2D operator-(const Point2D &other) const { + return Point2D(x - other.x, y - other.y); + } +}; + +// Point3D: Represents a point or vector in 3D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point3D p1(1.0, 2.0, 3.0), p2(2.0, 3.0, 4.0); +// double dist = p1.distanceTo(p2); +// Point3D sum = p1 + p2; +struct Point3D { + double x, y, z; + + Point3D(double x_ = 0, double y_ = 0, double z_ = 0) : x(x_), y(y_), z(z_) {} + + double distanceTo(const Point3D &other) const { + double dx = x - other.x; + double dy = y - other.y; + double dz = z - other.z; + return std::sqrt(dx * dx + dy * dy + dz * dz); + } + + Point3D operator+(const Point3D &other) const { + return Point3D(x + other.x, y + other.y, z + other.z); + } + + Point3D operator-(const Point3D &other) const { + return Point3D(x - other.x, y - other.y, z - other.z); + } +}; + +// ============================================================================ +// 19. COLLISION DETECTOR +// ============================================================================ + +// CollisionDetector: A utility class for detecting collisions between points +// in space. Purpose: +// - Counts the number of collisions within a given distance threshold. +// - Retrieves pairs of colliding points. +// Usage: +// std::vector points = {Point2D(0, 0), Point2D(1, 1)}; +// int numCollisions = CollisionDetector::countCollisions(points, 1.5); +// auto collidingPairs = CollisionDetector::getCollidingPairs(points, 1.5); +class CollisionDetector { +public: + template + static int countCollisions(const std::vector &positions, + double threshold) { + int collisions = 0; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + collisions++; + } + } + } + return collisions; + } + + template + static std::vector> + getCollidingPairs(const std::vector &positions, double threshold) { + std::vector> pairs; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + pairs.push_back({i, j}); + } + } + } + return pairs; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/1/include/IO.hpp b/exams/AxelRubini/2025-02-05/1/include/IO.hpp new file mode 100644 index 0000000..1a79cc6 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/1/include/IO.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 11. PARAMETER PARSER +// ============================================================================ + +// ParameterParser: A utility to parse and retrieve parameters from +// configuration files. Purpose: +// - Reads a file and stores key-value pairs for parameters. +// - Allows retrieval of parameters as integers, doubles, or strings. +// - Provides structured data for advanced parsing. +// Usage: +// ParameterParser parser; +// if (parser.parseFile("config.txt")) { +// int param = parser.getInt("example_key", 42); +// } +class ParameterParser { +private: + std::map params; + std::vector> structuredData; + +public: + bool parseFile(const std::string &filename) { + std::ifstream file(filename); + if (!file.is_open()) + return false; + + std::string line; + while (std::getline(file, line)) { + if (line.empty()) + continue; + + std::istringstream iss(line); + std::string key; + iss >> key; + + std::string value; + std::getline(iss, value); + if (!value.empty() && value[0] == ' ') { + value = value.substr(1); + } + + params[key] = value; + + std::vector tokens; + std::istringstream tokenStream(line); + std::string token; + while (tokenStream >> token) { + tokens.push_back(token); + } + structuredData.push_back(tokens); + } + return true; + } + + int getInt(const std::string &key, int defaultValue = 0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stoi(it->second); + } + return defaultValue; + } + + double getDouble(const std::string &key, double defaultValue = 0.0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stod(it->second); + } + return defaultValue; + } + + std::string getString(const std::string &key, + const std::string &defaultValue = "") const { + auto it = params.find(key); + if (it != params.end()) { + return it->second; + } + return defaultValue; + } + + const std::vector> &getStructuredData() const { + return structuredData; + } + + bool hasKey(const std::string &key) const { + return params.find(key) != params.end(); + } +}; + +// ============================================================================ +// 12. OUTPUT WRITER +// ============================================================================ + +// OutputWriter: A utility class for writing data to output files. +// Purpose: +// - Simplifies the process of writing data to files. +// - Supports writing lines, key-value pairs, and managing file headers. +// Usage: +// OutputWriter writer; +// if (writer.open("output.txt", "Header Info")) { +// writer.writeLine("Example Line"); +// writer.writeKeyValue("Key", 42.0); +// writer.close(); +// } +class OutputWriter { +private: + std::ofstream file; + +public: + bool open(const std::string &filename, const std::string &header) { + file.open(filename); + if (!file.is_open()) + return false; + file << header << std::endl; + return true; + } + + template void writeLine(const T &value) { + file << value << std::endl; + } + + template + void writeLine(const T &first, const Args &...args) { + file << first << " "; + writeLine(args...); + } + + void writeKeyValue(const std::string &key, double value, int precision = 2) { + file << key << " " << std::fixed << std::setprecision(precision) << value + << std::endl; + } + + void close() { + if (file.is_open()) { + file.close(); + } + } + + ~OutputWriter() { close(); } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/1/include/Inventory.hpp b/exams/AxelRubini/2025-02-05/1/include/Inventory.hpp new file mode 100644 index 0000000..c82d776 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/1/include/Inventory.hpp @@ -0,0 +1,423 @@ +#pragma once + +#include "DES.hpp" +#include "Random.hpp" +#include +#include + +namespace SELib { +// ============================================================================ +// 16. CUSTOMER MODEL +// ============================================================================ + +// Customer: Represents a customer model in a simulation. +// Purpose: +// - Simulates customer requests over time. +// - Generates random requests for products at specific intervals. +// Usage: +// Customer customer(5.0, 10.0, 3, rng); +// int productId = customer.step(0.1); // Simulates a time step of 0.1 seconds. +class Customer { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer(double A, double B, int N, RandomGenerator &generator) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator) { + nextRequestTime = rng.uniform(A, B); + } + + int step(double dt) { + nextRequestTime -= dt; + if (nextRequestTime <= 0) { + nextRequestTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } +}; +class Customer_Event_Driven { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer_Event_Driven(double A, double B, int N, RandomGenerator &generator, + double nextRequestTime) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator), + nextRequestTime(nextRequestTime) {} + + int step_event_driven(double *current_time) { + if (*current_time >= nextRequestTime) { + nextRequestTime = *current_time + rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + double getNextRequestTime() { return nextRequestTime; }; +}; + +// ============================================================================ +// 17. SERVER MODEL +// ============================================================================ + +// Server: Represents a server in a simulated environment. +// Purpose: +// - Handles product requests and manages inventory. +// - Simulates server busy time during request processing. +// Usage: +// Server server(numProducts, maxInventory, processingTimeNormal, +// processingTimeSupplier, rng); int result = +// server.processRequest(productId, needsSupply); +// server.addInventory(productId, quantityAdded); +class Server { +private: + std::vector inventory; + int busyTime; + int processingTimeNormal; + int processingTimeSupplier; + RandomGenerator *rng; + +public: + Server(int numProducts, int K, int F, int G, RandomGenerator &rng) + : inventory(numProducts + 1, 0), busyTime(0), processingTimeNormal(F), + processingTimeSupplier(G), rng(&rng) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + + bool isBusy() const { return busyTime > 0; } + + void decrementBusyTime() { + if (busyTime > 0) + busyTime--; + } + + int processRequest(int productId, bool needsSupply) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return productId; + } else { + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return -productId; + } + } + + int processRequest2(int productId, double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = processingTimeNormal; + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + busyTime = processingTimeSupplier; + return -productId; + } + } + + void addInventory(int productId, int quantity) { + inventory[productId] += quantity; + } + + int getInventory(int productId) const { return inventory[productId]; } +}; +// ============================================================================ +// 17.5 SERVER-EVENT-DRIVEN +// ============================================================================ +class Server_Event_Driven { +private: + std::vector inventory; + RandomGenerator *rng; + double next_step_time; + double min_interval; + double max_interval; + +public: + Server_Event_Driven(int numProducts, int K, double min_interval, + double max_interval, RandomGenerator &rng, + double nextStepTime) + : inventory(numProducts + 1, 0), rng(&rng), min_interval(min_interval), + max_interval(max_interval), next_step_time(nextStepTime) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + int processRequestEventDriven(double current_time, int productId, + double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (current_time >= next_step_time) { + if (inventory[productId] > 0) { + inventory[productId]--; + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + + return -productId; + } + } else { + return 0; + } + } + double getNextStepTime() { return next_step_time; } +}; + +// ============================================================================ +// 18. SUPPLIER MODEL +// ============================================================================ + +// Supplier: Represents a supplier in a simulation. +// Purpose: +// - Simulates the supply of products at specific intervals. +// - Generates random supply requests periodically based on the defined +// intervals. +// Usage: +// Supplier supplier(minInterval, maxInterval, numProducts, rng); +// int suppliedProduct = supplier.step(timeStep); +// supplier.reset(); +class Supplier { +private: + double nextSupplyTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Supplier(double V, double Q, int N, RandomGenerator &generator) + : minInterval(V), maxInterval(Q), numProducts(N), rng(generator) { + nextSupplyTime = rng.uniform(V, Q); + } + + int step(double dt) { + nextSupplyTime -= dt; + if (nextSupplyTime <= 0) { + nextSupplyTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + void reset() { nextSupplyTime = rng.uniform(minInterval, maxInterval); } +}; + +// Server di inventario con messaggi (customer/server network-style) +class InventoryServerProcess : public DiscreteEventProcess { +public: + InventoryServerProcess(int pid, int numItems, int initialStockPerItem, + double serviceTime, MessageBus &bus, + RandomGenerator &rng) + : pid_(pid), serviceTime_(serviceTime), bus_(bus), rng_(rng), + nextEventTime_(0.0), + storage_(static_cast(numItems), initialStockPerItem), + totalRequests_(0), missedRequests_(0) {} + + void initialize(double startTime) override { + nextEventTime_ = startTime + serviceTime_; + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // processa TUTTI i messaggi arrivati a questo server + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + totalRequests_++; + + // sicurezza sugli indici prodotto + if (m.item < 0 || static_cast(m.item) >= storage_.size()) { + continue; // scarta richieste invalide + } + + double servedQty = 0.0; + int &stock = storage_[static_cast(m.item)]; + + if (stock >= static_cast(m.quantity)) { + // richiesta soddisfatta + stock -= static_cast(m.quantity); + servedQty = m.quantity; + } else { + // non abbastanza stock + servedQty = static_cast(stock); + missedRequests_++; + stock = 0; + } + + // risposta al cliente + Message reply; + reply.time = currentTime; + reply.sender = pid_; + reply.receiver = m.sender; // rimanda al mittente + reply.item = m.item; + reply.quantity = servedQty; + + bus_.procToNet(pid_).push(reply); + + // eventuale logica di refill: esempio semplice + if (stock == 0) { + int refill = rng_.uniformInt(1, 10); // batch di rifornimento random + stock += refill; + } + } + + nextEventTime_ = currentTime + serviceTime_; + } + + const std::vector &storage() const { return storage_; } + + int totalRequests() const { return totalRequests_; } + int missedRequests() const { return missedRequests_; } + +private: + int pid_; + double serviceTime_; + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + std::vector storage_; + + int totalRequests_; + int missedRequests_; +}; + +// ============================================================================ +// 19. INVENTORY CUSTOMER PROCESS (DES-compatible) +// ============================================================================ +// +// InventoryCustomerProcess: +// - Processo a eventi discreti che rappresenta un cliente. +// - Invia richieste al server tramite MessageBus (procToNet -> NetworkRouter). +// - Legge le risposte dal canale netToProc. +// - Statistiche: quante richieste ha mandato e quante risposte ha ricevuto. +// +class InventoryCustomerProcess : public DiscreteEventProcess { +public: + // pid : id di questo processo (indice nei canali del MessageBus) + // serverPid : pid del server a cui mandare le richieste + // numItems : numero di prodotti gestiti (item in [0, numItems-1]) + // minInterval/maxInterval : intervallo di tempo tra una richiesta e la + // successiva + InventoryCustomerProcess(int pid, int serverPid, int numItems, + double minInterval, double maxInterval, + MessageBus &bus, RandomGenerator &rng) + : pid_(pid), serverPid_(serverPid), numItems_(numItems), + minInterval_(minInterval), maxInterval_(maxInterval), bus_(bus), + rng_(rng), nextEventTime_(0.0), issuedRequests_(0), receivedReplies_(0), + totalRequestedQty_(0.0), totalServedQty_(0.0) {} + + // inizializza il prossimo evento (prima richiesta) + void initialize(double startTime) override { + nextEventTime_ = startTime + rng_.uniform(minInterval_, maxInterval_); + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // 1) genera una nuova richiesta al server + const int item = rng_.uniformInt( + 0, numItems_ - 1); // coerente con InventoryServerProcess + const int qty = rng_.uniformInt(1, 3); // quantità richiesta (1..3) + + Message req; + req.time = currentTime; + req.sender = pid_; + req.receiver = serverPid_; + req.item = item; + req.quantity = static_cast(qty); + + bus_.procToNet(pid_).push(req); + issuedRequests_++; + totalRequestedQty_ += req.quantity; + + // 2) processa tutte le risposte arrivate su questo cliente + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + // opzionale: possiamo filtrare solo messaggi dal serverPid_ + // if (m.sender != serverPid_) continue; + + receivedReplies_++; + totalServedQty_ += m.quantity; + } + + // 3) pianifica la prossima richiesta + nextEventTime_ = currentTime + rng_.uniform(minInterval_, maxInterval_); + } + + // --- metriche accessibili dall’esterno --- + + int pid() const { return pid_; } + int serverPid() const { return serverPid_; } + + int issuedRequests() const { return issuedRequests_; } + int receivedReplies() const { return receivedReplies_; } + + double totalRequestedQty() const { return totalRequestedQty_; } + double totalServedQty() const { return totalServedQty_; } + + // fill-rate lato customer + double serviceLevel() const { + if (totalRequestedQty_ <= 0.0) + return 1.0; + return totalServedQty_ / totalRequestedQty_; + } + +private: + int pid_; + int serverPid_; + int numItems_; + double minInterval_; + double maxInterval_; + + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + + int issuedRequests_; + int receivedReplies_; + double totalRequestedQty_; + double totalServedQty_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/1/include/Queue.hpp b/exams/AxelRubini/2025-02-05/1/include/Queue.hpp new file mode 100644 index 0000000..0c12ce8 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/1/include/Queue.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 9. FIFO QUEUE +// ============================================================================ + +// FIFOQueue: Implements a first-in-first-out (FIFO) queue with a fixed maximum +// size. Purpose: +// - Enqueue and dequeue items in FIFO order. +// - Track statistics such as total items enqueued and dequeued. +// - Support dynamic size constraints. +// Usage: +// FIFOQueue queue(10); +// queue.enqueue(5); +// int item; +// if (queue.dequeue(item)) { +// // Process item. +// } +template class FIFOQueue { +private: + std::queue queue; + size_t maxSize; + size_t totalEnqueued; + size_t totalDequeued; + +public: + FIFOQueue(size_t max = SIZE_MAX) + : maxSize(max), totalEnqueued(0), totalDequeued(0) {} + + bool enqueue(const T &item) { + if (queue.size() >= maxSize) + return false; + queue.push(item); + totalEnqueued++; + return true; + } + + bool dequeue(T &item) { + if (queue.empty()) + return false; + item = queue.front(); + queue.pop(); + totalDequeued++; + return true; + } + + bool isEmpty() const { return queue.empty(); } + bool isFull() const { return queue.size() >= maxSize; } + size_t size() const { return queue.size(); } + size_t getTotalEnqueued() const { return totalEnqueued; } + size_t getTotalDequeued() const { return totalDequeued; } + std::queue getQueue() const { return queue; } + T getFirst() const { + if (queue.empty()) + throw std::runtime_error("Queue is empty"); + return queue.front(); + } + + void clear() { + while (!queue.empty()) + queue.pop(); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/1/include/Random.hpp b/exams/AxelRubini/2025-02-05/1/include/Random.hpp new file mode 100644 index 0000000..52356f9 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/1/include/Random.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include // std::isfinite +#include // std::accumulate +#include +#include +#include + +namespace SELib { + +class RandomGenerator { +private: + std::mt19937 gen; + +public: + explicit RandomGenerator(unsigned seed = std::random_device{}()) + : gen(seed) {} + + double uniform(double min, double max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_real_distribution dis(lo, high); + return dis(gen); + } + + int uniformInt(int min, int max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_int_distribution dis(lo, high); + return dis(gen); + } + + double gaussian(double mean, double stddev) { + if (stddev < 0.0) { + throw std::invalid_argument("gaussian: stddev must be >= 0"); + } + std::normal_distribution dis(mean, stddev); + return dis(gen); + } + + bool bernoulli(double p) { + if (p < 0.0 || p > 1.0 || !std::isfinite(p)) { + throw std::invalid_argument("bernoulli: p must be in [0,1]"); + } + std::bernoulli_distribution dis(p); + return dis(gen); + } + + int discrete(const std::vector &probabilities) { + if (probabilities.empty()) { + throw std::invalid_argument("discrete: empty probability vector"); + } + + for (double w : probabilities) { + if (w < 0.0 || !std::isfinite(w)) { + throw std::invalid_argument( + "discrete: weights must be non-negative and finite"); + } + } + + double sum = + std::accumulate(probabilities.begin(), probabilities.end(), 0.0); + if (sum <= 0.0) { + throw std::invalid_argument("discrete: sum of weights must be > 0"); + } + + std::discrete_distribution dis(probabilities.begin(), + probabilities.end()); + return dis(gen); + } + + void setSeed(unsigned seed) { gen.seed(seed); } + + // (opzionale) accesso al motore se ti serve per altre distribuzioni + std::mt19937 &engine() { return gen; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/1/include/Stat.hpp b/exams/AxelRubini/2025-02-05/1/include/Stat.hpp new file mode 100644 index 0000000..27b4656 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/1/include/Stat.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 2. STATISTICS +// ============================================================================ + +// Statistics: A utility class for managing and analyzing numerical data +// samples. Purpose: +// - Compute statistical metrics such as mean and standard deviation. +// - Estimate probabilities of custom conditions. +// - Check convergence of data over iterations. +// Usage: +// Statistics stats; +// stats.addSample(5.0); +// double meanValue = stats.mean(); +class Statistics { +private: + std::vector samples; + +public: + void addSample(double value) { samples.push_back(value); } + + void clear() { samples.clear(); } + + double mean() const { + if (samples.empty()) + return 0.0; + double sum = 0.0; + for (double s : samples) + sum += s; + return sum / samples.size(); + } + + double stddev() const { + if (samples.size() < 2) + return 0.0; + double m = mean(); + double variance = 0.0; + for (double s : samples) { + variance += (s - m) * (s - m); + } + return std::sqrt(variance / (samples.size() - 1)); + } + + double probability(std::function condition) const { + if (samples.empty()) + return 0.0; + int count = 0; + for (double s : samples) { + if (condition(s)) + count++; + } + return static_cast(count) / samples.size(); + } + + size_t size() const { return samples.size(); } + const std::vector &getSamples() const { return samples; } + + bool hasConverged(double tolerance = 0.1) const { + if (samples.size() < 30) + return false; + double m = mean(); + double sd = stddev(); + return sd <= tolerance * std::abs(m); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/1/main b/exams/AxelRubini/2025-02-05/1/main new file mode 100755 index 0000000000000000000000000000000000000000..c6dae2fbfed4ecc63dda0384bf43c32994b0a74d GIT binary patch literal 76608 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 chgB|x5m42uu=8 +#include +#include +#include +#include +#include + +namespace SELib { + +class DroneProcess : public DiscreteEventProcess { + public: + DroneProcess( + int pid, + int monitorPid, + MessageBus &bus, + RandomGenerator &rng, + double X1, + double X2, + double Y1, + double Y2, + const std::vector &points + ) + : pid_(pid), monitorPid_(monitorPid), bus_(bus), rng_(rng), X1_(X1), + X2_(X2), Y1_(Y1), Y2_(Y2), points_(points) { + pos_ = {rng_.uniform(X1_, X2_), rng_.uniform(Y1_, Y2_)}; + nextTime_ = 0.0; + } + + void initialize(double startTime) override { nextTime_ = startTime; } + + double nextEventTime() const override { return nextTime_; } + + void handleEvent(double currentTime) override { + for (size_t j = 0; j < points_.size(); ++j) { + if (pos_.x >= points_[j].x - 1.0 && pos_.x <= points_[j].x + 1.0 && + pos_.y >= points_[j].y - 1.0 && pos_.y <= points_[j].y + 1.0) { + Message m; + m.time = currentTime; + m.sender = pid_; + m.receiver = monitorPid_; + m.item = (int)j; + m.quantity = 1.0; + bus_.procToNet(pid_).push(m); + } + } + + double vx = rng_.uniform(-0.5, 0.5); + double vy = rng_.uniform(-0.5, 0.5); + pos_.x = std::min(X2_, std::max(X1_, pos_.x + vx)); + pos_.y = std::min(Y2_, std::max(Y1_, pos_.y + vy)); + + nextTime_ += 1.0; + } + + private: + int pid_, monitorPid_; + MessageBus &bus_; + RandomGenerator &rng_; + double X1_, X2_, Y1_, Y2_; + const std::vector &points_; + Point2D pos_; + double nextTime_; +}; + +class MonitorProcess : public DiscreteEventProcess { + public: + MonitorProcess(int pid, int numPoints, int H, MessageBus &bus) + : pid_(pid), H_(H), bus_(bus) { + coverage_.assign(numPoints, 0.0); + currentCounts_.assign(numPoints, 0); + nextTime_ = 0.9; + } + + void initialize(double startTime) override { nextTime_ = startTime + 0.9; } + + double nextEventTime() const override { return nextTime_; } + + void handleEvent(double currentTime) override { + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + if (m.item >= 0 && m.item < (int)currentCounts_.size()) { + currentCounts_[m.item]++; + } + } + + int t = (int)nextTime_; + for (size_t j = 0; j < coverage_.size(); ++j) { + coverage_[j] = (coverage_[j] * t + currentCounts_[j]) / (t + 1); + currentCounts_[j] = 0; + } + + nextTime_ += 1.0; + } + + const std::vector &getCoverage() const { return coverage_; } + + private: + int pid_, H_; + MessageBus &bus_; + std::vector coverage_; + std::vector currentCounts_; + double nextTime_; +}; + +} // namespace SELib + +int main() { + SELib::RandomGenerator rng(0); + SELib::ParameterParser parser; + if (!parser.parseFile("parameters.txt")) + return 1; + + int H = parser.getInt("H"); + int N = parser.getInt("N"); + double X1 = 0, X2 = 0, Y1 = 0, Y2 = 0; + const auto &structData = parser.getStructuredData(); + for (const auto &line : structData) { + if (line.size() == 4 && line[0] != "H" && line[0] != "N" && + line[0] != "M") { + X1 = std::stod(line[0]); + X2 = std::stod(line[1]); + Y1 = std::stod(line[2]); + Y2 = std::stod(line[3]); + } + } + std::vector points; + bool foundM = false; + for (const auto &line : structData) { + if (foundM && line.size() == 2) { + points.push_back({std::stod(line[0]), std::stod(line[1])}); + } + if (line.size() == 2 && line[0] == "M") + foundM = true; + } + + SELib::MessageBus bus(N + 1); + SELib::DiscreteEventSystem system; + for (int i = 0; i < N; ++i) { + system.emplaceProcess( + i, N, bus, rng, X1, X2, Y1, Y2, points + ); + } + auto &monitor = system.emplaceProcess( + N, (int)points.size(), H, bus + ); + system.emplaceProcess(bus, rng, 0.001); + + SELib::DiscreteEventSimulator simulator(system); + simulator.run((double)H); + + std::ofstream outFile("results.txt"); + outFile << "2025-02-05-Axel-Rubini-2158099" << std::endl; + for (size_t i = 0; i < points.size(); ++i) { + outFile << points[i].x << " " << points[i].y << " " << std::fixed + << std::setprecision(4) << monitor.getCoverage()[i] + << std::endl; + } + return 0; +} diff --git a/exams/AxelRubini/2025-02-05/1/parameters.txt b/exams/AxelRubini/2025-02-05/1/parameters.txt new file mode 100644 index 0000000..276913a --- /dev/null +++ b/exams/AxelRubini/2025-02-05/1/parameters.txt @@ -0,0 +1,9 @@ +H 10000 +N 10 +-5 5 -5 5 +M 5 +0 1 +0 -1 +0 0 +-1 0 +1 0 diff --git a/exams/AxelRubini/2025-02-05/1/results.txt b/exams/AxelRubini/2025-02-05/1/results.txt new file mode 100644 index 0000000..a58ef0b --- /dev/null +++ b/exams/AxelRubini/2025-02-05/1/results.txt @@ -0,0 +1,6 @@ +2025-02-05-Axel-Rubini-2158099 +0 1 0.3541 +0.0000 -1.0000 0.3702 +0.0000 0.0000 0.3507 +-1.0000 0.0000 0.3350 +1.0000 0.0000 0.3750 diff --git a/exams/AxelRubini/2025-02-05/2/Makefile b/exams/AxelRubini/2025-02-05/2/Makefile new file mode 100644 index 0000000..6bc53bf --- /dev/null +++ b/exams/AxelRubini/2025-02-05/2/Makefile @@ -0,0 +1,10 @@ +CC=g++ +CFLAGS=-std=c++23 -O3 -Wall + +all: main + +main: main.cpp + $(CC) $(CFLAGS) main.cpp -o main + +clean: + rm -f main *.o \ No newline at end of file diff --git a/exams/AxelRubini/2025-02-05/2/include/DES.hpp b/exams/AxelRubini/2025-02-05/2/include/DES.hpp new file mode 100644 index 0000000..1706b8f --- /dev/null +++ b/exams/AxelRubini/2025-02-05/2/include/DES.hpp @@ -0,0 +1,212 @@ + +#pragma once + +#include "Random.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// --------------------------------------------------------------------------- +// Messaggio +// --------------------------------------------------------------------------- +struct Message { + double time{0.0}; + int sender{-1}; + int receiver{-1}; + int item{0}; + double quantity{0.0}; +}; + +// --------------------------------------------------------------------------- +// Bus di messaggi: process -> network, network -> process +// --------------------------------------------------------------------------- +class MessageBus { +public: + explicit MessageBus(std::size_t n = 0) { resize(n); } + + void resize(std::size_t n) { + procToNet_.assign(n, {}); + netToProc_.assign(n, {}); + } + + std::size_t size() const { return procToNet_.size(); } + + std::queue &procToNet(int pid) { return procToNet_.at(pid); } + std::queue &netToProc(int pid) { return netToProc_.at(pid); } + const std::queue &procToNet(int pid) const { + return procToNet_.at(pid); + } + const std::queue &netToProc(int pid) const { + return netToProc_.at(pid); + } + +private: + std::vector> procToNet_; + std::vector> netToProc_; +}; + +// --------------------------------------------------------------------------- +// Processo base a eventi discreti +// --------------------------------------------------------------------------- +class DiscreteEventProcess { +public: + virtual ~DiscreteEventProcess() = default; + virtual void initialize(double startTime) = 0; + virtual double nextEventTime() const = 0; + virtual void handleEvent(double currentTime) = 0; +}; + +// --------------------------------------------------------------------------- +// NetworkRouter: inoltra i messaggi P->N->P +// --------------------------------------------------------------------------- +class NetworkRouter : public DiscreteEventProcess { +public: + NetworkRouter(MessageBus &bus, RandomGenerator &rng, double scanPeriod = 0.1) + : bus_(bus), rng_(rng), scanPeriod_(scanPeriod), time_(0.0), + nextScanTime_(0.0), scanned_(0) {} + + void initialize(double startTime) override { + time_ = startTime; + nextScanTime_ = startTime + scanPeriod_; + + const int n = static_cast(bus_.size()); + if (n <= 0) { + throw std::runtime_error( + "NetworkRouter: no processes configured in MessageBus"); + } + + scanner_.resize(n); + for (int i = 0; i < n; ++i) { + scanner_[i] = i; + } + reshuffleScanner(); + scanned_ = 0; + } + + double nextEventTime() const override { return nextScanTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextScanTime_) + return; + + const int n = static_cast(scanner_.size()); + if (n == 0) + return; + + if (scanned_ >= n) { + scanned_ = 0; + reshuffleScanner(); + } + + const int channel = scanner_[scanned_++]; + auto &fromQueue = bus_.procToNet(channel); + + if (!fromQueue.empty()) { + Message m = fromQueue.front(); + fromQueue.pop(); + + if (m.receiver < 0 || m.receiver >= static_cast(bus_.size())) { + throw std::runtime_error("NetworkRouter: invalid receiver index"); + } + + bus_.netToProc(m.receiver).push(m); + } + + nextScanTime_ = currentTime + scanPeriod_; + } + +private: + void reshuffleScanner() { + auto &eng = rng_.engine(); // serve engine() in RandomGenerator + std::shuffle(scanner_.begin(), scanner_.end(), eng); + } + + MessageBus &bus_; + RandomGenerator &rng_; + + double scanPeriod_; + double time_; + double nextScanTime_; + std::vector scanner_; + int scanned_; +}; + +// --------------------------------------------------------------------------- +// Sistema DES: collezione di processi +// --------------------------------------------------------------------------- +class DiscreteEventSystem { +public: + template + Proc &emplaceProcess(Args &&...args) { + auto ptr = std::make_unique(std::forward(args)...); + Proc &ref = *ptr; + processes_.push_back(std::move(ptr)); + return ref; + } + + void initialize(double startTime = 0.0) { + for (auto &p : processes_) { + p->initialize(startTime); + } + } + + double nextEventTime() const { + if (processes_.empty()) { + return std::numeric_limits::infinity(); + } + + double tMin = std::numeric_limits::infinity(); + for (const auto &p : processes_) { + double t = p->nextEventTime(); + if (t < tMin) + tMin = t; + } + return tMin; + } + + void handleEvent(double currentTime) { + for (auto &p : processes_) { + if (p->nextEventTime() <= currentTime) { + p->handleEvent(currentTime); + } + } + } + +private: + std::vector> processes_; +}; + +// --------------------------------------------------------------------------- +// Simulatore DES +// --------------------------------------------------------------------------- +class DiscreteEventSimulator { +public: + explicit DiscreteEventSimulator(DiscreteEventSystem &system) + : system_(system), currentTime_(0.0) {} + + void run(double horizon) { + system_.initialize(0.0); + currentTime_ = 0.0; + + while (currentTime_ < horizon) { + double nextTime = system_.nextEventTime(); + if (nextTime == std::numeric_limits::infinity()) + break; + currentTime_ = nextTime; + system_.handleEvent(currentTime_); + } + } + + double currentTime() const { return currentTime_; } + +private: + DiscreteEventSystem &system_; + double currentTime_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/2/include/Decision.hpp b/exams/AxelRubini/2025-02-05/2/include/Decision.hpp new file mode 100644 index 0000000..4d37cf0 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/2/include/Decision.hpp @@ -0,0 +1,266 @@ +#pragma once + +#include "Random.hpp" +#include "Stat.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 7. MARKOV CHAIN +// ============================================================================ + +// MarkovChain: Implements a discrete-time Markov chain. +// Purpose: +// - Simulates stochastic processes with defined states and transition +// probabilities. +// - Useful in modeling random systems and computing related probabilities. +// Usage: +// MarkovChain mc(3, rng); +// mc.setTransition(0, 1, 0.5); +// mc.setTransition(0, 2, 0.5); +// mc.setState(0); +// int nextState = mc.step(); +class MarkovChain { +private: + int numStates; + std::vector> transitionMatrix; + int currentState; + RandomGenerator &rng; + +public: + MarkovChain(int states, RandomGenerator &generator) + : numStates(states), currentState(0), rng(generator) { + transitionMatrix.resize(states, std::vector(states, 0.0)); + } + + void setTransition(int from, int to, double probability) { + if (from >= 0 && from < numStates && to >= 0 && to < numStates) { + transitionMatrix[from][to] = probability; + } + } + + void setState(int state) { + if (state >= 0 && state < numStates) { + currentState = state; + } + } + + int getState() const { return currentState; } + + int step() { + currentState = rng.discrete(transitionMatrix[currentState]); + return currentState; + } + + double getTransitionProb(int from, int to) const { + return transitionMatrix[from][to]; + } + + double expectedSojournTime(int state, double timeStep = 1.0) const { + double pii = transitionMatrix[state][state]; + if (pii >= 1.0) + return std::numeric_limits::infinity(); + return timeStep / (1.0 - pii); + } +}; + +// ============================================================================ +// 8. MDP +// ============================================================================ + +// MDP: Implements a Markov Decision Process (MDP) model. +// Purpose: +// - Simulate decision-making processes with states, transitions, and +// associated costs. +// - Calculate the total cost to reach a target state from a starting state. +// Usage: +// - Add transitions between states with associated probabilities and costs +// using `addTransition`. +// - Use `step` to simulate a single transition and calculate its cost. +// - Use `simulate` to estimate the total cost from a start to an end state. +class MDP { +private: + int numStates; + std::map>>> + transitions; + RandomGenerator &rng; + +public: + MDP(int states, RandomGenerator &generator) + : numStates(states), rng(generator) {} + + void addTransition(int from, int to, double probability, double cost) { + transitions[from].push_back({to, {probability, cost}}); + } + + std::pair step(int currentState) { + if (transitions.find(currentState) == transitions.end()) { + return {currentState, 0.0}; + } + + std::vector probs; + for (const auto &t : transitions[currentState]) { + probs.push_back(t.second.first); + } + + int idx = rng.discrete(probs); + int nextState = transitions[currentState][idx].first; + double cost = transitions[currentState][idx].second.second; + + return {nextState, cost}; + } + + double simulate(int startState, int endState) { + int state = startState; + double totalCost = 0.0; + int maxSteps = 100000; + + for (int step = 0; step < maxSteps && state != endState; step++) { + auto [nextState, cost] = this->step(state); + totalCost += cost; + state = nextState; + } + return totalCost; + } +}; + +// ============================================================================ +// 10. MONTE CARLO SIMULATOR +// ============================================================================ + +// MonteCarloSimulator: A utility for running Monte Carlo simulations. +// Purpose: +// - Estimate statistical metrics through repeated random sampling. +// - Provides methods to calculate mean results and probabilities of events. +// Usage: +// MonteCarloSimulator simulator(1000); +// double estimate = simulator.estimate([](int) { return some_random_value; }); +// double probability = simulator.estimateProbability([](int) { return +// some_event_occurs; }); +class MonteCarloSimulator { +private: + Statistics stats; + int numSimulations; + +public: + MonteCarloSimulator(int simulations = 1000) : numSimulations(simulations) {} + + template double estimate(SimFunc simulation) { + stats.clear(); + for (int i = 0; i < numSimulations; i++) { + double result = simulation(i); + stats.addSample(result); + } + return stats.mean(); + } + + template double estimateProbability(SimFunc simulation) { + int successes = 0; + for (int i = 0; i < numSimulations; i++) { + if (simulation(i)) + successes++; + } + return static_cast(successes) / numSimulations; + } + + const Statistics &getStatistics() const { return stats; } +}; + +// ============================================================================ +// 13. OPTIMIZATION UTILITIES +// ============================================================================ + +// Optimizer: Utility class offering optimization techniques such as random +// search and grid search. Purpose: +// - Explore parameter spaces to maximize or minimize an objective function. +// - Supports both continuous and discrete search spaces. +// Usage: +// - Use `randomSearch` for stochastic optimization over a defined range. +// - Use `gridSearch` for systematic exploration in evenly spaced steps. +// Example: +// auto [bestParam, bestValue] = Optimizer::randomSearch(objectiveFunction, +// 0.0, 10.0, 100, rng); auto [bestInt, bestIntValue] = +// Optimizer::randomSearchInt(objectiveFunction, 0, 10, 50, rng); +class Optimizer { +public: + template + static std::pair + randomSearch(ObjFunc objective, double minValue, double maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + for (int i = 0; i < budget; i++) { + double param = rng.uniform(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair + randomSearchInt(ObjFunc objective, int minValue, int maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + int bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + if (budget <= 0) { + throw std::invalid_argument("randomSearchInt: budget must be positive."); + } + + if (minValue >= maxValue) { + throw std::invalid_argument( + "randomSearchInt: minValue must be less than maxValue."); + } + + for (int i = 0; i < budget; i++) { + int param = rng.uniformInt(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair gridSearch(ObjFunc objective, + double minValue, double maxValue, + int steps, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + double stepSize = (maxValue - minValue) / steps; + + if (steps <= 0) { + throw std::invalid_argument("gridSearch: steps must be positive."); + } + + for (int i = 0; i <= steps; i++) { + double param = minValue + i * stepSize; + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/2/include/Dynamics.hpp b/exams/AxelRubini/2025-02-05/2/include/Dynamics.hpp new file mode 100644 index 0000000..044176a --- /dev/null +++ b/exams/AxelRubini/2025-02-05/2/include/Dynamics.hpp @@ -0,0 +1,214 @@ +#pragma once + +#include +#include +namespace SELib { +// ============================================================================ +// 3. ODE SOLVER (Runge-Kutta 4th order) +// ============================================================================ + +using StateVector = std::vector; +using DerivativeFunction = std::function; + +// ODESolver: Implements numerical methods for solving Ordinary Differential +// Equations (ODEs). Purpose: +// - Provides tools for simulating dynamic systems modeled by ODEs. +// - Includes methods such as Runge-Kutta 4th order and Euler's method for +// integration. +// Usage: +// - Pass a derivative function that defines the ODE system. +// - Use `rk4Step` for higher accuracy or `eulerStep` for simpler systems. +// Example: +// StateVector nextState = ODESolver::rk4Step(0.0, currentState, controlInputs, +// dt, dynamicsFunction); +class ODESolver { +public: + static StateVector rk4Step(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector k1 = f(t, x, u); + StateVector x2 = addScaled(x, k1, dt / 2.0); + StateVector k2 = f(t + dt / 2.0, x2, u); + StateVector x3 = addScaled(x, k2, dt / 2.0); + StateVector k3 = f(t + dt / 2.0, x3, u); + StateVector x4 = addScaled(x, k3, dt); + StateVector k4 = f(t + dt, x4, u); + + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += (dt / 6.0) * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]); + } + return xNew; + } + + static StateVector eulerStep(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector dx = f(t, x, u); + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += dx[i] * dt; + } + return xNew; + } + +private: + static StateVector addScaled(const StateVector &x, const StateVector &dx, + double scale) { + StateVector result = x; + for (size_t i = 0; i < x.size(); i++) { + result[i] += dx[i] * scale; + } + return result; + } +}; + +// ============================================================================ +// 4. PID CONTROLLER +// ============================================================================ + +// PIDController: Implements a Proportional-Integral-Derivative (PID) +// controller. Purpose: +// - Automatically adjusts a control signal to minimize the difference between +// a desired setpoint and a measured value. +// - Used in control systems to regulate processes like temperature, speed, or +// position. +// Usage: +// - Create an instance with proportional (Kp), integral (Ki), and derivative +// (Kd) gains, along with a time step. +// - Use `compute` to calculate the control signal based on the setpoint and +// measurement. +// Example: +// PIDController pid(1.0, 0.1, 0.01, 0.1); +// double controlSignal = pid.compute(desiredValue, actualValue); +class PIDController { +private: + double kp, ki, kd; + double integral; + double prevError; + double dt; + +public: + PIDController(double Kp, double Ki, double Kd, double timeStep) + : kp(Kp), ki(Ki), kd(Kd), integral(0), prevError(0), dt(timeStep) {} + + double compute(double setpoint, double measurement) { + double error = setpoint - measurement; + integral += error * dt; + double derivative = (error - prevError) / dt; + prevError = error; + return kp * error + ki * integral + kd * derivative; + } + + void reset() { + integral = 0; + prevError = 0; + } + + void setGains(double Kp, double Ki, double Kd) { + kp = Kp; + ki = Ki; + kd = Kd; + } +}; + +// ============================================================================ +// 5. CONTINUOUS TIME SIMULATION +// ============================================================================ + +// ContinuousTimeSimulation: A base class for simulating continuous-time dynamic +// systems. Purpose: +// - Provides a framework for simulating systems characterized by continuous +// dynamics over time. +// - Integrates system dynamics using a Runge-Kutta 4th order solver. +// Usage: +// - Derive from this class and implement the `dynamics`, `computeControl`, +// `initialize`, and `finalize` methods. +// - Customize the `onStep` method for per-step side effects, if required. +// Example: +// class MySimulation : public ContinuousTimeSimulation { +// // Implement the required virtual methods. +// }; +// MySimulation sim(dt, horizon, initialState); +// sim.run(); +class ContinuousTimeSimulation { +protected: + double currentTime; + double timeStep; + double horizon; + StateVector state; + StateVector control; + +public: + ContinuousTimeSimulation(double dt, double H, const StateVector &initialState) + : currentTime(0.0), timeStep(dt), horizon(H), state(initialState) { + control.resize(initialState.size(), 0.0); + } + + virtual ~ContinuousTimeSimulation() = default; + + virtual StateVector dynamics(double t, const StateVector &x, + const StateVector &u) = 0; + virtual StateVector computeControl(double t, const StateVector &x) = 0; + virtual void initialize() = 0; + virtual void onStep() {} + virtual void finalize() = 0; + + void run() { + initialize(); + while (currentTime < horizon) { + control = computeControl(currentTime, state); + state = ODESolver::rk4Step( + currentTime, state, control, timeStep, + [this](double t, const StateVector &x, const StateVector &u) { + return this->dynamics(t, x, u); + }); + currentTime += timeStep; + onStep(); + } + finalize(); + } + + double getCurrentTime() const { return currentTime; } + const StateVector &getState() const { return state; } + const StateVector &getControl() const { return control; } +}; + +// ============================================================================ +// 6. SAMPLE AND HOLD +// ============================================================================ + +// SampleAndHold: A utility to store a value and update it periodically. +// Purpose: +// - Maintain a value that gets updated periodically based on a time interval. +// - Useful for sampling data and ensuring updates happen at fixed intervals. +// Usage: +// SampleAndHold sampler(5.0, 0); +// if (sampler.shouldUpdate(currentTime)) { +// sampler.update(currentTime, newValue); +// } +// int currentValue = sampler.getValue(); +template class SampleAndHold { +private: + T value; + double lastUpdateTime; + double samplePeriod; + +public: + SampleAndHold(double period, const T &initialValue = T()) + : value(initialValue), lastUpdateTime(0), samplePeriod(period) {} + + bool shouldUpdate(double currentTime) const { + return (currentTime - lastUpdateTime) >= samplePeriod; + } + + void update(double currentTime, const T &newValue) { + value = newValue; + lastUpdateTime = currentTime; + } + + const T &getValue() const { return value; } + void reset() { lastUpdateTime = 0; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/2/include/Geometry.hpp b/exams/AxelRubini/2025-02-05/2/include/Geometry.hpp new file mode 100644 index 0000000..525a91d --- /dev/null +++ b/exams/AxelRubini/2025-02-05/2/include/Geometry.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 14. GEOMETRIC UTILITIES +// ============================================================================ + +// Point2D: Represents a point or vector in 2D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point2D p1(1.0, 2.0), p2(2.0, 3.0); +// double dist = p1.distanceTo(p2); +// Point2D sum = p1 + p2; +struct Point2D { + double x, y; + + Point2D(double x_ = 0, double y_ = 0) : x(x_), y(y_) {} + + double distanceTo(const Point2D &other) const { + double dx = x - other.x; + double dy = y - other.y; + return std::sqrt(dx * dx + dy * dy); + } + + Point2D operator+(const Point2D &other) const { + return Point2D(x + other.x, y + other.y); + } + + Point2D operator-(const Point2D &other) const { + return Point2D(x - other.x, y - other.y); + } +}; + +// Point3D: Represents a point or vector in 3D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point3D p1(1.0, 2.0, 3.0), p2(2.0, 3.0, 4.0); +// double dist = p1.distanceTo(p2); +// Point3D sum = p1 + p2; +struct Point3D { + double x, y, z; + + Point3D(double x_ = 0, double y_ = 0, double z_ = 0) : x(x_), y(y_), z(z_) {} + + double distanceTo(const Point3D &other) const { + double dx = x - other.x; + double dy = y - other.y; + double dz = z - other.z; + return std::sqrt(dx * dx + dy * dy + dz * dz); + } + + Point3D operator+(const Point3D &other) const { + return Point3D(x + other.x, y + other.y, z + other.z); + } + + Point3D operator-(const Point3D &other) const { + return Point3D(x - other.x, y - other.y, z - other.z); + } +}; + +// ============================================================================ +// 19. COLLISION DETECTOR +// ============================================================================ + +// CollisionDetector: A utility class for detecting collisions between points +// in space. Purpose: +// - Counts the number of collisions within a given distance threshold. +// - Retrieves pairs of colliding points. +// Usage: +// std::vector points = {Point2D(0, 0), Point2D(1, 1)}; +// int numCollisions = CollisionDetector::countCollisions(points, 1.5); +// auto collidingPairs = CollisionDetector::getCollidingPairs(points, 1.5); +class CollisionDetector { +public: + template + static int countCollisions(const std::vector &positions, + double threshold) { + int collisions = 0; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + collisions++; + } + } + } + return collisions; + } + + template + static std::vector> + getCollidingPairs(const std::vector &positions, double threshold) { + std::vector> pairs; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + pairs.push_back({i, j}); + } + } + } + return pairs; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/2/include/IO.hpp b/exams/AxelRubini/2025-02-05/2/include/IO.hpp new file mode 100644 index 0000000..1a79cc6 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/2/include/IO.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 11. PARAMETER PARSER +// ============================================================================ + +// ParameterParser: A utility to parse and retrieve parameters from +// configuration files. Purpose: +// - Reads a file and stores key-value pairs for parameters. +// - Allows retrieval of parameters as integers, doubles, or strings. +// - Provides structured data for advanced parsing. +// Usage: +// ParameterParser parser; +// if (parser.parseFile("config.txt")) { +// int param = parser.getInt("example_key", 42); +// } +class ParameterParser { +private: + std::map params; + std::vector> structuredData; + +public: + bool parseFile(const std::string &filename) { + std::ifstream file(filename); + if (!file.is_open()) + return false; + + std::string line; + while (std::getline(file, line)) { + if (line.empty()) + continue; + + std::istringstream iss(line); + std::string key; + iss >> key; + + std::string value; + std::getline(iss, value); + if (!value.empty() && value[0] == ' ') { + value = value.substr(1); + } + + params[key] = value; + + std::vector tokens; + std::istringstream tokenStream(line); + std::string token; + while (tokenStream >> token) { + tokens.push_back(token); + } + structuredData.push_back(tokens); + } + return true; + } + + int getInt(const std::string &key, int defaultValue = 0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stoi(it->second); + } + return defaultValue; + } + + double getDouble(const std::string &key, double defaultValue = 0.0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stod(it->second); + } + return defaultValue; + } + + std::string getString(const std::string &key, + const std::string &defaultValue = "") const { + auto it = params.find(key); + if (it != params.end()) { + return it->second; + } + return defaultValue; + } + + const std::vector> &getStructuredData() const { + return structuredData; + } + + bool hasKey(const std::string &key) const { + return params.find(key) != params.end(); + } +}; + +// ============================================================================ +// 12. OUTPUT WRITER +// ============================================================================ + +// OutputWriter: A utility class for writing data to output files. +// Purpose: +// - Simplifies the process of writing data to files. +// - Supports writing lines, key-value pairs, and managing file headers. +// Usage: +// OutputWriter writer; +// if (writer.open("output.txt", "Header Info")) { +// writer.writeLine("Example Line"); +// writer.writeKeyValue("Key", 42.0); +// writer.close(); +// } +class OutputWriter { +private: + std::ofstream file; + +public: + bool open(const std::string &filename, const std::string &header) { + file.open(filename); + if (!file.is_open()) + return false; + file << header << std::endl; + return true; + } + + template void writeLine(const T &value) { + file << value << std::endl; + } + + template + void writeLine(const T &first, const Args &...args) { + file << first << " "; + writeLine(args...); + } + + void writeKeyValue(const std::string &key, double value, int precision = 2) { + file << key << " " << std::fixed << std::setprecision(precision) << value + << std::endl; + } + + void close() { + if (file.is_open()) { + file.close(); + } + } + + ~OutputWriter() { close(); } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/2/include/Inventory.hpp b/exams/AxelRubini/2025-02-05/2/include/Inventory.hpp new file mode 100644 index 0000000..c82d776 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/2/include/Inventory.hpp @@ -0,0 +1,423 @@ +#pragma once + +#include "DES.hpp" +#include "Random.hpp" +#include +#include + +namespace SELib { +// ============================================================================ +// 16. CUSTOMER MODEL +// ============================================================================ + +// Customer: Represents a customer model in a simulation. +// Purpose: +// - Simulates customer requests over time. +// - Generates random requests for products at specific intervals. +// Usage: +// Customer customer(5.0, 10.0, 3, rng); +// int productId = customer.step(0.1); // Simulates a time step of 0.1 seconds. +class Customer { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer(double A, double B, int N, RandomGenerator &generator) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator) { + nextRequestTime = rng.uniform(A, B); + } + + int step(double dt) { + nextRequestTime -= dt; + if (nextRequestTime <= 0) { + nextRequestTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } +}; +class Customer_Event_Driven { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer_Event_Driven(double A, double B, int N, RandomGenerator &generator, + double nextRequestTime) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator), + nextRequestTime(nextRequestTime) {} + + int step_event_driven(double *current_time) { + if (*current_time >= nextRequestTime) { + nextRequestTime = *current_time + rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + double getNextRequestTime() { return nextRequestTime; }; +}; + +// ============================================================================ +// 17. SERVER MODEL +// ============================================================================ + +// Server: Represents a server in a simulated environment. +// Purpose: +// - Handles product requests and manages inventory. +// - Simulates server busy time during request processing. +// Usage: +// Server server(numProducts, maxInventory, processingTimeNormal, +// processingTimeSupplier, rng); int result = +// server.processRequest(productId, needsSupply); +// server.addInventory(productId, quantityAdded); +class Server { +private: + std::vector inventory; + int busyTime; + int processingTimeNormal; + int processingTimeSupplier; + RandomGenerator *rng; + +public: + Server(int numProducts, int K, int F, int G, RandomGenerator &rng) + : inventory(numProducts + 1, 0), busyTime(0), processingTimeNormal(F), + processingTimeSupplier(G), rng(&rng) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + + bool isBusy() const { return busyTime > 0; } + + void decrementBusyTime() { + if (busyTime > 0) + busyTime--; + } + + int processRequest(int productId, bool needsSupply) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return productId; + } else { + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return -productId; + } + } + + int processRequest2(int productId, double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = processingTimeNormal; + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + busyTime = processingTimeSupplier; + return -productId; + } + } + + void addInventory(int productId, int quantity) { + inventory[productId] += quantity; + } + + int getInventory(int productId) const { return inventory[productId]; } +}; +// ============================================================================ +// 17.5 SERVER-EVENT-DRIVEN +// ============================================================================ +class Server_Event_Driven { +private: + std::vector inventory; + RandomGenerator *rng; + double next_step_time; + double min_interval; + double max_interval; + +public: + Server_Event_Driven(int numProducts, int K, double min_interval, + double max_interval, RandomGenerator &rng, + double nextStepTime) + : inventory(numProducts + 1, 0), rng(&rng), min_interval(min_interval), + max_interval(max_interval), next_step_time(nextStepTime) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + int processRequestEventDriven(double current_time, int productId, + double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (current_time >= next_step_time) { + if (inventory[productId] > 0) { + inventory[productId]--; + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + + return -productId; + } + } else { + return 0; + } + } + double getNextStepTime() { return next_step_time; } +}; + +// ============================================================================ +// 18. SUPPLIER MODEL +// ============================================================================ + +// Supplier: Represents a supplier in a simulation. +// Purpose: +// - Simulates the supply of products at specific intervals. +// - Generates random supply requests periodically based on the defined +// intervals. +// Usage: +// Supplier supplier(minInterval, maxInterval, numProducts, rng); +// int suppliedProduct = supplier.step(timeStep); +// supplier.reset(); +class Supplier { +private: + double nextSupplyTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Supplier(double V, double Q, int N, RandomGenerator &generator) + : minInterval(V), maxInterval(Q), numProducts(N), rng(generator) { + nextSupplyTime = rng.uniform(V, Q); + } + + int step(double dt) { + nextSupplyTime -= dt; + if (nextSupplyTime <= 0) { + nextSupplyTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + void reset() { nextSupplyTime = rng.uniform(minInterval, maxInterval); } +}; + +// Server di inventario con messaggi (customer/server network-style) +class InventoryServerProcess : public DiscreteEventProcess { +public: + InventoryServerProcess(int pid, int numItems, int initialStockPerItem, + double serviceTime, MessageBus &bus, + RandomGenerator &rng) + : pid_(pid), serviceTime_(serviceTime), bus_(bus), rng_(rng), + nextEventTime_(0.0), + storage_(static_cast(numItems), initialStockPerItem), + totalRequests_(0), missedRequests_(0) {} + + void initialize(double startTime) override { + nextEventTime_ = startTime + serviceTime_; + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // processa TUTTI i messaggi arrivati a questo server + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + totalRequests_++; + + // sicurezza sugli indici prodotto + if (m.item < 0 || static_cast(m.item) >= storage_.size()) { + continue; // scarta richieste invalide + } + + double servedQty = 0.0; + int &stock = storage_[static_cast(m.item)]; + + if (stock >= static_cast(m.quantity)) { + // richiesta soddisfatta + stock -= static_cast(m.quantity); + servedQty = m.quantity; + } else { + // non abbastanza stock + servedQty = static_cast(stock); + missedRequests_++; + stock = 0; + } + + // risposta al cliente + Message reply; + reply.time = currentTime; + reply.sender = pid_; + reply.receiver = m.sender; // rimanda al mittente + reply.item = m.item; + reply.quantity = servedQty; + + bus_.procToNet(pid_).push(reply); + + // eventuale logica di refill: esempio semplice + if (stock == 0) { + int refill = rng_.uniformInt(1, 10); // batch di rifornimento random + stock += refill; + } + } + + nextEventTime_ = currentTime + serviceTime_; + } + + const std::vector &storage() const { return storage_; } + + int totalRequests() const { return totalRequests_; } + int missedRequests() const { return missedRequests_; } + +private: + int pid_; + double serviceTime_; + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + std::vector storage_; + + int totalRequests_; + int missedRequests_; +}; + +// ============================================================================ +// 19. INVENTORY CUSTOMER PROCESS (DES-compatible) +// ============================================================================ +// +// InventoryCustomerProcess: +// - Processo a eventi discreti che rappresenta un cliente. +// - Invia richieste al server tramite MessageBus (procToNet -> NetworkRouter). +// - Legge le risposte dal canale netToProc. +// - Statistiche: quante richieste ha mandato e quante risposte ha ricevuto. +// +class InventoryCustomerProcess : public DiscreteEventProcess { +public: + // pid : id di questo processo (indice nei canali del MessageBus) + // serverPid : pid del server a cui mandare le richieste + // numItems : numero di prodotti gestiti (item in [0, numItems-1]) + // minInterval/maxInterval : intervallo di tempo tra una richiesta e la + // successiva + InventoryCustomerProcess(int pid, int serverPid, int numItems, + double minInterval, double maxInterval, + MessageBus &bus, RandomGenerator &rng) + : pid_(pid), serverPid_(serverPid), numItems_(numItems), + minInterval_(minInterval), maxInterval_(maxInterval), bus_(bus), + rng_(rng), nextEventTime_(0.0), issuedRequests_(0), receivedReplies_(0), + totalRequestedQty_(0.0), totalServedQty_(0.0) {} + + // inizializza il prossimo evento (prima richiesta) + void initialize(double startTime) override { + nextEventTime_ = startTime + rng_.uniform(minInterval_, maxInterval_); + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // 1) genera una nuova richiesta al server + const int item = rng_.uniformInt( + 0, numItems_ - 1); // coerente con InventoryServerProcess + const int qty = rng_.uniformInt(1, 3); // quantità richiesta (1..3) + + Message req; + req.time = currentTime; + req.sender = pid_; + req.receiver = serverPid_; + req.item = item; + req.quantity = static_cast(qty); + + bus_.procToNet(pid_).push(req); + issuedRequests_++; + totalRequestedQty_ += req.quantity; + + // 2) processa tutte le risposte arrivate su questo cliente + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + // opzionale: possiamo filtrare solo messaggi dal serverPid_ + // if (m.sender != serverPid_) continue; + + receivedReplies_++; + totalServedQty_ += m.quantity; + } + + // 3) pianifica la prossima richiesta + nextEventTime_ = currentTime + rng_.uniform(minInterval_, maxInterval_); + } + + // --- metriche accessibili dall’esterno --- + + int pid() const { return pid_; } + int serverPid() const { return serverPid_; } + + int issuedRequests() const { return issuedRequests_; } + int receivedReplies() const { return receivedReplies_; } + + double totalRequestedQty() const { return totalRequestedQty_; } + double totalServedQty() const { return totalServedQty_; } + + // fill-rate lato customer + double serviceLevel() const { + if (totalRequestedQty_ <= 0.0) + return 1.0; + return totalServedQty_ / totalRequestedQty_; + } + +private: + int pid_; + int serverPid_; + int numItems_; + double minInterval_; + double maxInterval_; + + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + + int issuedRequests_; + int receivedReplies_; + double totalRequestedQty_; + double totalServedQty_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/2/include/Queue.hpp b/exams/AxelRubini/2025-02-05/2/include/Queue.hpp new file mode 100644 index 0000000..0c12ce8 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/2/include/Queue.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 9. FIFO QUEUE +// ============================================================================ + +// FIFOQueue: Implements a first-in-first-out (FIFO) queue with a fixed maximum +// size. Purpose: +// - Enqueue and dequeue items in FIFO order. +// - Track statistics such as total items enqueued and dequeued. +// - Support dynamic size constraints. +// Usage: +// FIFOQueue queue(10); +// queue.enqueue(5); +// int item; +// if (queue.dequeue(item)) { +// // Process item. +// } +template class FIFOQueue { +private: + std::queue queue; + size_t maxSize; + size_t totalEnqueued; + size_t totalDequeued; + +public: + FIFOQueue(size_t max = SIZE_MAX) + : maxSize(max), totalEnqueued(0), totalDequeued(0) {} + + bool enqueue(const T &item) { + if (queue.size() >= maxSize) + return false; + queue.push(item); + totalEnqueued++; + return true; + } + + bool dequeue(T &item) { + if (queue.empty()) + return false; + item = queue.front(); + queue.pop(); + totalDequeued++; + return true; + } + + bool isEmpty() const { return queue.empty(); } + bool isFull() const { return queue.size() >= maxSize; } + size_t size() const { return queue.size(); } + size_t getTotalEnqueued() const { return totalEnqueued; } + size_t getTotalDequeued() const { return totalDequeued; } + std::queue getQueue() const { return queue; } + T getFirst() const { + if (queue.empty()) + throw std::runtime_error("Queue is empty"); + return queue.front(); + } + + void clear() { + while (!queue.empty()) + queue.pop(); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/2/include/Random.hpp b/exams/AxelRubini/2025-02-05/2/include/Random.hpp new file mode 100644 index 0000000..52356f9 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/2/include/Random.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include // std::isfinite +#include // std::accumulate +#include +#include +#include + +namespace SELib { + +class RandomGenerator { +private: + std::mt19937 gen; + +public: + explicit RandomGenerator(unsigned seed = std::random_device{}()) + : gen(seed) {} + + double uniform(double min, double max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_real_distribution dis(lo, high); + return dis(gen); + } + + int uniformInt(int min, int max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_int_distribution dis(lo, high); + return dis(gen); + } + + double gaussian(double mean, double stddev) { + if (stddev < 0.0) { + throw std::invalid_argument("gaussian: stddev must be >= 0"); + } + std::normal_distribution dis(mean, stddev); + return dis(gen); + } + + bool bernoulli(double p) { + if (p < 0.0 || p > 1.0 || !std::isfinite(p)) { + throw std::invalid_argument("bernoulli: p must be in [0,1]"); + } + std::bernoulli_distribution dis(p); + return dis(gen); + } + + int discrete(const std::vector &probabilities) { + if (probabilities.empty()) { + throw std::invalid_argument("discrete: empty probability vector"); + } + + for (double w : probabilities) { + if (w < 0.0 || !std::isfinite(w)) { + throw std::invalid_argument( + "discrete: weights must be non-negative and finite"); + } + } + + double sum = + std::accumulate(probabilities.begin(), probabilities.end(), 0.0); + if (sum <= 0.0) { + throw std::invalid_argument("discrete: sum of weights must be > 0"); + } + + std::discrete_distribution dis(probabilities.begin(), + probabilities.end()); + return dis(gen); + } + + void setSeed(unsigned seed) { gen.seed(seed); } + + // (opzionale) accesso al motore se ti serve per altre distribuzioni + std::mt19937 &engine() { return gen; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/2/include/Stat.hpp b/exams/AxelRubini/2025-02-05/2/include/Stat.hpp new file mode 100644 index 0000000..27b4656 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/2/include/Stat.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 2. STATISTICS +// ============================================================================ + +// Statistics: A utility class for managing and analyzing numerical data +// samples. Purpose: +// - Compute statistical metrics such as mean and standard deviation. +// - Estimate probabilities of custom conditions. +// - Check convergence of data over iterations. +// Usage: +// Statistics stats; +// stats.addSample(5.0); +// double meanValue = stats.mean(); +class Statistics { +private: + std::vector samples; + +public: + void addSample(double value) { samples.push_back(value); } + + void clear() { samples.clear(); } + + double mean() const { + if (samples.empty()) + return 0.0; + double sum = 0.0; + for (double s : samples) + sum += s; + return sum / samples.size(); + } + + double stddev() const { + if (samples.size() < 2) + return 0.0; + double m = mean(); + double variance = 0.0; + for (double s : samples) { + variance += (s - m) * (s - m); + } + return std::sqrt(variance / (samples.size() - 1)); + } + + double probability(std::function condition) const { + if (samples.empty()) + return 0.0; + int count = 0; + for (double s : samples) { + if (condition(s)) + count++; + } + return static_cast(count) / samples.size(); + } + + size_t size() const { return samples.size(); } + const std::vector &getSamples() const { return samples; } + + bool hasConverged(double tolerance = 0.1) const { + if (samples.size() < 30) + return false; + double m = mean(); + double sd = stddev(); + return sd <= tolerance * std::abs(m); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/2/main b/exams/AxelRubini/2025-02-05/2/main new file mode 100755 index 0000000000000000000000000000000000000000..e46da14cf888f85df9902452d6298da1c8b9a76a GIT binary patch literal 44016 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 uizGj?U$_nw +#include +#include +#include +#include +#include + +namespace SELib { + +class DroneProcess : public DiscreteEventProcess { + public: + DroneProcess( + int pid, + int monitorPid, + MessageBus &bus, + RandomGenerator &rng, + double X1, + double X2, + double Y1, + double Y2, + const std::vector &points + ) + : pid_(pid), monitorPid_(monitorPid), bus_(bus), rng_(rng), X1_(X1), + X2_(X2), Y1_(Y1), Y2_(Y2), points_(points) { + pos_ = {rng_.uniform(X1_, X2_), rng_.uniform(Y1_, Y2_)}; + nextTime_ = 0.0; + } + + void initialize(double startTime) override { nextTime_ = startTime; } + + double nextEventTime() const override { return nextTime_; } + + void handleEvent(double currentTime) override { + for (size_t j = 0; j < points_.size(); ++j) { + if (pos_.x >= points_[j].x - 1.0 && pos_.x <= points_[j].x + 1.0 && + pos_.y >= points_[j].y - 1.0 && pos_.y <= points_[j].y + 1.0) { + Message m; + m.time = currentTime; + m.sender = pid_; + m.receiver = monitorPid_; + m.item = (int)j; + m.quantity = 1.0; + bus_.procToNet(pid_).push(m); + } + } + + double vx = rng_.uniform(-0.5, 0.5); + double vy = rng_.uniform(-0.5, 0.5); + pos_.x = std::min(X2_, std::max(X1_, pos_.x + vx)); + pos_.y = std::min(Y2_, std::max(Y1_, pos_.y + vy)); + + nextTime_ += 1.0; + } + + private: + int pid_, monitorPid_; + MessageBus &bus_; + RandomGenerator &rng_; + double X1_, X2_, Y1_, Y2_; + const std::vector &points_; + Point2D pos_; + double nextTime_; +}; + +class MonitorProcess : public DiscreteEventProcess { + public: + MonitorProcess(int pid, int numPoints, int H, MessageBus &bus) + : pid_(pid), H_(H), bus_(bus) { + coverage_.assign(numPoints, 0.0); + currentCounts_.assign(numPoints, 0); + nextTime_ = 0.9; + } + + void initialize(double startTime) override { nextTime_ = startTime + 0.9; } + + double nextEventTime() const override { return nextTime_; } + + void handleEvent(double currentTime) override { + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + if (m.item >= 0 && m.item < (int)currentCounts_.size()) { + currentCounts_[m.item]++; + } + } + + int t = (int)nextTime_; + for (size_t j = 0; j < coverage_.size(); ++j) { + coverage_[j] = (coverage_[j] * t + currentCounts_[j]) / (t + 1); + currentCounts_[j] = 0; + } + + nextTime_ += 1.0; + } + + const std::vector &getCoverage() const { return coverage_; } + + private: + int pid_, H_; + MessageBus &bus_; + std::vector coverage_; + std::vector currentCounts_; + double nextTime_; +}; + +bool runSimulation( + double C_threshold, + int H, + int N, + double X1, + double X2, + double Y1, + double Y2, + const std::vector &points, + RandomGenerator &rng +) { + MessageBus bus(N + 1); + DiscreteEventSystem system; + for (int i = 0; i < N; ++i) { + system.emplaceProcess( + i, N, bus, rng, X1, X2, Y1, Y2, points + ); + } + auto &monitor = + system.emplaceProcess(N, (int)points.size(), H, bus); + system.emplaceProcess(bus, rng, 0.001); + + DiscreteEventSimulator simulator(system); + simulator.run((double)H); + + for (double cov : monitor.getCoverage()) { + if (cov < C_threshold) + return false; + } + return true; +} + +} // namespace SELib + +int main() { + SELib::RandomGenerator rng(0); + SELib::ParameterParser parser; + if (!parser.parseFile("parameters.txt")) + return 1; + + double C_threshold = parser.getDouble("C"); + int H = parser.getInt("H"); + int N = parser.getInt("N"); + double X1 = 0, X2 = 0, Y1 = 0, Y2 = 0; + const auto &structData = parser.getStructuredData(); + for (const auto &line : structData) { + if (line.size() == 4 && line[0] != "H" && line[0] != "N" && + line[0] != "M" && line[0] != "C") { + X1 = std::stod(line[0]); + X2 = std::stod(line[1]); + Y1 = std::stod(line[2]); + Y2 = std::stod(line[3]); + } + } + std::vector points; + bool foundM = false; + for (const auto &line : structData) { + if (foundM && line.size() == 2) { + points.push_back({std::stod(line[0]), std::stod(line[1])}); + } + if (line.size() == 2 && line[0] == "M") + foundM = true; + } + + SELib::MonteCarloSimulator mc(1000); + double prob = mc.estimateProbability([&](int) { + return SELib::runSimulation( + C_threshold, H, N, X1, X2, Y1, Y2, points, rng + ); + }); + + std::ofstream outFile("results.txt"); + outFile << "2025-02-05-Axel-Rubini-2158099" << std::endl; + outFile << "P " << std::fixed << std::setprecision(4) << prob << std::endl; + return 0; +} diff --git a/exams/AxelRubini/2025-02-05/2/parameters.txt b/exams/AxelRubini/2025-02-05/2/parameters.txt new file mode 100644 index 0000000..b0ba50b --- /dev/null +++ b/exams/AxelRubini/2025-02-05/2/parameters.txt @@ -0,0 +1,10 @@ +C 0.02 +H 10000 +N 10 +-5 5 -5 5 +M 5 +0 1 +0 -1 +0 0 +-1 0 +1 0 diff --git a/exams/AxelRubini/2025-02-05/2/results.txt b/exams/AxelRubini/2025-02-05/2/results.txt new file mode 100644 index 0000000..434389d --- /dev/null +++ b/exams/AxelRubini/2025-02-05/2/results.txt @@ -0,0 +1,2 @@ +2025-02-05-Axel-Rubini-2158099 +P 1 diff --git a/exams/AxelRubini/2025-02-05/3/Makefile b/exams/AxelRubini/2025-02-05/3/Makefile new file mode 100644 index 0000000..6bc53bf --- /dev/null +++ b/exams/AxelRubini/2025-02-05/3/Makefile @@ -0,0 +1,10 @@ +CC=g++ +CFLAGS=-std=c++23 -O3 -Wall + +all: main + +main: main.cpp + $(CC) $(CFLAGS) main.cpp -o main + +clean: + rm -f main *.o \ No newline at end of file diff --git a/exams/AxelRubini/2025-02-05/3/include/DES.hpp b/exams/AxelRubini/2025-02-05/3/include/DES.hpp new file mode 100644 index 0000000..1706b8f --- /dev/null +++ b/exams/AxelRubini/2025-02-05/3/include/DES.hpp @@ -0,0 +1,212 @@ + +#pragma once + +#include "Random.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// --------------------------------------------------------------------------- +// Messaggio +// --------------------------------------------------------------------------- +struct Message { + double time{0.0}; + int sender{-1}; + int receiver{-1}; + int item{0}; + double quantity{0.0}; +}; + +// --------------------------------------------------------------------------- +// Bus di messaggi: process -> network, network -> process +// --------------------------------------------------------------------------- +class MessageBus { +public: + explicit MessageBus(std::size_t n = 0) { resize(n); } + + void resize(std::size_t n) { + procToNet_.assign(n, {}); + netToProc_.assign(n, {}); + } + + std::size_t size() const { return procToNet_.size(); } + + std::queue &procToNet(int pid) { return procToNet_.at(pid); } + std::queue &netToProc(int pid) { return netToProc_.at(pid); } + const std::queue &procToNet(int pid) const { + return procToNet_.at(pid); + } + const std::queue &netToProc(int pid) const { + return netToProc_.at(pid); + } + +private: + std::vector> procToNet_; + std::vector> netToProc_; +}; + +// --------------------------------------------------------------------------- +// Processo base a eventi discreti +// --------------------------------------------------------------------------- +class DiscreteEventProcess { +public: + virtual ~DiscreteEventProcess() = default; + virtual void initialize(double startTime) = 0; + virtual double nextEventTime() const = 0; + virtual void handleEvent(double currentTime) = 0; +}; + +// --------------------------------------------------------------------------- +// NetworkRouter: inoltra i messaggi P->N->P +// --------------------------------------------------------------------------- +class NetworkRouter : public DiscreteEventProcess { +public: + NetworkRouter(MessageBus &bus, RandomGenerator &rng, double scanPeriod = 0.1) + : bus_(bus), rng_(rng), scanPeriod_(scanPeriod), time_(0.0), + nextScanTime_(0.0), scanned_(0) {} + + void initialize(double startTime) override { + time_ = startTime; + nextScanTime_ = startTime + scanPeriod_; + + const int n = static_cast(bus_.size()); + if (n <= 0) { + throw std::runtime_error( + "NetworkRouter: no processes configured in MessageBus"); + } + + scanner_.resize(n); + for (int i = 0; i < n; ++i) { + scanner_[i] = i; + } + reshuffleScanner(); + scanned_ = 0; + } + + double nextEventTime() const override { return nextScanTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextScanTime_) + return; + + const int n = static_cast(scanner_.size()); + if (n == 0) + return; + + if (scanned_ >= n) { + scanned_ = 0; + reshuffleScanner(); + } + + const int channel = scanner_[scanned_++]; + auto &fromQueue = bus_.procToNet(channel); + + if (!fromQueue.empty()) { + Message m = fromQueue.front(); + fromQueue.pop(); + + if (m.receiver < 0 || m.receiver >= static_cast(bus_.size())) { + throw std::runtime_error("NetworkRouter: invalid receiver index"); + } + + bus_.netToProc(m.receiver).push(m); + } + + nextScanTime_ = currentTime + scanPeriod_; + } + +private: + void reshuffleScanner() { + auto &eng = rng_.engine(); // serve engine() in RandomGenerator + std::shuffle(scanner_.begin(), scanner_.end(), eng); + } + + MessageBus &bus_; + RandomGenerator &rng_; + + double scanPeriod_; + double time_; + double nextScanTime_; + std::vector scanner_; + int scanned_; +}; + +// --------------------------------------------------------------------------- +// Sistema DES: collezione di processi +// --------------------------------------------------------------------------- +class DiscreteEventSystem { +public: + template + Proc &emplaceProcess(Args &&...args) { + auto ptr = std::make_unique(std::forward(args)...); + Proc &ref = *ptr; + processes_.push_back(std::move(ptr)); + return ref; + } + + void initialize(double startTime = 0.0) { + for (auto &p : processes_) { + p->initialize(startTime); + } + } + + double nextEventTime() const { + if (processes_.empty()) { + return std::numeric_limits::infinity(); + } + + double tMin = std::numeric_limits::infinity(); + for (const auto &p : processes_) { + double t = p->nextEventTime(); + if (t < tMin) + tMin = t; + } + return tMin; + } + + void handleEvent(double currentTime) { + for (auto &p : processes_) { + if (p->nextEventTime() <= currentTime) { + p->handleEvent(currentTime); + } + } + } + +private: + std::vector> processes_; +}; + +// --------------------------------------------------------------------------- +// Simulatore DES +// --------------------------------------------------------------------------- +class DiscreteEventSimulator { +public: + explicit DiscreteEventSimulator(DiscreteEventSystem &system) + : system_(system), currentTime_(0.0) {} + + void run(double horizon) { + system_.initialize(0.0); + currentTime_ = 0.0; + + while (currentTime_ < horizon) { + double nextTime = system_.nextEventTime(); + if (nextTime == std::numeric_limits::infinity()) + break; + currentTime_ = nextTime; + system_.handleEvent(currentTime_); + } + } + + double currentTime() const { return currentTime_; } + +private: + DiscreteEventSystem &system_; + double currentTime_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/3/include/Decision.hpp b/exams/AxelRubini/2025-02-05/3/include/Decision.hpp new file mode 100644 index 0000000..4d37cf0 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/3/include/Decision.hpp @@ -0,0 +1,266 @@ +#pragma once + +#include "Random.hpp" +#include "Stat.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 7. MARKOV CHAIN +// ============================================================================ + +// MarkovChain: Implements a discrete-time Markov chain. +// Purpose: +// - Simulates stochastic processes with defined states and transition +// probabilities. +// - Useful in modeling random systems and computing related probabilities. +// Usage: +// MarkovChain mc(3, rng); +// mc.setTransition(0, 1, 0.5); +// mc.setTransition(0, 2, 0.5); +// mc.setState(0); +// int nextState = mc.step(); +class MarkovChain { +private: + int numStates; + std::vector> transitionMatrix; + int currentState; + RandomGenerator &rng; + +public: + MarkovChain(int states, RandomGenerator &generator) + : numStates(states), currentState(0), rng(generator) { + transitionMatrix.resize(states, std::vector(states, 0.0)); + } + + void setTransition(int from, int to, double probability) { + if (from >= 0 && from < numStates && to >= 0 && to < numStates) { + transitionMatrix[from][to] = probability; + } + } + + void setState(int state) { + if (state >= 0 && state < numStates) { + currentState = state; + } + } + + int getState() const { return currentState; } + + int step() { + currentState = rng.discrete(transitionMatrix[currentState]); + return currentState; + } + + double getTransitionProb(int from, int to) const { + return transitionMatrix[from][to]; + } + + double expectedSojournTime(int state, double timeStep = 1.0) const { + double pii = transitionMatrix[state][state]; + if (pii >= 1.0) + return std::numeric_limits::infinity(); + return timeStep / (1.0 - pii); + } +}; + +// ============================================================================ +// 8. MDP +// ============================================================================ + +// MDP: Implements a Markov Decision Process (MDP) model. +// Purpose: +// - Simulate decision-making processes with states, transitions, and +// associated costs. +// - Calculate the total cost to reach a target state from a starting state. +// Usage: +// - Add transitions between states with associated probabilities and costs +// using `addTransition`. +// - Use `step` to simulate a single transition and calculate its cost. +// - Use `simulate` to estimate the total cost from a start to an end state. +class MDP { +private: + int numStates; + std::map>>> + transitions; + RandomGenerator &rng; + +public: + MDP(int states, RandomGenerator &generator) + : numStates(states), rng(generator) {} + + void addTransition(int from, int to, double probability, double cost) { + transitions[from].push_back({to, {probability, cost}}); + } + + std::pair step(int currentState) { + if (transitions.find(currentState) == transitions.end()) { + return {currentState, 0.0}; + } + + std::vector probs; + for (const auto &t : transitions[currentState]) { + probs.push_back(t.second.first); + } + + int idx = rng.discrete(probs); + int nextState = transitions[currentState][idx].first; + double cost = transitions[currentState][idx].second.second; + + return {nextState, cost}; + } + + double simulate(int startState, int endState) { + int state = startState; + double totalCost = 0.0; + int maxSteps = 100000; + + for (int step = 0; step < maxSteps && state != endState; step++) { + auto [nextState, cost] = this->step(state); + totalCost += cost; + state = nextState; + } + return totalCost; + } +}; + +// ============================================================================ +// 10. MONTE CARLO SIMULATOR +// ============================================================================ + +// MonteCarloSimulator: A utility for running Monte Carlo simulations. +// Purpose: +// - Estimate statistical metrics through repeated random sampling. +// - Provides methods to calculate mean results and probabilities of events. +// Usage: +// MonteCarloSimulator simulator(1000); +// double estimate = simulator.estimate([](int) { return some_random_value; }); +// double probability = simulator.estimateProbability([](int) { return +// some_event_occurs; }); +class MonteCarloSimulator { +private: + Statistics stats; + int numSimulations; + +public: + MonteCarloSimulator(int simulations = 1000) : numSimulations(simulations) {} + + template double estimate(SimFunc simulation) { + stats.clear(); + for (int i = 0; i < numSimulations; i++) { + double result = simulation(i); + stats.addSample(result); + } + return stats.mean(); + } + + template double estimateProbability(SimFunc simulation) { + int successes = 0; + for (int i = 0; i < numSimulations; i++) { + if (simulation(i)) + successes++; + } + return static_cast(successes) / numSimulations; + } + + const Statistics &getStatistics() const { return stats; } +}; + +// ============================================================================ +// 13. OPTIMIZATION UTILITIES +// ============================================================================ + +// Optimizer: Utility class offering optimization techniques such as random +// search and grid search. Purpose: +// - Explore parameter spaces to maximize or minimize an objective function. +// - Supports both continuous and discrete search spaces. +// Usage: +// - Use `randomSearch` for stochastic optimization over a defined range. +// - Use `gridSearch` for systematic exploration in evenly spaced steps. +// Example: +// auto [bestParam, bestValue] = Optimizer::randomSearch(objectiveFunction, +// 0.0, 10.0, 100, rng); auto [bestInt, bestIntValue] = +// Optimizer::randomSearchInt(objectiveFunction, 0, 10, 50, rng); +class Optimizer { +public: + template + static std::pair + randomSearch(ObjFunc objective, double minValue, double maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + for (int i = 0; i < budget; i++) { + double param = rng.uniform(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair + randomSearchInt(ObjFunc objective, int minValue, int maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + int bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + if (budget <= 0) { + throw std::invalid_argument("randomSearchInt: budget must be positive."); + } + + if (minValue >= maxValue) { + throw std::invalid_argument( + "randomSearchInt: minValue must be less than maxValue."); + } + + for (int i = 0; i < budget; i++) { + int param = rng.uniformInt(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair gridSearch(ObjFunc objective, + double minValue, double maxValue, + int steps, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + double stepSize = (maxValue - minValue) / steps; + + if (steps <= 0) { + throw std::invalid_argument("gridSearch: steps must be positive."); + } + + for (int i = 0; i <= steps; i++) { + double param = minValue + i * stepSize; + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/3/include/Dynamics.hpp b/exams/AxelRubini/2025-02-05/3/include/Dynamics.hpp new file mode 100644 index 0000000..044176a --- /dev/null +++ b/exams/AxelRubini/2025-02-05/3/include/Dynamics.hpp @@ -0,0 +1,214 @@ +#pragma once + +#include +#include +namespace SELib { +// ============================================================================ +// 3. ODE SOLVER (Runge-Kutta 4th order) +// ============================================================================ + +using StateVector = std::vector; +using DerivativeFunction = std::function; + +// ODESolver: Implements numerical methods for solving Ordinary Differential +// Equations (ODEs). Purpose: +// - Provides tools for simulating dynamic systems modeled by ODEs. +// - Includes methods such as Runge-Kutta 4th order and Euler's method for +// integration. +// Usage: +// - Pass a derivative function that defines the ODE system. +// - Use `rk4Step` for higher accuracy or `eulerStep` for simpler systems. +// Example: +// StateVector nextState = ODESolver::rk4Step(0.0, currentState, controlInputs, +// dt, dynamicsFunction); +class ODESolver { +public: + static StateVector rk4Step(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector k1 = f(t, x, u); + StateVector x2 = addScaled(x, k1, dt / 2.0); + StateVector k2 = f(t + dt / 2.0, x2, u); + StateVector x3 = addScaled(x, k2, dt / 2.0); + StateVector k3 = f(t + dt / 2.0, x3, u); + StateVector x4 = addScaled(x, k3, dt); + StateVector k4 = f(t + dt, x4, u); + + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += (dt / 6.0) * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]); + } + return xNew; + } + + static StateVector eulerStep(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector dx = f(t, x, u); + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += dx[i] * dt; + } + return xNew; + } + +private: + static StateVector addScaled(const StateVector &x, const StateVector &dx, + double scale) { + StateVector result = x; + for (size_t i = 0; i < x.size(); i++) { + result[i] += dx[i] * scale; + } + return result; + } +}; + +// ============================================================================ +// 4. PID CONTROLLER +// ============================================================================ + +// PIDController: Implements a Proportional-Integral-Derivative (PID) +// controller. Purpose: +// - Automatically adjusts a control signal to minimize the difference between +// a desired setpoint and a measured value. +// - Used in control systems to regulate processes like temperature, speed, or +// position. +// Usage: +// - Create an instance with proportional (Kp), integral (Ki), and derivative +// (Kd) gains, along with a time step. +// - Use `compute` to calculate the control signal based on the setpoint and +// measurement. +// Example: +// PIDController pid(1.0, 0.1, 0.01, 0.1); +// double controlSignal = pid.compute(desiredValue, actualValue); +class PIDController { +private: + double kp, ki, kd; + double integral; + double prevError; + double dt; + +public: + PIDController(double Kp, double Ki, double Kd, double timeStep) + : kp(Kp), ki(Ki), kd(Kd), integral(0), prevError(0), dt(timeStep) {} + + double compute(double setpoint, double measurement) { + double error = setpoint - measurement; + integral += error * dt; + double derivative = (error - prevError) / dt; + prevError = error; + return kp * error + ki * integral + kd * derivative; + } + + void reset() { + integral = 0; + prevError = 0; + } + + void setGains(double Kp, double Ki, double Kd) { + kp = Kp; + ki = Ki; + kd = Kd; + } +}; + +// ============================================================================ +// 5. CONTINUOUS TIME SIMULATION +// ============================================================================ + +// ContinuousTimeSimulation: A base class for simulating continuous-time dynamic +// systems. Purpose: +// - Provides a framework for simulating systems characterized by continuous +// dynamics over time. +// - Integrates system dynamics using a Runge-Kutta 4th order solver. +// Usage: +// - Derive from this class and implement the `dynamics`, `computeControl`, +// `initialize`, and `finalize` methods. +// - Customize the `onStep` method for per-step side effects, if required. +// Example: +// class MySimulation : public ContinuousTimeSimulation { +// // Implement the required virtual methods. +// }; +// MySimulation sim(dt, horizon, initialState); +// sim.run(); +class ContinuousTimeSimulation { +protected: + double currentTime; + double timeStep; + double horizon; + StateVector state; + StateVector control; + +public: + ContinuousTimeSimulation(double dt, double H, const StateVector &initialState) + : currentTime(0.0), timeStep(dt), horizon(H), state(initialState) { + control.resize(initialState.size(), 0.0); + } + + virtual ~ContinuousTimeSimulation() = default; + + virtual StateVector dynamics(double t, const StateVector &x, + const StateVector &u) = 0; + virtual StateVector computeControl(double t, const StateVector &x) = 0; + virtual void initialize() = 0; + virtual void onStep() {} + virtual void finalize() = 0; + + void run() { + initialize(); + while (currentTime < horizon) { + control = computeControl(currentTime, state); + state = ODESolver::rk4Step( + currentTime, state, control, timeStep, + [this](double t, const StateVector &x, const StateVector &u) { + return this->dynamics(t, x, u); + }); + currentTime += timeStep; + onStep(); + } + finalize(); + } + + double getCurrentTime() const { return currentTime; } + const StateVector &getState() const { return state; } + const StateVector &getControl() const { return control; } +}; + +// ============================================================================ +// 6. SAMPLE AND HOLD +// ============================================================================ + +// SampleAndHold: A utility to store a value and update it periodically. +// Purpose: +// - Maintain a value that gets updated periodically based on a time interval. +// - Useful for sampling data and ensuring updates happen at fixed intervals. +// Usage: +// SampleAndHold sampler(5.0, 0); +// if (sampler.shouldUpdate(currentTime)) { +// sampler.update(currentTime, newValue); +// } +// int currentValue = sampler.getValue(); +template class SampleAndHold { +private: + T value; + double lastUpdateTime; + double samplePeriod; + +public: + SampleAndHold(double period, const T &initialValue = T()) + : value(initialValue), lastUpdateTime(0), samplePeriod(period) {} + + bool shouldUpdate(double currentTime) const { + return (currentTime - lastUpdateTime) >= samplePeriod; + } + + void update(double currentTime, const T &newValue) { + value = newValue; + lastUpdateTime = currentTime; + } + + const T &getValue() const { return value; } + void reset() { lastUpdateTime = 0; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/3/include/Geometry.hpp b/exams/AxelRubini/2025-02-05/3/include/Geometry.hpp new file mode 100644 index 0000000..525a91d --- /dev/null +++ b/exams/AxelRubini/2025-02-05/3/include/Geometry.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 14. GEOMETRIC UTILITIES +// ============================================================================ + +// Point2D: Represents a point or vector in 2D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point2D p1(1.0, 2.0), p2(2.0, 3.0); +// double dist = p1.distanceTo(p2); +// Point2D sum = p1 + p2; +struct Point2D { + double x, y; + + Point2D(double x_ = 0, double y_ = 0) : x(x_), y(y_) {} + + double distanceTo(const Point2D &other) const { + double dx = x - other.x; + double dy = y - other.y; + return std::sqrt(dx * dx + dy * dy); + } + + Point2D operator+(const Point2D &other) const { + return Point2D(x + other.x, y + other.y); + } + + Point2D operator-(const Point2D &other) const { + return Point2D(x - other.x, y - other.y); + } +}; + +// Point3D: Represents a point or vector in 3D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point3D p1(1.0, 2.0, 3.0), p2(2.0, 3.0, 4.0); +// double dist = p1.distanceTo(p2); +// Point3D sum = p1 + p2; +struct Point3D { + double x, y, z; + + Point3D(double x_ = 0, double y_ = 0, double z_ = 0) : x(x_), y(y_), z(z_) {} + + double distanceTo(const Point3D &other) const { + double dx = x - other.x; + double dy = y - other.y; + double dz = z - other.z; + return std::sqrt(dx * dx + dy * dy + dz * dz); + } + + Point3D operator+(const Point3D &other) const { + return Point3D(x + other.x, y + other.y, z + other.z); + } + + Point3D operator-(const Point3D &other) const { + return Point3D(x - other.x, y - other.y, z - other.z); + } +}; + +// ============================================================================ +// 19. COLLISION DETECTOR +// ============================================================================ + +// CollisionDetector: A utility class for detecting collisions between points +// in space. Purpose: +// - Counts the number of collisions within a given distance threshold. +// - Retrieves pairs of colliding points. +// Usage: +// std::vector points = {Point2D(0, 0), Point2D(1, 1)}; +// int numCollisions = CollisionDetector::countCollisions(points, 1.5); +// auto collidingPairs = CollisionDetector::getCollidingPairs(points, 1.5); +class CollisionDetector { +public: + template + static int countCollisions(const std::vector &positions, + double threshold) { + int collisions = 0; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + collisions++; + } + } + } + return collisions; + } + + template + static std::vector> + getCollidingPairs(const std::vector &positions, double threshold) { + std::vector> pairs; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + pairs.push_back({i, j}); + } + } + } + return pairs; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/3/include/IO.hpp b/exams/AxelRubini/2025-02-05/3/include/IO.hpp new file mode 100644 index 0000000..1a79cc6 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/3/include/IO.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 11. PARAMETER PARSER +// ============================================================================ + +// ParameterParser: A utility to parse and retrieve parameters from +// configuration files. Purpose: +// - Reads a file and stores key-value pairs for parameters. +// - Allows retrieval of parameters as integers, doubles, or strings. +// - Provides structured data for advanced parsing. +// Usage: +// ParameterParser parser; +// if (parser.parseFile("config.txt")) { +// int param = parser.getInt("example_key", 42); +// } +class ParameterParser { +private: + std::map params; + std::vector> structuredData; + +public: + bool parseFile(const std::string &filename) { + std::ifstream file(filename); + if (!file.is_open()) + return false; + + std::string line; + while (std::getline(file, line)) { + if (line.empty()) + continue; + + std::istringstream iss(line); + std::string key; + iss >> key; + + std::string value; + std::getline(iss, value); + if (!value.empty() && value[0] == ' ') { + value = value.substr(1); + } + + params[key] = value; + + std::vector tokens; + std::istringstream tokenStream(line); + std::string token; + while (tokenStream >> token) { + tokens.push_back(token); + } + structuredData.push_back(tokens); + } + return true; + } + + int getInt(const std::string &key, int defaultValue = 0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stoi(it->second); + } + return defaultValue; + } + + double getDouble(const std::string &key, double defaultValue = 0.0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stod(it->second); + } + return defaultValue; + } + + std::string getString(const std::string &key, + const std::string &defaultValue = "") const { + auto it = params.find(key); + if (it != params.end()) { + return it->second; + } + return defaultValue; + } + + const std::vector> &getStructuredData() const { + return structuredData; + } + + bool hasKey(const std::string &key) const { + return params.find(key) != params.end(); + } +}; + +// ============================================================================ +// 12. OUTPUT WRITER +// ============================================================================ + +// OutputWriter: A utility class for writing data to output files. +// Purpose: +// - Simplifies the process of writing data to files. +// - Supports writing lines, key-value pairs, and managing file headers. +// Usage: +// OutputWriter writer; +// if (writer.open("output.txt", "Header Info")) { +// writer.writeLine("Example Line"); +// writer.writeKeyValue("Key", 42.0); +// writer.close(); +// } +class OutputWriter { +private: + std::ofstream file; + +public: + bool open(const std::string &filename, const std::string &header) { + file.open(filename); + if (!file.is_open()) + return false; + file << header << std::endl; + return true; + } + + template void writeLine(const T &value) { + file << value << std::endl; + } + + template + void writeLine(const T &first, const Args &...args) { + file << first << " "; + writeLine(args...); + } + + void writeKeyValue(const std::string &key, double value, int precision = 2) { + file << key << " " << std::fixed << std::setprecision(precision) << value + << std::endl; + } + + void close() { + if (file.is_open()) { + file.close(); + } + } + + ~OutputWriter() { close(); } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/3/include/Inventory.hpp b/exams/AxelRubini/2025-02-05/3/include/Inventory.hpp new file mode 100644 index 0000000..c82d776 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/3/include/Inventory.hpp @@ -0,0 +1,423 @@ +#pragma once + +#include "DES.hpp" +#include "Random.hpp" +#include +#include + +namespace SELib { +// ============================================================================ +// 16. CUSTOMER MODEL +// ============================================================================ + +// Customer: Represents a customer model in a simulation. +// Purpose: +// - Simulates customer requests over time. +// - Generates random requests for products at specific intervals. +// Usage: +// Customer customer(5.0, 10.0, 3, rng); +// int productId = customer.step(0.1); // Simulates a time step of 0.1 seconds. +class Customer { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer(double A, double B, int N, RandomGenerator &generator) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator) { + nextRequestTime = rng.uniform(A, B); + } + + int step(double dt) { + nextRequestTime -= dt; + if (nextRequestTime <= 0) { + nextRequestTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } +}; +class Customer_Event_Driven { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer_Event_Driven(double A, double B, int N, RandomGenerator &generator, + double nextRequestTime) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator), + nextRequestTime(nextRequestTime) {} + + int step_event_driven(double *current_time) { + if (*current_time >= nextRequestTime) { + nextRequestTime = *current_time + rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + double getNextRequestTime() { return nextRequestTime; }; +}; + +// ============================================================================ +// 17. SERVER MODEL +// ============================================================================ + +// Server: Represents a server in a simulated environment. +// Purpose: +// - Handles product requests and manages inventory. +// - Simulates server busy time during request processing. +// Usage: +// Server server(numProducts, maxInventory, processingTimeNormal, +// processingTimeSupplier, rng); int result = +// server.processRequest(productId, needsSupply); +// server.addInventory(productId, quantityAdded); +class Server { +private: + std::vector inventory; + int busyTime; + int processingTimeNormal; + int processingTimeSupplier; + RandomGenerator *rng; + +public: + Server(int numProducts, int K, int F, int G, RandomGenerator &rng) + : inventory(numProducts + 1, 0), busyTime(0), processingTimeNormal(F), + processingTimeSupplier(G), rng(&rng) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + + bool isBusy() const { return busyTime > 0; } + + void decrementBusyTime() { + if (busyTime > 0) + busyTime--; + } + + int processRequest(int productId, bool needsSupply) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return productId; + } else { + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return -productId; + } + } + + int processRequest2(int productId, double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = processingTimeNormal; + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + busyTime = processingTimeSupplier; + return -productId; + } + } + + void addInventory(int productId, int quantity) { + inventory[productId] += quantity; + } + + int getInventory(int productId) const { return inventory[productId]; } +}; +// ============================================================================ +// 17.5 SERVER-EVENT-DRIVEN +// ============================================================================ +class Server_Event_Driven { +private: + std::vector inventory; + RandomGenerator *rng; + double next_step_time; + double min_interval; + double max_interval; + +public: + Server_Event_Driven(int numProducts, int K, double min_interval, + double max_interval, RandomGenerator &rng, + double nextStepTime) + : inventory(numProducts + 1, 0), rng(&rng), min_interval(min_interval), + max_interval(max_interval), next_step_time(nextStepTime) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + int processRequestEventDriven(double current_time, int productId, + double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (current_time >= next_step_time) { + if (inventory[productId] > 0) { + inventory[productId]--; + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + + return -productId; + } + } else { + return 0; + } + } + double getNextStepTime() { return next_step_time; } +}; + +// ============================================================================ +// 18. SUPPLIER MODEL +// ============================================================================ + +// Supplier: Represents a supplier in a simulation. +// Purpose: +// - Simulates the supply of products at specific intervals. +// - Generates random supply requests periodically based on the defined +// intervals. +// Usage: +// Supplier supplier(minInterval, maxInterval, numProducts, rng); +// int suppliedProduct = supplier.step(timeStep); +// supplier.reset(); +class Supplier { +private: + double nextSupplyTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Supplier(double V, double Q, int N, RandomGenerator &generator) + : minInterval(V), maxInterval(Q), numProducts(N), rng(generator) { + nextSupplyTime = rng.uniform(V, Q); + } + + int step(double dt) { + nextSupplyTime -= dt; + if (nextSupplyTime <= 0) { + nextSupplyTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + void reset() { nextSupplyTime = rng.uniform(minInterval, maxInterval); } +}; + +// Server di inventario con messaggi (customer/server network-style) +class InventoryServerProcess : public DiscreteEventProcess { +public: + InventoryServerProcess(int pid, int numItems, int initialStockPerItem, + double serviceTime, MessageBus &bus, + RandomGenerator &rng) + : pid_(pid), serviceTime_(serviceTime), bus_(bus), rng_(rng), + nextEventTime_(0.0), + storage_(static_cast(numItems), initialStockPerItem), + totalRequests_(0), missedRequests_(0) {} + + void initialize(double startTime) override { + nextEventTime_ = startTime + serviceTime_; + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // processa TUTTI i messaggi arrivati a questo server + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + totalRequests_++; + + // sicurezza sugli indici prodotto + if (m.item < 0 || static_cast(m.item) >= storage_.size()) { + continue; // scarta richieste invalide + } + + double servedQty = 0.0; + int &stock = storage_[static_cast(m.item)]; + + if (stock >= static_cast(m.quantity)) { + // richiesta soddisfatta + stock -= static_cast(m.quantity); + servedQty = m.quantity; + } else { + // non abbastanza stock + servedQty = static_cast(stock); + missedRequests_++; + stock = 0; + } + + // risposta al cliente + Message reply; + reply.time = currentTime; + reply.sender = pid_; + reply.receiver = m.sender; // rimanda al mittente + reply.item = m.item; + reply.quantity = servedQty; + + bus_.procToNet(pid_).push(reply); + + // eventuale logica di refill: esempio semplice + if (stock == 0) { + int refill = rng_.uniformInt(1, 10); // batch di rifornimento random + stock += refill; + } + } + + nextEventTime_ = currentTime + serviceTime_; + } + + const std::vector &storage() const { return storage_; } + + int totalRequests() const { return totalRequests_; } + int missedRequests() const { return missedRequests_; } + +private: + int pid_; + double serviceTime_; + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + std::vector storage_; + + int totalRequests_; + int missedRequests_; +}; + +// ============================================================================ +// 19. INVENTORY CUSTOMER PROCESS (DES-compatible) +// ============================================================================ +// +// InventoryCustomerProcess: +// - Processo a eventi discreti che rappresenta un cliente. +// - Invia richieste al server tramite MessageBus (procToNet -> NetworkRouter). +// - Legge le risposte dal canale netToProc. +// - Statistiche: quante richieste ha mandato e quante risposte ha ricevuto. +// +class InventoryCustomerProcess : public DiscreteEventProcess { +public: + // pid : id di questo processo (indice nei canali del MessageBus) + // serverPid : pid del server a cui mandare le richieste + // numItems : numero di prodotti gestiti (item in [0, numItems-1]) + // minInterval/maxInterval : intervallo di tempo tra una richiesta e la + // successiva + InventoryCustomerProcess(int pid, int serverPid, int numItems, + double minInterval, double maxInterval, + MessageBus &bus, RandomGenerator &rng) + : pid_(pid), serverPid_(serverPid), numItems_(numItems), + minInterval_(minInterval), maxInterval_(maxInterval), bus_(bus), + rng_(rng), nextEventTime_(0.0), issuedRequests_(0), receivedReplies_(0), + totalRequestedQty_(0.0), totalServedQty_(0.0) {} + + // inizializza il prossimo evento (prima richiesta) + void initialize(double startTime) override { + nextEventTime_ = startTime + rng_.uniform(minInterval_, maxInterval_); + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // 1) genera una nuova richiesta al server + const int item = rng_.uniformInt( + 0, numItems_ - 1); // coerente con InventoryServerProcess + const int qty = rng_.uniformInt(1, 3); // quantità richiesta (1..3) + + Message req; + req.time = currentTime; + req.sender = pid_; + req.receiver = serverPid_; + req.item = item; + req.quantity = static_cast(qty); + + bus_.procToNet(pid_).push(req); + issuedRequests_++; + totalRequestedQty_ += req.quantity; + + // 2) processa tutte le risposte arrivate su questo cliente + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + // opzionale: possiamo filtrare solo messaggi dal serverPid_ + // if (m.sender != serverPid_) continue; + + receivedReplies_++; + totalServedQty_ += m.quantity; + } + + // 3) pianifica la prossima richiesta + nextEventTime_ = currentTime + rng_.uniform(minInterval_, maxInterval_); + } + + // --- metriche accessibili dall’esterno --- + + int pid() const { return pid_; } + int serverPid() const { return serverPid_; } + + int issuedRequests() const { return issuedRequests_; } + int receivedReplies() const { return receivedReplies_; } + + double totalRequestedQty() const { return totalRequestedQty_; } + double totalServedQty() const { return totalServedQty_; } + + // fill-rate lato customer + double serviceLevel() const { + if (totalRequestedQty_ <= 0.0) + return 1.0; + return totalServedQty_ / totalRequestedQty_; + } + +private: + int pid_; + int serverPid_; + int numItems_; + double minInterval_; + double maxInterval_; + + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + + int issuedRequests_; + int receivedReplies_; + double totalRequestedQty_; + double totalServedQty_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/3/include/Queue.hpp b/exams/AxelRubini/2025-02-05/3/include/Queue.hpp new file mode 100644 index 0000000..0c12ce8 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/3/include/Queue.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 9. FIFO QUEUE +// ============================================================================ + +// FIFOQueue: Implements a first-in-first-out (FIFO) queue with a fixed maximum +// size. Purpose: +// - Enqueue and dequeue items in FIFO order. +// - Track statistics such as total items enqueued and dequeued. +// - Support dynamic size constraints. +// Usage: +// FIFOQueue queue(10); +// queue.enqueue(5); +// int item; +// if (queue.dequeue(item)) { +// // Process item. +// } +template class FIFOQueue { +private: + std::queue queue; + size_t maxSize; + size_t totalEnqueued; + size_t totalDequeued; + +public: + FIFOQueue(size_t max = SIZE_MAX) + : maxSize(max), totalEnqueued(0), totalDequeued(0) {} + + bool enqueue(const T &item) { + if (queue.size() >= maxSize) + return false; + queue.push(item); + totalEnqueued++; + return true; + } + + bool dequeue(T &item) { + if (queue.empty()) + return false; + item = queue.front(); + queue.pop(); + totalDequeued++; + return true; + } + + bool isEmpty() const { return queue.empty(); } + bool isFull() const { return queue.size() >= maxSize; } + size_t size() const { return queue.size(); } + size_t getTotalEnqueued() const { return totalEnqueued; } + size_t getTotalDequeued() const { return totalDequeued; } + std::queue getQueue() const { return queue; } + T getFirst() const { + if (queue.empty()) + throw std::runtime_error("Queue is empty"); + return queue.front(); + } + + void clear() { + while (!queue.empty()) + queue.pop(); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/3/include/Random.hpp b/exams/AxelRubini/2025-02-05/3/include/Random.hpp new file mode 100644 index 0000000..52356f9 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/3/include/Random.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include // std::isfinite +#include // std::accumulate +#include +#include +#include + +namespace SELib { + +class RandomGenerator { +private: + std::mt19937 gen; + +public: + explicit RandomGenerator(unsigned seed = std::random_device{}()) + : gen(seed) {} + + double uniform(double min, double max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_real_distribution dis(lo, high); + return dis(gen); + } + + int uniformInt(int min, int max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_int_distribution dis(lo, high); + return dis(gen); + } + + double gaussian(double mean, double stddev) { + if (stddev < 0.0) { + throw std::invalid_argument("gaussian: stddev must be >= 0"); + } + std::normal_distribution dis(mean, stddev); + return dis(gen); + } + + bool bernoulli(double p) { + if (p < 0.0 || p > 1.0 || !std::isfinite(p)) { + throw std::invalid_argument("bernoulli: p must be in [0,1]"); + } + std::bernoulli_distribution dis(p); + return dis(gen); + } + + int discrete(const std::vector &probabilities) { + if (probabilities.empty()) { + throw std::invalid_argument("discrete: empty probability vector"); + } + + for (double w : probabilities) { + if (w < 0.0 || !std::isfinite(w)) { + throw std::invalid_argument( + "discrete: weights must be non-negative and finite"); + } + } + + double sum = + std::accumulate(probabilities.begin(), probabilities.end(), 0.0); + if (sum <= 0.0) { + throw std::invalid_argument("discrete: sum of weights must be > 0"); + } + + std::discrete_distribution dis(probabilities.begin(), + probabilities.end()); + return dis(gen); + } + + void setSeed(unsigned seed) { gen.seed(seed); } + + // (opzionale) accesso al motore se ti serve per altre distribuzioni + std::mt19937 &engine() { return gen; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/3/include/Stat.hpp b/exams/AxelRubini/2025-02-05/3/include/Stat.hpp new file mode 100644 index 0000000..27b4656 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/3/include/Stat.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 2. STATISTICS +// ============================================================================ + +// Statistics: A utility class for managing and analyzing numerical data +// samples. Purpose: +// - Compute statistical metrics such as mean and standard deviation. +// - Estimate probabilities of custom conditions. +// - Check convergence of data over iterations. +// Usage: +// Statistics stats; +// stats.addSample(5.0); +// double meanValue = stats.mean(); +class Statistics { +private: + std::vector samples; + +public: + void addSample(double value) { samples.push_back(value); } + + void clear() { samples.clear(); } + + double mean() const { + if (samples.empty()) + return 0.0; + double sum = 0.0; + for (double s : samples) + sum += s; + return sum / samples.size(); + } + + double stddev() const { + if (samples.size() < 2) + return 0.0; + double m = mean(); + double variance = 0.0; + for (double s : samples) { + variance += (s - m) * (s - m); + } + return std::sqrt(variance / (samples.size() - 1)); + } + + double probability(std::function condition) const { + if (samples.empty()) + return 0.0; + int count = 0; + for (double s : samples) { + if (condition(s)) + count++; + } + return static_cast(count) / samples.size(); + } + + size_t size() const { return samples.size(); } + const std::vector &getSamples() const { return samples; } + + bool hasConverged(double tolerance = 0.1) const { + if (samples.size() < 30) + return false; + double m = mean(); + double sd = stddev(); + return sd <= tolerance * std::abs(m); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/3/main b/exams/AxelRubini/2025-02-05/3/main new file mode 100755 index 0000000000000000000000000000000000000000..dc8dc45ea2cd9f11f30c3e3aa49f3192dd799a3b GIT binary patch literal 43304 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 LX;Oitr1t*-Nt0N> literal 0 HcmV?d00001 diff --git a/exams/AxelRubini/2025-02-05/3/main.cpp b/exams/AxelRubini/2025-02-05/3/main.cpp new file mode 100644 index 0000000..aa86bb1 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/3/main.cpp @@ -0,0 +1,188 @@ +#include "include/DES.hpp" +#include "include/Decision.hpp" +#include "include/IO.hpp" +#include "include/Random.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace SELib { + +class CustomerProcess : public DiscreteEventProcess { + public: + CustomerProcess( + int pid, + int serverPid, + MessageBus &bus, + RandomGenerator &rng, + const std::vector> &matrix + ) + : pid_(pid), serverPid_(serverPid), bus_(bus), rng_(rng), + matrix_(matrix), currentState_(0), nextTime_(0.0) {} + + void initialize(double startTime) override { nextTime_ = startTime; } + double nextEventTime() const override { return nextTime_; } + + void handleEvent(double currentTime) override { + int nextState = rng_.discrete(matrix_[currentState_]); + bool isEndOfSession = (nextState == 0 && currentState_ != 0); + + if (currentState_ != 0) { + Message m; + m.time = currentTime; + m.sender = pid_; + m.receiver = serverPid_; + m.item = currentState_; + m.quantity = isEndOfSession ? 1.0 : 0.0; + bus_.procToNet(pid_).push(m); + } + currentState_ = nextState; + nextTime_ += 1.0; + } + + private: + int pid_, serverPid_, currentState_; + MessageBus &bus_; + RandomGenerator &rng_; + const std::vector> &matrix_; + double nextTime_; +}; + +class ServerProcess : public DiscreteEventProcess { + public: + ServerProcess( + int pid, + int monitorPid, + MessageBus &bus, + double T1, + double T2 + ) + : pid_(pid), monitorPid_(monitorPid), bus_(bus), T1_(T1), T2_(T2), + serverFreeAt_(0.0), + nextFinishTime_(std::numeric_limits::infinity()) {} + + void initialize(double startTime) override {} + double nextEventTime() const override { return nextFinishTime_; } + + void handleEvent(double currentTime) override { + if (currentTime >= nextFinishTime_) { + if (currentRequestIsLast_) { + Message m; + m.time = currentTime; + m.sender = pid_; + m.receiver = monitorPid_; + bus_.procToNet(pid_).push(m); + } + nextFinishTime_ = std::numeric_limits::infinity(); + } + processQueue(currentTime); + } + + void processQueue(double currentTime) { + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + queue_.push(inbox.front()); + inbox.pop(); + } + + if (currentTime >= serverFreeAt_ && !queue_.empty()) { + Message req = queue_.front(); + queue_.pop(); + double st = (req.item == 1) ? T1_ : T2_; + serverFreeAt_ = std::max(currentTime, serverFreeAt_) + st; + nextFinishTime_ = serverFreeAt_; + currentRequestIsLast_ = (req.quantity > 0.5); + } + } + + private: + int pid_, monitorPid_; + MessageBus &bus_; + double T1_, T2_, serverFreeAt_, nextFinishTime_; + bool currentRequestIsLast_; + std::queue queue_; +}; + +class MonitorProcess : public DiscreteEventProcess { + public: + MonitorProcess(int pid, MessageBus &bus) + : pid_(pid), bus_(bus), completed_(0) {} + void initialize(double startTime) override {} + double nextEventTime() const override { + return std::numeric_limits::infinity(); + } + void handleEvent(double currentTime) override {} + void processInbox() { + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + inbox.pop(); + completed_++; + } + } + int getCompleted() const { return completed_; } + + private: + int pid_; + MessageBus &bus_; + int completed_; +}; + +} // namespace SELib + +int main() { + SELib::RandomGenerator rng(0); + SELib::ParameterParser parser; + if (!parser.parseFile("parameters.txt")) + return 1; + + std::vector> matrix(3, std::vector(3)); + const auto &data = parser.getStructuredData(); + for (const auto &line : data) + if (line.size() == 3) + matrix[std::stoi(line[0])][std::stoi(line[1])] = std::stod(line[2]); + std::vector ts; + for (const auto &line : data) + if (line.size() == 1) + ts.push_back(std::stod(line[0])); + double T1 = ts[ts.size() - 2], T2 = ts[ts.size() - 1]; + + auto run_sim = [&](int simulations) { + double totalRate = 0; + for (int i = 0; i < simulations; ++i) { + SELib::MessageBus bus(3); + SELib::DiscreteEventSystem system; + system.emplaceProcess( + 0, 1, bus, rng, matrix + ); + auto &server = + system.emplaceProcess(1, 2, bus, T1, T2); + auto &monitor = + system.emplaceProcess(2, bus); + system.emplaceProcess(bus, rng, 0.001); + + double currentTime = 0.0, H = 10000.0; + system.initialize(0.0); + while (currentTime < H) { + double nextTime = system.nextEventTime(); + if (nextTime > H) + break; + currentTime = nextTime; + system.handleEvent(currentTime); + server.processQueue(currentTime); + monitor.processInbox(); + } + totalRate += (double)monitor.getCompleted() / H; + } + return totalRate / simulations; + }; + + std::ofstream outFile("results.txt"); + outFile << "2025-02-05-Axel-Rubini-2158099" << std::endl; + outFile << "Avg " << std::fixed << std::setprecision(4) << run_sim(1000) + << std::endl; + return 0; +} diff --git a/exams/AxelRubini/2025-02-05/3/parameters.txt b/exams/AxelRubini/2025-02-05/3/parameters.txt new file mode 100644 index 0000000..ba70834 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/3/parameters.txt @@ -0,0 +1,11 @@ +0 0 0.40 +0 1 0.30 +0 2 0.30 +1 0 0.8 +1 1 0.1 +1 2 0.1 +2 0 0.8 +2 1 0.1 +2 2 0.1 +2.5 +4.5 diff --git a/exams/AxelRubini/2025-02-05/3/results.txt b/exams/AxelRubini/2025-02-05/3/results.txt new file mode 100644 index 0000000..3b743b8 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/3/results.txt @@ -0,0 +1,2 @@ +2025-02-05-Axel-Rubini-2158099 +Avg 0.3428 diff --git a/exams/AxelRubini/2025-02-05/4/Makefile b/exams/AxelRubini/2025-02-05/4/Makefile new file mode 100644 index 0000000..6bc53bf --- /dev/null +++ b/exams/AxelRubini/2025-02-05/4/Makefile @@ -0,0 +1,10 @@ +CC=g++ +CFLAGS=-std=c++23 -O3 -Wall + +all: main + +main: main.cpp + $(CC) $(CFLAGS) main.cpp -o main + +clean: + rm -f main *.o \ No newline at end of file diff --git a/exams/AxelRubini/2025-02-05/4/include/DES.hpp b/exams/AxelRubini/2025-02-05/4/include/DES.hpp new file mode 100644 index 0000000..1706b8f --- /dev/null +++ b/exams/AxelRubini/2025-02-05/4/include/DES.hpp @@ -0,0 +1,212 @@ + +#pragma once + +#include "Random.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// --------------------------------------------------------------------------- +// Messaggio +// --------------------------------------------------------------------------- +struct Message { + double time{0.0}; + int sender{-1}; + int receiver{-1}; + int item{0}; + double quantity{0.0}; +}; + +// --------------------------------------------------------------------------- +// Bus di messaggi: process -> network, network -> process +// --------------------------------------------------------------------------- +class MessageBus { +public: + explicit MessageBus(std::size_t n = 0) { resize(n); } + + void resize(std::size_t n) { + procToNet_.assign(n, {}); + netToProc_.assign(n, {}); + } + + std::size_t size() const { return procToNet_.size(); } + + std::queue &procToNet(int pid) { return procToNet_.at(pid); } + std::queue &netToProc(int pid) { return netToProc_.at(pid); } + const std::queue &procToNet(int pid) const { + return procToNet_.at(pid); + } + const std::queue &netToProc(int pid) const { + return netToProc_.at(pid); + } + +private: + std::vector> procToNet_; + std::vector> netToProc_; +}; + +// --------------------------------------------------------------------------- +// Processo base a eventi discreti +// --------------------------------------------------------------------------- +class DiscreteEventProcess { +public: + virtual ~DiscreteEventProcess() = default; + virtual void initialize(double startTime) = 0; + virtual double nextEventTime() const = 0; + virtual void handleEvent(double currentTime) = 0; +}; + +// --------------------------------------------------------------------------- +// NetworkRouter: inoltra i messaggi P->N->P +// --------------------------------------------------------------------------- +class NetworkRouter : public DiscreteEventProcess { +public: + NetworkRouter(MessageBus &bus, RandomGenerator &rng, double scanPeriod = 0.1) + : bus_(bus), rng_(rng), scanPeriod_(scanPeriod), time_(0.0), + nextScanTime_(0.0), scanned_(0) {} + + void initialize(double startTime) override { + time_ = startTime; + nextScanTime_ = startTime + scanPeriod_; + + const int n = static_cast(bus_.size()); + if (n <= 0) { + throw std::runtime_error( + "NetworkRouter: no processes configured in MessageBus"); + } + + scanner_.resize(n); + for (int i = 0; i < n; ++i) { + scanner_[i] = i; + } + reshuffleScanner(); + scanned_ = 0; + } + + double nextEventTime() const override { return nextScanTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextScanTime_) + return; + + const int n = static_cast(scanner_.size()); + if (n == 0) + return; + + if (scanned_ >= n) { + scanned_ = 0; + reshuffleScanner(); + } + + const int channel = scanner_[scanned_++]; + auto &fromQueue = bus_.procToNet(channel); + + if (!fromQueue.empty()) { + Message m = fromQueue.front(); + fromQueue.pop(); + + if (m.receiver < 0 || m.receiver >= static_cast(bus_.size())) { + throw std::runtime_error("NetworkRouter: invalid receiver index"); + } + + bus_.netToProc(m.receiver).push(m); + } + + nextScanTime_ = currentTime + scanPeriod_; + } + +private: + void reshuffleScanner() { + auto &eng = rng_.engine(); // serve engine() in RandomGenerator + std::shuffle(scanner_.begin(), scanner_.end(), eng); + } + + MessageBus &bus_; + RandomGenerator &rng_; + + double scanPeriod_; + double time_; + double nextScanTime_; + std::vector scanner_; + int scanned_; +}; + +// --------------------------------------------------------------------------- +// Sistema DES: collezione di processi +// --------------------------------------------------------------------------- +class DiscreteEventSystem { +public: + template + Proc &emplaceProcess(Args &&...args) { + auto ptr = std::make_unique(std::forward(args)...); + Proc &ref = *ptr; + processes_.push_back(std::move(ptr)); + return ref; + } + + void initialize(double startTime = 0.0) { + for (auto &p : processes_) { + p->initialize(startTime); + } + } + + double nextEventTime() const { + if (processes_.empty()) { + return std::numeric_limits::infinity(); + } + + double tMin = std::numeric_limits::infinity(); + for (const auto &p : processes_) { + double t = p->nextEventTime(); + if (t < tMin) + tMin = t; + } + return tMin; + } + + void handleEvent(double currentTime) { + for (auto &p : processes_) { + if (p->nextEventTime() <= currentTime) { + p->handleEvent(currentTime); + } + } + } + +private: + std::vector> processes_; +}; + +// --------------------------------------------------------------------------- +// Simulatore DES +// --------------------------------------------------------------------------- +class DiscreteEventSimulator { +public: + explicit DiscreteEventSimulator(DiscreteEventSystem &system) + : system_(system), currentTime_(0.0) {} + + void run(double horizon) { + system_.initialize(0.0); + currentTime_ = 0.0; + + while (currentTime_ < horizon) { + double nextTime = system_.nextEventTime(); + if (nextTime == std::numeric_limits::infinity()) + break; + currentTime_ = nextTime; + system_.handleEvent(currentTime_); + } + } + + double currentTime() const { return currentTime_; } + +private: + DiscreteEventSystem &system_; + double currentTime_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/4/include/Decision.hpp b/exams/AxelRubini/2025-02-05/4/include/Decision.hpp new file mode 100644 index 0000000..4d37cf0 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/4/include/Decision.hpp @@ -0,0 +1,266 @@ +#pragma once + +#include "Random.hpp" +#include "Stat.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 7. MARKOV CHAIN +// ============================================================================ + +// MarkovChain: Implements a discrete-time Markov chain. +// Purpose: +// - Simulates stochastic processes with defined states and transition +// probabilities. +// - Useful in modeling random systems and computing related probabilities. +// Usage: +// MarkovChain mc(3, rng); +// mc.setTransition(0, 1, 0.5); +// mc.setTransition(0, 2, 0.5); +// mc.setState(0); +// int nextState = mc.step(); +class MarkovChain { +private: + int numStates; + std::vector> transitionMatrix; + int currentState; + RandomGenerator &rng; + +public: + MarkovChain(int states, RandomGenerator &generator) + : numStates(states), currentState(0), rng(generator) { + transitionMatrix.resize(states, std::vector(states, 0.0)); + } + + void setTransition(int from, int to, double probability) { + if (from >= 0 && from < numStates && to >= 0 && to < numStates) { + transitionMatrix[from][to] = probability; + } + } + + void setState(int state) { + if (state >= 0 && state < numStates) { + currentState = state; + } + } + + int getState() const { return currentState; } + + int step() { + currentState = rng.discrete(transitionMatrix[currentState]); + return currentState; + } + + double getTransitionProb(int from, int to) const { + return transitionMatrix[from][to]; + } + + double expectedSojournTime(int state, double timeStep = 1.0) const { + double pii = transitionMatrix[state][state]; + if (pii >= 1.0) + return std::numeric_limits::infinity(); + return timeStep / (1.0 - pii); + } +}; + +// ============================================================================ +// 8. MDP +// ============================================================================ + +// MDP: Implements a Markov Decision Process (MDP) model. +// Purpose: +// - Simulate decision-making processes with states, transitions, and +// associated costs. +// - Calculate the total cost to reach a target state from a starting state. +// Usage: +// - Add transitions between states with associated probabilities and costs +// using `addTransition`. +// - Use `step` to simulate a single transition and calculate its cost. +// - Use `simulate` to estimate the total cost from a start to an end state. +class MDP { +private: + int numStates; + std::map>>> + transitions; + RandomGenerator &rng; + +public: + MDP(int states, RandomGenerator &generator) + : numStates(states), rng(generator) {} + + void addTransition(int from, int to, double probability, double cost) { + transitions[from].push_back({to, {probability, cost}}); + } + + std::pair step(int currentState) { + if (transitions.find(currentState) == transitions.end()) { + return {currentState, 0.0}; + } + + std::vector probs; + for (const auto &t : transitions[currentState]) { + probs.push_back(t.second.first); + } + + int idx = rng.discrete(probs); + int nextState = transitions[currentState][idx].first; + double cost = transitions[currentState][idx].second.second; + + return {nextState, cost}; + } + + double simulate(int startState, int endState) { + int state = startState; + double totalCost = 0.0; + int maxSteps = 100000; + + for (int step = 0; step < maxSteps && state != endState; step++) { + auto [nextState, cost] = this->step(state); + totalCost += cost; + state = nextState; + } + return totalCost; + } +}; + +// ============================================================================ +// 10. MONTE CARLO SIMULATOR +// ============================================================================ + +// MonteCarloSimulator: A utility for running Monte Carlo simulations. +// Purpose: +// - Estimate statistical metrics through repeated random sampling. +// - Provides methods to calculate mean results and probabilities of events. +// Usage: +// MonteCarloSimulator simulator(1000); +// double estimate = simulator.estimate([](int) { return some_random_value; }); +// double probability = simulator.estimateProbability([](int) { return +// some_event_occurs; }); +class MonteCarloSimulator { +private: + Statistics stats; + int numSimulations; + +public: + MonteCarloSimulator(int simulations = 1000) : numSimulations(simulations) {} + + template double estimate(SimFunc simulation) { + stats.clear(); + for (int i = 0; i < numSimulations; i++) { + double result = simulation(i); + stats.addSample(result); + } + return stats.mean(); + } + + template double estimateProbability(SimFunc simulation) { + int successes = 0; + for (int i = 0; i < numSimulations; i++) { + if (simulation(i)) + successes++; + } + return static_cast(successes) / numSimulations; + } + + const Statistics &getStatistics() const { return stats; } +}; + +// ============================================================================ +// 13. OPTIMIZATION UTILITIES +// ============================================================================ + +// Optimizer: Utility class offering optimization techniques such as random +// search and grid search. Purpose: +// - Explore parameter spaces to maximize or minimize an objective function. +// - Supports both continuous and discrete search spaces. +// Usage: +// - Use `randomSearch` for stochastic optimization over a defined range. +// - Use `gridSearch` for systematic exploration in evenly spaced steps. +// Example: +// auto [bestParam, bestValue] = Optimizer::randomSearch(objectiveFunction, +// 0.0, 10.0, 100, rng); auto [bestInt, bestIntValue] = +// Optimizer::randomSearchInt(objectiveFunction, 0, 10, 50, rng); +class Optimizer { +public: + template + static std::pair + randomSearch(ObjFunc objective, double minValue, double maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + for (int i = 0; i < budget; i++) { + double param = rng.uniform(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair + randomSearchInt(ObjFunc objective, int minValue, int maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + int bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + if (budget <= 0) { + throw std::invalid_argument("randomSearchInt: budget must be positive."); + } + + if (minValue >= maxValue) { + throw std::invalid_argument( + "randomSearchInt: minValue must be less than maxValue."); + } + + for (int i = 0; i < budget; i++) { + int param = rng.uniformInt(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair gridSearch(ObjFunc objective, + double minValue, double maxValue, + int steps, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + double stepSize = (maxValue - minValue) / steps; + + if (steps <= 0) { + throw std::invalid_argument("gridSearch: steps must be positive."); + } + + for (int i = 0; i <= steps; i++) { + double param = minValue + i * stepSize; + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/4/include/Dynamics.hpp b/exams/AxelRubini/2025-02-05/4/include/Dynamics.hpp new file mode 100644 index 0000000..044176a --- /dev/null +++ b/exams/AxelRubini/2025-02-05/4/include/Dynamics.hpp @@ -0,0 +1,214 @@ +#pragma once + +#include +#include +namespace SELib { +// ============================================================================ +// 3. ODE SOLVER (Runge-Kutta 4th order) +// ============================================================================ + +using StateVector = std::vector; +using DerivativeFunction = std::function; + +// ODESolver: Implements numerical methods for solving Ordinary Differential +// Equations (ODEs). Purpose: +// - Provides tools for simulating dynamic systems modeled by ODEs. +// - Includes methods such as Runge-Kutta 4th order and Euler's method for +// integration. +// Usage: +// - Pass a derivative function that defines the ODE system. +// - Use `rk4Step` for higher accuracy or `eulerStep` for simpler systems. +// Example: +// StateVector nextState = ODESolver::rk4Step(0.0, currentState, controlInputs, +// dt, dynamicsFunction); +class ODESolver { +public: + static StateVector rk4Step(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector k1 = f(t, x, u); + StateVector x2 = addScaled(x, k1, dt / 2.0); + StateVector k2 = f(t + dt / 2.0, x2, u); + StateVector x3 = addScaled(x, k2, dt / 2.0); + StateVector k3 = f(t + dt / 2.0, x3, u); + StateVector x4 = addScaled(x, k3, dt); + StateVector k4 = f(t + dt, x4, u); + + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += (dt / 6.0) * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]); + } + return xNew; + } + + static StateVector eulerStep(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector dx = f(t, x, u); + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += dx[i] * dt; + } + return xNew; + } + +private: + static StateVector addScaled(const StateVector &x, const StateVector &dx, + double scale) { + StateVector result = x; + for (size_t i = 0; i < x.size(); i++) { + result[i] += dx[i] * scale; + } + return result; + } +}; + +// ============================================================================ +// 4. PID CONTROLLER +// ============================================================================ + +// PIDController: Implements a Proportional-Integral-Derivative (PID) +// controller. Purpose: +// - Automatically adjusts a control signal to minimize the difference between +// a desired setpoint and a measured value. +// - Used in control systems to regulate processes like temperature, speed, or +// position. +// Usage: +// - Create an instance with proportional (Kp), integral (Ki), and derivative +// (Kd) gains, along with a time step. +// - Use `compute` to calculate the control signal based on the setpoint and +// measurement. +// Example: +// PIDController pid(1.0, 0.1, 0.01, 0.1); +// double controlSignal = pid.compute(desiredValue, actualValue); +class PIDController { +private: + double kp, ki, kd; + double integral; + double prevError; + double dt; + +public: + PIDController(double Kp, double Ki, double Kd, double timeStep) + : kp(Kp), ki(Ki), kd(Kd), integral(0), prevError(0), dt(timeStep) {} + + double compute(double setpoint, double measurement) { + double error = setpoint - measurement; + integral += error * dt; + double derivative = (error - prevError) / dt; + prevError = error; + return kp * error + ki * integral + kd * derivative; + } + + void reset() { + integral = 0; + prevError = 0; + } + + void setGains(double Kp, double Ki, double Kd) { + kp = Kp; + ki = Ki; + kd = Kd; + } +}; + +// ============================================================================ +// 5. CONTINUOUS TIME SIMULATION +// ============================================================================ + +// ContinuousTimeSimulation: A base class for simulating continuous-time dynamic +// systems. Purpose: +// - Provides a framework for simulating systems characterized by continuous +// dynamics over time. +// - Integrates system dynamics using a Runge-Kutta 4th order solver. +// Usage: +// - Derive from this class and implement the `dynamics`, `computeControl`, +// `initialize`, and `finalize` methods. +// - Customize the `onStep` method for per-step side effects, if required. +// Example: +// class MySimulation : public ContinuousTimeSimulation { +// // Implement the required virtual methods. +// }; +// MySimulation sim(dt, horizon, initialState); +// sim.run(); +class ContinuousTimeSimulation { +protected: + double currentTime; + double timeStep; + double horizon; + StateVector state; + StateVector control; + +public: + ContinuousTimeSimulation(double dt, double H, const StateVector &initialState) + : currentTime(0.0), timeStep(dt), horizon(H), state(initialState) { + control.resize(initialState.size(), 0.0); + } + + virtual ~ContinuousTimeSimulation() = default; + + virtual StateVector dynamics(double t, const StateVector &x, + const StateVector &u) = 0; + virtual StateVector computeControl(double t, const StateVector &x) = 0; + virtual void initialize() = 0; + virtual void onStep() {} + virtual void finalize() = 0; + + void run() { + initialize(); + while (currentTime < horizon) { + control = computeControl(currentTime, state); + state = ODESolver::rk4Step( + currentTime, state, control, timeStep, + [this](double t, const StateVector &x, const StateVector &u) { + return this->dynamics(t, x, u); + }); + currentTime += timeStep; + onStep(); + } + finalize(); + } + + double getCurrentTime() const { return currentTime; } + const StateVector &getState() const { return state; } + const StateVector &getControl() const { return control; } +}; + +// ============================================================================ +// 6. SAMPLE AND HOLD +// ============================================================================ + +// SampleAndHold: A utility to store a value and update it periodically. +// Purpose: +// - Maintain a value that gets updated periodically based on a time interval. +// - Useful for sampling data and ensuring updates happen at fixed intervals. +// Usage: +// SampleAndHold sampler(5.0, 0); +// if (sampler.shouldUpdate(currentTime)) { +// sampler.update(currentTime, newValue); +// } +// int currentValue = sampler.getValue(); +template class SampleAndHold { +private: + T value; + double lastUpdateTime; + double samplePeriod; + +public: + SampleAndHold(double period, const T &initialValue = T()) + : value(initialValue), lastUpdateTime(0), samplePeriod(period) {} + + bool shouldUpdate(double currentTime) const { + return (currentTime - lastUpdateTime) >= samplePeriod; + } + + void update(double currentTime, const T &newValue) { + value = newValue; + lastUpdateTime = currentTime; + } + + const T &getValue() const { return value; } + void reset() { lastUpdateTime = 0; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/4/include/Geometry.hpp b/exams/AxelRubini/2025-02-05/4/include/Geometry.hpp new file mode 100644 index 0000000..525a91d --- /dev/null +++ b/exams/AxelRubini/2025-02-05/4/include/Geometry.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 14. GEOMETRIC UTILITIES +// ============================================================================ + +// Point2D: Represents a point or vector in 2D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point2D p1(1.0, 2.0), p2(2.0, 3.0); +// double dist = p1.distanceTo(p2); +// Point2D sum = p1 + p2; +struct Point2D { + double x, y; + + Point2D(double x_ = 0, double y_ = 0) : x(x_), y(y_) {} + + double distanceTo(const Point2D &other) const { + double dx = x - other.x; + double dy = y - other.y; + return std::sqrt(dx * dx + dy * dy); + } + + Point2D operator+(const Point2D &other) const { + return Point2D(x + other.x, y + other.y); + } + + Point2D operator-(const Point2D &other) const { + return Point2D(x - other.x, y - other.y); + } +}; + +// Point3D: Represents a point or vector in 3D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point3D p1(1.0, 2.0, 3.0), p2(2.0, 3.0, 4.0); +// double dist = p1.distanceTo(p2); +// Point3D sum = p1 + p2; +struct Point3D { + double x, y, z; + + Point3D(double x_ = 0, double y_ = 0, double z_ = 0) : x(x_), y(y_), z(z_) {} + + double distanceTo(const Point3D &other) const { + double dx = x - other.x; + double dy = y - other.y; + double dz = z - other.z; + return std::sqrt(dx * dx + dy * dy + dz * dz); + } + + Point3D operator+(const Point3D &other) const { + return Point3D(x + other.x, y + other.y, z + other.z); + } + + Point3D operator-(const Point3D &other) const { + return Point3D(x - other.x, y - other.y, z - other.z); + } +}; + +// ============================================================================ +// 19. COLLISION DETECTOR +// ============================================================================ + +// CollisionDetector: A utility class for detecting collisions between points +// in space. Purpose: +// - Counts the number of collisions within a given distance threshold. +// - Retrieves pairs of colliding points. +// Usage: +// std::vector points = {Point2D(0, 0), Point2D(1, 1)}; +// int numCollisions = CollisionDetector::countCollisions(points, 1.5); +// auto collidingPairs = CollisionDetector::getCollidingPairs(points, 1.5); +class CollisionDetector { +public: + template + static int countCollisions(const std::vector &positions, + double threshold) { + int collisions = 0; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + collisions++; + } + } + } + return collisions; + } + + template + static std::vector> + getCollidingPairs(const std::vector &positions, double threshold) { + std::vector> pairs; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + pairs.push_back({i, j}); + } + } + } + return pairs; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/4/include/IO.hpp b/exams/AxelRubini/2025-02-05/4/include/IO.hpp new file mode 100644 index 0000000..1a79cc6 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/4/include/IO.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 11. PARAMETER PARSER +// ============================================================================ + +// ParameterParser: A utility to parse and retrieve parameters from +// configuration files. Purpose: +// - Reads a file and stores key-value pairs for parameters. +// - Allows retrieval of parameters as integers, doubles, or strings. +// - Provides structured data for advanced parsing. +// Usage: +// ParameterParser parser; +// if (parser.parseFile("config.txt")) { +// int param = parser.getInt("example_key", 42); +// } +class ParameterParser { +private: + std::map params; + std::vector> structuredData; + +public: + bool parseFile(const std::string &filename) { + std::ifstream file(filename); + if (!file.is_open()) + return false; + + std::string line; + while (std::getline(file, line)) { + if (line.empty()) + continue; + + std::istringstream iss(line); + std::string key; + iss >> key; + + std::string value; + std::getline(iss, value); + if (!value.empty() && value[0] == ' ') { + value = value.substr(1); + } + + params[key] = value; + + std::vector tokens; + std::istringstream tokenStream(line); + std::string token; + while (tokenStream >> token) { + tokens.push_back(token); + } + structuredData.push_back(tokens); + } + return true; + } + + int getInt(const std::string &key, int defaultValue = 0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stoi(it->second); + } + return defaultValue; + } + + double getDouble(const std::string &key, double defaultValue = 0.0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stod(it->second); + } + return defaultValue; + } + + std::string getString(const std::string &key, + const std::string &defaultValue = "") const { + auto it = params.find(key); + if (it != params.end()) { + return it->second; + } + return defaultValue; + } + + const std::vector> &getStructuredData() const { + return structuredData; + } + + bool hasKey(const std::string &key) const { + return params.find(key) != params.end(); + } +}; + +// ============================================================================ +// 12. OUTPUT WRITER +// ============================================================================ + +// OutputWriter: A utility class for writing data to output files. +// Purpose: +// - Simplifies the process of writing data to files. +// - Supports writing lines, key-value pairs, and managing file headers. +// Usage: +// OutputWriter writer; +// if (writer.open("output.txt", "Header Info")) { +// writer.writeLine("Example Line"); +// writer.writeKeyValue("Key", 42.0); +// writer.close(); +// } +class OutputWriter { +private: + std::ofstream file; + +public: + bool open(const std::string &filename, const std::string &header) { + file.open(filename); + if (!file.is_open()) + return false; + file << header << std::endl; + return true; + } + + template void writeLine(const T &value) { + file << value << std::endl; + } + + template + void writeLine(const T &first, const Args &...args) { + file << first << " "; + writeLine(args...); + } + + void writeKeyValue(const std::string &key, double value, int precision = 2) { + file << key << " " << std::fixed << std::setprecision(precision) << value + << std::endl; + } + + void close() { + if (file.is_open()) { + file.close(); + } + } + + ~OutputWriter() { close(); } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/4/include/Inventory.hpp b/exams/AxelRubini/2025-02-05/4/include/Inventory.hpp new file mode 100644 index 0000000..c82d776 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/4/include/Inventory.hpp @@ -0,0 +1,423 @@ +#pragma once + +#include "DES.hpp" +#include "Random.hpp" +#include +#include + +namespace SELib { +// ============================================================================ +// 16. CUSTOMER MODEL +// ============================================================================ + +// Customer: Represents a customer model in a simulation. +// Purpose: +// - Simulates customer requests over time. +// - Generates random requests for products at specific intervals. +// Usage: +// Customer customer(5.0, 10.0, 3, rng); +// int productId = customer.step(0.1); // Simulates a time step of 0.1 seconds. +class Customer { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer(double A, double B, int N, RandomGenerator &generator) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator) { + nextRequestTime = rng.uniform(A, B); + } + + int step(double dt) { + nextRequestTime -= dt; + if (nextRequestTime <= 0) { + nextRequestTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } +}; +class Customer_Event_Driven { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer_Event_Driven(double A, double B, int N, RandomGenerator &generator, + double nextRequestTime) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator), + nextRequestTime(nextRequestTime) {} + + int step_event_driven(double *current_time) { + if (*current_time >= nextRequestTime) { + nextRequestTime = *current_time + rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + double getNextRequestTime() { return nextRequestTime; }; +}; + +// ============================================================================ +// 17. SERVER MODEL +// ============================================================================ + +// Server: Represents a server in a simulated environment. +// Purpose: +// - Handles product requests and manages inventory. +// - Simulates server busy time during request processing. +// Usage: +// Server server(numProducts, maxInventory, processingTimeNormal, +// processingTimeSupplier, rng); int result = +// server.processRequest(productId, needsSupply); +// server.addInventory(productId, quantityAdded); +class Server { +private: + std::vector inventory; + int busyTime; + int processingTimeNormal; + int processingTimeSupplier; + RandomGenerator *rng; + +public: + Server(int numProducts, int K, int F, int G, RandomGenerator &rng) + : inventory(numProducts + 1, 0), busyTime(0), processingTimeNormal(F), + processingTimeSupplier(G), rng(&rng) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + + bool isBusy() const { return busyTime > 0; } + + void decrementBusyTime() { + if (busyTime > 0) + busyTime--; + } + + int processRequest(int productId, bool needsSupply) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return productId; + } else { + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return -productId; + } + } + + int processRequest2(int productId, double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = processingTimeNormal; + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + busyTime = processingTimeSupplier; + return -productId; + } + } + + void addInventory(int productId, int quantity) { + inventory[productId] += quantity; + } + + int getInventory(int productId) const { return inventory[productId]; } +}; +// ============================================================================ +// 17.5 SERVER-EVENT-DRIVEN +// ============================================================================ +class Server_Event_Driven { +private: + std::vector inventory; + RandomGenerator *rng; + double next_step_time; + double min_interval; + double max_interval; + +public: + Server_Event_Driven(int numProducts, int K, double min_interval, + double max_interval, RandomGenerator &rng, + double nextStepTime) + : inventory(numProducts + 1, 0), rng(&rng), min_interval(min_interval), + max_interval(max_interval), next_step_time(nextStepTime) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + int processRequestEventDriven(double current_time, int productId, + double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (current_time >= next_step_time) { + if (inventory[productId] > 0) { + inventory[productId]--; + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + + return -productId; + } + } else { + return 0; + } + } + double getNextStepTime() { return next_step_time; } +}; + +// ============================================================================ +// 18. SUPPLIER MODEL +// ============================================================================ + +// Supplier: Represents a supplier in a simulation. +// Purpose: +// - Simulates the supply of products at specific intervals. +// - Generates random supply requests periodically based on the defined +// intervals. +// Usage: +// Supplier supplier(minInterval, maxInterval, numProducts, rng); +// int suppliedProduct = supplier.step(timeStep); +// supplier.reset(); +class Supplier { +private: + double nextSupplyTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Supplier(double V, double Q, int N, RandomGenerator &generator) + : minInterval(V), maxInterval(Q), numProducts(N), rng(generator) { + nextSupplyTime = rng.uniform(V, Q); + } + + int step(double dt) { + nextSupplyTime -= dt; + if (nextSupplyTime <= 0) { + nextSupplyTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + void reset() { nextSupplyTime = rng.uniform(minInterval, maxInterval); } +}; + +// Server di inventario con messaggi (customer/server network-style) +class InventoryServerProcess : public DiscreteEventProcess { +public: + InventoryServerProcess(int pid, int numItems, int initialStockPerItem, + double serviceTime, MessageBus &bus, + RandomGenerator &rng) + : pid_(pid), serviceTime_(serviceTime), bus_(bus), rng_(rng), + nextEventTime_(0.0), + storage_(static_cast(numItems), initialStockPerItem), + totalRequests_(0), missedRequests_(0) {} + + void initialize(double startTime) override { + nextEventTime_ = startTime + serviceTime_; + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // processa TUTTI i messaggi arrivati a questo server + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + totalRequests_++; + + // sicurezza sugli indici prodotto + if (m.item < 0 || static_cast(m.item) >= storage_.size()) { + continue; // scarta richieste invalide + } + + double servedQty = 0.0; + int &stock = storage_[static_cast(m.item)]; + + if (stock >= static_cast(m.quantity)) { + // richiesta soddisfatta + stock -= static_cast(m.quantity); + servedQty = m.quantity; + } else { + // non abbastanza stock + servedQty = static_cast(stock); + missedRequests_++; + stock = 0; + } + + // risposta al cliente + Message reply; + reply.time = currentTime; + reply.sender = pid_; + reply.receiver = m.sender; // rimanda al mittente + reply.item = m.item; + reply.quantity = servedQty; + + bus_.procToNet(pid_).push(reply); + + // eventuale logica di refill: esempio semplice + if (stock == 0) { + int refill = rng_.uniformInt(1, 10); // batch di rifornimento random + stock += refill; + } + } + + nextEventTime_ = currentTime + serviceTime_; + } + + const std::vector &storage() const { return storage_; } + + int totalRequests() const { return totalRequests_; } + int missedRequests() const { return missedRequests_; } + +private: + int pid_; + double serviceTime_; + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + std::vector storage_; + + int totalRequests_; + int missedRequests_; +}; + +// ============================================================================ +// 19. INVENTORY CUSTOMER PROCESS (DES-compatible) +// ============================================================================ +// +// InventoryCustomerProcess: +// - Processo a eventi discreti che rappresenta un cliente. +// - Invia richieste al server tramite MessageBus (procToNet -> NetworkRouter). +// - Legge le risposte dal canale netToProc. +// - Statistiche: quante richieste ha mandato e quante risposte ha ricevuto. +// +class InventoryCustomerProcess : public DiscreteEventProcess { +public: + // pid : id di questo processo (indice nei canali del MessageBus) + // serverPid : pid del server a cui mandare le richieste + // numItems : numero di prodotti gestiti (item in [0, numItems-1]) + // minInterval/maxInterval : intervallo di tempo tra una richiesta e la + // successiva + InventoryCustomerProcess(int pid, int serverPid, int numItems, + double minInterval, double maxInterval, + MessageBus &bus, RandomGenerator &rng) + : pid_(pid), serverPid_(serverPid), numItems_(numItems), + minInterval_(minInterval), maxInterval_(maxInterval), bus_(bus), + rng_(rng), nextEventTime_(0.0), issuedRequests_(0), receivedReplies_(0), + totalRequestedQty_(0.0), totalServedQty_(0.0) {} + + // inizializza il prossimo evento (prima richiesta) + void initialize(double startTime) override { + nextEventTime_ = startTime + rng_.uniform(minInterval_, maxInterval_); + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // 1) genera una nuova richiesta al server + const int item = rng_.uniformInt( + 0, numItems_ - 1); // coerente con InventoryServerProcess + const int qty = rng_.uniformInt(1, 3); // quantità richiesta (1..3) + + Message req; + req.time = currentTime; + req.sender = pid_; + req.receiver = serverPid_; + req.item = item; + req.quantity = static_cast(qty); + + bus_.procToNet(pid_).push(req); + issuedRequests_++; + totalRequestedQty_ += req.quantity; + + // 2) processa tutte le risposte arrivate su questo cliente + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + // opzionale: possiamo filtrare solo messaggi dal serverPid_ + // if (m.sender != serverPid_) continue; + + receivedReplies_++; + totalServedQty_ += m.quantity; + } + + // 3) pianifica la prossima richiesta + nextEventTime_ = currentTime + rng_.uniform(minInterval_, maxInterval_); + } + + // --- metriche accessibili dall’esterno --- + + int pid() const { return pid_; } + int serverPid() const { return serverPid_; } + + int issuedRequests() const { return issuedRequests_; } + int receivedReplies() const { return receivedReplies_; } + + double totalRequestedQty() const { return totalRequestedQty_; } + double totalServedQty() const { return totalServedQty_; } + + // fill-rate lato customer + double serviceLevel() const { + if (totalRequestedQty_ <= 0.0) + return 1.0; + return totalServedQty_ / totalRequestedQty_; + } + +private: + int pid_; + int serverPid_; + int numItems_; + double minInterval_; + double maxInterval_; + + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + + int issuedRequests_; + int receivedReplies_; + double totalRequestedQty_; + double totalServedQty_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/4/include/Queue.hpp b/exams/AxelRubini/2025-02-05/4/include/Queue.hpp new file mode 100644 index 0000000..0c12ce8 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/4/include/Queue.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 9. FIFO QUEUE +// ============================================================================ + +// FIFOQueue: Implements a first-in-first-out (FIFO) queue with a fixed maximum +// size. Purpose: +// - Enqueue and dequeue items in FIFO order. +// - Track statistics such as total items enqueued and dequeued. +// - Support dynamic size constraints. +// Usage: +// FIFOQueue queue(10); +// queue.enqueue(5); +// int item; +// if (queue.dequeue(item)) { +// // Process item. +// } +template class FIFOQueue { +private: + std::queue queue; + size_t maxSize; + size_t totalEnqueued; + size_t totalDequeued; + +public: + FIFOQueue(size_t max = SIZE_MAX) + : maxSize(max), totalEnqueued(0), totalDequeued(0) {} + + bool enqueue(const T &item) { + if (queue.size() >= maxSize) + return false; + queue.push(item); + totalEnqueued++; + return true; + } + + bool dequeue(T &item) { + if (queue.empty()) + return false; + item = queue.front(); + queue.pop(); + totalDequeued++; + return true; + } + + bool isEmpty() const { return queue.empty(); } + bool isFull() const { return queue.size() >= maxSize; } + size_t size() const { return queue.size(); } + size_t getTotalEnqueued() const { return totalEnqueued; } + size_t getTotalDequeued() const { return totalDequeued; } + std::queue getQueue() const { return queue; } + T getFirst() const { + if (queue.empty()) + throw std::runtime_error("Queue is empty"); + return queue.front(); + } + + void clear() { + while (!queue.empty()) + queue.pop(); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/4/include/Random.hpp b/exams/AxelRubini/2025-02-05/4/include/Random.hpp new file mode 100644 index 0000000..52356f9 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/4/include/Random.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include // std::isfinite +#include // std::accumulate +#include +#include +#include + +namespace SELib { + +class RandomGenerator { +private: + std::mt19937 gen; + +public: + explicit RandomGenerator(unsigned seed = std::random_device{}()) + : gen(seed) {} + + double uniform(double min, double max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_real_distribution dis(lo, high); + return dis(gen); + } + + int uniformInt(int min, int max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_int_distribution dis(lo, high); + return dis(gen); + } + + double gaussian(double mean, double stddev) { + if (stddev < 0.0) { + throw std::invalid_argument("gaussian: stddev must be >= 0"); + } + std::normal_distribution dis(mean, stddev); + return dis(gen); + } + + bool bernoulli(double p) { + if (p < 0.0 || p > 1.0 || !std::isfinite(p)) { + throw std::invalid_argument("bernoulli: p must be in [0,1]"); + } + std::bernoulli_distribution dis(p); + return dis(gen); + } + + int discrete(const std::vector &probabilities) { + if (probabilities.empty()) { + throw std::invalid_argument("discrete: empty probability vector"); + } + + for (double w : probabilities) { + if (w < 0.0 || !std::isfinite(w)) { + throw std::invalid_argument( + "discrete: weights must be non-negative and finite"); + } + } + + double sum = + std::accumulate(probabilities.begin(), probabilities.end(), 0.0); + if (sum <= 0.0) { + throw std::invalid_argument("discrete: sum of weights must be > 0"); + } + + std::discrete_distribution dis(probabilities.begin(), + probabilities.end()); + return dis(gen); + } + + void setSeed(unsigned seed) { gen.seed(seed); } + + // (opzionale) accesso al motore se ti serve per altre distribuzioni + std::mt19937 &engine() { return gen; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/4/include/Stat.hpp b/exams/AxelRubini/2025-02-05/4/include/Stat.hpp new file mode 100644 index 0000000..27b4656 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/4/include/Stat.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 2. STATISTICS +// ============================================================================ + +// Statistics: A utility class for managing and analyzing numerical data +// samples. Purpose: +// - Compute statistical metrics such as mean and standard deviation. +// - Estimate probabilities of custom conditions. +// - Check convergence of data over iterations. +// Usage: +// Statistics stats; +// stats.addSample(5.0); +// double meanValue = stats.mean(); +class Statistics { +private: + std::vector samples; + +public: + void addSample(double value) { samples.push_back(value); } + + void clear() { samples.clear(); } + + double mean() const { + if (samples.empty()) + return 0.0; + double sum = 0.0; + for (double s : samples) + sum += s; + return sum / samples.size(); + } + + double stddev() const { + if (samples.size() < 2) + return 0.0; + double m = mean(); + double variance = 0.0; + for (double s : samples) { + variance += (s - m) * (s - m); + } + return std::sqrt(variance / (samples.size() - 1)); + } + + double probability(std::function condition) const { + if (samples.empty()) + return 0.0; + int count = 0; + for (double s : samples) { + if (condition(s)) + count++; + } + return static_cast(count) / samples.size(); + } + + size_t size() const { return samples.size(); } + const std::vector &getSamples() const { return samples; } + + bool hasConverged(double tolerance = 0.1) const { + if (samples.size() < 30) + return false; + double m = mean(); + double sd = stddev(); + return sd <= tolerance * std::abs(m); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/4/main b/exams/AxelRubini/2025-02-05/4/main new file mode 100755 index 0000000000000000000000000000000000000000..5b98a875f3567516afe49b26fb0b8ded4cdb0850 GIT binary patch literal 43440 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

-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 LoTLItN$r0DT?;F- literal 0 HcmV?d00001 diff --git a/exams/AxelRubini/2025-02-05/4/main.cpp b/exams/AxelRubini/2025-02-05/4/main.cpp new file mode 100644 index 0000000..8031838 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/4/main.cpp @@ -0,0 +1,150 @@ +#include "include/DES.hpp" +#include "include/Decision.hpp" +#include "include/IO.hpp" +#include "include/Random.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace SELib { + +class CustomerProcess : public DiscreteEventProcess { + public: + CustomerProcess( + int pid, + int serverPid, + MessageBus &bus, + RandomGenerator &rng, + const std::vector> &matrix + ) + : pid_(pid), serverPid_(serverPid), bus_(bus), rng_(rng), + matrix_(matrix), currentState_(0), nextTime_(0.0) {} + + void initialize(double startTime) override { nextTime_ = startTime; } + double nextEventTime() const override { return nextTime_; } + + void handleEvent(double currentTime) override { + int nextState = rng_.discrete(matrix_[currentState_]); + if (currentState_ != 0) { + Message m; + m.time = currentTime; + m.sender = pid_; + m.receiver = serverPid_; + m.item = currentState_; + bus_.procToNet(pid_).push(m); + } + currentState_ = nextState; + nextTime_ += 1.0; + } + + private: + int pid_, serverPid_, currentState_; + MessageBus &bus_; + RandomGenerator &rng_; + const std::vector> &matrix_; + double nextTime_; +}; + +class ServerProcess : public DiscreteEventProcess { + public: + ServerProcess(int pid, MessageBus &bus, double T1, double T2) + : pid_(pid), bus_(bus), T1_(T1), T2_(T2), serverFreeAt_(0.0), + nextFinishTime_(std::numeric_limits::infinity()) {} + + void initialize(double startTime) override {} + double nextEventTime() const override { return nextFinishTime_; } + + void handleEvent(double currentTime) override { + if (currentTime >= nextFinishTime_) + nextFinishTime_ = std::numeric_limits::infinity(); + processQueue(currentTime); + } + + void processQueue(double currentTime) { + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + queue_.push(inbox.front()); + inbox.pop(); + } + + if (currentTime >= serverFreeAt_ && !queue_.empty()) { + Message req = queue_.front(); + queue_.pop(); + double st = (req.item == 1) ? T1_ : T2_; + serverFreeAt_ = std::max(currentTime, serverFreeAt_) + st; + nextFinishTime_ = serverFreeAt_; + } + } + + size_t getQueueSize() const { return queue_.size(); } + bool isBusy() const { + return nextFinishTime_ != std::numeric_limits::infinity(); + } + + private: + int pid_; + MessageBus &bus_; + double T1_, T2_, serverFreeAt_, nextFinishTime_; + std::queue queue_; +}; + +} // namespace SELib + +int main() { + SELib::RandomGenerator rng(0); + SELib::ParameterParser parser; + if (!parser.parseFile("parameters.txt")) + return 1; + + std::vector> matrix(3, std::vector(3)); + const auto &data = parser.getStructuredData(); + for (const auto &line : data) + if (line.size() == 3) + matrix[std::stoi(line[0])][std::stoi(line[1])] = std::stod(line[2]); + std::vector ts; + for (const auto &line : data) + if (line.size() == 1) + ts.push_back(std::stod(line[0])); + double T1 = ts[ts.size() - 2], T2 = ts[ts.size() - 1]; + + auto run_sim = [&](int simulations) { + double totalAvgB = 0; + for (int i = 0; i < simulations; ++i) { + SELib::MessageBus bus(2); + SELib::DiscreteEventSystem system; + system.emplaceProcess( + 0, 1, bus, rng, matrix + ); + auto &server = + system.emplaceProcess(1, bus, T1, T2); + system.emplaceProcess(bus, rng, 0.001); + + double currentTime = 0.0, H = 10000.0, area = 0.0; + system.initialize(0.0); + while (currentTime < H) { + double nextTime = system.nextEventTime(); + if (nextTime > H) + nextTime = H; + int B = (int)server.getQueueSize() + (server.isBusy() ? 1 : 0); + area += B * (nextTime - currentTime); + currentTime = nextTime; + if (currentTime >= H) + break; + system.handleEvent(currentTime); + server.processQueue(currentTime); + } + totalAvgB += area / H; + } + return totalAvgB / simulations; + }; + + std::ofstream outFile("results.txt"); + outFile << "2025-02-05-Axel-Rubini-2158099" << std::endl; + outFile << "Avg " << std::fixed << std::setprecision(4) << run_sim(1000) + << std::endl; + return 0; +} diff --git a/exams/AxelRubini/2025-02-05/4/parameters.txt b/exams/AxelRubini/2025-02-05/4/parameters.txt new file mode 100644 index 0000000..ba70834 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/4/parameters.txt @@ -0,0 +1,11 @@ +0 0 0.40 +0 1 0.30 +0 2 0.30 +1 0 0.8 +1 1 0.1 +1 2 0.1 +2 0 0.8 +2 1 0.1 +2 2 0.1 +2.5 +4.5 diff --git a/exams/AxelRubini/2025-02-05/4/results.txt b/exams/AxelRubini/2025-02-05/4/results.txt new file mode 100644 index 0000000..622c7e2 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/4/results.txt @@ -0,0 +1,2 @@ +2025-02-05-Axel-Rubini-2158099 +Avg 715.882 diff --git a/exams/AxelRubini/2025-02-05/5/Makefile b/exams/AxelRubini/2025-02-05/5/Makefile new file mode 100644 index 0000000..6bc53bf --- /dev/null +++ b/exams/AxelRubini/2025-02-05/5/Makefile @@ -0,0 +1,10 @@ +CC=g++ +CFLAGS=-std=c++23 -O3 -Wall + +all: main + +main: main.cpp + $(CC) $(CFLAGS) main.cpp -o main + +clean: + rm -f main *.o \ No newline at end of file diff --git a/exams/AxelRubini/2025-02-05/5/include/DES.hpp b/exams/AxelRubini/2025-02-05/5/include/DES.hpp new file mode 100644 index 0000000..1706b8f --- /dev/null +++ b/exams/AxelRubini/2025-02-05/5/include/DES.hpp @@ -0,0 +1,212 @@ + +#pragma once + +#include "Random.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// --------------------------------------------------------------------------- +// Messaggio +// --------------------------------------------------------------------------- +struct Message { + double time{0.0}; + int sender{-1}; + int receiver{-1}; + int item{0}; + double quantity{0.0}; +}; + +// --------------------------------------------------------------------------- +// Bus di messaggi: process -> network, network -> process +// --------------------------------------------------------------------------- +class MessageBus { +public: + explicit MessageBus(std::size_t n = 0) { resize(n); } + + void resize(std::size_t n) { + procToNet_.assign(n, {}); + netToProc_.assign(n, {}); + } + + std::size_t size() const { return procToNet_.size(); } + + std::queue &procToNet(int pid) { return procToNet_.at(pid); } + std::queue &netToProc(int pid) { return netToProc_.at(pid); } + const std::queue &procToNet(int pid) const { + return procToNet_.at(pid); + } + const std::queue &netToProc(int pid) const { + return netToProc_.at(pid); + } + +private: + std::vector> procToNet_; + std::vector> netToProc_; +}; + +// --------------------------------------------------------------------------- +// Processo base a eventi discreti +// --------------------------------------------------------------------------- +class DiscreteEventProcess { +public: + virtual ~DiscreteEventProcess() = default; + virtual void initialize(double startTime) = 0; + virtual double nextEventTime() const = 0; + virtual void handleEvent(double currentTime) = 0; +}; + +// --------------------------------------------------------------------------- +// NetworkRouter: inoltra i messaggi P->N->P +// --------------------------------------------------------------------------- +class NetworkRouter : public DiscreteEventProcess { +public: + NetworkRouter(MessageBus &bus, RandomGenerator &rng, double scanPeriod = 0.1) + : bus_(bus), rng_(rng), scanPeriod_(scanPeriod), time_(0.0), + nextScanTime_(0.0), scanned_(0) {} + + void initialize(double startTime) override { + time_ = startTime; + nextScanTime_ = startTime + scanPeriod_; + + const int n = static_cast(bus_.size()); + if (n <= 0) { + throw std::runtime_error( + "NetworkRouter: no processes configured in MessageBus"); + } + + scanner_.resize(n); + for (int i = 0; i < n; ++i) { + scanner_[i] = i; + } + reshuffleScanner(); + scanned_ = 0; + } + + double nextEventTime() const override { return nextScanTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextScanTime_) + return; + + const int n = static_cast(scanner_.size()); + if (n == 0) + return; + + if (scanned_ >= n) { + scanned_ = 0; + reshuffleScanner(); + } + + const int channel = scanner_[scanned_++]; + auto &fromQueue = bus_.procToNet(channel); + + if (!fromQueue.empty()) { + Message m = fromQueue.front(); + fromQueue.pop(); + + if (m.receiver < 0 || m.receiver >= static_cast(bus_.size())) { + throw std::runtime_error("NetworkRouter: invalid receiver index"); + } + + bus_.netToProc(m.receiver).push(m); + } + + nextScanTime_ = currentTime + scanPeriod_; + } + +private: + void reshuffleScanner() { + auto &eng = rng_.engine(); // serve engine() in RandomGenerator + std::shuffle(scanner_.begin(), scanner_.end(), eng); + } + + MessageBus &bus_; + RandomGenerator &rng_; + + double scanPeriod_; + double time_; + double nextScanTime_; + std::vector scanner_; + int scanned_; +}; + +// --------------------------------------------------------------------------- +// Sistema DES: collezione di processi +// --------------------------------------------------------------------------- +class DiscreteEventSystem { +public: + template + Proc &emplaceProcess(Args &&...args) { + auto ptr = std::make_unique(std::forward(args)...); + Proc &ref = *ptr; + processes_.push_back(std::move(ptr)); + return ref; + } + + void initialize(double startTime = 0.0) { + for (auto &p : processes_) { + p->initialize(startTime); + } + } + + double nextEventTime() const { + if (processes_.empty()) { + return std::numeric_limits::infinity(); + } + + double tMin = std::numeric_limits::infinity(); + for (const auto &p : processes_) { + double t = p->nextEventTime(); + if (t < tMin) + tMin = t; + } + return tMin; + } + + void handleEvent(double currentTime) { + for (auto &p : processes_) { + if (p->nextEventTime() <= currentTime) { + p->handleEvent(currentTime); + } + } + } + +private: + std::vector> processes_; +}; + +// --------------------------------------------------------------------------- +// Simulatore DES +// --------------------------------------------------------------------------- +class DiscreteEventSimulator { +public: + explicit DiscreteEventSimulator(DiscreteEventSystem &system) + : system_(system), currentTime_(0.0) {} + + void run(double horizon) { + system_.initialize(0.0); + currentTime_ = 0.0; + + while (currentTime_ < horizon) { + double nextTime = system_.nextEventTime(); + if (nextTime == std::numeric_limits::infinity()) + break; + currentTime_ = nextTime; + system_.handleEvent(currentTime_); + } + } + + double currentTime() const { return currentTime_; } + +private: + DiscreteEventSystem &system_; + double currentTime_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/5/include/Decision.hpp b/exams/AxelRubini/2025-02-05/5/include/Decision.hpp new file mode 100644 index 0000000..4d37cf0 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/5/include/Decision.hpp @@ -0,0 +1,266 @@ +#pragma once + +#include "Random.hpp" +#include "Stat.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 7. MARKOV CHAIN +// ============================================================================ + +// MarkovChain: Implements a discrete-time Markov chain. +// Purpose: +// - Simulates stochastic processes with defined states and transition +// probabilities. +// - Useful in modeling random systems and computing related probabilities. +// Usage: +// MarkovChain mc(3, rng); +// mc.setTransition(0, 1, 0.5); +// mc.setTransition(0, 2, 0.5); +// mc.setState(0); +// int nextState = mc.step(); +class MarkovChain { +private: + int numStates; + std::vector> transitionMatrix; + int currentState; + RandomGenerator &rng; + +public: + MarkovChain(int states, RandomGenerator &generator) + : numStates(states), currentState(0), rng(generator) { + transitionMatrix.resize(states, std::vector(states, 0.0)); + } + + void setTransition(int from, int to, double probability) { + if (from >= 0 && from < numStates && to >= 0 && to < numStates) { + transitionMatrix[from][to] = probability; + } + } + + void setState(int state) { + if (state >= 0 && state < numStates) { + currentState = state; + } + } + + int getState() const { return currentState; } + + int step() { + currentState = rng.discrete(transitionMatrix[currentState]); + return currentState; + } + + double getTransitionProb(int from, int to) const { + return transitionMatrix[from][to]; + } + + double expectedSojournTime(int state, double timeStep = 1.0) const { + double pii = transitionMatrix[state][state]; + if (pii >= 1.0) + return std::numeric_limits::infinity(); + return timeStep / (1.0 - pii); + } +}; + +// ============================================================================ +// 8. MDP +// ============================================================================ + +// MDP: Implements a Markov Decision Process (MDP) model. +// Purpose: +// - Simulate decision-making processes with states, transitions, and +// associated costs. +// - Calculate the total cost to reach a target state from a starting state. +// Usage: +// - Add transitions between states with associated probabilities and costs +// using `addTransition`. +// - Use `step` to simulate a single transition and calculate its cost. +// - Use `simulate` to estimate the total cost from a start to an end state. +class MDP { +private: + int numStates; + std::map>>> + transitions; + RandomGenerator &rng; + +public: + MDP(int states, RandomGenerator &generator) + : numStates(states), rng(generator) {} + + void addTransition(int from, int to, double probability, double cost) { + transitions[from].push_back({to, {probability, cost}}); + } + + std::pair step(int currentState) { + if (transitions.find(currentState) == transitions.end()) { + return {currentState, 0.0}; + } + + std::vector probs; + for (const auto &t : transitions[currentState]) { + probs.push_back(t.second.first); + } + + int idx = rng.discrete(probs); + int nextState = transitions[currentState][idx].first; + double cost = transitions[currentState][idx].second.second; + + return {nextState, cost}; + } + + double simulate(int startState, int endState) { + int state = startState; + double totalCost = 0.0; + int maxSteps = 100000; + + for (int step = 0; step < maxSteps && state != endState; step++) { + auto [nextState, cost] = this->step(state); + totalCost += cost; + state = nextState; + } + return totalCost; + } +}; + +// ============================================================================ +// 10. MONTE CARLO SIMULATOR +// ============================================================================ + +// MonteCarloSimulator: A utility for running Monte Carlo simulations. +// Purpose: +// - Estimate statistical metrics through repeated random sampling. +// - Provides methods to calculate mean results and probabilities of events. +// Usage: +// MonteCarloSimulator simulator(1000); +// double estimate = simulator.estimate([](int) { return some_random_value; }); +// double probability = simulator.estimateProbability([](int) { return +// some_event_occurs; }); +class MonteCarloSimulator { +private: + Statistics stats; + int numSimulations; + +public: + MonteCarloSimulator(int simulations = 1000) : numSimulations(simulations) {} + + template double estimate(SimFunc simulation) { + stats.clear(); + for (int i = 0; i < numSimulations; i++) { + double result = simulation(i); + stats.addSample(result); + } + return stats.mean(); + } + + template double estimateProbability(SimFunc simulation) { + int successes = 0; + for (int i = 0; i < numSimulations; i++) { + if (simulation(i)) + successes++; + } + return static_cast(successes) / numSimulations; + } + + const Statistics &getStatistics() const { return stats; } +}; + +// ============================================================================ +// 13. OPTIMIZATION UTILITIES +// ============================================================================ + +// Optimizer: Utility class offering optimization techniques such as random +// search and grid search. Purpose: +// - Explore parameter spaces to maximize or minimize an objective function. +// - Supports both continuous and discrete search spaces. +// Usage: +// - Use `randomSearch` for stochastic optimization over a defined range. +// - Use `gridSearch` for systematic exploration in evenly spaced steps. +// Example: +// auto [bestParam, bestValue] = Optimizer::randomSearch(objectiveFunction, +// 0.0, 10.0, 100, rng); auto [bestInt, bestIntValue] = +// Optimizer::randomSearchInt(objectiveFunction, 0, 10, 50, rng); +class Optimizer { +public: + template + static std::pair + randomSearch(ObjFunc objective, double minValue, double maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + for (int i = 0; i < budget; i++) { + double param = rng.uniform(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair + randomSearchInt(ObjFunc objective, int minValue, int maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + int bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + if (budget <= 0) { + throw std::invalid_argument("randomSearchInt: budget must be positive."); + } + + if (minValue >= maxValue) { + throw std::invalid_argument( + "randomSearchInt: minValue must be less than maxValue."); + } + + for (int i = 0; i < budget; i++) { + int param = rng.uniformInt(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair gridSearch(ObjFunc objective, + double minValue, double maxValue, + int steps, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + double stepSize = (maxValue - minValue) / steps; + + if (steps <= 0) { + throw std::invalid_argument("gridSearch: steps must be positive."); + } + + for (int i = 0; i <= steps; i++) { + double param = minValue + i * stepSize; + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/5/include/Dynamics.hpp b/exams/AxelRubini/2025-02-05/5/include/Dynamics.hpp new file mode 100644 index 0000000..044176a --- /dev/null +++ b/exams/AxelRubini/2025-02-05/5/include/Dynamics.hpp @@ -0,0 +1,214 @@ +#pragma once + +#include +#include +namespace SELib { +// ============================================================================ +// 3. ODE SOLVER (Runge-Kutta 4th order) +// ============================================================================ + +using StateVector = std::vector; +using DerivativeFunction = std::function; + +// ODESolver: Implements numerical methods for solving Ordinary Differential +// Equations (ODEs). Purpose: +// - Provides tools for simulating dynamic systems modeled by ODEs. +// - Includes methods such as Runge-Kutta 4th order and Euler's method for +// integration. +// Usage: +// - Pass a derivative function that defines the ODE system. +// - Use `rk4Step` for higher accuracy or `eulerStep` for simpler systems. +// Example: +// StateVector nextState = ODESolver::rk4Step(0.0, currentState, controlInputs, +// dt, dynamicsFunction); +class ODESolver { +public: + static StateVector rk4Step(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector k1 = f(t, x, u); + StateVector x2 = addScaled(x, k1, dt / 2.0); + StateVector k2 = f(t + dt / 2.0, x2, u); + StateVector x3 = addScaled(x, k2, dt / 2.0); + StateVector k3 = f(t + dt / 2.0, x3, u); + StateVector x4 = addScaled(x, k3, dt); + StateVector k4 = f(t + dt, x4, u); + + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += (dt / 6.0) * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]); + } + return xNew; + } + + static StateVector eulerStep(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector dx = f(t, x, u); + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += dx[i] * dt; + } + return xNew; + } + +private: + static StateVector addScaled(const StateVector &x, const StateVector &dx, + double scale) { + StateVector result = x; + for (size_t i = 0; i < x.size(); i++) { + result[i] += dx[i] * scale; + } + return result; + } +}; + +// ============================================================================ +// 4. PID CONTROLLER +// ============================================================================ + +// PIDController: Implements a Proportional-Integral-Derivative (PID) +// controller. Purpose: +// - Automatically adjusts a control signal to minimize the difference between +// a desired setpoint and a measured value. +// - Used in control systems to regulate processes like temperature, speed, or +// position. +// Usage: +// - Create an instance with proportional (Kp), integral (Ki), and derivative +// (Kd) gains, along with a time step. +// - Use `compute` to calculate the control signal based on the setpoint and +// measurement. +// Example: +// PIDController pid(1.0, 0.1, 0.01, 0.1); +// double controlSignal = pid.compute(desiredValue, actualValue); +class PIDController { +private: + double kp, ki, kd; + double integral; + double prevError; + double dt; + +public: + PIDController(double Kp, double Ki, double Kd, double timeStep) + : kp(Kp), ki(Ki), kd(Kd), integral(0), prevError(0), dt(timeStep) {} + + double compute(double setpoint, double measurement) { + double error = setpoint - measurement; + integral += error * dt; + double derivative = (error - prevError) / dt; + prevError = error; + return kp * error + ki * integral + kd * derivative; + } + + void reset() { + integral = 0; + prevError = 0; + } + + void setGains(double Kp, double Ki, double Kd) { + kp = Kp; + ki = Ki; + kd = Kd; + } +}; + +// ============================================================================ +// 5. CONTINUOUS TIME SIMULATION +// ============================================================================ + +// ContinuousTimeSimulation: A base class for simulating continuous-time dynamic +// systems. Purpose: +// - Provides a framework for simulating systems characterized by continuous +// dynamics over time. +// - Integrates system dynamics using a Runge-Kutta 4th order solver. +// Usage: +// - Derive from this class and implement the `dynamics`, `computeControl`, +// `initialize`, and `finalize` methods. +// - Customize the `onStep` method for per-step side effects, if required. +// Example: +// class MySimulation : public ContinuousTimeSimulation { +// // Implement the required virtual methods. +// }; +// MySimulation sim(dt, horizon, initialState); +// sim.run(); +class ContinuousTimeSimulation { +protected: + double currentTime; + double timeStep; + double horizon; + StateVector state; + StateVector control; + +public: + ContinuousTimeSimulation(double dt, double H, const StateVector &initialState) + : currentTime(0.0), timeStep(dt), horizon(H), state(initialState) { + control.resize(initialState.size(), 0.0); + } + + virtual ~ContinuousTimeSimulation() = default; + + virtual StateVector dynamics(double t, const StateVector &x, + const StateVector &u) = 0; + virtual StateVector computeControl(double t, const StateVector &x) = 0; + virtual void initialize() = 0; + virtual void onStep() {} + virtual void finalize() = 0; + + void run() { + initialize(); + while (currentTime < horizon) { + control = computeControl(currentTime, state); + state = ODESolver::rk4Step( + currentTime, state, control, timeStep, + [this](double t, const StateVector &x, const StateVector &u) { + return this->dynamics(t, x, u); + }); + currentTime += timeStep; + onStep(); + } + finalize(); + } + + double getCurrentTime() const { return currentTime; } + const StateVector &getState() const { return state; } + const StateVector &getControl() const { return control; } +}; + +// ============================================================================ +// 6. SAMPLE AND HOLD +// ============================================================================ + +// SampleAndHold: A utility to store a value and update it periodically. +// Purpose: +// - Maintain a value that gets updated periodically based on a time interval. +// - Useful for sampling data and ensuring updates happen at fixed intervals. +// Usage: +// SampleAndHold sampler(5.0, 0); +// if (sampler.shouldUpdate(currentTime)) { +// sampler.update(currentTime, newValue); +// } +// int currentValue = sampler.getValue(); +template class SampleAndHold { +private: + T value; + double lastUpdateTime; + double samplePeriod; + +public: + SampleAndHold(double period, const T &initialValue = T()) + : value(initialValue), lastUpdateTime(0), samplePeriod(period) {} + + bool shouldUpdate(double currentTime) const { + return (currentTime - lastUpdateTime) >= samplePeriod; + } + + void update(double currentTime, const T &newValue) { + value = newValue; + lastUpdateTime = currentTime; + } + + const T &getValue() const { return value; } + void reset() { lastUpdateTime = 0; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/5/include/Geometry.hpp b/exams/AxelRubini/2025-02-05/5/include/Geometry.hpp new file mode 100644 index 0000000..525a91d --- /dev/null +++ b/exams/AxelRubini/2025-02-05/5/include/Geometry.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 14. GEOMETRIC UTILITIES +// ============================================================================ + +// Point2D: Represents a point or vector in 2D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point2D p1(1.0, 2.0), p2(2.0, 3.0); +// double dist = p1.distanceTo(p2); +// Point2D sum = p1 + p2; +struct Point2D { + double x, y; + + Point2D(double x_ = 0, double y_ = 0) : x(x_), y(y_) {} + + double distanceTo(const Point2D &other) const { + double dx = x - other.x; + double dy = y - other.y; + return std::sqrt(dx * dx + dy * dy); + } + + Point2D operator+(const Point2D &other) const { + return Point2D(x + other.x, y + other.y); + } + + Point2D operator-(const Point2D &other) const { + return Point2D(x - other.x, y - other.y); + } +}; + +// Point3D: Represents a point or vector in 3D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point3D p1(1.0, 2.0, 3.0), p2(2.0, 3.0, 4.0); +// double dist = p1.distanceTo(p2); +// Point3D sum = p1 + p2; +struct Point3D { + double x, y, z; + + Point3D(double x_ = 0, double y_ = 0, double z_ = 0) : x(x_), y(y_), z(z_) {} + + double distanceTo(const Point3D &other) const { + double dx = x - other.x; + double dy = y - other.y; + double dz = z - other.z; + return std::sqrt(dx * dx + dy * dy + dz * dz); + } + + Point3D operator+(const Point3D &other) const { + return Point3D(x + other.x, y + other.y, z + other.z); + } + + Point3D operator-(const Point3D &other) const { + return Point3D(x - other.x, y - other.y, z - other.z); + } +}; + +// ============================================================================ +// 19. COLLISION DETECTOR +// ============================================================================ + +// CollisionDetector: A utility class for detecting collisions between points +// in space. Purpose: +// - Counts the number of collisions within a given distance threshold. +// - Retrieves pairs of colliding points. +// Usage: +// std::vector points = {Point2D(0, 0), Point2D(1, 1)}; +// int numCollisions = CollisionDetector::countCollisions(points, 1.5); +// auto collidingPairs = CollisionDetector::getCollidingPairs(points, 1.5); +class CollisionDetector { +public: + template + static int countCollisions(const std::vector &positions, + double threshold) { + int collisions = 0; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + collisions++; + } + } + } + return collisions; + } + + template + static std::vector> + getCollidingPairs(const std::vector &positions, double threshold) { + std::vector> pairs; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + pairs.push_back({i, j}); + } + } + } + return pairs; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/5/include/IO.hpp b/exams/AxelRubini/2025-02-05/5/include/IO.hpp new file mode 100644 index 0000000..1a79cc6 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/5/include/IO.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 11. PARAMETER PARSER +// ============================================================================ + +// ParameterParser: A utility to parse and retrieve parameters from +// configuration files. Purpose: +// - Reads a file and stores key-value pairs for parameters. +// - Allows retrieval of parameters as integers, doubles, or strings. +// - Provides structured data for advanced parsing. +// Usage: +// ParameterParser parser; +// if (parser.parseFile("config.txt")) { +// int param = parser.getInt("example_key", 42); +// } +class ParameterParser { +private: + std::map params; + std::vector> structuredData; + +public: + bool parseFile(const std::string &filename) { + std::ifstream file(filename); + if (!file.is_open()) + return false; + + std::string line; + while (std::getline(file, line)) { + if (line.empty()) + continue; + + std::istringstream iss(line); + std::string key; + iss >> key; + + std::string value; + std::getline(iss, value); + if (!value.empty() && value[0] == ' ') { + value = value.substr(1); + } + + params[key] = value; + + std::vector tokens; + std::istringstream tokenStream(line); + std::string token; + while (tokenStream >> token) { + tokens.push_back(token); + } + structuredData.push_back(tokens); + } + return true; + } + + int getInt(const std::string &key, int defaultValue = 0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stoi(it->second); + } + return defaultValue; + } + + double getDouble(const std::string &key, double defaultValue = 0.0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stod(it->second); + } + return defaultValue; + } + + std::string getString(const std::string &key, + const std::string &defaultValue = "") const { + auto it = params.find(key); + if (it != params.end()) { + return it->second; + } + return defaultValue; + } + + const std::vector> &getStructuredData() const { + return structuredData; + } + + bool hasKey(const std::string &key) const { + return params.find(key) != params.end(); + } +}; + +// ============================================================================ +// 12. OUTPUT WRITER +// ============================================================================ + +// OutputWriter: A utility class for writing data to output files. +// Purpose: +// - Simplifies the process of writing data to files. +// - Supports writing lines, key-value pairs, and managing file headers. +// Usage: +// OutputWriter writer; +// if (writer.open("output.txt", "Header Info")) { +// writer.writeLine("Example Line"); +// writer.writeKeyValue("Key", 42.0); +// writer.close(); +// } +class OutputWriter { +private: + std::ofstream file; + +public: + bool open(const std::string &filename, const std::string &header) { + file.open(filename); + if (!file.is_open()) + return false; + file << header << std::endl; + return true; + } + + template void writeLine(const T &value) { + file << value << std::endl; + } + + template + void writeLine(const T &first, const Args &...args) { + file << first << " "; + writeLine(args...); + } + + void writeKeyValue(const std::string &key, double value, int precision = 2) { + file << key << " " << std::fixed << std::setprecision(precision) << value + << std::endl; + } + + void close() { + if (file.is_open()) { + file.close(); + } + } + + ~OutputWriter() { close(); } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/5/include/Inventory.hpp b/exams/AxelRubini/2025-02-05/5/include/Inventory.hpp new file mode 100644 index 0000000..c82d776 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/5/include/Inventory.hpp @@ -0,0 +1,423 @@ +#pragma once + +#include "DES.hpp" +#include "Random.hpp" +#include +#include + +namespace SELib { +// ============================================================================ +// 16. CUSTOMER MODEL +// ============================================================================ + +// Customer: Represents a customer model in a simulation. +// Purpose: +// - Simulates customer requests over time. +// - Generates random requests for products at specific intervals. +// Usage: +// Customer customer(5.0, 10.0, 3, rng); +// int productId = customer.step(0.1); // Simulates a time step of 0.1 seconds. +class Customer { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer(double A, double B, int N, RandomGenerator &generator) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator) { + nextRequestTime = rng.uniform(A, B); + } + + int step(double dt) { + nextRequestTime -= dt; + if (nextRequestTime <= 0) { + nextRequestTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } +}; +class Customer_Event_Driven { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer_Event_Driven(double A, double B, int N, RandomGenerator &generator, + double nextRequestTime) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator), + nextRequestTime(nextRequestTime) {} + + int step_event_driven(double *current_time) { + if (*current_time >= nextRequestTime) { + nextRequestTime = *current_time + rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + double getNextRequestTime() { return nextRequestTime; }; +}; + +// ============================================================================ +// 17. SERVER MODEL +// ============================================================================ + +// Server: Represents a server in a simulated environment. +// Purpose: +// - Handles product requests and manages inventory. +// - Simulates server busy time during request processing. +// Usage: +// Server server(numProducts, maxInventory, processingTimeNormal, +// processingTimeSupplier, rng); int result = +// server.processRequest(productId, needsSupply); +// server.addInventory(productId, quantityAdded); +class Server { +private: + std::vector inventory; + int busyTime; + int processingTimeNormal; + int processingTimeSupplier; + RandomGenerator *rng; + +public: + Server(int numProducts, int K, int F, int G, RandomGenerator &rng) + : inventory(numProducts + 1, 0), busyTime(0), processingTimeNormal(F), + processingTimeSupplier(G), rng(&rng) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + + bool isBusy() const { return busyTime > 0; } + + void decrementBusyTime() { + if (busyTime > 0) + busyTime--; + } + + int processRequest(int productId, bool needsSupply) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return productId; + } else { + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return -productId; + } + } + + int processRequest2(int productId, double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = processingTimeNormal; + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + busyTime = processingTimeSupplier; + return -productId; + } + } + + void addInventory(int productId, int quantity) { + inventory[productId] += quantity; + } + + int getInventory(int productId) const { return inventory[productId]; } +}; +// ============================================================================ +// 17.5 SERVER-EVENT-DRIVEN +// ============================================================================ +class Server_Event_Driven { +private: + std::vector inventory; + RandomGenerator *rng; + double next_step_time; + double min_interval; + double max_interval; + +public: + Server_Event_Driven(int numProducts, int K, double min_interval, + double max_interval, RandomGenerator &rng, + double nextStepTime) + : inventory(numProducts + 1, 0), rng(&rng), min_interval(min_interval), + max_interval(max_interval), next_step_time(nextStepTime) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + int processRequestEventDriven(double current_time, int productId, + double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (current_time >= next_step_time) { + if (inventory[productId] > 0) { + inventory[productId]--; + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + + return -productId; + } + } else { + return 0; + } + } + double getNextStepTime() { return next_step_time; } +}; + +// ============================================================================ +// 18. SUPPLIER MODEL +// ============================================================================ + +// Supplier: Represents a supplier in a simulation. +// Purpose: +// - Simulates the supply of products at specific intervals. +// - Generates random supply requests periodically based on the defined +// intervals. +// Usage: +// Supplier supplier(minInterval, maxInterval, numProducts, rng); +// int suppliedProduct = supplier.step(timeStep); +// supplier.reset(); +class Supplier { +private: + double nextSupplyTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Supplier(double V, double Q, int N, RandomGenerator &generator) + : minInterval(V), maxInterval(Q), numProducts(N), rng(generator) { + nextSupplyTime = rng.uniform(V, Q); + } + + int step(double dt) { + nextSupplyTime -= dt; + if (nextSupplyTime <= 0) { + nextSupplyTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + void reset() { nextSupplyTime = rng.uniform(minInterval, maxInterval); } +}; + +// Server di inventario con messaggi (customer/server network-style) +class InventoryServerProcess : public DiscreteEventProcess { +public: + InventoryServerProcess(int pid, int numItems, int initialStockPerItem, + double serviceTime, MessageBus &bus, + RandomGenerator &rng) + : pid_(pid), serviceTime_(serviceTime), bus_(bus), rng_(rng), + nextEventTime_(0.0), + storage_(static_cast(numItems), initialStockPerItem), + totalRequests_(0), missedRequests_(0) {} + + void initialize(double startTime) override { + nextEventTime_ = startTime + serviceTime_; + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // processa TUTTI i messaggi arrivati a questo server + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + totalRequests_++; + + // sicurezza sugli indici prodotto + if (m.item < 0 || static_cast(m.item) >= storage_.size()) { + continue; // scarta richieste invalide + } + + double servedQty = 0.0; + int &stock = storage_[static_cast(m.item)]; + + if (stock >= static_cast(m.quantity)) { + // richiesta soddisfatta + stock -= static_cast(m.quantity); + servedQty = m.quantity; + } else { + // non abbastanza stock + servedQty = static_cast(stock); + missedRequests_++; + stock = 0; + } + + // risposta al cliente + Message reply; + reply.time = currentTime; + reply.sender = pid_; + reply.receiver = m.sender; // rimanda al mittente + reply.item = m.item; + reply.quantity = servedQty; + + bus_.procToNet(pid_).push(reply); + + // eventuale logica di refill: esempio semplice + if (stock == 0) { + int refill = rng_.uniformInt(1, 10); // batch di rifornimento random + stock += refill; + } + } + + nextEventTime_ = currentTime + serviceTime_; + } + + const std::vector &storage() const { return storage_; } + + int totalRequests() const { return totalRequests_; } + int missedRequests() const { return missedRequests_; } + +private: + int pid_; + double serviceTime_; + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + std::vector storage_; + + int totalRequests_; + int missedRequests_; +}; + +// ============================================================================ +// 19. INVENTORY CUSTOMER PROCESS (DES-compatible) +// ============================================================================ +// +// InventoryCustomerProcess: +// - Processo a eventi discreti che rappresenta un cliente. +// - Invia richieste al server tramite MessageBus (procToNet -> NetworkRouter). +// - Legge le risposte dal canale netToProc. +// - Statistiche: quante richieste ha mandato e quante risposte ha ricevuto. +// +class InventoryCustomerProcess : public DiscreteEventProcess { +public: + // pid : id di questo processo (indice nei canali del MessageBus) + // serverPid : pid del server a cui mandare le richieste + // numItems : numero di prodotti gestiti (item in [0, numItems-1]) + // minInterval/maxInterval : intervallo di tempo tra una richiesta e la + // successiva + InventoryCustomerProcess(int pid, int serverPid, int numItems, + double minInterval, double maxInterval, + MessageBus &bus, RandomGenerator &rng) + : pid_(pid), serverPid_(serverPid), numItems_(numItems), + minInterval_(minInterval), maxInterval_(maxInterval), bus_(bus), + rng_(rng), nextEventTime_(0.0), issuedRequests_(0), receivedReplies_(0), + totalRequestedQty_(0.0), totalServedQty_(0.0) {} + + // inizializza il prossimo evento (prima richiesta) + void initialize(double startTime) override { + nextEventTime_ = startTime + rng_.uniform(minInterval_, maxInterval_); + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // 1) genera una nuova richiesta al server + const int item = rng_.uniformInt( + 0, numItems_ - 1); // coerente con InventoryServerProcess + const int qty = rng_.uniformInt(1, 3); // quantità richiesta (1..3) + + Message req; + req.time = currentTime; + req.sender = pid_; + req.receiver = serverPid_; + req.item = item; + req.quantity = static_cast(qty); + + bus_.procToNet(pid_).push(req); + issuedRequests_++; + totalRequestedQty_ += req.quantity; + + // 2) processa tutte le risposte arrivate su questo cliente + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + // opzionale: possiamo filtrare solo messaggi dal serverPid_ + // if (m.sender != serverPid_) continue; + + receivedReplies_++; + totalServedQty_ += m.quantity; + } + + // 3) pianifica la prossima richiesta + nextEventTime_ = currentTime + rng_.uniform(minInterval_, maxInterval_); + } + + // --- metriche accessibili dall’esterno --- + + int pid() const { return pid_; } + int serverPid() const { return serverPid_; } + + int issuedRequests() const { return issuedRequests_; } + int receivedReplies() const { return receivedReplies_; } + + double totalRequestedQty() const { return totalRequestedQty_; } + double totalServedQty() const { return totalServedQty_; } + + // fill-rate lato customer + double serviceLevel() const { + if (totalRequestedQty_ <= 0.0) + return 1.0; + return totalServedQty_ / totalRequestedQty_; + } + +private: + int pid_; + int serverPid_; + int numItems_; + double minInterval_; + double maxInterval_; + + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + + int issuedRequests_; + int receivedReplies_; + double totalRequestedQty_; + double totalServedQty_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/5/include/Queue.hpp b/exams/AxelRubini/2025-02-05/5/include/Queue.hpp new file mode 100644 index 0000000..0c12ce8 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/5/include/Queue.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 9. FIFO QUEUE +// ============================================================================ + +// FIFOQueue: Implements a first-in-first-out (FIFO) queue with a fixed maximum +// size. Purpose: +// - Enqueue and dequeue items in FIFO order. +// - Track statistics such as total items enqueued and dequeued. +// - Support dynamic size constraints. +// Usage: +// FIFOQueue queue(10); +// queue.enqueue(5); +// int item; +// if (queue.dequeue(item)) { +// // Process item. +// } +template class FIFOQueue { +private: + std::queue queue; + size_t maxSize; + size_t totalEnqueued; + size_t totalDequeued; + +public: + FIFOQueue(size_t max = SIZE_MAX) + : maxSize(max), totalEnqueued(0), totalDequeued(0) {} + + bool enqueue(const T &item) { + if (queue.size() >= maxSize) + return false; + queue.push(item); + totalEnqueued++; + return true; + } + + bool dequeue(T &item) { + if (queue.empty()) + return false; + item = queue.front(); + queue.pop(); + totalDequeued++; + return true; + } + + bool isEmpty() const { return queue.empty(); } + bool isFull() const { return queue.size() >= maxSize; } + size_t size() const { return queue.size(); } + size_t getTotalEnqueued() const { return totalEnqueued; } + size_t getTotalDequeued() const { return totalDequeued; } + std::queue getQueue() const { return queue; } + T getFirst() const { + if (queue.empty()) + throw std::runtime_error("Queue is empty"); + return queue.front(); + } + + void clear() { + while (!queue.empty()) + queue.pop(); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/5/include/Random.hpp b/exams/AxelRubini/2025-02-05/5/include/Random.hpp new file mode 100644 index 0000000..52356f9 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/5/include/Random.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include // std::isfinite +#include // std::accumulate +#include +#include +#include + +namespace SELib { + +class RandomGenerator { +private: + std::mt19937 gen; + +public: + explicit RandomGenerator(unsigned seed = std::random_device{}()) + : gen(seed) {} + + double uniform(double min, double max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_real_distribution dis(lo, high); + return dis(gen); + } + + int uniformInt(int min, int max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_int_distribution dis(lo, high); + return dis(gen); + } + + double gaussian(double mean, double stddev) { + if (stddev < 0.0) { + throw std::invalid_argument("gaussian: stddev must be >= 0"); + } + std::normal_distribution dis(mean, stddev); + return dis(gen); + } + + bool bernoulli(double p) { + if (p < 0.0 || p > 1.0 || !std::isfinite(p)) { + throw std::invalid_argument("bernoulli: p must be in [0,1]"); + } + std::bernoulli_distribution dis(p); + return dis(gen); + } + + int discrete(const std::vector &probabilities) { + if (probabilities.empty()) { + throw std::invalid_argument("discrete: empty probability vector"); + } + + for (double w : probabilities) { + if (w < 0.0 || !std::isfinite(w)) { + throw std::invalid_argument( + "discrete: weights must be non-negative and finite"); + } + } + + double sum = + std::accumulate(probabilities.begin(), probabilities.end(), 0.0); + if (sum <= 0.0) { + throw std::invalid_argument("discrete: sum of weights must be > 0"); + } + + std::discrete_distribution dis(probabilities.begin(), + probabilities.end()); + return dis(gen); + } + + void setSeed(unsigned seed) { gen.seed(seed); } + + // (opzionale) accesso al motore se ti serve per altre distribuzioni + std::mt19937 &engine() { return gen; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/5/include/Stat.hpp b/exams/AxelRubini/2025-02-05/5/include/Stat.hpp new file mode 100644 index 0000000..27b4656 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/5/include/Stat.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 2. STATISTICS +// ============================================================================ + +// Statistics: A utility class for managing and analyzing numerical data +// samples. Purpose: +// - Compute statistical metrics such as mean and standard deviation. +// - Estimate probabilities of custom conditions. +// - Check convergence of data over iterations. +// Usage: +// Statistics stats; +// stats.addSample(5.0); +// double meanValue = stats.mean(); +class Statistics { +private: + std::vector samples; + +public: + void addSample(double value) { samples.push_back(value); } + + void clear() { samples.clear(); } + + double mean() const { + if (samples.empty()) + return 0.0; + double sum = 0.0; + for (double s : samples) + sum += s; + return sum / samples.size(); + } + + double stddev() const { + if (samples.size() < 2) + return 0.0; + double m = mean(); + double variance = 0.0; + for (double s : samples) { + variance += (s - m) * (s - m); + } + return std::sqrt(variance / (samples.size() - 1)); + } + + double probability(std::function condition) const { + if (samples.empty()) + return 0.0; + int count = 0; + for (double s : samples) { + if (condition(s)) + count++; + } + return static_cast(count) / samples.size(); + } + + size_t size() const { return samples.size(); } + const std::vector &getSamples() const { return samples; } + + bool hasConverged(double tolerance = 0.1) const { + if (samples.size() < 30) + return false; + double m = mean(); + double sd = stddev(); + return sd <= tolerance * std::abs(m); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-02-05/5/main b/exams/AxelRubini/2025-02-05/5/main new file mode 100755 index 0000000000000000000000000000000000000000..3c72e6ab97ea85e93b7a2ca8bc62c3341c1d1828 GIT binary patch literal 43448 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 bywVx$dTkhvX~!&o{oN8{MoNK{r1rl6a>|rj literal 0 HcmV?d00001 diff --git a/exams/AxelRubini/2025-02-05/5/main.cpp b/exams/AxelRubini/2025-02-05/5/main.cpp new file mode 100644 index 0000000..0d3bdd7 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/5/main.cpp @@ -0,0 +1,152 @@ +#include "include/DES.hpp" +#include "include/Decision.hpp" +#include "include/IO.hpp" +#include "include/Random.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace SELib { + +class CustomerProcess : public DiscreteEventProcess { + public: + CustomerProcess( + int pid, + int serverPid, + MessageBus &bus, + RandomGenerator &rng, + const std::vector> &matrix + ) + : pid_(pid), serverPid_(serverPid), bus_(bus), rng_(rng), + matrix_(matrix), currentState_(0), nextTime_(0.0) {} + + void initialize(double startTime) override { nextTime_ = startTime; } + double nextEventTime() const override { return nextTime_; } + + void handleEvent(double currentTime) override { + int nextState = rng_.discrete(matrix_[currentState_]); + if (currentState_ != 0) { + Message m; + m.time = currentTime; + m.sender = pid_; + m.receiver = serverPid_; + m.item = currentState_; + bus_.procToNet(pid_).push(m); + } + currentState_ = nextState; + nextTime_ += 1.0; + } + + private: + int pid_, serverPid_, currentState_; + MessageBus &bus_; + RandomGenerator &rng_; + const std::vector> &matrix_; + double nextTime_; +}; + +class ServerProcess : public DiscreteEventProcess { + public: + ServerProcess(int pid, MessageBus &bus, double T1, double T2) + : pid_(pid), bus_(bus), T1_(T1), T2_(T2), serverFreeAt_(0.0), + nextFinishTime_(std::numeric_limits::infinity()) {} + + void initialize(double startTime) override {} + double nextEventTime() const override { return nextFinishTime_; } + + void handleEvent(double currentTime) override { + if (currentTime >= nextFinishTime_) + nextFinishTime_ = std::numeric_limits::infinity(); + processQueue(currentTime); + } + + void processQueue(double currentTime) { + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + queue_.push(inbox.front()); + inbox.pop(); + } + + if (currentTime >= serverFreeAt_ && !queue_.empty()) { + Message req = queue_.front(); + queue_.pop(); + double st = (req.item == 1) ? T1_ : T2_; + serverFreeAt_ = std::max(currentTime, serverFreeAt_) + st; + nextFinishTime_ = serverFreeAt_; + } + } + + size_t getQueueSize() const { return queue_.size(); } + bool isBusy() const { + return nextFinishTime_ != std::numeric_limits::infinity(); + } + + private: + int pid_; + MessageBus &bus_; + double T1_, T2_, serverFreeAt_, nextFinishTime_; + std::queue queue_; +}; + +} // namespace SELib + +int main() { + SELib::RandomGenerator rng(0); + SELib::ParameterParser parser; + if (!parser.parseFile("parameters.txt")) + return 1; + + double L_threshold = parser.getDouble("L"); + std::vector> matrix(3, std::vector(3)); + const auto &data = parser.getStructuredData(); + for (const auto &line : data) + if (line.size() == 3) + matrix[std::stoi(line[0])][std::stoi(line[1])] = std::stod(line[2]); + std::vector ts; + for (const auto &line : data) + if (line.size() == 1 && line[0] != "L") + ts.push_back(std::stod(line[0])); + double T1 = ts[ts.size() - 2], T2 = ts[ts.size() - 1]; + + auto run_sim = [&](int simulations) { + double totalProb = 0; + for (int i = 0; i < simulations; ++i) { + SELib::MessageBus bus(2); + SELib::DiscreteEventSystem system; + system.emplaceProcess( + 0, 1, bus, rng, matrix + ); + auto &server = + system.emplaceProcess(1, bus, T1, T2); + system.emplaceProcess(bus, rng, 0.001); + + double currentTime = 0.0, H = 10000.0, timeAboveL = 0.0; + system.initialize(0.0); + while (currentTime < H) { + double nextTime = system.nextEventTime(); + if (nextTime > H) + nextTime = H; + int B = (int)server.getQueueSize() + (server.isBusy() ? 1 : 0); + if (B > L_threshold) + timeAboveL += (nextTime - currentTime); + currentTime = nextTime; + if (currentTime >= H) + break; + system.handleEvent(currentTime); + server.processQueue(currentTime); + } + totalProb += (timeAboveL / H); + } + return totalProb / simulations; + }; + + std::ofstream outFile("results.txt"); + outFile << "2025-02-05-Axel-Rubini-2158099" << std::endl; + outFile << "P " << std::fixed << std::setprecision(4) << run_sim(1000) + << std::endl; + return 0; +} diff --git a/exams/AxelRubini/2025-02-05/5/parameters.txt b/exams/AxelRubini/2025-02-05/5/parameters.txt new file mode 100644 index 0000000..ec8fa29 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/5/parameters.txt @@ -0,0 +1,12 @@ +L 893 +0 0 0.40 +0 1 0.30 +0 2 0.30 +1 0 0.8 +1 1 0.1 +1 2 0.1 +2 0 0.8 +2 1 0.1 +2 2 0.1 +2.5 +4.5 diff --git a/exams/AxelRubini/2025-02-05/5/results.txt b/exams/AxelRubini/2025-02-05/5/results.txt new file mode 100644 index 0000000..1b6dfc2 --- /dev/null +++ b/exams/AxelRubini/2025-02-05/5/results.txt @@ -0,0 +1,2 @@ +2025-02-05-Axel-Rubini-2158099 +P 0.375374 diff --git a/exams/AxelRubini/2025-03-21/1/Makefile b/exams/AxelRubini/2025-03-21/1/Makefile new file mode 100644 index 0000000..6910fc5 --- /dev/null +++ b/exams/AxelRubini/2025-03-21/1/Makefile @@ -0,0 +1,10 @@ +CC=g++ +CFLAGS=-std=c++23 -O3 -Wall + +all: main + +main: main.cpp + $(CC) $(CFLAGS) main.cpp -o main + +clean: + rm -f main *.o diff --git a/exams/AxelRubini/2025-03-21/1/include/DES.hpp b/exams/AxelRubini/2025-03-21/1/include/DES.hpp new file mode 100644 index 0000000..1706b8f --- /dev/null +++ b/exams/AxelRubini/2025-03-21/1/include/DES.hpp @@ -0,0 +1,212 @@ + +#pragma once + +#include "Random.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// --------------------------------------------------------------------------- +// Messaggio +// --------------------------------------------------------------------------- +struct Message { + double time{0.0}; + int sender{-1}; + int receiver{-1}; + int item{0}; + double quantity{0.0}; +}; + +// --------------------------------------------------------------------------- +// Bus di messaggi: process -> network, network -> process +// --------------------------------------------------------------------------- +class MessageBus { +public: + explicit MessageBus(std::size_t n = 0) { resize(n); } + + void resize(std::size_t n) { + procToNet_.assign(n, {}); + netToProc_.assign(n, {}); + } + + std::size_t size() const { return procToNet_.size(); } + + std::queue &procToNet(int pid) { return procToNet_.at(pid); } + std::queue &netToProc(int pid) { return netToProc_.at(pid); } + const std::queue &procToNet(int pid) const { + return procToNet_.at(pid); + } + const std::queue &netToProc(int pid) const { + return netToProc_.at(pid); + } + +private: + std::vector> procToNet_; + std::vector> netToProc_; +}; + +// --------------------------------------------------------------------------- +// Processo base a eventi discreti +// --------------------------------------------------------------------------- +class DiscreteEventProcess { +public: + virtual ~DiscreteEventProcess() = default; + virtual void initialize(double startTime) = 0; + virtual double nextEventTime() const = 0; + virtual void handleEvent(double currentTime) = 0; +}; + +// --------------------------------------------------------------------------- +// NetworkRouter: inoltra i messaggi P->N->P +// --------------------------------------------------------------------------- +class NetworkRouter : public DiscreteEventProcess { +public: + NetworkRouter(MessageBus &bus, RandomGenerator &rng, double scanPeriod = 0.1) + : bus_(bus), rng_(rng), scanPeriod_(scanPeriod), time_(0.0), + nextScanTime_(0.0), scanned_(0) {} + + void initialize(double startTime) override { + time_ = startTime; + nextScanTime_ = startTime + scanPeriod_; + + const int n = static_cast(bus_.size()); + if (n <= 0) { + throw std::runtime_error( + "NetworkRouter: no processes configured in MessageBus"); + } + + scanner_.resize(n); + for (int i = 0; i < n; ++i) { + scanner_[i] = i; + } + reshuffleScanner(); + scanned_ = 0; + } + + double nextEventTime() const override { return nextScanTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextScanTime_) + return; + + const int n = static_cast(scanner_.size()); + if (n == 0) + return; + + if (scanned_ >= n) { + scanned_ = 0; + reshuffleScanner(); + } + + const int channel = scanner_[scanned_++]; + auto &fromQueue = bus_.procToNet(channel); + + if (!fromQueue.empty()) { + Message m = fromQueue.front(); + fromQueue.pop(); + + if (m.receiver < 0 || m.receiver >= static_cast(bus_.size())) { + throw std::runtime_error("NetworkRouter: invalid receiver index"); + } + + bus_.netToProc(m.receiver).push(m); + } + + nextScanTime_ = currentTime + scanPeriod_; + } + +private: + void reshuffleScanner() { + auto &eng = rng_.engine(); // serve engine() in RandomGenerator + std::shuffle(scanner_.begin(), scanner_.end(), eng); + } + + MessageBus &bus_; + RandomGenerator &rng_; + + double scanPeriod_; + double time_; + double nextScanTime_; + std::vector scanner_; + int scanned_; +}; + +// --------------------------------------------------------------------------- +// Sistema DES: collezione di processi +// --------------------------------------------------------------------------- +class DiscreteEventSystem { +public: + template + Proc &emplaceProcess(Args &&...args) { + auto ptr = std::make_unique(std::forward(args)...); + Proc &ref = *ptr; + processes_.push_back(std::move(ptr)); + return ref; + } + + void initialize(double startTime = 0.0) { + for (auto &p : processes_) { + p->initialize(startTime); + } + } + + double nextEventTime() const { + if (processes_.empty()) { + return std::numeric_limits::infinity(); + } + + double tMin = std::numeric_limits::infinity(); + for (const auto &p : processes_) { + double t = p->nextEventTime(); + if (t < tMin) + tMin = t; + } + return tMin; + } + + void handleEvent(double currentTime) { + for (auto &p : processes_) { + if (p->nextEventTime() <= currentTime) { + p->handleEvent(currentTime); + } + } + } + +private: + std::vector> processes_; +}; + +// --------------------------------------------------------------------------- +// Simulatore DES +// --------------------------------------------------------------------------- +class DiscreteEventSimulator { +public: + explicit DiscreteEventSimulator(DiscreteEventSystem &system) + : system_(system), currentTime_(0.0) {} + + void run(double horizon) { + system_.initialize(0.0); + currentTime_ = 0.0; + + while (currentTime_ < horizon) { + double nextTime = system_.nextEventTime(); + if (nextTime == std::numeric_limits::infinity()) + break; + currentTime_ = nextTime; + system_.handleEvent(currentTime_); + } + } + + double currentTime() const { return currentTime_; } + +private: + DiscreteEventSystem &system_; + double currentTime_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/1/include/Decision.hpp b/exams/AxelRubini/2025-03-21/1/include/Decision.hpp new file mode 100644 index 0000000..4d37cf0 --- /dev/null +++ b/exams/AxelRubini/2025-03-21/1/include/Decision.hpp @@ -0,0 +1,266 @@ +#pragma once + +#include "Random.hpp" +#include "Stat.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 7. MARKOV CHAIN +// ============================================================================ + +// MarkovChain: Implements a discrete-time Markov chain. +// Purpose: +// - Simulates stochastic processes with defined states and transition +// probabilities. +// - Useful in modeling random systems and computing related probabilities. +// Usage: +// MarkovChain mc(3, rng); +// mc.setTransition(0, 1, 0.5); +// mc.setTransition(0, 2, 0.5); +// mc.setState(0); +// int nextState = mc.step(); +class MarkovChain { +private: + int numStates; + std::vector> transitionMatrix; + int currentState; + RandomGenerator &rng; + +public: + MarkovChain(int states, RandomGenerator &generator) + : numStates(states), currentState(0), rng(generator) { + transitionMatrix.resize(states, std::vector(states, 0.0)); + } + + void setTransition(int from, int to, double probability) { + if (from >= 0 && from < numStates && to >= 0 && to < numStates) { + transitionMatrix[from][to] = probability; + } + } + + void setState(int state) { + if (state >= 0 && state < numStates) { + currentState = state; + } + } + + int getState() const { return currentState; } + + int step() { + currentState = rng.discrete(transitionMatrix[currentState]); + return currentState; + } + + double getTransitionProb(int from, int to) const { + return transitionMatrix[from][to]; + } + + double expectedSojournTime(int state, double timeStep = 1.0) const { + double pii = transitionMatrix[state][state]; + if (pii >= 1.0) + return std::numeric_limits::infinity(); + return timeStep / (1.0 - pii); + } +}; + +// ============================================================================ +// 8. MDP +// ============================================================================ + +// MDP: Implements a Markov Decision Process (MDP) model. +// Purpose: +// - Simulate decision-making processes with states, transitions, and +// associated costs. +// - Calculate the total cost to reach a target state from a starting state. +// Usage: +// - Add transitions between states with associated probabilities and costs +// using `addTransition`. +// - Use `step` to simulate a single transition and calculate its cost. +// - Use `simulate` to estimate the total cost from a start to an end state. +class MDP { +private: + int numStates; + std::map>>> + transitions; + RandomGenerator &rng; + +public: + MDP(int states, RandomGenerator &generator) + : numStates(states), rng(generator) {} + + void addTransition(int from, int to, double probability, double cost) { + transitions[from].push_back({to, {probability, cost}}); + } + + std::pair step(int currentState) { + if (transitions.find(currentState) == transitions.end()) { + return {currentState, 0.0}; + } + + std::vector probs; + for (const auto &t : transitions[currentState]) { + probs.push_back(t.second.first); + } + + int idx = rng.discrete(probs); + int nextState = transitions[currentState][idx].first; + double cost = transitions[currentState][idx].second.second; + + return {nextState, cost}; + } + + double simulate(int startState, int endState) { + int state = startState; + double totalCost = 0.0; + int maxSteps = 100000; + + for (int step = 0; step < maxSteps && state != endState; step++) { + auto [nextState, cost] = this->step(state); + totalCost += cost; + state = nextState; + } + return totalCost; + } +}; + +// ============================================================================ +// 10. MONTE CARLO SIMULATOR +// ============================================================================ + +// MonteCarloSimulator: A utility for running Monte Carlo simulations. +// Purpose: +// - Estimate statistical metrics through repeated random sampling. +// - Provides methods to calculate mean results and probabilities of events. +// Usage: +// MonteCarloSimulator simulator(1000); +// double estimate = simulator.estimate([](int) { return some_random_value; }); +// double probability = simulator.estimateProbability([](int) { return +// some_event_occurs; }); +class MonteCarloSimulator { +private: + Statistics stats; + int numSimulations; + +public: + MonteCarloSimulator(int simulations = 1000) : numSimulations(simulations) {} + + template double estimate(SimFunc simulation) { + stats.clear(); + for (int i = 0; i < numSimulations; i++) { + double result = simulation(i); + stats.addSample(result); + } + return stats.mean(); + } + + template double estimateProbability(SimFunc simulation) { + int successes = 0; + for (int i = 0; i < numSimulations; i++) { + if (simulation(i)) + successes++; + } + return static_cast(successes) / numSimulations; + } + + const Statistics &getStatistics() const { return stats; } +}; + +// ============================================================================ +// 13. OPTIMIZATION UTILITIES +// ============================================================================ + +// Optimizer: Utility class offering optimization techniques such as random +// search and grid search. Purpose: +// - Explore parameter spaces to maximize or minimize an objective function. +// - Supports both continuous and discrete search spaces. +// Usage: +// - Use `randomSearch` for stochastic optimization over a defined range. +// - Use `gridSearch` for systematic exploration in evenly spaced steps. +// Example: +// auto [bestParam, bestValue] = Optimizer::randomSearch(objectiveFunction, +// 0.0, 10.0, 100, rng); auto [bestInt, bestIntValue] = +// Optimizer::randomSearchInt(objectiveFunction, 0, 10, 50, rng); +class Optimizer { +public: + template + static std::pair + randomSearch(ObjFunc objective, double minValue, double maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + for (int i = 0; i < budget; i++) { + double param = rng.uniform(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair + randomSearchInt(ObjFunc objective, int minValue, int maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + int bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + if (budget <= 0) { + throw std::invalid_argument("randomSearchInt: budget must be positive."); + } + + if (minValue >= maxValue) { + throw std::invalid_argument( + "randomSearchInt: minValue must be less than maxValue."); + } + + for (int i = 0; i < budget; i++) { + int param = rng.uniformInt(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair gridSearch(ObjFunc objective, + double minValue, double maxValue, + int steps, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + double stepSize = (maxValue - minValue) / steps; + + if (steps <= 0) { + throw std::invalid_argument("gridSearch: steps must be positive."); + } + + for (int i = 0; i <= steps; i++) { + double param = minValue + i * stepSize; + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/1/include/Dynamics.hpp b/exams/AxelRubini/2025-03-21/1/include/Dynamics.hpp new file mode 100644 index 0000000..044176a --- /dev/null +++ b/exams/AxelRubini/2025-03-21/1/include/Dynamics.hpp @@ -0,0 +1,214 @@ +#pragma once + +#include +#include +namespace SELib { +// ============================================================================ +// 3. ODE SOLVER (Runge-Kutta 4th order) +// ============================================================================ + +using StateVector = std::vector; +using DerivativeFunction = std::function; + +// ODESolver: Implements numerical methods for solving Ordinary Differential +// Equations (ODEs). Purpose: +// - Provides tools for simulating dynamic systems modeled by ODEs. +// - Includes methods such as Runge-Kutta 4th order and Euler's method for +// integration. +// Usage: +// - Pass a derivative function that defines the ODE system. +// - Use `rk4Step` for higher accuracy or `eulerStep` for simpler systems. +// Example: +// StateVector nextState = ODESolver::rk4Step(0.0, currentState, controlInputs, +// dt, dynamicsFunction); +class ODESolver { +public: + static StateVector rk4Step(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector k1 = f(t, x, u); + StateVector x2 = addScaled(x, k1, dt / 2.0); + StateVector k2 = f(t + dt / 2.0, x2, u); + StateVector x3 = addScaled(x, k2, dt / 2.0); + StateVector k3 = f(t + dt / 2.0, x3, u); + StateVector x4 = addScaled(x, k3, dt); + StateVector k4 = f(t + dt, x4, u); + + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += (dt / 6.0) * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]); + } + return xNew; + } + + static StateVector eulerStep(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector dx = f(t, x, u); + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += dx[i] * dt; + } + return xNew; + } + +private: + static StateVector addScaled(const StateVector &x, const StateVector &dx, + double scale) { + StateVector result = x; + for (size_t i = 0; i < x.size(); i++) { + result[i] += dx[i] * scale; + } + return result; + } +}; + +// ============================================================================ +// 4. PID CONTROLLER +// ============================================================================ + +// PIDController: Implements a Proportional-Integral-Derivative (PID) +// controller. Purpose: +// - Automatically adjusts a control signal to minimize the difference between +// a desired setpoint and a measured value. +// - Used in control systems to regulate processes like temperature, speed, or +// position. +// Usage: +// - Create an instance with proportional (Kp), integral (Ki), and derivative +// (Kd) gains, along with a time step. +// - Use `compute` to calculate the control signal based on the setpoint and +// measurement. +// Example: +// PIDController pid(1.0, 0.1, 0.01, 0.1); +// double controlSignal = pid.compute(desiredValue, actualValue); +class PIDController { +private: + double kp, ki, kd; + double integral; + double prevError; + double dt; + +public: + PIDController(double Kp, double Ki, double Kd, double timeStep) + : kp(Kp), ki(Ki), kd(Kd), integral(0), prevError(0), dt(timeStep) {} + + double compute(double setpoint, double measurement) { + double error = setpoint - measurement; + integral += error * dt; + double derivative = (error - prevError) / dt; + prevError = error; + return kp * error + ki * integral + kd * derivative; + } + + void reset() { + integral = 0; + prevError = 0; + } + + void setGains(double Kp, double Ki, double Kd) { + kp = Kp; + ki = Ki; + kd = Kd; + } +}; + +// ============================================================================ +// 5. CONTINUOUS TIME SIMULATION +// ============================================================================ + +// ContinuousTimeSimulation: A base class for simulating continuous-time dynamic +// systems. Purpose: +// - Provides a framework for simulating systems characterized by continuous +// dynamics over time. +// - Integrates system dynamics using a Runge-Kutta 4th order solver. +// Usage: +// - Derive from this class and implement the `dynamics`, `computeControl`, +// `initialize`, and `finalize` methods. +// - Customize the `onStep` method for per-step side effects, if required. +// Example: +// class MySimulation : public ContinuousTimeSimulation { +// // Implement the required virtual methods. +// }; +// MySimulation sim(dt, horizon, initialState); +// sim.run(); +class ContinuousTimeSimulation { +protected: + double currentTime; + double timeStep; + double horizon; + StateVector state; + StateVector control; + +public: + ContinuousTimeSimulation(double dt, double H, const StateVector &initialState) + : currentTime(0.0), timeStep(dt), horizon(H), state(initialState) { + control.resize(initialState.size(), 0.0); + } + + virtual ~ContinuousTimeSimulation() = default; + + virtual StateVector dynamics(double t, const StateVector &x, + const StateVector &u) = 0; + virtual StateVector computeControl(double t, const StateVector &x) = 0; + virtual void initialize() = 0; + virtual void onStep() {} + virtual void finalize() = 0; + + void run() { + initialize(); + while (currentTime < horizon) { + control = computeControl(currentTime, state); + state = ODESolver::rk4Step( + currentTime, state, control, timeStep, + [this](double t, const StateVector &x, const StateVector &u) { + return this->dynamics(t, x, u); + }); + currentTime += timeStep; + onStep(); + } + finalize(); + } + + double getCurrentTime() const { return currentTime; } + const StateVector &getState() const { return state; } + const StateVector &getControl() const { return control; } +}; + +// ============================================================================ +// 6. SAMPLE AND HOLD +// ============================================================================ + +// SampleAndHold: A utility to store a value and update it periodically. +// Purpose: +// - Maintain a value that gets updated periodically based on a time interval. +// - Useful for sampling data and ensuring updates happen at fixed intervals. +// Usage: +// SampleAndHold sampler(5.0, 0); +// if (sampler.shouldUpdate(currentTime)) { +// sampler.update(currentTime, newValue); +// } +// int currentValue = sampler.getValue(); +template class SampleAndHold { +private: + T value; + double lastUpdateTime; + double samplePeriod; + +public: + SampleAndHold(double period, const T &initialValue = T()) + : value(initialValue), lastUpdateTime(0), samplePeriod(period) {} + + bool shouldUpdate(double currentTime) const { + return (currentTime - lastUpdateTime) >= samplePeriod; + } + + void update(double currentTime, const T &newValue) { + value = newValue; + lastUpdateTime = currentTime; + } + + const T &getValue() const { return value; } + void reset() { lastUpdateTime = 0; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/1/include/Geometry.hpp b/exams/AxelRubini/2025-03-21/1/include/Geometry.hpp new file mode 100644 index 0000000..525a91d --- /dev/null +++ b/exams/AxelRubini/2025-03-21/1/include/Geometry.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 14. GEOMETRIC UTILITIES +// ============================================================================ + +// Point2D: Represents a point or vector in 2D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point2D p1(1.0, 2.0), p2(2.0, 3.0); +// double dist = p1.distanceTo(p2); +// Point2D sum = p1 + p2; +struct Point2D { + double x, y; + + Point2D(double x_ = 0, double y_ = 0) : x(x_), y(y_) {} + + double distanceTo(const Point2D &other) const { + double dx = x - other.x; + double dy = y - other.y; + return std::sqrt(dx * dx + dy * dy); + } + + Point2D operator+(const Point2D &other) const { + return Point2D(x + other.x, y + other.y); + } + + Point2D operator-(const Point2D &other) const { + return Point2D(x - other.x, y - other.y); + } +}; + +// Point3D: Represents a point or vector in 3D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point3D p1(1.0, 2.0, 3.0), p2(2.0, 3.0, 4.0); +// double dist = p1.distanceTo(p2); +// Point3D sum = p1 + p2; +struct Point3D { + double x, y, z; + + Point3D(double x_ = 0, double y_ = 0, double z_ = 0) : x(x_), y(y_), z(z_) {} + + double distanceTo(const Point3D &other) const { + double dx = x - other.x; + double dy = y - other.y; + double dz = z - other.z; + return std::sqrt(dx * dx + dy * dy + dz * dz); + } + + Point3D operator+(const Point3D &other) const { + return Point3D(x + other.x, y + other.y, z + other.z); + } + + Point3D operator-(const Point3D &other) const { + return Point3D(x - other.x, y - other.y, z - other.z); + } +}; + +// ============================================================================ +// 19. COLLISION DETECTOR +// ============================================================================ + +// CollisionDetector: A utility class for detecting collisions between points +// in space. Purpose: +// - Counts the number of collisions within a given distance threshold. +// - Retrieves pairs of colliding points. +// Usage: +// std::vector points = {Point2D(0, 0), Point2D(1, 1)}; +// int numCollisions = CollisionDetector::countCollisions(points, 1.5); +// auto collidingPairs = CollisionDetector::getCollidingPairs(points, 1.5); +class CollisionDetector { +public: + template + static int countCollisions(const std::vector &positions, + double threshold) { + int collisions = 0; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + collisions++; + } + } + } + return collisions; + } + + template + static std::vector> + getCollidingPairs(const std::vector &positions, double threshold) { + std::vector> pairs; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + pairs.push_back({i, j}); + } + } + } + return pairs; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/1/include/IO.hpp b/exams/AxelRubini/2025-03-21/1/include/IO.hpp new file mode 100644 index 0000000..1a79cc6 --- /dev/null +++ b/exams/AxelRubini/2025-03-21/1/include/IO.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 11. PARAMETER PARSER +// ============================================================================ + +// ParameterParser: A utility to parse and retrieve parameters from +// configuration files. Purpose: +// - Reads a file and stores key-value pairs for parameters. +// - Allows retrieval of parameters as integers, doubles, or strings. +// - Provides structured data for advanced parsing. +// Usage: +// ParameterParser parser; +// if (parser.parseFile("config.txt")) { +// int param = parser.getInt("example_key", 42); +// } +class ParameterParser { +private: + std::map params; + std::vector> structuredData; + +public: + bool parseFile(const std::string &filename) { + std::ifstream file(filename); + if (!file.is_open()) + return false; + + std::string line; + while (std::getline(file, line)) { + if (line.empty()) + continue; + + std::istringstream iss(line); + std::string key; + iss >> key; + + std::string value; + std::getline(iss, value); + if (!value.empty() && value[0] == ' ') { + value = value.substr(1); + } + + params[key] = value; + + std::vector tokens; + std::istringstream tokenStream(line); + std::string token; + while (tokenStream >> token) { + tokens.push_back(token); + } + structuredData.push_back(tokens); + } + return true; + } + + int getInt(const std::string &key, int defaultValue = 0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stoi(it->second); + } + return defaultValue; + } + + double getDouble(const std::string &key, double defaultValue = 0.0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stod(it->second); + } + return defaultValue; + } + + std::string getString(const std::string &key, + const std::string &defaultValue = "") const { + auto it = params.find(key); + if (it != params.end()) { + return it->second; + } + return defaultValue; + } + + const std::vector> &getStructuredData() const { + return structuredData; + } + + bool hasKey(const std::string &key) const { + return params.find(key) != params.end(); + } +}; + +// ============================================================================ +// 12. OUTPUT WRITER +// ============================================================================ + +// OutputWriter: A utility class for writing data to output files. +// Purpose: +// - Simplifies the process of writing data to files. +// - Supports writing lines, key-value pairs, and managing file headers. +// Usage: +// OutputWriter writer; +// if (writer.open("output.txt", "Header Info")) { +// writer.writeLine("Example Line"); +// writer.writeKeyValue("Key", 42.0); +// writer.close(); +// } +class OutputWriter { +private: + std::ofstream file; + +public: + bool open(const std::string &filename, const std::string &header) { + file.open(filename); + if (!file.is_open()) + return false; + file << header << std::endl; + return true; + } + + template void writeLine(const T &value) { + file << value << std::endl; + } + + template + void writeLine(const T &first, const Args &...args) { + file << first << " "; + writeLine(args...); + } + + void writeKeyValue(const std::string &key, double value, int precision = 2) { + file << key << " " << std::fixed << std::setprecision(precision) << value + << std::endl; + } + + void close() { + if (file.is_open()) { + file.close(); + } + } + + ~OutputWriter() { close(); } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/1/include/Inventory.hpp b/exams/AxelRubini/2025-03-21/1/include/Inventory.hpp new file mode 100644 index 0000000..c82d776 --- /dev/null +++ b/exams/AxelRubini/2025-03-21/1/include/Inventory.hpp @@ -0,0 +1,423 @@ +#pragma once + +#include "DES.hpp" +#include "Random.hpp" +#include +#include + +namespace SELib { +// ============================================================================ +// 16. CUSTOMER MODEL +// ============================================================================ + +// Customer: Represents a customer model in a simulation. +// Purpose: +// - Simulates customer requests over time. +// - Generates random requests for products at specific intervals. +// Usage: +// Customer customer(5.0, 10.0, 3, rng); +// int productId = customer.step(0.1); // Simulates a time step of 0.1 seconds. +class Customer { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer(double A, double B, int N, RandomGenerator &generator) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator) { + nextRequestTime = rng.uniform(A, B); + } + + int step(double dt) { + nextRequestTime -= dt; + if (nextRequestTime <= 0) { + nextRequestTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } +}; +class Customer_Event_Driven { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer_Event_Driven(double A, double B, int N, RandomGenerator &generator, + double nextRequestTime) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator), + nextRequestTime(nextRequestTime) {} + + int step_event_driven(double *current_time) { + if (*current_time >= nextRequestTime) { + nextRequestTime = *current_time + rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + double getNextRequestTime() { return nextRequestTime; }; +}; + +// ============================================================================ +// 17. SERVER MODEL +// ============================================================================ + +// Server: Represents a server in a simulated environment. +// Purpose: +// - Handles product requests and manages inventory. +// - Simulates server busy time during request processing. +// Usage: +// Server server(numProducts, maxInventory, processingTimeNormal, +// processingTimeSupplier, rng); int result = +// server.processRequest(productId, needsSupply); +// server.addInventory(productId, quantityAdded); +class Server { +private: + std::vector inventory; + int busyTime; + int processingTimeNormal; + int processingTimeSupplier; + RandomGenerator *rng; + +public: + Server(int numProducts, int K, int F, int G, RandomGenerator &rng) + : inventory(numProducts + 1, 0), busyTime(0), processingTimeNormal(F), + processingTimeSupplier(G), rng(&rng) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + + bool isBusy() const { return busyTime > 0; } + + void decrementBusyTime() { + if (busyTime > 0) + busyTime--; + } + + int processRequest(int productId, bool needsSupply) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return productId; + } else { + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return -productId; + } + } + + int processRequest2(int productId, double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = processingTimeNormal; + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + busyTime = processingTimeSupplier; + return -productId; + } + } + + void addInventory(int productId, int quantity) { + inventory[productId] += quantity; + } + + int getInventory(int productId) const { return inventory[productId]; } +}; +// ============================================================================ +// 17.5 SERVER-EVENT-DRIVEN +// ============================================================================ +class Server_Event_Driven { +private: + std::vector inventory; + RandomGenerator *rng; + double next_step_time; + double min_interval; + double max_interval; + +public: + Server_Event_Driven(int numProducts, int K, double min_interval, + double max_interval, RandomGenerator &rng, + double nextStepTime) + : inventory(numProducts + 1, 0), rng(&rng), min_interval(min_interval), + max_interval(max_interval), next_step_time(nextStepTime) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + int processRequestEventDriven(double current_time, int productId, + double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (current_time >= next_step_time) { + if (inventory[productId] > 0) { + inventory[productId]--; + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + + return -productId; + } + } else { + return 0; + } + } + double getNextStepTime() { return next_step_time; } +}; + +// ============================================================================ +// 18. SUPPLIER MODEL +// ============================================================================ + +// Supplier: Represents a supplier in a simulation. +// Purpose: +// - Simulates the supply of products at specific intervals. +// - Generates random supply requests periodically based on the defined +// intervals. +// Usage: +// Supplier supplier(minInterval, maxInterval, numProducts, rng); +// int suppliedProduct = supplier.step(timeStep); +// supplier.reset(); +class Supplier { +private: + double nextSupplyTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Supplier(double V, double Q, int N, RandomGenerator &generator) + : minInterval(V), maxInterval(Q), numProducts(N), rng(generator) { + nextSupplyTime = rng.uniform(V, Q); + } + + int step(double dt) { + nextSupplyTime -= dt; + if (nextSupplyTime <= 0) { + nextSupplyTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + void reset() { nextSupplyTime = rng.uniform(minInterval, maxInterval); } +}; + +// Server di inventario con messaggi (customer/server network-style) +class InventoryServerProcess : public DiscreteEventProcess { +public: + InventoryServerProcess(int pid, int numItems, int initialStockPerItem, + double serviceTime, MessageBus &bus, + RandomGenerator &rng) + : pid_(pid), serviceTime_(serviceTime), bus_(bus), rng_(rng), + nextEventTime_(0.0), + storage_(static_cast(numItems), initialStockPerItem), + totalRequests_(0), missedRequests_(0) {} + + void initialize(double startTime) override { + nextEventTime_ = startTime + serviceTime_; + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // processa TUTTI i messaggi arrivati a questo server + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + totalRequests_++; + + // sicurezza sugli indici prodotto + if (m.item < 0 || static_cast(m.item) >= storage_.size()) { + continue; // scarta richieste invalide + } + + double servedQty = 0.0; + int &stock = storage_[static_cast(m.item)]; + + if (stock >= static_cast(m.quantity)) { + // richiesta soddisfatta + stock -= static_cast(m.quantity); + servedQty = m.quantity; + } else { + // non abbastanza stock + servedQty = static_cast(stock); + missedRequests_++; + stock = 0; + } + + // risposta al cliente + Message reply; + reply.time = currentTime; + reply.sender = pid_; + reply.receiver = m.sender; // rimanda al mittente + reply.item = m.item; + reply.quantity = servedQty; + + bus_.procToNet(pid_).push(reply); + + // eventuale logica di refill: esempio semplice + if (stock == 0) { + int refill = rng_.uniformInt(1, 10); // batch di rifornimento random + stock += refill; + } + } + + nextEventTime_ = currentTime + serviceTime_; + } + + const std::vector &storage() const { return storage_; } + + int totalRequests() const { return totalRequests_; } + int missedRequests() const { return missedRequests_; } + +private: + int pid_; + double serviceTime_; + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + std::vector storage_; + + int totalRequests_; + int missedRequests_; +}; + +// ============================================================================ +// 19. INVENTORY CUSTOMER PROCESS (DES-compatible) +// ============================================================================ +// +// InventoryCustomerProcess: +// - Processo a eventi discreti che rappresenta un cliente. +// - Invia richieste al server tramite MessageBus (procToNet -> NetworkRouter). +// - Legge le risposte dal canale netToProc. +// - Statistiche: quante richieste ha mandato e quante risposte ha ricevuto. +// +class InventoryCustomerProcess : public DiscreteEventProcess { +public: + // pid : id di questo processo (indice nei canali del MessageBus) + // serverPid : pid del server a cui mandare le richieste + // numItems : numero di prodotti gestiti (item in [0, numItems-1]) + // minInterval/maxInterval : intervallo di tempo tra una richiesta e la + // successiva + InventoryCustomerProcess(int pid, int serverPid, int numItems, + double minInterval, double maxInterval, + MessageBus &bus, RandomGenerator &rng) + : pid_(pid), serverPid_(serverPid), numItems_(numItems), + minInterval_(minInterval), maxInterval_(maxInterval), bus_(bus), + rng_(rng), nextEventTime_(0.0), issuedRequests_(0), receivedReplies_(0), + totalRequestedQty_(0.0), totalServedQty_(0.0) {} + + // inizializza il prossimo evento (prima richiesta) + void initialize(double startTime) override { + nextEventTime_ = startTime + rng_.uniform(minInterval_, maxInterval_); + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // 1) genera una nuova richiesta al server + const int item = rng_.uniformInt( + 0, numItems_ - 1); // coerente con InventoryServerProcess + const int qty = rng_.uniformInt(1, 3); // quantità richiesta (1..3) + + Message req; + req.time = currentTime; + req.sender = pid_; + req.receiver = serverPid_; + req.item = item; + req.quantity = static_cast(qty); + + bus_.procToNet(pid_).push(req); + issuedRequests_++; + totalRequestedQty_ += req.quantity; + + // 2) processa tutte le risposte arrivate su questo cliente + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + // opzionale: possiamo filtrare solo messaggi dal serverPid_ + // if (m.sender != serverPid_) continue; + + receivedReplies_++; + totalServedQty_ += m.quantity; + } + + // 3) pianifica la prossima richiesta + nextEventTime_ = currentTime + rng_.uniform(minInterval_, maxInterval_); + } + + // --- metriche accessibili dall’esterno --- + + int pid() const { return pid_; } + int serverPid() const { return serverPid_; } + + int issuedRequests() const { return issuedRequests_; } + int receivedReplies() const { return receivedReplies_; } + + double totalRequestedQty() const { return totalRequestedQty_; } + double totalServedQty() const { return totalServedQty_; } + + // fill-rate lato customer + double serviceLevel() const { + if (totalRequestedQty_ <= 0.0) + return 1.0; + return totalServedQty_ / totalRequestedQty_; + } + +private: + int pid_; + int serverPid_; + int numItems_; + double minInterval_; + double maxInterval_; + + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + + int issuedRequests_; + int receivedReplies_; + double totalRequestedQty_; + double totalServedQty_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/1/include/Queue.hpp b/exams/AxelRubini/2025-03-21/1/include/Queue.hpp new file mode 100644 index 0000000..0c12ce8 --- /dev/null +++ b/exams/AxelRubini/2025-03-21/1/include/Queue.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 9. FIFO QUEUE +// ============================================================================ + +// FIFOQueue: Implements a first-in-first-out (FIFO) queue with a fixed maximum +// size. Purpose: +// - Enqueue and dequeue items in FIFO order. +// - Track statistics such as total items enqueued and dequeued. +// - Support dynamic size constraints. +// Usage: +// FIFOQueue queue(10); +// queue.enqueue(5); +// int item; +// if (queue.dequeue(item)) { +// // Process item. +// } +template class FIFOQueue { +private: + std::queue queue; + size_t maxSize; + size_t totalEnqueued; + size_t totalDequeued; + +public: + FIFOQueue(size_t max = SIZE_MAX) + : maxSize(max), totalEnqueued(0), totalDequeued(0) {} + + bool enqueue(const T &item) { + if (queue.size() >= maxSize) + return false; + queue.push(item); + totalEnqueued++; + return true; + } + + bool dequeue(T &item) { + if (queue.empty()) + return false; + item = queue.front(); + queue.pop(); + totalDequeued++; + return true; + } + + bool isEmpty() const { return queue.empty(); } + bool isFull() const { return queue.size() >= maxSize; } + size_t size() const { return queue.size(); } + size_t getTotalEnqueued() const { return totalEnqueued; } + size_t getTotalDequeued() const { return totalDequeued; } + std::queue getQueue() const { return queue; } + T getFirst() const { + if (queue.empty()) + throw std::runtime_error("Queue is empty"); + return queue.front(); + } + + void clear() { + while (!queue.empty()) + queue.pop(); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/1/include/Random.hpp b/exams/AxelRubini/2025-03-21/1/include/Random.hpp new file mode 100644 index 0000000..52356f9 --- /dev/null +++ b/exams/AxelRubini/2025-03-21/1/include/Random.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include // std::isfinite +#include // std::accumulate +#include +#include +#include + +namespace SELib { + +class RandomGenerator { +private: + std::mt19937 gen; + +public: + explicit RandomGenerator(unsigned seed = std::random_device{}()) + : gen(seed) {} + + double uniform(double min, double max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_real_distribution dis(lo, high); + return dis(gen); + } + + int uniformInt(int min, int max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_int_distribution dis(lo, high); + return dis(gen); + } + + double gaussian(double mean, double stddev) { + if (stddev < 0.0) { + throw std::invalid_argument("gaussian: stddev must be >= 0"); + } + std::normal_distribution dis(mean, stddev); + return dis(gen); + } + + bool bernoulli(double p) { + if (p < 0.0 || p > 1.0 || !std::isfinite(p)) { + throw std::invalid_argument("bernoulli: p must be in [0,1]"); + } + std::bernoulli_distribution dis(p); + return dis(gen); + } + + int discrete(const std::vector &probabilities) { + if (probabilities.empty()) { + throw std::invalid_argument("discrete: empty probability vector"); + } + + for (double w : probabilities) { + if (w < 0.0 || !std::isfinite(w)) { + throw std::invalid_argument( + "discrete: weights must be non-negative and finite"); + } + } + + double sum = + std::accumulate(probabilities.begin(), probabilities.end(), 0.0); + if (sum <= 0.0) { + throw std::invalid_argument("discrete: sum of weights must be > 0"); + } + + std::discrete_distribution dis(probabilities.begin(), + probabilities.end()); + return dis(gen); + } + + void setSeed(unsigned seed) { gen.seed(seed); } + + // (opzionale) accesso al motore se ti serve per altre distribuzioni + std::mt19937 &engine() { return gen; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/1/include/Stat.hpp b/exams/AxelRubini/2025-03-21/1/include/Stat.hpp new file mode 100644 index 0000000..27b4656 --- /dev/null +++ b/exams/AxelRubini/2025-03-21/1/include/Stat.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 2. STATISTICS +// ============================================================================ + +// Statistics: A utility class for managing and analyzing numerical data +// samples. Purpose: +// - Compute statistical metrics such as mean and standard deviation. +// - Estimate probabilities of custom conditions. +// - Check convergence of data over iterations. +// Usage: +// Statistics stats; +// stats.addSample(5.0); +// double meanValue = stats.mean(); +class Statistics { +private: + std::vector samples; + +public: + void addSample(double value) { samples.push_back(value); } + + void clear() { samples.clear(); } + + double mean() const { + if (samples.empty()) + return 0.0; + double sum = 0.0; + for (double s : samples) + sum += s; + return sum / samples.size(); + } + + double stddev() const { + if (samples.size() < 2) + return 0.0; + double m = mean(); + double variance = 0.0; + for (double s : samples) { + variance += (s - m) * (s - m); + } + return std::sqrt(variance / (samples.size() - 1)); + } + + double probability(std::function condition) const { + if (samples.empty()) + return 0.0; + int count = 0; + for (double s : samples) { + if (condition(s)) + count++; + } + return static_cast(count) / samples.size(); + } + + size_t size() const { return samples.size(); } + const std::vector &getSamples() const { return samples; } + + bool hasConverged(double tolerance = 0.1) const { + if (samples.size() < 30) + return false; + double m = mean(); + double sd = stddev(); + return sd <= tolerance * std::abs(m); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/1/main b/exams/AxelRubini/2025-03-21/1/main new file mode 100755 index 0000000000000000000000000000000000000000..3137e490f60ff45d19100d86d75d8211851fa9e3 GIT binary patch literal 43512 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 HDk=THr{Ph~ literal 0 HcmV?d00001 diff --git a/exams/AxelRubini/2025-03-21/1/main.cpp b/exams/AxelRubini/2025-03-21/1/main.cpp new file mode 100644 index 0000000..6e3469e --- /dev/null +++ b/exams/AxelRubini/2025-03-21/1/main.cpp @@ -0,0 +1,98 @@ +#include "include/Geometry.hpp" +#include "include/IO.hpp" +#include "include/Random.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +struct Drone { + int id; + Point3D pos; + bool operative; +}; + +} // namespace SELib + +int main() { + SELib::RandomGenerator rng(0); + SELib::ParameterParser parser; + if (!parser.parseFile("parameters.txt")) + return 1; + + int H = parser.getInt("H"); + int N = parser.getInt("N"); + double alpha = parser.getDouble("A"); + double R = parser.getDouble("R"); + + double X1 = 0, X2 = 0, Y1 = 0, Y2 = 0, Z1 = 0, Z2 = 0; + const auto &data = parser.getStructuredData(); + for (const auto &line : data) { + if (line.size() == 6) { + X1 = std::stod(line[0]); + X2 = std::stod(line[1]); + Y1 = std::stod(line[2]); + Y2 = std::stod(line[3]); + Z1 = std::stod(line[4]); + Z2 = std::stod(line[5]); + } + } + + std::vector drones(N); + for (int i = 0; i < N; ++i) { + drones[i].id = i; + drones[i].pos = { + rng.uniform(X1, X2), rng.uniform(Y1, Y2), rng.uniform(Z1, Z2) + }; + drones[i].operative = true; + } + + for (int t = 0; t < H; ++t) { + // 1. Check collisions (before movement? usually yes) + std::vector collisionThisStep(N, false); + for (int i = 0; i < N; ++i) { + if (!drones[i].operative) + continue; + for (int j = i + 1; j < N; ++j) { + if (!drones[j].operative) + continue; + if (drones[i].pos.distanceTo(drones[j].pos) <= R) { + collisionThisStep[i] = true; + collisionThisStep[j] = true; + } + } + } + for (int i = 0; i < N; ++i) { + if (collisionThisStep[i]) + drones[i].operative = false; + } + + // 2. Move drones + for (int i = 0; i < N; ++i) { + if (!drones[i].operative) + continue; + double vx = rng.uniform(-alpha, alpha); + double vy = rng.uniform(-alpha, alpha); + double vz = rng.uniform(-alpha, alpha); + drones[i].pos.x = std::min(X2, std::max(X1, drones[i].pos.x + vx)); + drones[i].pos.y = std::min(Y2, std::max(Y1, drones[i].pos.y + vy)); + drones[i].pos.z = std::min(Z2, std::max(Z1, drones[i].pos.z + vz)); + } + } + + int Q = 0; + for (int i = 0; i < N; ++i) + if (drones[i].operative) + Q++; + + std::ofstream outFile("results.txt"); + outFile << "2025-03-21-Axel-Rubini-2158099" << std::endl; + outFile << "Q " << Q << std::endl; + outFile << "N " << N << std::endl; + outFile << "P " << (double)Q / N << std::endl; + return 0; +} diff --git a/exams/AxelRubini/2025-03-21/1/parameters.txt b/exams/AxelRubini/2025-03-21/1/parameters.txt new file mode 100644 index 0000000..22aa83b --- /dev/null +++ b/exams/AxelRubini/2025-03-21/1/parameters.txt @@ -0,0 +1,5 @@ +H 100000 +N 1000 +A 0.2 +R 0.1 +-10 10 -10 10 -10 10 diff --git a/exams/AxelRubini/2025-03-21/1/results.txt b/exams/AxelRubini/2025-03-21/1/results.txt new file mode 100644 index 0000000..6b23883 --- /dev/null +++ b/exams/AxelRubini/2025-03-21/1/results.txt @@ -0,0 +1,4 @@ +2025-03-21-Axel-Rubini-2158099 +Q 22 +N 1000 +P 0.022 diff --git a/exams/AxelRubini/2025-03-21/2/Makefile b/exams/AxelRubini/2025-03-21/2/Makefile new file mode 100644 index 0000000..6910fc5 --- /dev/null +++ b/exams/AxelRubini/2025-03-21/2/Makefile @@ -0,0 +1,10 @@ +CC=g++ +CFLAGS=-std=c++23 -O3 -Wall + +all: main + +main: main.cpp + $(CC) $(CFLAGS) main.cpp -o main + +clean: + rm -f main *.o diff --git a/exams/AxelRubini/2025-03-21/2/include/DES.hpp b/exams/AxelRubini/2025-03-21/2/include/DES.hpp new file mode 100644 index 0000000..1706b8f --- /dev/null +++ b/exams/AxelRubini/2025-03-21/2/include/DES.hpp @@ -0,0 +1,212 @@ + +#pragma once + +#include "Random.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// --------------------------------------------------------------------------- +// Messaggio +// --------------------------------------------------------------------------- +struct Message { + double time{0.0}; + int sender{-1}; + int receiver{-1}; + int item{0}; + double quantity{0.0}; +}; + +// --------------------------------------------------------------------------- +// Bus di messaggi: process -> network, network -> process +// --------------------------------------------------------------------------- +class MessageBus { +public: + explicit MessageBus(std::size_t n = 0) { resize(n); } + + void resize(std::size_t n) { + procToNet_.assign(n, {}); + netToProc_.assign(n, {}); + } + + std::size_t size() const { return procToNet_.size(); } + + std::queue &procToNet(int pid) { return procToNet_.at(pid); } + std::queue &netToProc(int pid) { return netToProc_.at(pid); } + const std::queue &procToNet(int pid) const { + return procToNet_.at(pid); + } + const std::queue &netToProc(int pid) const { + return netToProc_.at(pid); + } + +private: + std::vector> procToNet_; + std::vector> netToProc_; +}; + +// --------------------------------------------------------------------------- +// Processo base a eventi discreti +// --------------------------------------------------------------------------- +class DiscreteEventProcess { +public: + virtual ~DiscreteEventProcess() = default; + virtual void initialize(double startTime) = 0; + virtual double nextEventTime() const = 0; + virtual void handleEvent(double currentTime) = 0; +}; + +// --------------------------------------------------------------------------- +// NetworkRouter: inoltra i messaggi P->N->P +// --------------------------------------------------------------------------- +class NetworkRouter : public DiscreteEventProcess { +public: + NetworkRouter(MessageBus &bus, RandomGenerator &rng, double scanPeriod = 0.1) + : bus_(bus), rng_(rng), scanPeriod_(scanPeriod), time_(0.0), + nextScanTime_(0.0), scanned_(0) {} + + void initialize(double startTime) override { + time_ = startTime; + nextScanTime_ = startTime + scanPeriod_; + + const int n = static_cast(bus_.size()); + if (n <= 0) { + throw std::runtime_error( + "NetworkRouter: no processes configured in MessageBus"); + } + + scanner_.resize(n); + for (int i = 0; i < n; ++i) { + scanner_[i] = i; + } + reshuffleScanner(); + scanned_ = 0; + } + + double nextEventTime() const override { return nextScanTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextScanTime_) + return; + + const int n = static_cast(scanner_.size()); + if (n == 0) + return; + + if (scanned_ >= n) { + scanned_ = 0; + reshuffleScanner(); + } + + const int channel = scanner_[scanned_++]; + auto &fromQueue = bus_.procToNet(channel); + + if (!fromQueue.empty()) { + Message m = fromQueue.front(); + fromQueue.pop(); + + if (m.receiver < 0 || m.receiver >= static_cast(bus_.size())) { + throw std::runtime_error("NetworkRouter: invalid receiver index"); + } + + bus_.netToProc(m.receiver).push(m); + } + + nextScanTime_ = currentTime + scanPeriod_; + } + +private: + void reshuffleScanner() { + auto &eng = rng_.engine(); // serve engine() in RandomGenerator + std::shuffle(scanner_.begin(), scanner_.end(), eng); + } + + MessageBus &bus_; + RandomGenerator &rng_; + + double scanPeriod_; + double time_; + double nextScanTime_; + std::vector scanner_; + int scanned_; +}; + +// --------------------------------------------------------------------------- +// Sistema DES: collezione di processi +// --------------------------------------------------------------------------- +class DiscreteEventSystem { +public: + template + Proc &emplaceProcess(Args &&...args) { + auto ptr = std::make_unique(std::forward(args)...); + Proc &ref = *ptr; + processes_.push_back(std::move(ptr)); + return ref; + } + + void initialize(double startTime = 0.0) { + for (auto &p : processes_) { + p->initialize(startTime); + } + } + + double nextEventTime() const { + if (processes_.empty()) { + return std::numeric_limits::infinity(); + } + + double tMin = std::numeric_limits::infinity(); + for (const auto &p : processes_) { + double t = p->nextEventTime(); + if (t < tMin) + tMin = t; + } + return tMin; + } + + void handleEvent(double currentTime) { + for (auto &p : processes_) { + if (p->nextEventTime() <= currentTime) { + p->handleEvent(currentTime); + } + } + } + +private: + std::vector> processes_; +}; + +// --------------------------------------------------------------------------- +// Simulatore DES +// --------------------------------------------------------------------------- +class DiscreteEventSimulator { +public: + explicit DiscreteEventSimulator(DiscreteEventSystem &system) + : system_(system), currentTime_(0.0) {} + + void run(double horizon) { + system_.initialize(0.0); + currentTime_ = 0.0; + + while (currentTime_ < horizon) { + double nextTime = system_.nextEventTime(); + if (nextTime == std::numeric_limits::infinity()) + break; + currentTime_ = nextTime; + system_.handleEvent(currentTime_); + } + } + + double currentTime() const { return currentTime_; } + +private: + DiscreteEventSystem &system_; + double currentTime_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/2/include/Decision.hpp b/exams/AxelRubini/2025-03-21/2/include/Decision.hpp new file mode 100644 index 0000000..4d37cf0 --- /dev/null +++ b/exams/AxelRubini/2025-03-21/2/include/Decision.hpp @@ -0,0 +1,266 @@ +#pragma once + +#include "Random.hpp" +#include "Stat.hpp" +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 7. MARKOV CHAIN +// ============================================================================ + +// MarkovChain: Implements a discrete-time Markov chain. +// Purpose: +// - Simulates stochastic processes with defined states and transition +// probabilities. +// - Useful in modeling random systems and computing related probabilities. +// Usage: +// MarkovChain mc(3, rng); +// mc.setTransition(0, 1, 0.5); +// mc.setTransition(0, 2, 0.5); +// mc.setState(0); +// int nextState = mc.step(); +class MarkovChain { +private: + int numStates; + std::vector> transitionMatrix; + int currentState; + RandomGenerator &rng; + +public: + MarkovChain(int states, RandomGenerator &generator) + : numStates(states), currentState(0), rng(generator) { + transitionMatrix.resize(states, std::vector(states, 0.0)); + } + + void setTransition(int from, int to, double probability) { + if (from >= 0 && from < numStates && to >= 0 && to < numStates) { + transitionMatrix[from][to] = probability; + } + } + + void setState(int state) { + if (state >= 0 && state < numStates) { + currentState = state; + } + } + + int getState() const { return currentState; } + + int step() { + currentState = rng.discrete(transitionMatrix[currentState]); + return currentState; + } + + double getTransitionProb(int from, int to) const { + return transitionMatrix[from][to]; + } + + double expectedSojournTime(int state, double timeStep = 1.0) const { + double pii = transitionMatrix[state][state]; + if (pii >= 1.0) + return std::numeric_limits::infinity(); + return timeStep / (1.0 - pii); + } +}; + +// ============================================================================ +// 8. MDP +// ============================================================================ + +// MDP: Implements a Markov Decision Process (MDP) model. +// Purpose: +// - Simulate decision-making processes with states, transitions, and +// associated costs. +// - Calculate the total cost to reach a target state from a starting state. +// Usage: +// - Add transitions between states with associated probabilities and costs +// using `addTransition`. +// - Use `step` to simulate a single transition and calculate its cost. +// - Use `simulate` to estimate the total cost from a start to an end state. +class MDP { +private: + int numStates; + std::map>>> + transitions; + RandomGenerator &rng; + +public: + MDP(int states, RandomGenerator &generator) + : numStates(states), rng(generator) {} + + void addTransition(int from, int to, double probability, double cost) { + transitions[from].push_back({to, {probability, cost}}); + } + + std::pair step(int currentState) { + if (transitions.find(currentState) == transitions.end()) { + return {currentState, 0.0}; + } + + std::vector probs; + for (const auto &t : transitions[currentState]) { + probs.push_back(t.second.first); + } + + int idx = rng.discrete(probs); + int nextState = transitions[currentState][idx].first; + double cost = transitions[currentState][idx].second.second; + + return {nextState, cost}; + } + + double simulate(int startState, int endState) { + int state = startState; + double totalCost = 0.0; + int maxSteps = 100000; + + for (int step = 0; step < maxSteps && state != endState; step++) { + auto [nextState, cost] = this->step(state); + totalCost += cost; + state = nextState; + } + return totalCost; + } +}; + +// ============================================================================ +// 10. MONTE CARLO SIMULATOR +// ============================================================================ + +// MonteCarloSimulator: A utility for running Monte Carlo simulations. +// Purpose: +// - Estimate statistical metrics through repeated random sampling. +// - Provides methods to calculate mean results and probabilities of events. +// Usage: +// MonteCarloSimulator simulator(1000); +// double estimate = simulator.estimate([](int) { return some_random_value; }); +// double probability = simulator.estimateProbability([](int) { return +// some_event_occurs; }); +class MonteCarloSimulator { +private: + Statistics stats; + int numSimulations; + +public: + MonteCarloSimulator(int simulations = 1000) : numSimulations(simulations) {} + + template double estimate(SimFunc simulation) { + stats.clear(); + for (int i = 0; i < numSimulations; i++) { + double result = simulation(i); + stats.addSample(result); + } + return stats.mean(); + } + + template double estimateProbability(SimFunc simulation) { + int successes = 0; + for (int i = 0; i < numSimulations; i++) { + if (simulation(i)) + successes++; + } + return static_cast(successes) / numSimulations; + } + + const Statistics &getStatistics() const { return stats; } +}; + +// ============================================================================ +// 13. OPTIMIZATION UTILITIES +// ============================================================================ + +// Optimizer: Utility class offering optimization techniques such as random +// search and grid search. Purpose: +// - Explore parameter spaces to maximize or minimize an objective function. +// - Supports both continuous and discrete search spaces. +// Usage: +// - Use `randomSearch` for stochastic optimization over a defined range. +// - Use `gridSearch` for systematic exploration in evenly spaced steps. +// Example: +// auto [bestParam, bestValue] = Optimizer::randomSearch(objectiveFunction, +// 0.0, 10.0, 100, rng); auto [bestInt, bestIntValue] = +// Optimizer::randomSearchInt(objectiveFunction, 0, 10, 50, rng); +class Optimizer { +public: + template + static std::pair + randomSearch(ObjFunc objective, double minValue, double maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + for (int i = 0; i < budget; i++) { + double param = rng.uniform(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair + randomSearchInt(ObjFunc objective, int minValue, int maxValue, int budget, + RandomGenerator &rng, bool maximize = true) { + int bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + + if (budget <= 0) { + throw std::invalid_argument("randomSearchInt: budget must be positive."); + } + + if (minValue >= maxValue) { + throw std::invalid_argument( + "randomSearchInt: minValue must be less than maxValue."); + } + + for (int i = 0; i < budget; i++) { + int param = rng.uniformInt(minValue, maxValue); + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } + + template + static std::pair gridSearch(ObjFunc objective, + double minValue, double maxValue, + int steps, bool maximize = true) { + double bestParam = minValue; + double bestValue = maximize ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + double stepSize = (maxValue - minValue) / steps; + + if (steps <= 0) { + throw std::invalid_argument("gridSearch: steps must be positive."); + } + + for (int i = 0; i <= steps; i++) { + double param = minValue + i * stepSize; + double value = objective(param); + + if ((maximize && value > bestValue) || (!maximize && value < bestValue)) { + bestValue = value; + bestParam = param; + } + } + return {bestParam, bestValue}; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/2/include/Dynamics.hpp b/exams/AxelRubini/2025-03-21/2/include/Dynamics.hpp new file mode 100644 index 0000000..044176a --- /dev/null +++ b/exams/AxelRubini/2025-03-21/2/include/Dynamics.hpp @@ -0,0 +1,214 @@ +#pragma once + +#include +#include +namespace SELib { +// ============================================================================ +// 3. ODE SOLVER (Runge-Kutta 4th order) +// ============================================================================ + +using StateVector = std::vector; +using DerivativeFunction = std::function; + +// ODESolver: Implements numerical methods for solving Ordinary Differential +// Equations (ODEs). Purpose: +// - Provides tools for simulating dynamic systems modeled by ODEs. +// - Includes methods such as Runge-Kutta 4th order and Euler's method for +// integration. +// Usage: +// - Pass a derivative function that defines the ODE system. +// - Use `rk4Step` for higher accuracy or `eulerStep` for simpler systems. +// Example: +// StateVector nextState = ODESolver::rk4Step(0.0, currentState, controlInputs, +// dt, dynamicsFunction); +class ODESolver { +public: + static StateVector rk4Step(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector k1 = f(t, x, u); + StateVector x2 = addScaled(x, k1, dt / 2.0); + StateVector k2 = f(t + dt / 2.0, x2, u); + StateVector x3 = addScaled(x, k2, dt / 2.0); + StateVector k3 = f(t + dt / 2.0, x3, u); + StateVector x4 = addScaled(x, k3, dt); + StateVector k4 = f(t + dt, x4, u); + + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += (dt / 6.0) * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]); + } + return xNew; + } + + static StateVector eulerStep(double t, const StateVector &x, + const StateVector &u, double dt, + DerivativeFunction f) { + StateVector dx = f(t, x, u); + StateVector xNew = x; + for (size_t i = 0; i < x.size(); i++) { + xNew[i] += dx[i] * dt; + } + return xNew; + } + +private: + static StateVector addScaled(const StateVector &x, const StateVector &dx, + double scale) { + StateVector result = x; + for (size_t i = 0; i < x.size(); i++) { + result[i] += dx[i] * scale; + } + return result; + } +}; + +// ============================================================================ +// 4. PID CONTROLLER +// ============================================================================ + +// PIDController: Implements a Proportional-Integral-Derivative (PID) +// controller. Purpose: +// - Automatically adjusts a control signal to minimize the difference between +// a desired setpoint and a measured value. +// - Used in control systems to regulate processes like temperature, speed, or +// position. +// Usage: +// - Create an instance with proportional (Kp), integral (Ki), and derivative +// (Kd) gains, along with a time step. +// - Use `compute` to calculate the control signal based on the setpoint and +// measurement. +// Example: +// PIDController pid(1.0, 0.1, 0.01, 0.1); +// double controlSignal = pid.compute(desiredValue, actualValue); +class PIDController { +private: + double kp, ki, kd; + double integral; + double prevError; + double dt; + +public: + PIDController(double Kp, double Ki, double Kd, double timeStep) + : kp(Kp), ki(Ki), kd(Kd), integral(0), prevError(0), dt(timeStep) {} + + double compute(double setpoint, double measurement) { + double error = setpoint - measurement; + integral += error * dt; + double derivative = (error - prevError) / dt; + prevError = error; + return kp * error + ki * integral + kd * derivative; + } + + void reset() { + integral = 0; + prevError = 0; + } + + void setGains(double Kp, double Ki, double Kd) { + kp = Kp; + ki = Ki; + kd = Kd; + } +}; + +// ============================================================================ +// 5. CONTINUOUS TIME SIMULATION +// ============================================================================ + +// ContinuousTimeSimulation: A base class for simulating continuous-time dynamic +// systems. Purpose: +// - Provides a framework for simulating systems characterized by continuous +// dynamics over time. +// - Integrates system dynamics using a Runge-Kutta 4th order solver. +// Usage: +// - Derive from this class and implement the `dynamics`, `computeControl`, +// `initialize`, and `finalize` methods. +// - Customize the `onStep` method for per-step side effects, if required. +// Example: +// class MySimulation : public ContinuousTimeSimulation { +// // Implement the required virtual methods. +// }; +// MySimulation sim(dt, horizon, initialState); +// sim.run(); +class ContinuousTimeSimulation { +protected: + double currentTime; + double timeStep; + double horizon; + StateVector state; + StateVector control; + +public: + ContinuousTimeSimulation(double dt, double H, const StateVector &initialState) + : currentTime(0.0), timeStep(dt), horizon(H), state(initialState) { + control.resize(initialState.size(), 0.0); + } + + virtual ~ContinuousTimeSimulation() = default; + + virtual StateVector dynamics(double t, const StateVector &x, + const StateVector &u) = 0; + virtual StateVector computeControl(double t, const StateVector &x) = 0; + virtual void initialize() = 0; + virtual void onStep() {} + virtual void finalize() = 0; + + void run() { + initialize(); + while (currentTime < horizon) { + control = computeControl(currentTime, state); + state = ODESolver::rk4Step( + currentTime, state, control, timeStep, + [this](double t, const StateVector &x, const StateVector &u) { + return this->dynamics(t, x, u); + }); + currentTime += timeStep; + onStep(); + } + finalize(); + } + + double getCurrentTime() const { return currentTime; } + const StateVector &getState() const { return state; } + const StateVector &getControl() const { return control; } +}; + +// ============================================================================ +// 6. SAMPLE AND HOLD +// ============================================================================ + +// SampleAndHold: A utility to store a value and update it periodically. +// Purpose: +// - Maintain a value that gets updated periodically based on a time interval. +// - Useful for sampling data and ensuring updates happen at fixed intervals. +// Usage: +// SampleAndHold sampler(5.0, 0); +// if (sampler.shouldUpdate(currentTime)) { +// sampler.update(currentTime, newValue); +// } +// int currentValue = sampler.getValue(); +template class SampleAndHold { +private: + T value; + double lastUpdateTime; + double samplePeriod; + +public: + SampleAndHold(double period, const T &initialValue = T()) + : value(initialValue), lastUpdateTime(0), samplePeriod(period) {} + + bool shouldUpdate(double currentTime) const { + return (currentTime - lastUpdateTime) >= samplePeriod; + } + + void update(double currentTime, const T &newValue) { + value = newValue; + lastUpdateTime = currentTime; + } + + const T &getValue() const { return value; } + void reset() { lastUpdateTime = 0; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/2/include/Geometry.hpp b/exams/AxelRubini/2025-03-21/2/include/Geometry.hpp new file mode 100644 index 0000000..525a91d --- /dev/null +++ b/exams/AxelRubini/2025-03-21/2/include/Geometry.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 14. GEOMETRIC UTILITIES +// ============================================================================ + +// Point2D: Represents a point or vector in 2D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point2D p1(1.0, 2.0), p2(2.0, 3.0); +// double dist = p1.distanceTo(p2); +// Point2D sum = p1 + p2; +struct Point2D { + double x, y; + + Point2D(double x_ = 0, double y_ = 0) : x(x_), y(y_) {} + + double distanceTo(const Point2D &other) const { + double dx = x - other.x; + double dy = y - other.y; + return std::sqrt(dx * dx + dy * dy); + } + + Point2D operator+(const Point2D &other) const { + return Point2D(x + other.x, y + other.y); + } + + Point2D operator-(const Point2D &other) const { + return Point2D(x - other.x, y - other.y); + } +}; + +// Point3D: Represents a point or vector in 3D space. +// Purpose: +// - Provides basic operations like addition, subtraction, and distance +// calculation. +// Usage: +// Point3D p1(1.0, 2.0, 3.0), p2(2.0, 3.0, 4.0); +// double dist = p1.distanceTo(p2); +// Point3D sum = p1 + p2; +struct Point3D { + double x, y, z; + + Point3D(double x_ = 0, double y_ = 0, double z_ = 0) : x(x_), y(y_), z(z_) {} + + double distanceTo(const Point3D &other) const { + double dx = x - other.x; + double dy = y - other.y; + double dz = z - other.z; + return std::sqrt(dx * dx + dy * dy + dz * dz); + } + + Point3D operator+(const Point3D &other) const { + return Point3D(x + other.x, y + other.y, z + other.z); + } + + Point3D operator-(const Point3D &other) const { + return Point3D(x - other.x, y - other.y, z - other.z); + } +}; + +// ============================================================================ +// 19. COLLISION DETECTOR +// ============================================================================ + +// CollisionDetector: A utility class for detecting collisions between points +// in space. Purpose: +// - Counts the number of collisions within a given distance threshold. +// - Retrieves pairs of colliding points. +// Usage: +// std::vector points = {Point2D(0, 0), Point2D(1, 1)}; +// int numCollisions = CollisionDetector::countCollisions(points, 1.5); +// auto collidingPairs = CollisionDetector::getCollidingPairs(points, 1.5); +class CollisionDetector { +public: + template + static int countCollisions(const std::vector &positions, + double threshold) { + int collisions = 0; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + collisions++; + } + } + } + return collisions; + } + + template + static std::vector> + getCollidingPairs(const std::vector &positions, double threshold) { + std::vector> pairs; + for (size_t i = 0; i < positions.size(); i++) { + for (size_t j = i + 1; j < positions.size(); j++) { + if (positions[i].distanceTo(positions[j]) <= threshold) { + pairs.push_back({i, j}); + } + } + } + return pairs; + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/2/include/IO.hpp b/exams/AxelRubini/2025-03-21/2/include/IO.hpp new file mode 100644 index 0000000..1a79cc6 --- /dev/null +++ b/exams/AxelRubini/2025-03-21/2/include/IO.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace SELib { + +// ============================================================================ +// 11. PARAMETER PARSER +// ============================================================================ + +// ParameterParser: A utility to parse and retrieve parameters from +// configuration files. Purpose: +// - Reads a file and stores key-value pairs for parameters. +// - Allows retrieval of parameters as integers, doubles, or strings. +// - Provides structured data for advanced parsing. +// Usage: +// ParameterParser parser; +// if (parser.parseFile("config.txt")) { +// int param = parser.getInt("example_key", 42); +// } +class ParameterParser { +private: + std::map params; + std::vector> structuredData; + +public: + bool parseFile(const std::string &filename) { + std::ifstream file(filename); + if (!file.is_open()) + return false; + + std::string line; + while (std::getline(file, line)) { + if (line.empty()) + continue; + + std::istringstream iss(line); + std::string key; + iss >> key; + + std::string value; + std::getline(iss, value); + if (!value.empty() && value[0] == ' ') { + value = value.substr(1); + } + + params[key] = value; + + std::vector tokens; + std::istringstream tokenStream(line); + std::string token; + while (tokenStream >> token) { + tokens.push_back(token); + } + structuredData.push_back(tokens); + } + return true; + } + + int getInt(const std::string &key, int defaultValue = 0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stoi(it->second); + } + return defaultValue; + } + + double getDouble(const std::string &key, double defaultValue = 0.0) const { + auto it = params.find(key); + if (it != params.end()) { + return std::stod(it->second); + } + return defaultValue; + } + + std::string getString(const std::string &key, + const std::string &defaultValue = "") const { + auto it = params.find(key); + if (it != params.end()) { + return it->second; + } + return defaultValue; + } + + const std::vector> &getStructuredData() const { + return structuredData; + } + + bool hasKey(const std::string &key) const { + return params.find(key) != params.end(); + } +}; + +// ============================================================================ +// 12. OUTPUT WRITER +// ============================================================================ + +// OutputWriter: A utility class for writing data to output files. +// Purpose: +// - Simplifies the process of writing data to files. +// - Supports writing lines, key-value pairs, and managing file headers. +// Usage: +// OutputWriter writer; +// if (writer.open("output.txt", "Header Info")) { +// writer.writeLine("Example Line"); +// writer.writeKeyValue("Key", 42.0); +// writer.close(); +// } +class OutputWriter { +private: + std::ofstream file; + +public: + bool open(const std::string &filename, const std::string &header) { + file.open(filename); + if (!file.is_open()) + return false; + file << header << std::endl; + return true; + } + + template void writeLine(const T &value) { + file << value << std::endl; + } + + template + void writeLine(const T &first, const Args &...args) { + file << first << " "; + writeLine(args...); + } + + void writeKeyValue(const std::string &key, double value, int precision = 2) { + file << key << " " << std::fixed << std::setprecision(precision) << value + << std::endl; + } + + void close() { + if (file.is_open()) { + file.close(); + } + } + + ~OutputWriter() { close(); } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/2/include/Inventory.hpp b/exams/AxelRubini/2025-03-21/2/include/Inventory.hpp new file mode 100644 index 0000000..c82d776 --- /dev/null +++ b/exams/AxelRubini/2025-03-21/2/include/Inventory.hpp @@ -0,0 +1,423 @@ +#pragma once + +#include "DES.hpp" +#include "Random.hpp" +#include +#include + +namespace SELib { +// ============================================================================ +// 16. CUSTOMER MODEL +// ============================================================================ + +// Customer: Represents a customer model in a simulation. +// Purpose: +// - Simulates customer requests over time. +// - Generates random requests for products at specific intervals. +// Usage: +// Customer customer(5.0, 10.0, 3, rng); +// int productId = customer.step(0.1); // Simulates a time step of 0.1 seconds. +class Customer { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer(double A, double B, int N, RandomGenerator &generator) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator) { + nextRequestTime = rng.uniform(A, B); + } + + int step(double dt) { + nextRequestTime -= dt; + if (nextRequestTime <= 0) { + nextRequestTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } +}; +class Customer_Event_Driven { +private: + double nextRequestTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Customer_Event_Driven(double A, double B, int N, RandomGenerator &generator, + double nextRequestTime) + : minInterval(A), maxInterval(B), numProducts(N), rng(generator), + nextRequestTime(nextRequestTime) {} + + int step_event_driven(double *current_time) { + if (*current_time >= nextRequestTime) { + nextRequestTime = *current_time + rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + double getNextRequestTime() { return nextRequestTime; }; +}; + +// ============================================================================ +// 17. SERVER MODEL +// ============================================================================ + +// Server: Represents a server in a simulated environment. +// Purpose: +// - Handles product requests and manages inventory. +// - Simulates server busy time during request processing. +// Usage: +// Server server(numProducts, maxInventory, processingTimeNormal, +// processingTimeSupplier, rng); int result = +// server.processRequest(productId, needsSupply); +// server.addInventory(productId, quantityAdded); +class Server { +private: + std::vector inventory; + int busyTime; + int processingTimeNormal; + int processingTimeSupplier; + RandomGenerator *rng; + +public: + Server(int numProducts, int K, int F, int G, RandomGenerator &rng) + : inventory(numProducts + 1, 0), busyTime(0), processingTimeNormal(F), + processingTimeSupplier(G), rng(&rng) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + + bool isBusy() const { return busyTime > 0; } + + void decrementBusyTime() { + if (busyTime > 0) + busyTime--; + } + + int processRequest(int productId, bool needsSupply) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return productId; + } else { + busyTime = needsSupply ? processingTimeSupplier : processingTimeNormal; + return -productId; + } + } + + int processRequest2(int productId, double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (inventory[productId] > 0) { + inventory[productId]--; + busyTime = processingTimeNormal; + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + busyTime = processingTimeSupplier; + return -productId; + } + } + + void addInventory(int productId, int quantity) { + inventory[productId] += quantity; + } + + int getInventory(int productId) const { return inventory[productId]; } +}; +// ============================================================================ +// 17.5 SERVER-EVENT-DRIVEN +// ============================================================================ +class Server_Event_Driven { +private: + std::vector inventory; + RandomGenerator *rng; + double next_step_time; + double min_interval; + double max_interval; + +public: + Server_Event_Driven(int numProducts, int K, double min_interval, + double max_interval, RandomGenerator &rng, + double nextStepTime) + : inventory(numProducts + 1, 0), rng(&rng), min_interval(min_interval), + max_interval(max_interval), next_step_time(nextStepTime) { + for (int i = 1; i <= numProducts; i++) { + inventory[i] = rng.uniformInt(0, K); + } + } + int processRequestEventDriven(double current_time, int productId, + double supply_prob, int min_prod_refill, + int max_prod_refill) { + if (current_time >= next_step_time) { + if (inventory[productId] > 0) { + inventory[productId]--; + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + return productId; + } else { + if (rng->bernoulli(supply_prob)) { + int refill_amount = rng->uniformInt(min_prod_refill, max_prod_refill); + inventory[productId] += refill_amount; + } + next_step_time = + current_time + + rng->uniform( + min_interval, + max_interval); // assuming processing time normal is 1.0 + + return -productId; + } + } else { + return 0; + } + } + double getNextStepTime() { return next_step_time; } +}; + +// ============================================================================ +// 18. SUPPLIER MODEL +// ============================================================================ + +// Supplier: Represents a supplier in a simulation. +// Purpose: +// - Simulates the supply of products at specific intervals. +// - Generates random supply requests periodically based on the defined +// intervals. +// Usage: +// Supplier supplier(minInterval, maxInterval, numProducts, rng); +// int suppliedProduct = supplier.step(timeStep); +// supplier.reset(); +class Supplier { +private: + double nextSupplyTime; + double minInterval; + double maxInterval; + int numProducts; + RandomGenerator &rng; + +public: + Supplier(double V, double Q, int N, RandomGenerator &generator) + : minInterval(V), maxInterval(Q), numProducts(N), rng(generator) { + nextSupplyTime = rng.uniform(V, Q); + } + + int step(double dt) { + nextSupplyTime -= dt; + if (nextSupplyTime <= 0) { + nextSupplyTime = rng.uniform(minInterval, maxInterval); + return rng.uniformInt(1, numProducts); + } + return 0; + } + + void reset() { nextSupplyTime = rng.uniform(minInterval, maxInterval); } +}; + +// Server di inventario con messaggi (customer/server network-style) +class InventoryServerProcess : public DiscreteEventProcess { +public: + InventoryServerProcess(int pid, int numItems, int initialStockPerItem, + double serviceTime, MessageBus &bus, + RandomGenerator &rng) + : pid_(pid), serviceTime_(serviceTime), bus_(bus), rng_(rng), + nextEventTime_(0.0), + storage_(static_cast(numItems), initialStockPerItem), + totalRequests_(0), missedRequests_(0) {} + + void initialize(double startTime) override { + nextEventTime_ = startTime + serviceTime_; + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // processa TUTTI i messaggi arrivati a questo server + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + totalRequests_++; + + // sicurezza sugli indici prodotto + if (m.item < 0 || static_cast(m.item) >= storage_.size()) { + continue; // scarta richieste invalide + } + + double servedQty = 0.0; + int &stock = storage_[static_cast(m.item)]; + + if (stock >= static_cast(m.quantity)) { + // richiesta soddisfatta + stock -= static_cast(m.quantity); + servedQty = m.quantity; + } else { + // non abbastanza stock + servedQty = static_cast(stock); + missedRequests_++; + stock = 0; + } + + // risposta al cliente + Message reply; + reply.time = currentTime; + reply.sender = pid_; + reply.receiver = m.sender; // rimanda al mittente + reply.item = m.item; + reply.quantity = servedQty; + + bus_.procToNet(pid_).push(reply); + + // eventuale logica di refill: esempio semplice + if (stock == 0) { + int refill = rng_.uniformInt(1, 10); // batch di rifornimento random + stock += refill; + } + } + + nextEventTime_ = currentTime + serviceTime_; + } + + const std::vector &storage() const { return storage_; } + + int totalRequests() const { return totalRequests_; } + int missedRequests() const { return missedRequests_; } + +private: + int pid_; + double serviceTime_; + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + std::vector storage_; + + int totalRequests_; + int missedRequests_; +}; + +// ============================================================================ +// 19. INVENTORY CUSTOMER PROCESS (DES-compatible) +// ============================================================================ +// +// InventoryCustomerProcess: +// - Processo a eventi discreti che rappresenta un cliente. +// - Invia richieste al server tramite MessageBus (procToNet -> NetworkRouter). +// - Legge le risposte dal canale netToProc. +// - Statistiche: quante richieste ha mandato e quante risposte ha ricevuto. +// +class InventoryCustomerProcess : public DiscreteEventProcess { +public: + // pid : id di questo processo (indice nei canali del MessageBus) + // serverPid : pid del server a cui mandare le richieste + // numItems : numero di prodotti gestiti (item in [0, numItems-1]) + // minInterval/maxInterval : intervallo di tempo tra una richiesta e la + // successiva + InventoryCustomerProcess(int pid, int serverPid, int numItems, + double minInterval, double maxInterval, + MessageBus &bus, RandomGenerator &rng) + : pid_(pid), serverPid_(serverPid), numItems_(numItems), + minInterval_(minInterval), maxInterval_(maxInterval), bus_(bus), + rng_(rng), nextEventTime_(0.0), issuedRequests_(0), receivedReplies_(0), + totalRequestedQty_(0.0), totalServedQty_(0.0) {} + + // inizializza il prossimo evento (prima richiesta) + void initialize(double startTime) override { + nextEventTime_ = startTime + rng_.uniform(minInterval_, maxInterval_); + } + + double nextEventTime() const override { return nextEventTime_; } + + void handleEvent(double currentTime) override { + if (currentTime < nextEventTime_) { + return; + } + + // 1) genera una nuova richiesta al server + const int item = rng_.uniformInt( + 0, numItems_ - 1); // coerente con InventoryServerProcess + const int qty = rng_.uniformInt(1, 3); // quantità richiesta (1..3) + + Message req; + req.time = currentTime; + req.sender = pid_; + req.receiver = serverPid_; + req.item = item; + req.quantity = static_cast(qty); + + bus_.procToNet(pid_).push(req); + issuedRequests_++; + totalRequestedQty_ += req.quantity; + + // 2) processa tutte le risposte arrivate su questo cliente + auto &inbox = bus_.netToProc(pid_); + while (!inbox.empty()) { + Message m = inbox.front(); + inbox.pop(); + + // opzionale: possiamo filtrare solo messaggi dal serverPid_ + // if (m.sender != serverPid_) continue; + + receivedReplies_++; + totalServedQty_ += m.quantity; + } + + // 3) pianifica la prossima richiesta + nextEventTime_ = currentTime + rng_.uniform(minInterval_, maxInterval_); + } + + // --- metriche accessibili dall’esterno --- + + int pid() const { return pid_; } + int serverPid() const { return serverPid_; } + + int issuedRequests() const { return issuedRequests_; } + int receivedReplies() const { return receivedReplies_; } + + double totalRequestedQty() const { return totalRequestedQty_; } + double totalServedQty() const { return totalServedQty_; } + + // fill-rate lato customer + double serviceLevel() const { + if (totalRequestedQty_ <= 0.0) + return 1.0; + return totalServedQty_ / totalRequestedQty_; + } + +private: + int pid_; + int serverPid_; + int numItems_; + double minInterval_; + double maxInterval_; + + MessageBus &bus_; + RandomGenerator &rng_; + + double nextEventTime_; + + int issuedRequests_; + int receivedReplies_; + double totalRequestedQty_; + double totalServedQty_; +}; + +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/2/include/Queue.hpp b/exams/AxelRubini/2025-03-21/2/include/Queue.hpp new file mode 100644 index 0000000..0c12ce8 --- /dev/null +++ b/exams/AxelRubini/2025-03-21/2/include/Queue.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 9. FIFO QUEUE +// ============================================================================ + +// FIFOQueue: Implements a first-in-first-out (FIFO) queue with a fixed maximum +// size. Purpose: +// - Enqueue and dequeue items in FIFO order. +// - Track statistics such as total items enqueued and dequeued. +// - Support dynamic size constraints. +// Usage: +// FIFOQueue queue(10); +// queue.enqueue(5); +// int item; +// if (queue.dequeue(item)) { +// // Process item. +// } +template class FIFOQueue { +private: + std::queue queue; + size_t maxSize; + size_t totalEnqueued; + size_t totalDequeued; + +public: + FIFOQueue(size_t max = SIZE_MAX) + : maxSize(max), totalEnqueued(0), totalDequeued(0) {} + + bool enqueue(const T &item) { + if (queue.size() >= maxSize) + return false; + queue.push(item); + totalEnqueued++; + return true; + } + + bool dequeue(T &item) { + if (queue.empty()) + return false; + item = queue.front(); + queue.pop(); + totalDequeued++; + return true; + } + + bool isEmpty() const { return queue.empty(); } + bool isFull() const { return queue.size() >= maxSize; } + size_t size() const { return queue.size(); } + size_t getTotalEnqueued() const { return totalEnqueued; } + size_t getTotalDequeued() const { return totalDequeued; } + std::queue getQueue() const { return queue; } + T getFirst() const { + if (queue.empty()) + throw std::runtime_error("Queue is empty"); + return queue.front(); + } + + void clear() { + while (!queue.empty()) + queue.pop(); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/2/include/Random.hpp b/exams/AxelRubini/2025-03-21/2/include/Random.hpp new file mode 100644 index 0000000..52356f9 --- /dev/null +++ b/exams/AxelRubini/2025-03-21/2/include/Random.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include // std::isfinite +#include // std::accumulate +#include +#include +#include + +namespace SELib { + +class RandomGenerator { +private: + std::mt19937 gen; + +public: + explicit RandomGenerator(unsigned seed = std::random_device{}()) + : gen(seed) {} + + double uniform(double min, double max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_real_distribution dis(lo, high); + return dis(gen); + } + + int uniformInt(int min, int max) { + auto lo = std::min(min, max); + auto high = std::max(min, max); + std::uniform_int_distribution dis(lo, high); + return dis(gen); + } + + double gaussian(double mean, double stddev) { + if (stddev < 0.0) { + throw std::invalid_argument("gaussian: stddev must be >= 0"); + } + std::normal_distribution dis(mean, stddev); + return dis(gen); + } + + bool bernoulli(double p) { + if (p < 0.0 || p > 1.0 || !std::isfinite(p)) { + throw std::invalid_argument("bernoulli: p must be in [0,1]"); + } + std::bernoulli_distribution dis(p); + return dis(gen); + } + + int discrete(const std::vector &probabilities) { + if (probabilities.empty()) { + throw std::invalid_argument("discrete: empty probability vector"); + } + + for (double w : probabilities) { + if (w < 0.0 || !std::isfinite(w)) { + throw std::invalid_argument( + "discrete: weights must be non-negative and finite"); + } + } + + double sum = + std::accumulate(probabilities.begin(), probabilities.end(), 0.0); + if (sum <= 0.0) { + throw std::invalid_argument("discrete: sum of weights must be > 0"); + } + + std::discrete_distribution dis(probabilities.begin(), + probabilities.end()); + return dis(gen); + } + + void setSeed(unsigned seed) { gen.seed(seed); } + + // (opzionale) accesso al motore se ti serve per altre distribuzioni + std::mt19937 &engine() { return gen; } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/2/include/Stat.hpp b/exams/AxelRubini/2025-03-21/2/include/Stat.hpp new file mode 100644 index 0000000..27b4656 --- /dev/null +++ b/exams/AxelRubini/2025-03-21/2/include/Stat.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace SELib { +// ============================================================================ +// 2. STATISTICS +// ============================================================================ + +// Statistics: A utility class for managing and analyzing numerical data +// samples. Purpose: +// - Compute statistical metrics such as mean and standard deviation. +// - Estimate probabilities of custom conditions. +// - Check convergence of data over iterations. +// Usage: +// Statistics stats; +// stats.addSample(5.0); +// double meanValue = stats.mean(); +class Statistics { +private: + std::vector samples; + +public: + void addSample(double value) { samples.push_back(value); } + + void clear() { samples.clear(); } + + double mean() const { + if (samples.empty()) + return 0.0; + double sum = 0.0; + for (double s : samples) + sum += s; + return sum / samples.size(); + } + + double stddev() const { + if (samples.size() < 2) + return 0.0; + double m = mean(); + double variance = 0.0; + for (double s : samples) { + variance += (s - m) * (s - m); + } + return std::sqrt(variance / (samples.size() - 1)); + } + + double probability(std::function condition) const { + if (samples.empty()) + return 0.0; + int count = 0; + for (double s : samples) { + if (condition(s)) + count++; + } + return static_cast(count) / samples.size(); + } + + size_t size() const { return samples.size(); } + const std::vector &getSamples() const { return samples; } + + bool hasConverged(double tolerance = 0.1) const { + if (samples.size() < 30) + return false; + double m = mean(); + double sd = stddev(); + return sd <= tolerance * std::abs(m); + } +}; +} // namespace SELib diff --git a/exams/AxelRubini/2025-03-21/2/main b/exams/AxelRubini/2025-03-21/2/main new file mode 100755 index 0000000000000000000000000000000000000000..4a6c69185785f910436e80caf77f06e3506bd8fa GIT binary patch literal 43504 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