Skip to content

Commit f0da7b7

Browse files
committed
poisson-poisson requests sampling
1 parent bdb9ca0 commit f0da7b7

File tree

6 files changed

+200
-78
lines changed

6 files changed

+200
-78
lines changed

DEV_WORKFLOW_GUIDE.md

Lines changed: 2 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# **Development Workflow & Architecture Guide**
22

3-
This document outlines the standardized development workflow, repository architecture, and branching strategy for this project. Adhering to these guidelines ensures consistency, maintainability, and a scalable development process.
3+
This document outlines the standardized development workflow, repository architecture, and branching strategy for the backend of the FastSim project. Adhering to these guidelines ensures consistency, maintainability, and a scalable development process.
44

55
## 1. Technology Stack
66

@@ -13,13 +13,7 @@ The project is built upon the following core technologies:
1313
- **Caching**: Redis
1414
- **Containerization**: Docker
1515

16-
## 2. Architectural Overview: A Multi-Repo Strategy
17-
18-
To promote scalability, team autonomy, and clear separation of concerns, this project adopts a **multi-repo architecture**. Each core component of the system resides in its own dedicated repository. This approach allows for independent development cycles, testing, and deployment.
19-
20-
Our architecture is composed of three main repositories:
21-
22-
### 2.1. Backend Service (`project-backend`)
16+
### 2.1. Backend Service (`FastSim-backend`)
2317

2418
This repository contains all code related to the FastAPI backend service. Its primary responsibility is to handle business logic, interact with the database, and expose a RESTful API.
2519

@@ -61,69 +55,6 @@ project-backend/
6155
* To be testable in isolation.
6256
* To produce a versioned Docker image (`backend:<tag>`) as its main artifact.
6357

64-
### 2.2. Frontend Service (`project-frontend`)
65-
66-
This repository contains all code for the React web application. Its responsibility is to provide the user interface and interact with the backend via its API.
67-
68-
**Folder Structure:**
69-
```
70-
project-frontend/
71-
├── .github/
72-
│ └── workflows/
73-
│ └── main.yml # CI: Tests and publishes the frontend Docker image
74-
├── public/ # Static assets (index.html, favicon, etc.)
75-
├── src/ # Application source code
76-
│ ├── api/ # Functions for backend API calls
77-
│ ├── components/ # Reusable UI components
78-
│ ├── hooks/ # Custom React hooks
79-
│ ├── mocks/ # Service worker mocks for API (for isolated testing)
80-
│ ├── pages/ # Page components
81-
│ └── index.js # React application entrypoint
82-
├── .env.example
83-
├── .gitignore
84-
├── docker-compose.yml # Base local development definition
85-
├── Dockerfile # Multi-stage build for a lean production image
86-
├── package.json
87-
├── package-lock.json
88-
└── README.md # Setup instructions for the frontend service
89-
```
90-
91-
**Key Responsibilities:**
92-
* To be testable in isolation (using a mocked API).
93-
* To produce a versioned, production-ready Docker image (`frontend:<tag>`), typically served by Nginx.
94-
95-
### 2.3. Infrastructure & E2E Tests (`project-master`)
96-
97-
This repository is the "glue" that holds the system together. It does not contain application code but rather the configuration to orchestrate, test, and deploy the entire system.
98-
99-
**Folder Structure:**
100-
```
101-
project-master/
102-
├── .github/
103-
│ └── workflows/
104-
│ ├── e2e-tests.yml # CI: Runs End-to-End tests on a complete stack
105-
│ └── deploy.yml # CD: Handles deployment to environments
106-
├── e2e-tests/ # End-to-End test suite (e.g., Cypress, Playwright)
107-
│ ├── cypress/ # Test code
108-
│ └── cypress.config.js
109-
├── environments/ # Environment-specific configurations
110-
│ ├── staging/
111-
│ │ ├── docker-compose.yml # Docker Compose file for the Staging environment
112-
│ │ └── .env.example
113-
│ └── production/
114-
│ ├── docker-compose.yml # Docker Compose file for the Production environment
115-
│ └── .env.example
116-
├── scripts/ # Deployment and utility scripts
117-
│ ├── deploy-staging.sh
118-
│ └── deploy-prod.sh
119-
└── README.md # Main project README: explains the overall architecture
120-
```
121-
122-
**Key Responsibilities:**
123-
* To define the composition of services for each environment (Staging, Production).
124-
* To run End-to-End tests that validate the integration between services.
125-
* To manage the Continuous Deployment (CD) process.
126-
12758
## 3. Branching Strategy: Git Flow
12859

12960
To manage code development and releases in a structured manner, we use the **Git Flow** branching model.

docker_fs/.env.dev

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
DB_HOST=db
22
DB_PORT=5432
3-
DB_NAME=project_backend_dev
4-
DB_USER=dev_user
5-
DB_PASSWORD=dev_pass
6-
DB_URL=postgresql+asyncpg://dev_user:dev_pass@db:5432/project_backend_dev
3+
DB_NAME=FastSim_dev
4+
DB_USER=FastSim_dev_user
5+
DB_PASSWORD=FastSim_dev_pass
6+
DB_URL=postgresql+asyncpg://FastSim_dev_user:dev_pass@db:5432/FastSim_dev
77

88
PGADMIN_DEFAULT_EMAIL=[email protected]
99
PGADMIN_DEFAULT_PASSWORD=secret

poetry.lock

