{
 "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",
    "from dataclasses import dataclass, field\n",
    "from typing import List, Union, Dict\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": "code",
   "execution_count": 2,
   "id": "d88effd8-d92b-47d1-9e15-527166073e81",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Types of cache\n",
    "class EvictionStrategy(Enum):\n",
    "    LRU = 1\n",
    "    RANDOM_EVICTION = 2\n",
    "    TTL = 3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "1d6a3c67-f9a5-4d9c-8ade-e1ca6944867c",
   "metadata": {},
   "outputs": [],
   "source": [
    "@dataclass\n",
    "class DatabaseObject:\n",
    "    id: int\n",
    "    data: str\n",
    "    lambda_value: int\n",
    "    mu_value: Union[float, None]\n",
    "    ttl: Union[float, None]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "f40af914-a6c3-4e44-b7de-b3b40a743fb2",
   "metadata": {},
   "outputs": [],
   "source": [
    "@dataclass\n",
    "class CacheObject:\n",
    "    id: int # id of object\n",
    "    data: DatabaseObject # body of object\n",
    "    initial_fetch_timer: float # time at which the object was initially pulled into the cache (object_start_time)\n",
    "    age_timer: float # time at which the object was last pulled into the cache (initial fetch)\n",
    "    next_refresh: Union[float, None] # scheduled time for the object to be requested (for refresh cache)\n",
    "    next_expiry: Union[float, None] # scheduled time for the object to be evicted (for ttl cache) (ttl)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "00a944e4-842b-49ba-bb36-587d9c12fdf4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Base class for all cache types\n",
    "@dataclass\n",
    "class SimulationConfig:\n",
    "    db_objects: Union[int, List[DatabaseObject]]\n",
    "    cache_size: int\n",
    "    eviction_strategy: EvictionStrategy\n",
    "\n",
    "    def __post_init__(self):\n",
    "        if not hasattr(self, 'eviction_strategy') or self.eviction_strategy is None:\n",
    "            raise ValueError(\"Eviction strategy must be defined in subclasses.\")\n",
    "    \n",
    "    def generate_objects(self):\n",
    "        if isinstance(self.db_objects, int):\n",
    "            self.db_objects = [\n",
    "                DatabaseObject(id=i, data=f\"Generated Object {i}\", lambda_value=np.random.zipf(ZIPF_CONSTANT), mu_value=None, ttl=None) \n",
    "                for i in range(self.db_objects)\n",
    "            ]\n",
    "\n",
    "    def from_file(self, path: str, lambda_column_name: str):\n",
    "        df = pd.read_csv(path)\n",
    "        lambdas = df[lambda_column_name]\n",
    "\n",
    "        self.db_objects = [\n",
    "                DatabaseObject(id=i, data=f\"Generated Object {i}\", lambda_value=[i], mu_value=None, ttl=None) \n",
    "                for i in range(self.db_objects)\n",
    "            ]\n",
    "            \n",
    "# Specific cache type variants\n",
    "@dataclass\n",
    "class TTLSimulation(SimulationConfig):\n",
    "    eviction_strategy: EvictionStrategy = field(default=EvictionStrategy.TTL, init=False)\n",
    "    \n",
    "    def generate_objects(self, fixed_ttl):\n",
    "        if isinstance(self.db_objects, int):\n",
    "            self.db_objects = [\n",
    "                DatabaseObject(id=i, data=f\"Generated Object {i}\", lambda_value=np.random.zipf(ZIPF_CONSTANT), mu_value=None, ttl=fixed_ttl) \n",
    "                for i in range(self.db_objects)\n",
    "            ]\n",
    "            \n",
    "    def from_file(self, path: str, lambda_column_name: str, ttl_column_name: str):\n",
    "        df = pd.read_csv(path)\n",
    "        lambdas = df[lambda_column_name]\n",
    "        ttls = df[ttl_column_name]\n",
    "\n",
    "        self.db_objects = [\n",
    "                DatabaseObject(id=i, data=f\"Generated Object {i}\", lambda_value=lambdas[i], mu_value=None, ttl=ttls[i]) \n",
    "                for i in range(self.db_objects)\n",
    "            ]\n",
    "            \n",
    "@dataclass\n",
    "class LRUSimulation(SimulationConfig):\n",
    "    eviction_strategy: EvictionStrategy = field(default=EvictionStrategy.LRU, init=False)\n",
    "\n",
    "@dataclass\n",
    "class RandomEvictionSimulation(SimulationConfig):\n",
    "    eviction_strategy: EvictionStrategy = field(default=EvictionStrategy.RANDOM_EVICTION, init=False)\n",
    "\n",
    "@dataclass\n",
    "class RefreshSimulation(TTLSimulation):\n",
    "    def generate_objects(self, fixed_ttl, max_refresh_rate):\n",
    "        if isinstance(self.db_objects, int):\n",
    "            self.db_objects = [\n",
    "                DatabaseObject(id=i, data=f\"Generated Object {i}\", lambda_value=np.random.zipf(ZIPF_CONSTANT), mu_value=np.random.uniform(1, max_refresh_rate), ttl=fixed_ttl) \n",
    "                for i in range(self.db_objects)\n",
    "            ]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "5cea042f-e9fc-4a1e-9750-de212ca70601",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Database:\n",
    "    data: Dict[int, DatabaseObject]\n",
    "    \n",
    "    def __init__(self, data: List[DatabaseObject]):\n",
    "        self.data = {i: data[i] for i in range(len(data))}\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": 7,
   "id": "499bf543-b2c6-4e4d-afcc-0a6665ce3ae1",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Cache:\n",
    "    capacity: int\n",
    "    eviction_strategy: EvictionStrategy\n",
    "    cache_size_over_time: List[int]  # To record cache state at each interval\n",
    "    storage: Dict[int, CacheObject]\n",
    "    hits: Dict[int, int] # hit counter for each object\n",
    "    misses: Dict[int, int] # miss counter for each object\n",
    "    access_count: Dict[int, int] # access counter for each object (should be hit+miss)\n",
    "    next_request: Dict[int, float] # scheduled time for each object to be requested\n",
    "    cumulative_age: Dict[int, List[float]] # list of ages of each object at the time it was requested (current time - age_timer)\n",
    "    cumulative_cache_time: Dict[int, List[float]] # list of total time of each object spent in cache when it was evicted (current time - initial fetch time)\n",
    "    request_log: Dict[int, List[float]] # list of timestamps when each object was requested\n",
    "    \n",
    "    def __init__(self, env, db, simulation_config):\n",
    "        self.env = env\n",
    "        self.db = db\n",
    "        self.capacity = simulation_config.cache_size\n",
    "        self.eviction_strategy = simulation_config.eviction_strategy\n",
    "        self.cache_size_over_time = []\n",
    "        self.storage = {}\n",
    "\n",
    "        db_object_count = len(self.db.data)\n",
    "        \n",
    "        self.hits = {i: 0 for i in range(db_object_count)}\n",
    "        self.misses = {i: 0 for i in range(db_object_count)}\n",
    "        self.access_count = {i: 0 for i in range(db_object_count)}\n",
    "        self.next_request = {i: np.random.exponential(1/self.db.data[i].lambda_value) for i in range(len(self.db.data))}\n",
    "        self.cumulative_age = {i: [] for i in range(db_object_count)}\n",
    "        self.cumulative_cache_time = {i: [] for i in range(db_object_count)}\n",
    "        self.request_log = {i: [] for i in range(db_object_count)}\n",
    "\n",
    "        \n",
    "    def get(self, obj_id):\n",
    "        assert len(self.storage) <= self.capacity, f\"Too many objects in cache ({len(self.storage)}). \"\n",
    "\n",
    "        # Schedule next request\n",
    "        next_request = self.env.now + np.random.exponential(1/self.db.data[obj_id].lambda_value)\n",
    "        self.request_log[obj_id].append(next_request)\n",
    "        self.next_request[obj_id] = next_request\n",
    "        # print(f\"[{self.env.now:.2f}] Client: Schedule next request for {obj_id}@{next_request:.2f}\")\n",
    "        \n",
    "        if obj_id in self.storage:\n",
    "            # Cache hit: Refresh TTL if TTL-Cache\n",
    "            if self.storage[obj_id].next_expiry:\n",
    "                assert self.env.now <= self.storage[obj_id].next_expiry, f\"[{self.env.now:.2f}] Cache should never hit on an expired cache entry.\"\n",
    "                self.storage[obj_id].next_expiry = self.env.now + self.db.data[obj_id].ttl\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",
    "            age = self.env.now - self.storage[obj_id].age_timer\n",
    "            self.cumulative_age[obj_id].append(age)\n",
    "\n",
    "            assert len(self.cumulative_age[obj_id]) == self.access_count[obj_id], f\"[{self.env.now:.2f}] 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",
    "            # 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 len(self.storage) == self.capacity:\n",
    "                if self.eviction_strategy == EvictionStrategy.LRU:\n",
    "                    self.evict_oldest()\n",
    "                elif self.eviction_strategy == EvictionStrategy.RANDOM_EVICTION:\n",
    "                    self.evict_random()\n",
    "                elif self.eviction_strategy == EvictionStrategy.TTL:\n",
    "                    # print(f\"[{self.env.now:.2f}] Cache: Capacity reached. Not accepting new request.\")\n",
    "                    return\n",
    "    \n",
    "            # Cache miss: increment miss count\n",
    "            self.misses[obj_id] += 1\n",
    "            self.access_count[obj_id] += 1\n",
    "            self.cumulative_age[obj_id].append(0)\n",
    "\n",
    "            # Cache miss: Construct CacheObject from Database Object\n",
    "            db_object = self.db.get_object(obj_id)\n",
    "            initial_fetch_timer=self.env.now\n",
    "            age_timer=self.env.now\n",
    "            next_refresh = (self.env.now + np.random.exponential(1/db_object.mu_value)) if db_object.mu_value is not None else None\n",
    "            next_expiry = (self.env.now + db_object.ttl) if db_object.ttl is not None else None\n",
    "            cache_object = CacheObject(id=obj_id, data=db_object, \n",
    "                                       initial_fetch_timer=initial_fetch_timer, age_timer=age_timer, \n",
    "                                       next_refresh=next_refresh, next_expiry=next_expiry\n",
    "                                      )\n",
    "            self.storage[obj_id] = cache_object\n",
    "            \n",
    "            assert len(self.cumulative_age[obj_id]) == self.access_count[obj_id], f\"[{self.env.now:.2f}] 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",
    "    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",
    "        assert obj_id in self.storage, f\"[{self.env.now:.2f}] Refreshed object has to be in cache\"\n",
    "        db_object = self.db.get_object(obj_id)\n",
    "        age_timer = self.env.now\n",
    "        next_refresh = self.env.now + np.random.exponential(1/db_object.mu_value)\n",
    "        # next_expiry = self.env.now + db_object.ttl if db_object.ttl is not None else None\n",
    "\n",
    "        self.storage[obj_id].data = db_object\n",
    "        self.storage[obj_id].age_timer = age_timer\n",
    "        self.storage[obj_id].next_refresh = next_refresh\n",
    "\n",
    "        # print(f\"[{self.env.now:.2f}] Cache: Refreshed object {obj_id}\")\n",
    "        \n",
    "    def evict_oldest(self):\n",
    "        \"\"\"Remove the oldest item from the cache to make space.\"\"\"\n",
    "        assert self.capacity == len(self.storage), f\"[{self.env.now:.2f}] Expecting cache to be at capacity\"\n",
    "        oldest_id = min(self.storage.items(), key=lambda item: item[1].initial_fetch_timer)[0]\n",
    "        \n",
    "        # print(f\"[{self.env.now:.2f}] Cache: Evicting oldest object {oldest_id}.\")\n",
    "        self.cumulative_cache_time[oldest_id].append(self.env.now - self.storage[oldest_id].initial_fetch_timer)\n",
    "        del self.storage[oldest_id]\n",
    "        \n",
    "    def evict_random(self):\n",
    "        \"\"\"Remove a random item from the cache to make space.\"\"\"\n",
    "        assert self.capacity == len(self.storage), f\"[{self.env.now:.2f}] Expecting cache to be at capacity\"\n",
    "        random_id = np.random.choice(list(self.storage.keys()))  # Select a random key from the cache\n",
    "        \n",
    "        # print(f\"[{self.env.now:.2f}] Cache: Evicting random object {random_id}.\")\n",
    "        self.cumulative_cache_time[random_id].append(self.env.now - self.storage[random_id].initial_fetch_timer)\n",
    "        del self.storage[random_id]\n",
    "        \n",
    "    def check_expired(self, obj_id):\n",
    "        \"\"\"Remove object if its TTL expired.\"\"\"\n",
    "        assert self.storage, f\"[{self.env.now:.2f}] Expecting cache to be not empty\"\n",
    "        assert self.env.now >= self.storage[obj_id].next_expiry\n",
    "        \n",
    "        # print(f\"[{self.env.now:.2f}] Cache: Object {obj_id} expired\")\n",
    "        self.cumulative_cache_time[obj_id].append(self.env.now - self.storage[obj_id].initial_fetch_timer)\n",
    "        del self.storage[obj_id]\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((self.env.now, len(self.storage)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "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.next_request.items(), key=lambda x: x[1])\n",
    "            expiry_id = -1\n",
    "            next_expiry = float('inf')\n",
    "            refresh_id = -1\n",
    "            next_refresh = float('inf')\n",
    "\n",
    "            if cache.storage:\n",
    "                expiry_id, next_expiry = min(cache.storage.items(), key=lambda x: x[1].next_expiry if x[1].next_expiry is not None else float('inf'))\n",
    "                next_expiry = cache.storage[expiry_id].next_expiry\n",
    "                refresh_id, next_refresh = min(cache.storage.items(), key=lambda x: x[1].next_refresh if x[1].next_refresh is not None else float('inf'))\n",
    "                next_refresh = cache.storage[refresh_id].next_refresh\n",
    "\n",
    "            events = [\n",
    "                (request_id, next_request),\n",
    "                (expiry_id, next_expiry),\n",
    "                (refresh_id, next_refresh)\n",
    "            ]\n",
    "\n",
    "            event_id, event_timestamp = min(events, key=lambda x: x[1] if x[1] is not None else float('inf'))\n",
    "            \n",
    "            # if event_id == request_id and event_timestamp == next_request:\n",
    "            #     print(f\"[{env.now:.2f}] Waiting for request...\")\n",
    "            # elif event_id == expiry_id and event_timestamp == next_expiry:\n",
    "            #     print(f\"[{env.now:.2f}] Waiting for expiry until...\")\n",
    "            # elif event_id == refresh_id and event_timestamp == next_refresh:\n",
    "            #     print(f\"[{env.now:.2f}] Waiting for refresh...\")\n",
    "                \n",
    "            yield(env.timeout(event_timestamp - env.now))\n",
    "\n",
    "            if event_id == request_id and event_timestamp == next_request:\n",
    "                assert env.now >= next_request, f\"[{env.now}] Time for request should've been reached for Object {request_id}\"\n",
    "                cache.get(request_id)\n",
    "            elif event_id == expiry_id and event_timestamp == next_expiry:\n",
    "                assert env.now >= next_expiry, f\"[{env.now}] Time for expiry should've been reached for Object {expiry_id}\"\n",
    "                cache.check_expired(expiry_id)\n",
    "            elif event_id == refresh_id and event_timestamp == next_refresh:\n",
    "                assert env.now >= next_refresh, f\"[{env.now}] Time for refresh should've been reached for Object {refresh_id}\"\n",
    "                cache.refresh_object(refresh_id)\n",
    "            else:\n",
    "                assert False, \"Unreachable\"\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].append(env.now - cache.storage[obj_id].initial_fetch_timer)\n",
    "                event.succeed()\n",
    "            \n",
    "            cache.record_cache_state()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "c8516830-9880-4d9e-a91b-000338baf9d6",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "class Simulation:\n",
    "    def __init__(self, simulation_config: Union[TTLSimulation, LRUSimulation, RandomEvictionSimulation, RefreshSimulation]):\n",
    "        # Initialize simulation environment\n",
    "        self.env = simpy.Environment()\n",
    "        \n",
    "        # Instantiate components\n",
    "        self.db = Database(simulation_config.db_objects)\n",
    "        self.cache = Cache(self.env, self.db, simulation_config)\n",
    "\n",
    "    def run_simulation(self):\n",
    "        # Start processes\n",
    "        # env.process(age_cache_process(env, cache))\n",
    "        stop_event = self.env.event()\n",
    "        self.env.process(client_request_process(self.env, self.cache, stop_event))\n",
    "        \n",
    "        # Run the simulation\n",
    "        self.env.run(until=stop_event)\n",
    "        self.end_time = self.env.now"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "e269b607-16b9-46d0-8a97-7324f2002c72",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Simulate with a Cache that does random evictions, We'll have 100 Database Objects and a Cache Size of 10\n",
    "# We'll generate lambdas from a zipf distribution\n",
    "# config = RandomEvictionSimulation(100, 10)\n",
    "# config.generate_objects()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "33fdc5fd-1f39-4b51-b2c7-6ea6acf2b753",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Simulate with a Cache that does lru, We'll have 100 Database Objects and a Cache Size of 10\n",
    "# We'll generate lambdas from a zipf distribution\n",
    "# config = LRUSimulation(100, 10)\n",
    "# config.generate_objects()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "6c391bfd-b294-4ff7-8b22-51777368a6b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Simulate with a Cache that does Refreshes with TTL based eviction, We'll have 100 Database Objects and a Cache Size of 10\n",
    "# We'll generate lambdas from a zipf distribution. Each object will have a fixed ttl of 1 when its pulled into the cache. Mu for the refresh rate is 10\n",
    "# config = RefreshSimulation(100, 80)\n",
    "# config.generate_objects(1, 10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "0a444c9d-53dd-4cab-b8f1-100ad3ab213a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Simulate with a Cache that does TTL based eviction, We'll have 100 Database Objects and a Cache Size of 10\n",
    "# We'll take lambdas from the \"lambda\" column of the file \"../calculated.csv\" and the TTLs for each object from the \"optimal_TTL\" column of the same file.\n",
    "config = TTLSimulation(100, 10)\n",
    "config.from_file(\"../calculated.csv\", \"lambda\", \"optimal_TTL\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "66f65699-a3c9-48c4-8f1f-b9d7834c026a",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Progress: 100%|█████████████████████████████████████████████████████████████████████████████████████████▊| 998/1000 [00:10<00:00, 98.43it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Simulation ended after 1423.771050302509 seconds.\n"
     ]
    }
   ],
   "source": [
    "%%time\n",
    "\n",
    "simulation = Simulation(config)\n",
    "simulation.run_simulation()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "6f900c68-1f34-48d1-b346-ef6ea6911fa5",
   "metadata": {},
   "outputs": [],
   "source": [
    "cache = simulation.cache\n",
    "db = simulation.db\n",
    "simulation_end_time = simulation.end_time\n",
    "database_object_count = len(db.data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "3b6f7c1f-ea54-4496-bb9a-370cee2d2751",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Object 0: 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 1: 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 2: 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 3: 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 4: 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 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.79, Expected Hit Rate = 0.78, Average Time spend in Cache: 0.75, Average Age = 0.05, Expected Age = 0.05\n",
      "Object 7: 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 8: 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 9: 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 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.23, Expected Hit Rate = 0.23, Average Time spend in Cache: 0.19, Average Age = 0.01, Expected Age = 0.08\n",
      "Object 13: 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 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.65, Expected Hit Rate = 0.65, Average Time spend in Cache: 0.60, Average Age = 0.06, Expected Age = 0.08\n",
      "Object 19: 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 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.68, Expected Hit Rate = 0.67, Average Time spend in Cache: 0.63, Average Age = 0.06, Expected Age = 0.08\n",
      "Object 23: 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 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.33, Expected Hit Rate = 0.33, Average Time spend in Cache: 0.28, Average Age = 0.02, Expected Age = 0.09\n",
      "Object 28: 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 29: 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 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.39, Expected Hit Rate = 0.40, Average Time spend in Cache: 0.35, Average Age = 0.03, Expected Age = 0.09\n",
      "Object 35: 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 36: 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 37: 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 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.33, Expected Hit Rate = 0.33, Average Time spend in Cache: 0.28, Average Age = 0.02, Expected Age = 0.09\n",
      "Object 41: 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 42: 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 43: 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 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.69, Expected Hit Rate = 0.69, Average Time spend in Cache: 0.65, Average Age = 0.06, Expected Age = 0.07\n",
      "Object 46: 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 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.05, Expected Hit Rate = 0.05, Average Time spend in Cache: 0.04, Average Age = 0.00, Expected Age = 0.03\n",
      "Object 51: 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 52: 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 53: 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 54: 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 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.57, Expected Hit Rate = 0.58, Average Time spend in Cache: 0.52, Average Age = 0.05, Expected Age = 0.08\n",
      "Object 57: 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 58: 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 59: 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 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.22, Expected Hit Rate = 0.23, Average Time spend in Cache: 0.19, Average Age = 0.01, Expected Age = 0.08\n",
      "Object 68: 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 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.55, Expected Hit Rate = 0.55, Average Time spend in Cache: 0.50, Average Age = 0.05, Expected Age = 0.09\n",
      "Object 86: 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 87: 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 88: 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 89: 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 90: 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 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.40, Expected Hit Rate = 0.40, Average Time spend in Cache: 0.35, Average Age = 0.03, Expected Age = 0.10\n",
      "Object 93: 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 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.55, Expected Hit Rate = 0.55, Average Time spend in Cache: 0.50, Average Age = 0.05, Expected Age = 0.09\n",
      "Object 99: 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(database_object_count):\n",
    "    if cache.access_count[obj_id] != 0:\n",
    "        hit_rate = cache.hits[obj_id] / max(1, cache.access_count[obj_id])\n",
    "        expected_hit_rate = 1-math.exp(-db.data[obj_id].lambda_value*(db.data[obj_id].ttl if db.data[obj_id].ttl is not None else 1))\n",
    "        avg_cache_time = sum(cache.cumulative_cache_time[obj_id]) / max(1, simulation_end_time) \n",
    "        avg_age = sum(cache.cumulative_age[obj_id]) / max(len(cache.cumulative_age[obj_id]), 1)\n",
    "        expected_age = hit_rate / (db.data[obj_id].lambda_value * (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": 17,
   "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": 18,
   "id": "80971714-44f1-47db-9e89-85be7c885bde",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_1122751/3693436280.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>0</th>\n",
       "      <td>2194</td>\n",
       "      <td>122</td>\n",
       "      <td>2072</td>\n",
       "      <td>None</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>1</th>\n",
       "      <td>2237</td>\n",
       "      <td>98</td>\n",
       "      <td>2139</td>\n",
       "      <td>None</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>2</th>\n",
       "      <td>6160</td>\n",
       "      <td>2458</td>\n",
       "      <td>3702</td>\n",
       "      <td>None</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>3</th>\n",
       "      <td>3576</td>\n",
       "      <td>842</td>\n",
       "      <td>2734</td>\n",
       "      <td>None</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>4</th>\n",
       "      <td>1106</td>\n",
       "      <td>0</td>\n",
       "      <td>1106</td>\n",
       "      <td>None</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>95</th>\n",
       "      <td>1000</td>\n",
       "      <td>0</td>\n",
       "      <td>1000</td>\n",
       "      <td>None</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>96</th>\n",
       "      <td>1055</td>\n",
       "      <td>0</td>\n",
       "      <td>1055</td>\n",
       "      <td>None</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>1063</td>\n",
       "      <td>0</td>\n",
       "      <td>1063</td>\n",
       "      <td>None</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>11421</td>\n",
       "      <td>6278</td>\n",
       "      <td>5143</td>\n",
       "      <td>None</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>99</th>\n",
       "      <td>1091</td>\n",
       "      <td>0</td>\n",
       "      <td>1091</td>\n",
       "      <td>None</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",
       "0           2194   122    2072  None     2.0  0.055606            0.0513   \n",
       "1           2237    98    2139  None     2.0  0.043809            0.0513   \n",
       "2           6160  2458    3702  None     5.0  0.399026            0.4000   \n",
       "3           3576   842    2734  None     3.0  0.235459            0.2254   \n",
       "4           1106     0    1106  None     1.0  0.000000            0.0000   \n",
       "..           ...   ...     ...   ...     ...       ...               ...   \n",
       "95          1000     0    1000  None     1.0  0.000000            0.0000   \n",
       "96          1055     0    1055  None     1.0  0.000000            0.0000   \n",
       "97          1063     0    1063  None     1.0  0.000000            0.0000   \n",
       "98         11421  6278    5143  None     9.0  0.549689            0.5528   \n",
       "99          1091     0    1091  None     1.0  0.000000            0.0000   \n",
       "\n",
       "    expected_hit_rate  expected_hit_rate_delta  avg_cache_time  \\\n",
       "0            0.051241                 0.004366        0.039414   \n",
       "1            0.051241                -0.007432        0.040331   \n",
       "2            0.400105                -0.001079        0.346128   \n",
       "3            0.225316                 0.010143        0.187388   \n",
       "4            0.000000                 0.000000        0.000000   \n",
       "..                ...                      ...             ...   \n",
       "95           0.000000                 0.000000        0.000000   \n",
       "96           0.000000                 0.000000        0.000000   \n",
       "97           0.000000                 0.000000        0.000000   \n",
       "98           0.552733                -0.003044        0.495182   \n",
       "99           0.000000                 0.000000        0.000000   \n",
       "\n",
       "    cache_time_delta   avg_age  expected_age  age_delta  age_delta in %  \n",
       "0           0.016193  0.000781      0.027889  -0.027108       -0.971993  \n",
       "1           0.003478  0.000547      0.021946  -0.021399       -0.975066  \n",
       "2           0.052898  0.030540      0.094918  -0.064378       -0.678247  \n",
       "3           0.048071  0.012580      0.083093  -0.070513       -0.848607  \n",
       "4           0.000000  0.000000      0.000000   0.000000        0.000000  \n",
       "..               ...       ...           ...        ...             ...  \n",
       "95          0.000000  0.000000      0.000000   0.000000        0.000000  \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.054507  0.048121      0.087522  -0.039401       -0.450188  \n",
       "99          0.000000  0.000000      0.000000   0.000000        0.000000  \n",
       "\n",
       "[100 rows x 15 columns]"
      ]
     },
     "execution_count": 18,
     "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({l: db.data[l].mu_value for l in range(database_object_count)}, orient='index', columns=['mu'])\n",
    "lmbda = pd.DataFrame.from_dict({l: db.data[l].lambda_value for l in range(database_object_count)}, orient='index', columns=['lambda'])\n",
    "\n",
    "hit_rate = pd.DataFrame(stats['hit_rate'])\n",
    "hit_rate.index = range(database_object_count)\n",
    "optimal_hitrate = pd.DataFrame(BASE_FILE['optimal_hitrates'])\n",
    "optimal_hitrate.index = range(database_object_count)\n",
    "expected_hit_rate = pd.DataFrame(stats['expected_hit_rate'])\n",
    "expected_hit_rate.index = range(database_object_count)\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(database_object_count)\n",
    "avg_cache_time = pd.DataFrame(stats['avg_cache_time'])\n",
    "avg_cache_time.index = range(database_object_count)\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(database_object_count)\n",
    "\n",
    "avg_age = pd.DataFrame(stats['avg_age'])\n",
    "avg_age.index = range(database_object_count)\n",
    "expected_age = pd.DataFrame(stats['expected_age'])\n",
    "expected_age.index = range(database_object_count)\n",
    "age_delta = pd.DataFrame((avg_age.to_numpy()-expected_age.to_numpy()), columns=['age_delta'])\n",
    "age_delta.index = range(database_object_count)\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(database_object_count)\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": 19,
   "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": 20,
   "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([l.lambda_value for l in db.data.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": 21,
   "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": 22,
   "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([l.mu_value if l.mu_value is not None else 0.0 for l in db.data.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": 23,
   "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": 24,
   "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": 25,
   "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": 26,
   "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
}
