From ab12ffbc7b5cfa7d31578b4e5501b7e90f73b3ad Mon Sep 17 00:00:00 2001 From: Matheus Sena Vasconcelos Date: Fri, 24 Sep 2021 23:24:04 -0300 Subject: [PATCH] feat: get settings from .config file --- .config | 37 +++++++++++ application/__init__.py | 0 application/app.py | 40 ------------ requirements.txt | 5 +- simulation/automatos/board.py | 8 +-- simulation/automatos/progress.py | 12 ++-- simulation/core/pathology.py | 12 ++-- simulation/core/prevention.py | 14 ++--- simulation/core/subject.py | 6 +- simulation/report/printer.py | 14 ++--- simulation/report/reporter.py | 14 ++--- simulation/settings.py | 103 +++++++++++++++++++++---------- 12 files changed, 149 insertions(+), 116 deletions(-) create mode 100644 .config delete mode 100644 application/__init__.py delete mode 100644 application/app.py diff --git a/.config b/.config new file mode 100644 index 0000000..0309074 --- /dev/null +++ b/.config @@ -0,0 +1,37 @@ +# SUBJECT +MIN_AGE=5 +MAX_AGE=90 + + +# PATHOLOGY +PROB_INFECTION=0.15 +PROB_DEATH=0.03 +EXPOSED_MAX_DAY=7 +INFECTIOUS_MAX_DAY=14 + + +# PREVENTION MASK +MASK_PROB_INFECTION=0.2 +MASK_PROB_DEATH=1 +MASK_PERC_ACTIVATION=0.4 + +# PREVENTION ISOLATION +ISOLATION_PROB_INFECTION=0.5 +ISOLATION_PROB_DEATH=1 +ISOLATION_PERC_ACTIVATION=0.2 + +# PREVENTION VACCINE +VACCINE_PROB_INFECTION=1 +VACCINE_PROB_DEATH=0.05 +VACCINE_PERC_ACTIVATION=0.6 + + +# BOARD +DIMENSION=51 +CELL_SICK_LOCATION=25 + + +# REPORT +OUTPUT_BASE_DIR='./' +OUTPUT_BOARD_RESOLUTION=800 +OUTPUT_BOARD_DPI=96 diff --git a/application/__init__.py b/application/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/application/app.py b/application/app.py deleted file mode 100644 index 86e4bc7..0000000 --- a/application/app.py +++ /dev/null @@ -1,40 +0,0 @@ -import random -import time - -import uvicorn -from fastapi import FastAPI -from starlette.middleware.cors import CORSMiddleware -from starlette.responses import StreamingResponse - - -def stream() -> bytes: - for _ in range(10): - x = random.random() - yield f'{x:.5f}\n'.encode() - print(f'{x:.5f}') - time.sleep(x) - print('done') - - -app = FastAPI() - -origins = [ - "*" -] - -app.add_middleware( - CORSMiddleware, - allow_origins=origins, - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - - -@app.get('/') -async def _stream(): - return StreamingResponse(stream(), media_type='text') - - -if __name__ == '__main__': - uvicorn.run(app) diff --git a/requirements.txt b/requirements.txt index 74b88b2..fbdc9fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,8 +9,5 @@ pyparsing==2.4.7 python-dateutil==2.8.2 pytz==2021.1 six==1.16.0 -fastapi==0.68.1 imageio==2.9.0 -uvicorn==0.15.0 -starlette==0.14.2 -pydantic==1.8.2 \ No newline at end of file +pydantic[dotenv]==1.8.2 \ No newline at end of file diff --git a/simulation/automatos/board.py b/simulation/automatos/board.py index 7cf9be5..9095bc5 100644 --- a/simulation/automatos/board.py +++ b/simulation/automatos/board.py @@ -3,18 +3,18 @@ from simulation.automatos.cell import Cell from simulation.core.subject import Subject from simulation.core.pathology import ConcretePathology -from simulation.settings import BoardSettings +from simulation.settings import board_settings class Board: def __init__(self): self.board = self.initialize_board() - self.n_cells = BoardSettings.DIMENSION * BoardSettings.DIMENSION + self.n_cells = board_settings.DIMENSION * board_settings.DIMENSION @property def dimension(self) -> tuple[int, int]: - selected_dimension = BoardSettings.DIMENSION + selected_dimension = board_settings.DIMENSION dimension = selected_dimension if selected_dimension % 2 == 1 else selected_dimension + 1 return dimension, dimension @@ -27,7 +27,7 @@ def initialize_board(self) -> np.ndarray: for j in range(y): board[i, j] = Cell(i, j, Subject()) - x, y = BoardSettings.CELL_SICK_POSITION + x, y = board_settings.CELL_SICK_POSITION board[x, y] = Cell(x, y, Subject.create_sick_subject()) return board diff --git a/simulation/automatos/progress.py b/simulation/automatos/progress.py index 044df24..b089377 100644 --- a/simulation/automatos/progress.py +++ b/simulation/automatos/progress.py @@ -1,16 +1,14 @@ -from typing import Type - from simulation.automatos.board import Board from simulation.core.subject import Subject from simulation.report.printer import Printer -from simulation.core.prevention import SocialIsolation, Mask, Vaccine, Prevention, PreventionEnum +from simulation.core.prevention import SocialIsolation, Mask, Vaccine, PreventionEnum from simulation.report.reporter import Reporter -from simulation.settings import PreventionSettings +from simulation.settings import prevention_settings PREVS = { - 'mask': [Mask, PreventionSettings.MASK_PERC_ACTIVATION], - 'isolation': [SocialIsolation, PreventionSettings.ISOLATION_PERC_ACTIVATION], - 'vaccine': [Vaccine, PreventionSettings.VACCINE_PERC_ACTIVATION], + 'mask': [Mask, prevention_settings.MASK_PERC_ACTIVATION], + 'isolation': [SocialIsolation, prevention_settings.ISOLATION_PERC_ACTIVATION], + 'vaccine': [Vaccine, prevention_settings.VACCINE_PERC_ACTIVATION], } diff --git a/simulation/core/pathology.py b/simulation/core/pathology.py index 2c83a85..b0e9a03 100644 --- a/simulation/core/pathology.py +++ b/simulation/core/pathology.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING -from simulation.settings import SubjectSettings, PathologySettings +from simulation.settings import subject_settings, pathology_settings from simulation.core.condition import Condition if TYPE_CHECKING: @@ -15,10 +15,10 @@ def __init__(self, subject: 'Subject'): self.infected_days: int = 0 self.subject = subject - self.prob_infection: float = PathologySettings.PROB_INFECTION - self.prob_death: float = PathologySettings.PROB_DEATH - self.exposed_max_day: float = PathologySettings.EXPOSED_MAX_DAY - self.infectious_max_day: float = PathologySettings.INFECTIOUS_MAX_DAY + self.prob_infection: float = pathology_settings.PROB_INFECTION + self.prob_death: float = pathology_settings.PROB_DEATH + self.exposed_max_day: float = pathology_settings.EXPOSED_MAX_DAY + self.infectious_max_day: float = pathology_settings.INFECTIOUS_MAX_DAY @abstractmethod def infect(self, subject: 'Subject') -> bool: @@ -81,7 +81,7 @@ def should_kill(self) -> bool: if self.subject.condition not in (Condition.EXPOSED, Condition.INFECTIOUS): return False - prob = (self.prob_death * (self.subject.age / SubjectSettings.MAX_AGE)) / self.subject.healthy_lifestyle * self.subject.preventions.get_prob_death() + prob = (self.prob_death * (self.subject.age / subject_settings.MAX_AGE)) / self.subject.healthy_lifestyle * self.subject.preventions.get_prob_death() return random.random() <= prob def evolve(self): diff --git a/simulation/core/prevention.py b/simulation/core/prevention.py index 67ccf1b..adaba8b 100644 --- a/simulation/core/prevention.py +++ b/simulation/core/prevention.py @@ -4,7 +4,7 @@ from operator import mul from typing import TYPE_CHECKING, Type -from simulation.settings import PreventionSettings +from simulation.settings import prevention_settings if TYPE_CHECKING: from simulation.core.subject import Subject @@ -66,8 +66,8 @@ class SocialIsolation(Prevention): def __init__(self, subject: 'Subject'): super().__init__( subject, - PreventionSettings.ISOLATION_PROB_INFECTION, - PreventionSettings.ISOLATION_PROB_DEATH + prevention_settings.ISOLATION_PROB_INFECTION, + prevention_settings.ISOLATION_PROB_DEATH ) def evolve(self): @@ -79,8 +79,8 @@ class Mask(Prevention): def __init__(self, subject: 'Subject'): super().__init__( subject, - PreventionSettings.MASK_PROB_INFECTION, - PreventionSettings.MASK_PROB_DEATH + prevention_settings.MASK_PROB_INFECTION, + prevention_settings.MASK_PROB_DEATH ) def evolve(self): @@ -92,8 +92,8 @@ class Vaccine(Prevention): def __init__(self, subject: 'Subject'): super().__init__( subject, - PreventionSettings.VACCINE_PROB_INFECTION, - PreventionSettings.VACCINE_PROB_DEATH + prevention_settings.VACCINE_PROB_INFECTION, + prevention_settings.VACCINE_PROB_DEATH ) def evolve(self): diff --git a/simulation/core/subject.py b/simulation/core/subject.py index f5d0786..f8a9418 100644 --- a/simulation/core/subject.py +++ b/simulation/core/subject.py @@ -4,8 +4,8 @@ from simulation.core.pathology import ConcretePathology, Pathology, NullPathology from simulation.core.condition import Condition -from simulation.core.prevention import PreventionGroup, SocialIsolation, Mask, Vaccine, Prevention -from simulation.settings import SubjectSettings +from simulation.core.prevention import PreventionGroup, Prevention +from simulation.settings import subject_settings class Subject: @@ -18,7 +18,7 @@ def __new__(cls): def __init__(self): self.id: int = next(self._icounter) - self.age: int = random.randint(SubjectSettings.MIN_AGE, SubjectSettings.MAX_AGE) + self.age: int = random.randint(subject_settings.MIN_AGE, subject_settings.MAX_AGE) self.condition: Condition = Condition.NORMAL self.pathology: Pathology = NullPathology(self) self.preventions: PreventionGroup = PreventionGroup(self, *self._preventions) diff --git a/simulation/report/printer.py b/simulation/report/printer.py index c65f431..311373f 100644 --- a/simulation/report/printer.py +++ b/simulation/report/printer.py @@ -7,7 +7,7 @@ from matplotlib.figure import Figure from simulation.automatos.board import Board -from simulation.settings import ReportSettings +from simulation.settings import report_settings class Printer: @@ -17,8 +17,8 @@ def __init__(self): @staticmethod def initialize_figure() -> tuple[Figure, SubplotBase]: - width, height = ReportSettings.OUTPUT_BOARD_RESOLUTION - dpi = ReportSettings.OUTPUT_BOARD_DPI + width, height = report_settings.OUTPUT_BOARD_DIMENSION + dpi = report_settings.OUTPUT_BOARD_DPI figure = plt.figure(figsize=(width / dpi, height / dpi), dpi=dpi) axis = plt.subplot(1, 1, 1) @@ -43,13 +43,13 @@ def draw(self, board: Board, file_name: str): draw_board = np.array(func(board.board).tolist(), dtype=np.uint8) image = self.imshow(draw_board) - self.figure.savefig(f'{ReportSettings.OUTPUT_BOARD_DIR}/{file_name}.png', bbox_inches='tight') + self.figure.savefig(f'{report_settings.OUTPUT_BOARD_DIR}/{file_name}.png', bbox_inches='tight') return image @classmethod def make_gif(cls): images = [] - for filename in sorted(os.listdir(ReportSettings.OUTPUT_BOARD_DIR)): - images.append(imageio.imread(os.path.join(ReportSettings.OUTPUT_BOARD_DIR, filename))) - imageio.mimsave(f'{ReportSettings.OUTPUT_GIF_DIR}/movie.gif', images) + for filename in sorted(os.listdir(report_settings.OUTPUT_BOARD_DIR)): + images.append(imageio.imread(os.path.join(report_settings.OUTPUT_BOARD_DIR, filename))) + imageio.mimsave(f'{report_settings.OUTPUT_GIF_DIR}/movie.gif', images) diff --git a/simulation/report/reporter.py b/simulation/report/reporter.py index 5cb28cc..39ef66a 100644 --- a/simulation/report/reporter.py +++ b/simulation/report/reporter.py @@ -4,7 +4,7 @@ import pandas as pd from simulation.automatos.board import Board -from simulation.settings import ReportSettings +from simulation.settings import report_settings class Reporter: @@ -30,12 +30,12 @@ def report_steps(self): self.steps_data.loc[len(self.steps_data)] = func(self.flatten_board) - def report_prevt(self, type: str, started_day: int): - assert type in self.prevt_data.columns + def report_prevt(self, prevt_type: str, started_day: int): + assert prevt_type in self.prevt_data.columns - self.prevt_data[type] = started_day + self.prevt_data[prevt_type] = started_day def save(self): - self.cells_data.to_csv(f'{ReportSettings.OUTPUT_SHEET_DIR}/cells.csv', index=False) # noqa - self.steps_data.to_csv(f'{ReportSettings.OUTPUT_SHEET_DIR}/steps.csv', index=False) # noqa - self.prevt_data.to_csv(f'{ReportSettings.OUTPUT_SHEET_DIR}/prevt.csv', index=False) # noqa + self.cells_data.to_csv(f'{report_settings.OUTPUT_SHEET_DIR}/cells.csv', index=False) # noqa + self.steps_data.to_csv(f'{report_settings.OUTPUT_SHEET_DIR}/steps.csv', index=False) # noqa + self.prevt_data.to_csv(f'{report_settings.OUTPUT_SHEET_DIR}/prevt.csv', index=False) # noqa diff --git a/simulation/settings.py b/simulation/settings.py index d74b996..919bb88 100644 --- a/simulation/settings.py +++ b/simulation/settings.py @@ -1,51 +1,92 @@ import os from uuid import uuid4 +from pydantic import BaseSettings, Field, validator + SIMULATION_UUID = str(uuid4()) -class SubjectSettings: - MIN_AGE: int = 5 - MAX_AGE: int = 90 +class SimulationBaseSetting(BaseSettings): + class Config: + env_file = '.config' + env_file_encoding = 'utf-8' + + +class SubjectSettings(SimulationBaseSetting): + MIN_AGE: int = Field(1, gt=0) + MAX_AGE: int = Field(100, le=100) -class PathologySettings: - PROB_INFECTION: float = 0.15 - PROB_DEATH: float = 0.03 - EXPOSED_MAX_DAY: int = 7 - INFECTIOUS_MAX_DAY: int = 14 +class PathologySettings(SimulationBaseSetting): + PROB_INFECTION: float = Field(0.15, ge=0, le=1) + PROB_DEATH: float = Field(0.03, ge=0, le=1) + EXPOSED_MAX_DAY: int = Field(7, gt=0) + INFECTIOUS_MAX_DAY: int = Field(14, gt=0) -class PreventionSettings: +class PreventionSettings(SimulationBaseSetting): # MASK - MASK_PROB_INFECTION: float = 0.2 - MASK_PROB_DEATH: float = 1 - MASK_PERC_ACTIVATION: float = 0.4 + MASK_PROB_INFECTION: float = Field(0.2, ge=0, le=1) + MASK_PROB_DEATH: float = Field(1, ge=0, le=1) + MASK_PERC_ACTIVATION: float = Field(0.4, ge=0, le=1) # ISOLATION - ISOLATION_PROB_INFECTION: float = 0.5 - ISOLATION_PROB_DEATH: float = 1 - ISOLATION_PERC_ACTIVATION: float = 0.2 + ISOLATION_PROB_INFECTION: float = Field(0.5, ge=0, le=1) + ISOLATION_PROB_DEATH: float = Field(1, ge=0, le=1) + ISOLATION_PERC_ACTIVATION: float = Field(0.2, ge=0, le=1) # VACCINE - VACCINE_PROB_INFECTION: float = 1 - VACCINE_PROB_DEATH: float = 0.05 - VACCINE_PERC_ACTIVATION: float = 0.6 + VACCINE_PROB_INFECTION: float = Field(1, ge=0, le=1) + VACCINE_PROB_DEATH: float = Field(0.05, ge=0, le=1) + VACCINE_PERC_ACTIVATION: float = Field(0.6, ge=0, le=1) -class BoardSettings: - DIMENSION: int = 51 - CELL_SICK_POSITION: tuple[int, int] = (25, 25) +class BoardSettings(SimulationBaseSetting): + DIMENSION: int = Field(51, ge=9) + CELL_SICK_LOCATION: int = 25 + @validator('DIMENSION') + def validate_dimension(cls, value: int) -> int: + if value % 2 == 0: + raise RuntimeError('dimension must be an odd number') + return value -class ReportSettings: - OUTPUT_BOARD_RESOLUTION: tuple[int, int] = (800, 800) + @property + def CELL_SICK_POSITION(self) -> tuple[int, int]: # noqa + return self.CELL_SICK_LOCATION, self.CELL_SICK_LOCATION + + +class ReportSettings(SimulationBaseSetting): + OUTPUT_BASE_DIR: str = './' + OUTPUT_BOARD_RESOLUTION: int = Field(800, ge=800, le=1920) OUTPUT_BOARD_DPI: int = 96 - OUTPUT_BOARD_DIR: str = f'./results/{SIMULATION_UUID}/board' - OUTPUT_SHEET_DIR: str = f'./results/{SIMULATION_UUID}/sheet' - OUTPUT_GRAPH_DIR: str = f'./results/{SIMULATION_UUID}/graph' - OUTPUT_GIF_DIR: str = f'./results/{SIMULATION_UUID}/gif' + @property + def OUTPUT_BOARD_DIR(self) -> str: # noqa + return f'./results/{SIMULATION_UUID}/board' + + @property + def OUTPUT_SHEET_DIR(self) -> str: # noqa + return f'./results/{SIMULATION_UUID}/sheet' + + @property + def OUTPUT_GRAPH_DIR(self) -> str: # noqa + return f'./results/{SIMULATION_UUID}/graph' + + @property + def OUTPUT_GIF_DIR(self) -> str: # noqa + return f'./results/{SIMULATION_UUID}/gif' + + @property + def OUTPUT_BOARD_DIMENSION(self) -> tuple[int, int]: # noqa + return self.OUTPUT_BOARD_RESOLUTION, self.OUTPUT_BOARD_RESOLUTION + + +subject_settings = SubjectSettings() +pathology_settings = PathologySettings() +prevention_settings = PreventionSettings() +board_settings = BoardSettings() +report_settings = ReportSettings() -os.makedirs(ReportSettings.OUTPUT_BOARD_DIR, exist_ok=True) -os.makedirs(ReportSettings.OUTPUT_SHEET_DIR, exist_ok=True) -os.makedirs(ReportSettings.OUTPUT_GRAPH_DIR, exist_ok=True) -os.makedirs(ReportSettings.OUTPUT_GIF_DIR, exist_ok=True) +os.makedirs(report_settings.OUTPUT_BOARD_DIR, exist_ok=True) +os.makedirs(report_settings.OUTPUT_SHEET_DIR, exist_ok=True) +os.makedirs(report_settings.OUTPUT_GRAPH_DIR, exist_ok=True) +os.makedirs(report_settings.OUTPUT_GIF_DIR, exist_ok=True)