feat(generator): добавлено ускорение модельного времени
- Зачем: - ускоренный стенд должен проходить больше модельного времени и давать соответствующий событийный бюджет. - Что: - расчёт интенсивности переведён на модельную длительность тика. - добавлена устойчивая выборка бюджета при больших λ. - усилены тесты ×K, дневного коэффициента и независимости от настенного часа. - Проверка: - make generator-test. - review gate после issue 03 пройден после исправления underflow Poisson.
This commit is contained in:
@@ -129,7 +129,7 @@ class EventGenerator:
|
||||
|
||||
def _hour_factor(self, now: datetime | None = None) -> float:
|
||||
"""Совместимый wrapper над расчётом часового коэффициента."""
|
||||
return hour_factor(now, self.config.model_timezone)
|
||||
return hour_factor(now or self.config.model_t0, self.config.model_timezone)
|
||||
|
||||
def _calculate_events_count(self, now: datetime | None = None) -> int:
|
||||
"""Совместимый wrapper над расчётом событийного бюджета."""
|
||||
|
||||
@@ -2,17 +2,22 @@
|
||||
|
||||
import math
|
||||
import random
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from clickstream_generator.config import Config
|
||||
|
||||
|
||||
POISSON_KNUTH_MAX_LAMBDA = 100.0
|
||||
|
||||
|
||||
def hour_factor(now: datetime | None = None, model_timezone: str = "UTC") -> float:
|
||||
"""Возвращает коэффициент интенсивности в зависимости от часа дня."""
|
||||
current = now or datetime.now(timezone.utc)
|
||||
current = now
|
||||
if current is None:
|
||||
raise ValueError("now is required for hour_factor")
|
||||
if current.tzinfo is None:
|
||||
current = current.replace(tzinfo=timezone.utc)
|
||||
current = current.replace(tzinfo=ZoneInfo("UTC"))
|
||||
current = current.astimezone(ZoneInfo(model_timezone))
|
||||
hour = current.hour
|
||||
if 9 <= hour <= 18:
|
||||
@@ -22,18 +27,10 @@ def hour_factor(now: datetime | None = None, model_timezone: str = "UTC") -> flo
|
||||
return 1.0
|
||||
|
||||
|
||||
def calculate_events_count(
|
||||
config: Config,
|
||||
rng: random.Random,
|
||||
now: datetime | None = None,
|
||||
) -> int:
|
||||
"""Вычисляет количество событий для текущего тика (Poisson + jitter)."""
|
||||
if now is None:
|
||||
factor = hour_factor()
|
||||
else:
|
||||
factor = hour_factor(now, config.model_timezone)
|
||||
lambda_minute = config.lambda_base_per_min * factor
|
||||
lambda_tick = lambda_minute * (config.tick_seconds / 60.0)
|
||||
def _sample_poisson(lambda_tick: float, rng: random.Random) -> int:
|
||||
"""Разыгрывает Poisson без underflow на больших λ."""
|
||||
if lambda_tick >= POISSON_KNUTH_MAX_LAMBDA:
|
||||
return max(0, round(rng.gauss(lambda_tick, math.sqrt(lambda_tick))))
|
||||
|
||||
count = 0
|
||||
threshold = math.exp(-lambda_tick)
|
||||
@@ -41,7 +38,22 @@ def calculate_events_count(
|
||||
while product > threshold:
|
||||
product *= rng.random()
|
||||
count += 1
|
||||
count -= 1
|
||||
return count - 1
|
||||
|
||||
|
||||
def calculate_events_count(
|
||||
config: Config,
|
||||
rng: random.Random,
|
||||
now: datetime | None = None,
|
||||
) -> int:
|
||||
"""Вычисляет количество событий для текущего тика (Poisson + jitter)."""
|
||||
model_time = now or config.model_t0
|
||||
factor = hour_factor(model_time, config.model_timezone)
|
||||
lambda_minute = config.lambda_base_per_min * factor
|
||||
model_tick_seconds = config.tick_seconds * config.model_time_speed
|
||||
lambda_tick = lambda_minute * (model_tick_seconds / 60.0)
|
||||
|
||||
count = _sample_poisson(lambda_tick, rng)
|
||||
|
||||
if config.jitter_pct > 0:
|
||||
jitter_factor = 1.0 + rng.uniform(
|
||||
|
||||
Reference in New Issue
Block a user