{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "71f85f2a-423f-44d2-b80d-da9ac8d3961a",
   "metadata": {},
   "outputs": [],
   "source": [
    "import simpy\n",
    "import random\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import pandas as pd\n",
    "from enum import Enum\n",
    "import os\n",
    "import shutil\n",
    "from tqdm import tqdm\n",
    "import math\n",
    "\n",
    "# Types of cache\n",
    "class CacheType(Enum):\n",
    "    LRU = 1\n",
    "    RANDOM_EVICTION = 2\n",
    "    TTL = 3\n",
    "    \n",
    "# Constants\n",
    "SEED = 42\n",
    "ACCESS_COUNT_LIMIT = 1000   # Total time to run the simulation\n",
    "EXPERIMENT_BASE_DIR = \"./experiments/\"\n",
    "TEMP_BASE_DIR = \"./.aoi_cache/\"\n",
    "BASE_FILE = pd.read_csv(\"../calculated.csv\")\n",
    "BASE_FILE.index += 1\n",
    "\n",
    "ZIPF_CONSTANT = 2      # Shape parameter for the Zipf distribution (controls skewness) Needs to be: 1< \n",
    "\n",
    "# Set random seeds\n",
    "random.seed(SEED)\n",
    "np.random.seed(SEED)\n",
    "\n",
    "os.makedirs(TEMP_BASE_DIR, exist_ok=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9a37d7a3-3e11-4b89-8dce-6091dd38b16f",
   "metadata": {},
   "source": [
    "How to set certain parameters for specific scenarios\n",
    "\n",
    "\n",
    "| Name                 | Cache Capacity       | MAX_REFRESH_RATE | cache_type                | CACHE_TTL |\n",
    "| -------------------- | -------------------- | ---------------- | ------------------------- | --------- |\n",
    "| Default              | DATABASE_OBJECTS     | 1<               | CacheType.LRU             | 5         |\n",
    "| No Refresh           | DATABASE_OBJECTS     | 0                | CacheType.LRU             | 5         |\n",
    "| Infinite TTL         | DATABASE_OBJECTS / 2 | 0                | CacheType.LRU             | 0         |\n",
    "| Random Eviction (RE) | DATABASE_OBJECTS / 2 | 1<               | CacheType.RANDOM_EVICTION | 5         |\n",
    "| RE without Refresh   | DATABASE_OBJECTS / 2 | 0                | CacheType.RANDOM_EVICTION | 5         |\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "3d0ab5b1-162a-42c8-80a3-d31f763101f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Configuration (Just example, will be overwritten in next block\n",
    "DATABASE_OBJECTS = 100\n",
    "CACHE_CAPACITY = DATABASE_OBJECTS   # Maximum number of objects the cache can hold\n",
    "\n",
    "# MAX_REFRESH_RATE is used as the maximum for a uniform\n",
    "# distribution for mu.\n",
    "# If MAX_REFRESH_RATE is 0, we do not do any refreshes.\n",
    "MAX_REFRESH_RATE = 0\n",
    "\n",
    "cache_type = CacheType.LRU\n",
    "\n",
    "# CACHE_TTL is used to determin which TTL to set when an\n",
    "# object is pulled into the cache\n",
    "# If CACHE_TTL is set to 0, the TTL is infinite\n",
    "CACHE_TTL = 5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "3ff299ca-ec65-453b-b167-9a0f7728a207",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Each configuration is structured as follows:\n",
    "# \"Configuration Name: (Database Objects, Cache Capacity, Max Refresh Rate, Cache Type, TTL)\n",
    "configurations = {\n",
    "    \"default\": (100, 100, 10, CacheType.LRU, 5),\n",
    "    \"No Refresh\": (100, 100, 0, CacheType.LRU, 5),\n",
    "    \"Infinite TTL\": (100, 50, 0, CacheType.LRU, 0),\n",
    "    \"Random Eviction\": (100, 50, 10, CacheType.RANDOM_EVICTION, 5),\n",
    "    \"RE without Refresh\": (100, 50, 0, CacheType.RANDOM_EVICTION, 5),\n",
    "    \"No Refresh (0.5s ttl)\": (100, 100, 0, CacheType.TTL, 0.5),\n",
    "    \"No Refresh (1.0s ttl)\": (100, 100, 0, CacheType.TTL, 1),\n",
    "    \"No Refresh (2.0s ttl)\": (100, 100, 0, CacheType.TTL, 2),\n",
    "    \"No Refresh (3.0s ttl)\": (100, 100, 0, CacheType.TTL, 3),\n",
    "    \"No Refresh (4.0s ttl)\": (100, 100, 0, CacheType.TTL, 4),\n",
    "    \"No Refresh (5.0s ttl)\": (100, 100, 0, CacheType.TTL, 5),\n",
    "    \"No Refresh (file ttl)\": (BASE_FILE['lambda'], 10, 0, CacheType.TTL, BASE_FILE['optimal_TTL']),\n",
    "}\n",
    "\n",
    "experiment_name = \"No Refresh (file ttl)\"\n",
    "config = configurations[experiment_name]\n",
    "\n",
    "DATABASE_OBJECTS = config[0]\n",
    "CACHE_CAPACITY = config[1]\n",
    "MAX_REFRESH_RATE = config[2]\n",
    "cache_type = config[3]\n",
    "CACHE_TTL = config[4]\n",
    "\n",
    "if cache_type == CacheType.TTL:\n",
    "    if isinstance(DATABASE_OBJECTS, int):\n",
    "        assert CACHE_TTL > 0, \"Needs CACHE_TTL to be greater than 0 when using TTL-Cache.\"\n",
    "    if isinstance(DATABASE_OBJECTS, int):\n",
    "        assert CACHE_CAPACITY >= DATABASE_OBJECTS, \"Cache Size needs to be greater or equal to the amount of Database Objects.\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "5cea042f-e9fc-4a1e-9750-de212ca70601",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Database:\n",
    "    def __init__(self):\n",
    "        # Each object now has a specific refresh rate 'mu'\n",
    "        self.data = {i: f\"Object {i}\" for i in range(1, len(DATABASE_OBJECTS) + 1)}\n",
    "        if isinstance(DATABASE_OBJECTS, int):\n",
    "            self.lambda_values = {i: np.random.zipf(ZIPF_CONSTANT) for i in range(1, DATABASE_OBJECTS + 1)}  # Request rate 'lambda' for each object\n",
    "        else:\n",
    "            self.lambda_values = DATABASE_OBJECTS.to_dict()\n",
    "        # Refresh rate 'mu' for each object\n",
    "        if MAX_REFRESH_RATE == 0:\n",
    "            self.mu_values = {i: 0 for i in range(1,len(DATABASE_OBJECTS) + 1)}    \n",
    "        else:\n",
    "            self.mu_values = {i: np.random.uniform(1, MAX_REFRESH_RATE) for i in range(1, len(DATABASE_OBJECTS) + 1)}\n",
    "        self.next_request = {i: np.random.exponential(1/self.lambda_values[i]) for i in range(1, len(DATABASE_OBJECTS) + 1)}\n",
    "        if isinstance(CACHE_TTL, int):\n",
    "            self.ttl = {i: CACHE_TTL for i in range(1, len(DATABASE_OBJECTS) + 1)}\n",
    "        else:\n",
    "            self.ttl = CACHE_TTL.to_dict()\n",
    "\n",
    "\n",
    "    def get_object(self, obj_id):\n",
    "        # print(f\"[{env.now:.2f}] Database: Fetched {self.data.get(obj_id, 'Unknown')} for ID {obj_id}\")\n",
    "        return self.data.get(obj_id, None)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "499bf543-b2c6-4e4d-afcc-0a6665ce3ae1",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Cache:\n",
    "    def __init__(self, env, db, cache_type):\n",
    "        self.cache_type = cache_type\n",
    "        self.env = env\n",
    "        self.db = db\n",
    "        self.storage = {}  # Dictionary to store cached objects\n",
    "        self.ttl = {}      # Dictionary to store TTLs\n",
    "        self.initial_fetch = {}      # Dictionary to store when an object was fetched from the databse to determine the age\n",
    "        self.cache_size_over_time = []  # To record cache state at each interval\n",
    "        self.cache_next_request_over_time = []\n",
    "        self.request_log = {i: [] for i in range(1, len(DATABASE_OBJECTS) + 1)}\n",
    "        self.hits = {i: 0 for i in range(1, len(DATABASE_OBJECTS) + 1)}  # Track hits per object\n",
    "        self.misses = {i: 0 for i in range(1, len(DATABASE_OBJECTS) + 1)}  # Track misses per object\n",
    "        self.cumulative_age = {i: [] for i in range(1, len(DATABASE_OBJECTS) + 1)}  # Track cumulative age per object\n",
    "        self.access_count = {i: 0 for i in range(1, len(DATABASE_OBJECTS) + 1)}  # Track access count per object\n",
    "        self.next_refresh = {}  # Track the next refresh time for each cached object\n",
    "        self.object_start_time = {} # Used as helper variable to determine the starting time of an object in the cache\n",
    "        self.cumulative_cache_time = {i: 0 for i in range(1, len(DATABASE_OBJECTS) + 1)} # Stores the cumulative time the object has spent between its eviction and when it was first pulled into the cache\n",
    "        \n",
    "    def get(self, obj_id):\n",
    "        assert len(self.storage) <= CACHE_CAPACITY, f\"Too many objects in cache ({len(self.storage)}). \"\n",
    "        if obj_id in self.storage:\n",
    "            # Cache hit: Refresh TTL if TTL-Cache\n",
    "            if self.cache_type == CacheType.TTL:\n",
    "                if self.ttl[obj_id] > env.now:\n",
    "                    self.ttl[obj_id] = env.now + self.db.ttl[obj_id]\n",
    "                    \n",
    "            # Cache hit: increment hit count and update cumulative age\n",
    "            self.hits[obj_id] += 1\n",
    "            self.access_count[obj_id] += 1\n",
    "\n",
    "            age = env.now - self.initial_fetch[obj_id]\n",
    "            self.cumulative_age[obj_id].append(age)\n",
    "\n",
    "            assert len(self.cumulative_age[obj_id]) == self.access_count[obj_id], \"Age values collected and object access count do not match.\"\n",
    "\n",
    "            # print(f\"[{env.now:.2f}] {obj_id} Hit: Current Age {age:.2f} (Average: {sum(self.cumulative_age[obj_id])/len(self.cumulative_age[obj_id]):.2f}) \")\n",
    "        else:\n",
    "            assert obj_id not in self.storage.keys(), \"Found object in cache on miss. It should've been deleted.\"\n",
    "            assert obj_id not in self.initial_fetch.keys(), \"Found age timer on miss. It should've been deleted.\"\n",
    "            assert obj_id not in self.object_start_time.keys(), \"Found cache time ratio timer on miss. It should've been deleted.\"\n",
    "            # Cache miss: Add TTL if TTL-Cache\n",
    "            # When full cache: If non-TTL-Cache: Evict. If TTL-Cache: Don't add to Cache.\n",
    "            if self.cache_type == CacheType.TTL:\n",
    "                assert obj_id not in self.ttl.keys(), \"Found cache time ratio timer on miss. It should've been deleted.\"\n",
    "                \n",
    "                if len(self.storage) == CACHE_CAPACITY:\n",
    "                    # print(f\"[{env.now:.2f}] Cache: Cache capacity reached. Not accepting new request.\")\n",
    "                    return\n",
    "                else:\n",
    "                    self.ttl[obj_id] = env.now + self.db.ttl[obj_id]\n",
    "            else:\n",
    "                if len(self.storage) == CACHE_CAPACITY:\n",
    "                    if self.cache_type == CacheType.LRU:\n",
    "                        self.evict_oldest()\n",
    "                    elif self.cache_type == CacheType.RANDOM_EVICTION:\n",
    "                        self.evict_random()\n",
    "            \n",
    "            # Cache miss: increment miss count\n",
    "            self.misses[obj_id] += 1\n",
    "            self.access_count[obj_id] += 1\n",
    "            \n",
    "            # Cache miss: Fetch the object from the database\n",
    "            self.storage[obj_id] = self.db.get_object(obj_id)\n",
    "            self.object_start_time[obj_id] = env.now\n",
    "        \n",
    "            self.initial_fetch[obj_id] = env.now\n",
    "            age = env.now - self.initial_fetch[obj_id]\n",
    "            assert age == 0, \"Initial age at miss is not 0\"\n",
    "            self.cumulative_age[obj_id].append(age)\n",
    "\n",
    "            assert len(self.cumulative_age[obj_id]) == self.access_count[obj_id], \"Age values collected and object access count do not match.\"\n",
    "            \n",
    "            # print(f\"[{env.now:.2f}] {obj_id} Miss: Average Age {sum(self.cumulative_age[obj_id])/len(self.cumulative_age[obj_id]):.2f} \")\n",
    "            \n",
    "            if MAX_REFRESH_RATE != 0:\n",
    "                self.next_refresh[obj_id] = env.now + np.random.exponential(1/self.db.mu_values[obj_id])  # Schedule refresh\n",
    "            \n",
    "        \n",
    "    def evict_oldest(self):\n",
    "        \"\"\"Remove the oldest item from the cache to make space.\"\"\"\n",
    "        oldest_id = min(self.initial_fetch, key=self.initial_fetch.get)  # Find the oldest item by age\n",
    "        # print(f\"[{env.now:.2f}] Cache: Evicting oldest object {oldest_id} to make space at {self.ttl[oldest_id]:.2f}\")\n",
    "        self.cumulative_cache_time[obj_id] += (env.now - self.object_start_time[obj_id])\n",
    "        del self.storage[oldest_id]\n",
    "        del self.initial_fetch[oldest_id]\n",
    "        del self.object_start_time[obj_id]\n",
    "\n",
    "    def evict_random(self):\n",
    "        \"\"\"Remove a random item from the cache to make space.\"\"\"\n",
    "        random_id = np.random.choice(list(self.storage.keys()))  # Select a random key from the cache\n",
    "        # print(f\"[{env.now:.2f}] Cache: Evicting random object {random_id} to make space at {self.ttl[random_id]:.2f}\")\n",
    "        self.cumulative_cache_time[obj_id] += (env.now - self.object_start_time[obj_id])\n",
    "        del self.storage[random_id]\n",
    "        del self.initial_fetch[random_id]\n",
    "        del self.object_start_time[obj_id]\n",
    "        \n",
    "    def refresh_object(self, obj_id):\n",
    "        \"\"\"Refresh the object from the database to keep it up-to-date. TTL is increased on refresh.\"\"\"\n",
    "        obj = self.db.get_object(obj_id)\n",
    "        self.storage[obj_id] = obj\n",
    "        if self.cache_type == CacheType.TTL:\n",
    "            self.ttl[obj_id] = env.now + self.db.ttl[obj_id]\n",
    "        self.cumulative_cache_time[obj_id] += (env.now - self.object_start_time[obj_id])\n",
    "        # print(f\"[{env.now:.2f}] Cache: Refreshed object {obj_id}\")\n",
    "        \n",
    "    def check_expired(self):\n",
    "        \"\"\"Increment age of each cached object.\"\"\"\n",
    "        evicted_objects = []\n",
    "        for obj_id in list(self.ttl.keys()):\n",
    "            if self.ttl[obj_id] <= env.now:\n",
    "                # Remove object if its TTL expired\n",
    "                # print(f\"[{env.now:.2f}] Cache: Object {obj_id} expired\")\n",
    "                evicted_objects.append(obj_id)\n",
    "                self.cumulative_cache_time[obj_id] += (env.now - self.object_start_time[obj_id])\n",
    "                del self.storage[obj_id]\n",
    "                del self.ttl[obj_id]\n",
    "                del self.initial_fetch[obj_id]\n",
    "                del self.object_start_time[obj_id]\n",
    "        return evicted_objects\n",
    "\n",
    "                \n",
    "    def record_cache_state(self):\n",
    "        \"\"\"Record the current cache state (number of objects in cache) over time.\"\"\"\n",
    "        self.cache_size_over_time.append((env.now, len(self.storage)))\n",
    "        self.cache_next_request_over_time.append((env.now, self.db.next_request.copy()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "687f5634-8edf-4337-b42f-bbb292d47f0f",
   "metadata": {},
   "outputs": [],
   "source": [
    "def client_request_process(env, cache, event):\n",
    "    \"\"\"Client process that makes requests for objects from the cache.\"\"\"\n",
    "    last_print = 0\n",
    "    with tqdm(total=ACCESS_COUNT_LIMIT, desc=\"Progress\", leave=True) as pbar:\n",
    "        while True:\n",
    "            request_id, next_request = min(cache.db.next_request.items(), key=lambda x: x[1])\n",
    "            eviction_id = -1\n",
    "            next_eviction = float('inf')\n",
    "            if cache.cache_type == CacheType.TTL:\n",
    "                if cache.storage:\n",
    "                    eviction_id, next_eviction = min(cache.ttl.items(), key=lambda x: x[1])\n",
    "                    \n",
    "            if next_request < next_eviction:\n",
    "                # print(f\"[{env.now:.2f}] Waiting for next request...\")\n",
    "                yield env.timeout(next_request - env.now)\n",
    "\n",
    "                if env.now >= next_request:\n",
    "                    # print(f\"[{env.now:.2f}] Client: Requesting object {request_id}. (Cache Size: {len(cache.storage)})\")\n",
    "                    cache.get(request_id)\n",
    "                    \n",
    "                    # print(f\"[{env.now:.2f}] Client: Schedule next request for {request_id}\")\n",
    "                    next_request = env.now + np.random.exponential(1/cache.db.lambda_values[request_id])\n",
    "                    cache.request_log[request_id].append(next_request)\n",
    "                    cache.db.next_request[request_id] = next_request\n",
    "            else:\n",
    "                # print(f\"[{env.now:.2f}] Waiting for next eviction...\")\n",
    "                yield env.timeout(next_eviction - env.now)\n",
    "\n",
    "                if env.now >= next_eviction:\n",
    "                    # print(f\"[{env.now:.2f}] Cache: Object {eviction_id} needs to be evicted (At time: {next_eviction:.2f})\")\n",
    "                    evicted_objects = cache.check_expired()\n",
    "                    assert len(evicted_objects) != 0, \"There was no object to evict.\"\n",
    "                else:\n",
    "                    # print(f\"[{env.now:.2f}] Object {obj_id} was extended.\")\n",
    "                    pass\n",
    "\n",
    "            # For progress bar\n",
    "            if (int(env.now) % 1) == 0 and int(env.now) != last_print:\n",
    "                last_print = int(env.now)\n",
    "                pbar.n = min(cache.access_count.values())\n",
    "                pbar.refresh()\n",
    "            \n",
    "            # Simulation stop condition\n",
    "            if all(access_count >= ACCESS_COUNT_LIMIT for access_count in cache.access_count.values()):\n",
    "                print(f\"Simulation ended after {env.now} seconds.\")\n",
    "                for obj_id in cache.storage.keys():\n",
    "                    cache.cumulative_cache_time[obj_id] += (env.now - cache.object_start_time[obj_id])\n",
    "                event.succeed()\n",
    "            \n",
    "            cache.record_cache_state()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "c8516830-9880-4d9e-a91b-000338baf9d6",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# Initialize simulation environment\n",
    "env = simpy.Environment()\n",
    "\n",
    "# Instantiate components\n",
    "db = Database()\n",
    "cache = Cache(env, db, cache_type)\n",
    "stop_event = env.event()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "2ba34b36-9ed5-4996-9600-11dfd25d8e60",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Progress: 100%|█████████████████████████████████████████████████████████████████████████████████████████▊| 998/1000 [00:11<00:00, 86.93it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Simulation ended after 1423.771050302509 seconds.\n",
      "CPU times: user 9.69 s, sys: 2.03 s, total: 11.7 s\n",
      "Wall time: 11.5 s\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "\n",
    "# Start processes\n",
    "# env.process(age_cache_process(env, cache))\n",
    "env.process(client_request_process(env, cache, stop_event))\n",
    "\n",
    "# Run the simulation\n",
    "env.run(until=stop_event)\n",
    "simulation_end_time = env.now"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "3b6f7c1f-ea54-4496-bb9a-370cee2d2751",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Object 1: Hit Rate = 0.06, Expected Hit Rate = 0.05, Average Time spend in Cache: 0.04, Average Age = 0.00, Expected Age = 0.03\n",
      "Object 2: Hit Rate = 0.04, Expected Hit Rate = 0.05, Average Time spend in Cache: 0.04, Average Age = 0.00, Expected Age = 0.02\n",
      "Object 3: Hit Rate = 0.40, Expected Hit Rate = 0.40, Average Time spend in Cache: 0.35, Average Age = 0.03, Expected Age = 0.09\n",
      "Object 4: Hit Rate = 0.24, Expected Hit Rate = 0.23, Average Time spend in Cache: 0.19, Average Age = 0.01, Expected Age = 0.08\n",
      "Object 5: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 6: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 7: Hit Rate = 0.79, Expected Hit Rate = 0.78, Average Time spend in Cache: 0.75, Average Age = 0.05, Expected Age = 0.05\n",
      "Object 8: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 9: Hit Rate = 0.22, Expected Hit Rate = 0.23, Average Time spend in Cache: 0.19, Average Age = 0.01, Expected Age = 0.08\n",
      "Object 10: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 11: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 12: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 13: Hit Rate = 0.23, Expected Hit Rate = 0.23, Average Time spend in Cache: 0.19, Average Age = 0.01, Expected Age = 0.08\n",
      "Object 14: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 15: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 16: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 17: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 18: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 19: Hit Rate = 0.65, Expected Hit Rate = 0.65, Average Time spend in Cache: 0.60, Average Age = 0.06, Expected Age = 0.08\n",
      "Object 20: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 21: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 22: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 23: Hit Rate = 0.68, Expected Hit Rate = 0.67, Average Time spend in Cache: 0.63, Average Age = 0.06, Expected Age = 0.08\n",
      "Object 24: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 25: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 26: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 27: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 28: Hit Rate = 0.33, Expected Hit Rate = 0.33, Average Time spend in Cache: 0.28, Average Age = 0.02, Expected Age = 0.09\n",
      "Object 29: Hit Rate = 0.05, Expected Hit Rate = 0.05, Average Time spend in Cache: 0.04, Average Age = 0.00, Expected Age = 0.02\n",
      "Object 30: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 31: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 32: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 33: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 34: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 35: Hit Rate = 0.39, Expected Hit Rate = 0.40, Average Time spend in Cache: 0.35, Average Age = 0.03, Expected Age = 0.09\n",
      "Object 36: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 37: Hit Rate = 0.05, Expected Hit Rate = 0.05, Average Time spend in Cache: 0.04, Average Age = 0.00, Expected Age = 0.03\n",
      "Object 38: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 39: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 40: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 41: Hit Rate = 0.33, Expected Hit Rate = 0.33, Average Time spend in Cache: 0.28, Average Age = 0.02, Expected Age = 0.09\n",
      "Object 42: Hit Rate = 0.45, Expected Hit Rate = 0.45, Average Time spend in Cache: 0.39, Average Age = 0.04, Expected Age = 0.09\n",
      "Object 43: Hit Rate = 0.40, Expected Hit Rate = 0.40, Average Time spend in Cache: 0.34, Average Age = 0.03, Expected Age = 0.09\n",
      "Object 44: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 45: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 46: Hit Rate = 0.69, Expected Hit Rate = 0.69, Average Time spend in Cache: 0.65, Average Age = 0.06, Expected Age = 0.07\n",
      "Object 47: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 48: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 49: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 50: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 51: Hit Rate = 0.05, Expected Hit Rate = 0.05, Average Time spend in Cache: 0.04, Average Age = 0.00, Expected Age = 0.03\n",
      "Object 52: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 53: Hit Rate = 0.22, Expected Hit Rate = 0.23, Average Time spend in Cache: 0.18, Average Age = 0.01, Expected Age = 0.08\n",
      "Object 54: Hit Rate = 0.04, Expected Hit Rate = 0.05, Average Time spend in Cache: 0.04, Average Age = 0.00, Expected Age = 0.02\n",
      "Object 55: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 56: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 57: Hit Rate = 0.57, Expected Hit Rate = 0.58, Average Time spend in Cache: 0.52, Average Age = 0.05, Expected Age = 0.08\n",
      "Object 58: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 59: Hit Rate = 0.05, Expected Hit Rate = 0.05, Average Time spend in Cache: 0.04, Average Age = 0.00, Expected Age = 0.03\n",
      "Object 60: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 61: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 62: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 63: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 64: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 65: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 66: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 67: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 68: Hit Rate = 0.22, Expected Hit Rate = 0.23, Average Time spend in Cache: 0.19, Average Age = 0.01, Expected Age = 0.08\n",
      "Object 69: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 70: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 71: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 72: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 73: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 74: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 75: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 76: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 77: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 78: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 79: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 80: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 81: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 82: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 83: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 84: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 85: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 86: Hit Rate = 0.55, Expected Hit Rate = 0.55, Average Time spend in Cache: 0.50, Average Age = 0.05, Expected Age = 0.09\n",
      "Object 87: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 88: Hit Rate = 0.22, Expected Hit Rate = 0.23, Average Time spend in Cache: 0.18, Average Age = 0.01, Expected Age = 0.08\n",
      "Object 89: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 90: Hit Rate = 0.40, Expected Hit Rate = 0.40, Average Time spend in Cache: 0.34, Average Age = 0.03, Expected Age = 0.10\n",
      "Object 91: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 92: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 93: Hit Rate = 0.40, Expected Hit Rate = 0.40, Average Time spend in Cache: 0.35, Average Age = 0.03, Expected Age = 0.10\n",
      "Object 94: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 95: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 96: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 97: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 98: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n",
      "Object 99: Hit Rate = 0.55, Expected Hit Rate = 0.55, Average Time spend in Cache: 0.50, Average Age = 0.05, Expected Age = 0.09\n",
      "Object 100: Hit Rate = 0.00, Expected Hit Rate = 0.00, Average Time spend in Cache: 0.00, Average Age = 0.00, Expected Age = 0.00\n"
     ]
    }
   ],
   "source": [
    "statistics = []\n",
    "# Calculate and print hit rate and average age for each object\n",
    "for obj_id in range(1, len(DATABASE_OBJECTS) + 1):\n",
    "    if cache.access_count[obj_id] != 0:\n",
    "        hit_rate = cache.hits[obj_id] / max(1, cache.access_count[obj_id])  # Avoid division by zero\n",
    "        expected_hit_rate = 1-math.exp(-db.lambda_values[obj_id]*db.ttl[obj_id])\n",
    "        avg_cache_time = cache.cumulative_cache_time[obj_id] / max(1, simulation_end_time)  # Only average over hits\n",
    "        avg_age = sum(cache.cumulative_age[obj_id]) / max(len(cache.cumulative_age[obj_id]), 1)\n",
    "        expected_age = hit_rate / (db.lambda_values[obj_id] * (1 - pow(hit_rate,2)))\n",
    "        print(f\"Object {obj_id}: Hit Rate = {hit_rate:.2f}, Expected Hit Rate = {expected_hit_rate:.2f}, Average Time spend in Cache: {avg_cache_time:.2f}, Average Age = {avg_age:.2f}, Expected Age = {expected_age:.2f}\")\n",
    "        statistics.append({\"obj_id\": obj_id,\"hit_rate\": hit_rate, \"expected_hit_rate\": expected_hit_rate, \"avg_cache_time\":avg_cache_time, \"avg_age\": avg_age, \"expected_age\": expected_age})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "b2d18372-cdba-4151-ae32-5bf45466bf94",
   "metadata": {},
   "outputs": [],
   "source": [
    "stats = pd.DataFrame(statistics)\n",
    "stats.to_csv(f\"{TEMP_BASE_DIR}/hit_age.csv\",index=False)\n",
    "stats.drop(\"obj_id\", axis=1).describe().to_csv(f\"{TEMP_BASE_DIR}/overall_hit_age.csv\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "80971714-44f1-47db-9e89-85be7c885bde",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_1122917/2928359720.py:26: RuntimeWarning: invalid value encountered in divide\n",
      "  age_delta_p = pd.DataFrame(np.where(expected_age.to_numpy().T[0] != 0, age_delta.to_numpy().T[0] / expected_age.to_numpy().T[0], 0), columns=['age_delta in %'])\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>access_count</th>\n",
       "      <th>hits</th>\n",
       "      <th>misses</th>\n",
       "      <th>mu</th>\n",
       "      <th>lambda</th>\n",
       "      <th>hit_rate</th>\n",
       "      <th>optimal_hitrates</th>\n",
       "      <th>expected_hit_rate</th>\n",
       "      <th>expected_hit_rate_delta</th>\n",
       "      <th>avg_cache_time</th>\n",
       "      <th>cache_time_delta</th>\n",
       "      <th>avg_age</th>\n",
       "      <th>expected_age</th>\n",
       "      <th>age_delta</th>\n",
       "      <th>age_delta in %</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2194</td>\n",
       "      <td>122</td>\n",
       "      <td>2072</td>\n",
       "      <td>0</td>\n",
       "      <td>2.0</td>\n",
       "      <td>0.055606</td>\n",
       "      <td>0.0513</td>\n",
       "      <td>0.051241</td>\n",
       "      <td>0.004366</td>\n",
       "      <td>0.039414</td>\n",
       "      <td>0.016193</td>\n",
       "      <td>0.000781</td>\n",
       "      <td>0.027889</td>\n",
       "      <td>-0.027108</td>\n",
       "      <td>-0.971993</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2237</td>\n",
       "      <td>98</td>\n",
       "      <td>2139</td>\n",
       "      <td>0</td>\n",
       "      <td>2.0</td>\n",
       "      <td>0.043809</td>\n",
       "      <td>0.0513</td>\n",
       "      <td>0.051241</td>\n",
       "      <td>-0.007432</td>\n",
       "      <td>0.040331</td>\n",
       "      <td>0.003478</td>\n",
       "      <td>0.000547</td>\n",
       "      <td>0.021946</td>\n",
       "      <td>-0.021399</td>\n",
       "      <td>-0.975066</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>6160</td>\n",
       "      <td>2458</td>\n",
       "      <td>3702</td>\n",
       "      <td>0</td>\n",
       "      <td>5.0</td>\n",
       "      <td>0.399026</td>\n",
       "      <td>0.4000</td>\n",
       "      <td>0.400105</td>\n",
       "      <td>-0.001079</td>\n",
       "      <td>0.346128</td>\n",
       "      <td>0.052898</td>\n",
       "      <td>0.030540</td>\n",
       "      <td>0.094918</td>\n",
       "      <td>-0.064378</td>\n",
       "      <td>-0.678247</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>3576</td>\n",
       "      <td>842</td>\n",
       "      <td>2734</td>\n",
       "      <td>0</td>\n",
       "      <td>3.0</td>\n",
       "      <td>0.235459</td>\n",
       "      <td>0.2254</td>\n",
       "      <td>0.225316</td>\n",
       "      <td>0.010143</td>\n",
       "      <td>0.187388</td>\n",
       "      <td>0.048071</td>\n",
       "      <td>0.012580</td>\n",
       "      <td>0.083093</td>\n",
       "      <td>-0.070513</td>\n",
       "      <td>-0.848607</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>1106</td>\n",
       "      <td>0</td>\n",
       "      <td>1106</td>\n",
       "      <td>0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.0000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>96</th>\n",
       "      <td>1000</td>\n",
       "      <td>0</td>\n",
       "      <td>1000</td>\n",
       "      <td>0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.0000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>97</th>\n",
       "      <td>1055</td>\n",
       "      <td>0</td>\n",
       "      <td>1055</td>\n",
       "      <td>0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.0000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>98</th>\n",
       "      <td>1063</td>\n",
       "      <td>0</td>\n",
       "      <td>1063</td>\n",
       "      <td>0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.0000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>99</th>\n",
       "      <td>11421</td>\n",
       "      <td>6278</td>\n",
       "      <td>5143</td>\n",
       "      <td>0</td>\n",
       "      <td>9.0</td>\n",
       "      <td>0.549689</td>\n",
       "      <td>0.5528</td>\n",
       "      <td>0.552733</td>\n",
       "      <td>-0.003044</td>\n",
       "      <td>0.495182</td>\n",
       "      <td>0.054507</td>\n",
       "      <td>0.048121</td>\n",
       "      <td>0.087522</td>\n",
       "      <td>-0.039401</td>\n",
       "      <td>-0.450188</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>100</th>\n",
       "      <td>1091</td>\n",
       "      <td>0</td>\n",
       "      <td>1091</td>\n",
       "      <td>0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.0000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>100 rows × 15 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "     access_count  hits  misses  mu  lambda  hit_rate  optimal_hitrates  \\\n",
       "1            2194   122    2072   0     2.0  0.055606            0.0513   \n",
       "2            2237    98    2139   0     2.0  0.043809            0.0513   \n",
       "3            6160  2458    3702   0     5.0  0.399026            0.4000   \n",
       "4            3576   842    2734   0     3.0  0.235459            0.2254   \n",
       "5            1106     0    1106   0     1.0  0.000000            0.0000   \n",
       "..            ...   ...     ...  ..     ...       ...               ...   \n",
       "96           1000     0    1000   0     1.0  0.000000            0.0000   \n",
       "97           1055     0    1055   0     1.0  0.000000            0.0000   \n",
       "98           1063     0    1063   0     1.0  0.000000            0.0000   \n",
       "99          11421  6278    5143   0     9.0  0.549689            0.5528   \n",
       "100          1091     0    1091   0     1.0  0.000000            0.0000   \n",
       "\n",
       "     expected_hit_rate  expected_hit_rate_delta  avg_cache_time  \\\n",
       "1             0.051241                 0.004366        0.039414   \n",
       "2             0.051241                -0.007432        0.040331   \n",
       "3             0.400105                -0.001079        0.346128   \n",
       "4             0.225316                 0.010143        0.187388   \n",
       "5             0.000000                 0.000000        0.000000   \n",
       "..                 ...                      ...             ...   \n",
       "96            0.000000                 0.000000        0.000000   \n",
       "97            0.000000                 0.000000        0.000000   \n",
       "98            0.000000                 0.000000        0.000000   \n",
       "99            0.552733                -0.003044        0.495182   \n",
       "100           0.000000                 0.000000        0.000000   \n",
       "\n",
       "     cache_time_delta   avg_age  expected_age  age_delta  age_delta in %  \n",
       "1            0.016193  0.000781      0.027889  -0.027108       -0.971993  \n",
       "2            0.003478  0.000547      0.021946  -0.021399       -0.975066  \n",
       "3            0.052898  0.030540      0.094918  -0.064378       -0.678247  \n",
       "4            0.048071  0.012580      0.083093  -0.070513       -0.848607  \n",
       "5            0.000000  0.000000      0.000000   0.000000        0.000000  \n",
       "..                ...       ...           ...        ...             ...  \n",
       "96           0.000000  0.000000      0.000000   0.000000        0.000000  \n",
       "97           0.000000  0.000000      0.000000   0.000000        0.000000  \n",
       "98           0.000000  0.000000      0.000000   0.000000        0.000000  \n",
       "99           0.054507  0.048121      0.087522  -0.039401       -0.450188  \n",
       "100          0.000000  0.000000      0.000000   0.000000        0.000000  \n",
       "\n",
       "[100 rows x 15 columns]"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "access_count = pd.DataFrame.from_dict(cache.access_count, orient='index', columns=['access_count'])\n",
    "hits = pd.DataFrame.from_dict(cache.hits, orient='index', columns=['hits'])\n",
    "misses = pd.DataFrame.from_dict(cache.misses, orient='index', columns=['misses'])\n",
    "mu = pd.DataFrame.from_dict(db.mu_values, orient='index', columns=['mu'])\n",
    "lmbda = pd.DataFrame.from_dict(db.lambda_values, orient='index', columns=['lambda'])\n",
    "\n",
    "hit_rate = pd.DataFrame(stats['hit_rate'])\n",
    "hit_rate.index = range(1,len(DATABASE_OBJECTS) + 1)\n",
    "optimal_hitrate = pd.DataFrame(BASE_FILE['optimal_hitrates'])\n",
    "optimal_hitrate.index = range(1,len(DATABASE_OBJECTS) + 1)\n",
    "expected_hit_rate = pd.DataFrame(stats['expected_hit_rate'])\n",
    "expected_hit_rate.index = range(1,len(DATABASE_OBJECTS) + 1)\n",
    "expected_hit_rate_delta = pd.DataFrame((hit_rate.to_numpy()-expected_hit_rate.to_numpy()), columns=['expected_hit_rate_delta'])\n",
    "expected_hit_rate_delta.index = range(1,len(DATABASE_OBJECTS) + 1)\n",
    "avg_cache_time = pd.DataFrame(stats['avg_cache_time'])\n",
    "avg_cache_time.index = range(1,len(DATABASE_OBJECTS) + 1)\n",
    "cache_time_delta = pd.DataFrame((hit_rate.to_numpy()-avg_cache_time.to_numpy()), columns=['cache_time_delta'])\n",
    "cache_time_delta.index = range(1,len(DATABASE_OBJECTS) + 1)\n",
    "\n",
    "avg_age = pd.DataFrame(stats['avg_age'])\n",
    "avg_age.index = range(1,len(DATABASE_OBJECTS) + 1)\n",
    "expected_age = pd.DataFrame(stats['expected_age'])\n",
    "expected_age.index = range(1,len(DATABASE_OBJECTS) + 1)\n",
    "age_delta = pd.DataFrame((avg_age.to_numpy()-expected_age.to_numpy()), columns=['age_delta'])\n",
    "age_delta.index = range(1,len(DATABASE_OBJECTS) + 1)\n",
    "age_delta_p = pd.DataFrame(np.where(expected_age.to_numpy().T[0] != 0, age_delta.to_numpy().T[0] / expected_age.to_numpy().T[0], 0), columns=['age_delta in %'])\n",
    "age_delta_p.index = range(1,len(DATABASE_OBJECTS) + 1)\n",
    "\n",
    "merged = access_count.merge(hits, left_index=True, right_index=True).merge(misses, left_index=True, right_index=True) \\\n",
    "    .merge(mu, left_index=True, right_index=True).merge(lmbda, left_index=True, right_index=True) \\\n",
    "    .merge(hit_rate, left_index=True, right_index=True).merge(optimal_hitrate, left_index=True, right_index=True).merge(expected_hit_rate, left_index=True, right_index=True).merge(expected_hit_rate_delta, left_index=True, right_index=True) \\\n",
    "    .merge(avg_cache_time, left_index=True, right_index=True).merge(cache_time_delta, left_index=True, right_index=True) \\\n",
    "    .merge(avg_age, left_index=True, right_index=True).merge(expected_age, left_index=True, right_index=True).merge(age_delta, left_index=True, right_index=True).merge(age_delta_p, left_index=True, right_index=True)\n",
    "merged.to_csv(f\"{TEMP_BASE_DIR}/details.csv\", index_label=\"obj_id\")\n",
    "merged"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "01f8f9ee-c278-4a22-8562-ba02e77f5ddd",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 71352 characters]",
      "text/plain": [
       "<Figure size 3000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Extract recorded data for plotting\n",
    "times, cache_sizes = zip(*cache.cache_size_over_time)\n",
    "\n",
    "# Plot the cache size over time\n",
    "plt.figure(figsize=(30, 5))\n",
    "plt.plot(times, cache_sizes, label=\"Objects in Cache\")\n",
    "plt.xlabel(\"Time (s)\")\n",
    "plt.ylabel(\"Number of Cached Objects\")\n",
    "plt.title(\"Number of Objects in Cache Over Time\")\n",
    "plt.legend()\n",
    "plt.grid(True)\n",
    "plt.savefig(f\"{TEMP_BASE_DIR}/objects_in_cache_over_time.pdf\")\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "f30a0497-9b2e-4ea9-8ebf-6687de19aaa9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 23240 characters]",
      "text/plain": [
       "<Figure size 800x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from collections import Counter\n",
    "# Count occurrences of each number\n",
    "count = Counter(list(db.lambda_values.values()))\n",
    "\n",
    "# Separate the counts into two lists for plotting\n",
    "x = list(count.keys())  # List of unique numbers\n",
    "y = list(count.values())  # List of their respective counts\n",
    "\n",
    "# Plot the data\n",
    "plt.figure(figsize=(8, 6))\n",
    "plt.bar(x, y, color='skyblue')\n",
    "\n",
    "# Adding labels and title\n",
    "plt.xlabel('Number')\n",
    "plt.ylabel('Occurrences')\n",
    "plt.title('Occurance of each lambda in db')\n",
    "plt.savefig(f\"{TEMP_BASE_DIR}/lambda_distribution.pdf\")\n",
    "\n",
    "# Show the plot\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "c192564b-d3c6-40e1-a614-f7a5ee787c4e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 34776 characters]",
      "text/plain": [
       "<Figure size 800x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Plotting lambda against access_count.\n",
    "\n",
    "plt.figure(figsize=(8, 6))\n",
    "plt.scatter(merged['lambda'], merged['access_count'], alpha=0.7, edgecolor='k')\n",
    "plt.title('Lambda vs Access Count', fontsize=14)\n",
    "plt.xlabel('Lambda', fontsize=12)\n",
    "plt.ylabel('Access Count', fontsize=12)\n",
    "plt.grid(alpha=0.3)\n",
    "\n",
    "plt.savefig(f\"{TEMP_BASE_DIR}/lambda_vs_access_count.pdf\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "00a12eea-c805-4209-9143-48fa65619873",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 23424 characters]",
      "text/plain": [
       "<Figure size 800x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from collections import Counter\n",
    "# Count occurrences of each number\n",
    "count = Counter(np.array(list(db.mu_values.values())).round(0))\n",
    "\n",
    "# Separate the counts into two lists for plotting\n",
    "x = list(count.keys())  # List of unique numbers\n",
    "y = list(count.values())  # List of their respective counts\n",
    "\n",
    "# Plot the data\n",
    "plt.figure(figsize=(8, 6))\n",
    "plt.bar(x, y, color='skyblue')\n",
    "\n",
    "# Adding labels and title\n",
    "plt.xlabel('Number')\n",
    "plt.ylabel('Occurrences')\n",
    "plt.title('Occurance of each mu in db (rounded)')\n",
    "\n",
    "# Show the plot\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "adbfeb40-76bd-4224-ac45-65c7b2b2cb7b",
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_requests(object_id: int):\n",
    "    mu = db.mu_values[object_id]\n",
    "    lmb = db.lambda_values[object_id]\n",
    "    rq_log = np.array(cache.request_log[object_id])\n",
    "    df = rq_log[1:] - rq_log[:-1]\n",
    "    pd.DataFrame(df, columns=[f\"{object_id}, mu:{mu:.2f}, lambda: {lmb:.2f}\"]).plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "1f550686-3463-4e50-be83-ceafb27512b0",
   "metadata": {},
   "outputs": [],
   "source": [
    "def print_rate(object_id: int):\n",
    "    # Calculate time intervals between consecutive events\n",
    "    intervals = np.diff(np.array(cache.request_log[object_id]))  # Differences between each event time\n",
    "    \n",
    "    # Calculate the rate per second for each interval\n",
    "    rates = 1 / intervals  # Inverse of the time interval gives rate per second\n",
    "    \n",
    "    # Optional: Calculate the average event rate over all intervals\n",
    "    average_rate = np.mean(rates)\n",
    "    print(\"Average event rate per second:\", average_rate)\n",
    "    print(\"The mu is: \", db.lambda_values[object_id])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "b47990b1-0231-43ac-8bc5-8340abe4a8b3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# os.makedirs(EXPERIMENT_BASE_DIR, exist_ok=True)\n",
    "# folder_name = experiment_name.replace(\" \", \"_\").replace(\"(\", \"\").replace(\")\", \"\").replace(\".\", \"_\")\n",
    "# folder_path = os.path.join(EXPERIMENT_BASE_DIR, folder_name)\n",
    "# os.makedirs(folder_path, exist_ok=True)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "db83cad4-7cc6-4702-ae3a-d1af30a561d2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# file_names = os.listdir(TEMP_BASE_DIR)\n",
    "    \n",
    "# for file_name in file_names:\n",
    "#     shutil.move(os.path.join(TEMP_BASE_DIR, file_name), folder_path)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "graphs",
   "language": "python",
   "name": "graphs"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