Lines changed: 72 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ pydantic-settings = "^2.10.1"
2020
pydantic = {extras = ["email"], version = "^2.11.7"}
2121
asyncpg = "^0.30.0"
2222
sqlalchemy-utils = "^0.41.2"
23+
numpy = "^2.3.1"
24+
simpy = "^4.1.1"
2325

2426
[tool.poetry.group.dev.dependencies]
2527
pytest = "^8.4.1"

src/app/core/requests_generator.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
"""
2+
Continuous-time event sampling for the Poisson-Poisson
3+
and Gaussian-Poisson workload model.
4+
"""
5+
6+
from __future__ import annotations
7+
8+
import math
9+
from typing import Iterator, Optional
10+
11+
import numpy as np
12+
from app.schemas.simulation_input import SimulationInput
13+
14+
MIN_TO_SEC_CONVERSION = 60 # 1 minute → 60 s
15+
16+
def uniform_variable_generator(rng: Optional[np.random.Generator] = None) -> float:
17+
"""Return U~Uniform(0, 1)."""
18+
rng = rng or np.random.default_rng()
19+
return float(rng.random())
20+
21+
22+
def poisson_variable_generator(
23+
mean: float,
24+
rng: Optional[np.random.Generator] = None,
25+
) -> int:
26+
"""Return a Poisson-distributed integer with expectation *mean*."""
27+
rng = rng or np.random.default_rng()
28+
return int(rng.poisson(mean))
29+
30+
31+
def poisson_poisson_sampling(
32+
input_data: SimulationInput,
33+
*,
34+
simulation_time_second: int = 3_600,
35+
sampling_window_s: int = MIN_TO_SEC_CONVERSION,
36+
rng: Optional[np.random.Generator] = None,
37+
) -> Iterator[float]:
38+
"""
39+
Yield inter-arrival gaps (seconds) for the compound Poisson–Poisson process.
40+
41+
Algorithm
42+
---------
43+
1. Every *sampling_window_s* seconds, draw
44+
U ~ Poisson(mean_concurrent_user).
45+
2. Compute the aggregate rate
46+
Λ = U * (mean_req_per_minute_per_user / 60) [req/s].
47+
3. While inside the current window, draw gaps
48+
Δt ~ Exponential(Λ) using inverse-CDF.
49+
4. Stop once the virtual clock exceeds *simulation_time_second*.
50+
"""
51+
rng = rng or np.random.default_rng()
52+
53+
# λ_u : mean concurrent users per window
54+
mean_concurrent_user = float(input_data.avg_active_users.mean)
55+
56+
# λ_r / 60 : mean req/s per user
57+
mean_req_per_sec_per_user = (
58+
float(input_data.avg_request_per_minute_per_user.mean) / MIN_TO_SEC_CONVERSION
59+
)
60+
61+
now = 0.0 # virtual clock (s)
62+
window_end = 0.0 # end of the current user window
63+
lam = 0.0 # aggregate rate Λ (req/s)
64+
65+
while now < simulation_time_second:
66+
# (Re)sample U at the start of each window
67+
if now >= window_end:
68+
window_end = now + float(sampling_window_s)
69+
users = poisson_variable_generator(mean_concurrent_user, rng)
70+
lam = users * mean_req_per_sec_per_user
71+
72+
# No users → fast-forward to next window
73+
if lam <= 0.0:
74+
now = window_end
75+
continue
76+
77+
# Exponential gap from a protected uniform value
78+
u_raw = max(uniform_variable_generator(rng), 1e-15)
79+
delta_t = -math.log(1.0 - u_raw) / lam
80+
81+
# End simulation if the next event exceeds the horizon
82+
if now + delta_t > simulation_time_second:
83+
break
84+
85+
# If the gap crosses the window boundary, jump to it
86+
if now + delta_t >= window_end:
87+
now = window_end
88+
continue
89+
90+
now += delta_t
91+
yield delta_t
92+
93+
94+
def request_generator(
95+
input_data: SimulationInput,
96+
*,
97+
simulation_time: int = 3_600,
98+
rng: Optional[np.random.Generator] = None,
99+
) -> Iterator[float]:
100+
"""
101+
Select and return the appropriate inter-arrival generator.
102+
103+
Currently implemented:
104+
• Poisson + Poisson (default)
105+
Gaussian + Poisson will be added later.
106+
"""
107+
dist = input_data.avg_active_users.distribution.lower()
108+
109+
if dist in {"gaussian", "normal"}:
110+
# TODO: implement gaussian_poisson_sampling(...)
111+
raise NotImplementedError("Gaussian–Poisson sampling not yet implemented")
112+
113+
# Default → Poisson + Poisson
114+
return poisson_poisson_sampling(
115+
input_data=input_data,
116+
simulation_time_second=simulation_time,
117+
rng=rng,
118+
)

src/app/schemas/simulation_input.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
class RVConfig(BaseModel):
99
"""class to configure random variables"""
1010

11-
mean: int | float
11+
mean: float
1212
distribution: Literal["poisson", "normal"] = "poisson"
1313
variance: float | None = None
1414

@@ -19,7 +19,7 @@ def check_mean_is_number(
1919
) -> float:
2020
"""Ensure `mean` is numeric, then coerce to float."""
2121
err_msg = "mean must be a number (int or float)"
22-
if not isinstance(v, (int, float)):
22+
if not isinstance(v, float):
2323
raise ValueError(err_msg) # noqa: TRY004
2424
return float(v)
2525

0 commit comments

Comments
 (0)