| from __future__ import annotations |
|
|
| import numpy as np |
|
|
|
|
| INITIAL_ATTACK_STRENGTH = 0.8 |
| INITIAL_ENERGY = 1.0 |
| INITIAL_FATIGUE = 0.0 |
| VALUE_RANGE = (0.0, 1.0) |
| FATIGUE_EFFECT_FACTOR = 0.8 |
| TISSUE_DAMAGE_FACTOR = 0.3 |
| EXHAUSTION_ON_THRESHOLD = 0.8 |
| EXHAUSTION_OFF_THRESHOLD = 0.2 |
|
|
| T_CELL_TRANSITIONS = { |
| "ATTACK_HIGH": {"base": 0.18, "energy_delta": -0.22, "fatigue_delta": 0.14}, |
| "ATTACK_MEDIUM": {"base": 0.08, "energy_delta": -0.10, "fatigue_delta": 0.05}, |
| "ATTACK_LOW": {"base": 0.05, "energy_delta": -0.06, "fatigue_delta": 0.02}, |
| "REST": {"base": 0.00, "energy_delta": 0.16, "fatigue_delta": -0.15}, |
| } |
|
|
| EXHAUSTED_ACTION_MAP = { |
| "ATTACK_HIGH": "ATTACK_MEDIUM", |
| "ATTACK_MEDIUM": "REST", |
| "ATTACK_LOW": "REST", |
| "REST": "REST", |
| } |
|
|
|
|
| def _clip_unit(value: float) -> float: |
| return float(np.clip(value, *VALUE_RANGE)) |
|
|
|
|
| class TCellAgent: |
| def __init__(self) -> None: |
| self.reset() |
|
|
| def reset(self) -> None: |
| self.attack_strength = _clip_unit(INITIAL_ATTACK_STRENGTH) |
| self.energy = _clip_unit(INITIAL_ENERGY) |
| self.fatigue = _clip_unit(INITIAL_FATIGUE) |
| self.exhausted = False |
|
|
| def step( |
| self, |
| action: str, |
| b_detection: float, |
| pdl1_suppression: float, |
| tumor_resistance: float, |
| ) -> dict: |
| if self.exhausted: |
| |
| |
| action = EXHAUSTED_ACTION_MAP.get(action, "REST") |
|
|
| transition = T_CELL_TRANSITIONS.get(action, T_CELL_TRANSITIONS["ATTACK_LOW"]) |
| base = float(transition["base"]) |
| self.energy = _clip_unit(self.energy + float(transition["energy_delta"])) |
| self.fatigue = _clip_unit(self.fatigue + float(transition["fatigue_delta"])) |
|
|
| |
| |
| effective_damage = ( |
| base |
| * float(np.clip(b_detection, *VALUE_RANGE)) |
| * (1.0 - float(np.clip(pdl1_suppression, *VALUE_RANGE))) |
| * (1.0 - (self.fatigue * FATIGUE_EFFECT_FACTOR)) |
| * (1.0 - float(np.clip(tumor_resistance, *VALUE_RANGE))) |
| ) |
| tissue_damage = base * self.fatigue * TISSUE_DAMAGE_FACTOR |
|
|
| if self.fatigue >= EXHAUSTION_ON_THRESHOLD: |
| self.exhausted = True |
| if self.fatigue <= EXHAUSTION_OFF_THRESHOLD: |
| self.exhausted = False |
|
|
| self.attack_strength = _clip_unit(base) |
| return { |
| "effective_damage": _clip_unit(effective_damage), |
| "tissue_damage": _clip_unit(tissue_damage), |
| "base_damage": _clip_unit(base), |
| } |
|
|
| def get_state(self) -> dict: |
| return { |
| "attack_strength": float(self.attack_strength), |
| "energy": float(self.energy), |
| "fatigue": float(self.fatigue), |
| "exhausted": bool(self.exhausted), |
| } |
|
|