Source code for demos.config
import os
import toml
import orca
import pandas as pd
from pydantic import BaseModel, model_validator, Field
from typing import Literal, Optional
from loguru import logger
from templates.calibration import CalibrationConfig, SimultaneousCalibrationConfig
from datasources import DataSourceModel
CONFIG = None
[docs]
class HHRebalancingModuleConfig(BaseModel):
"""
Configuration for Household Rebalancing module
"""
control_table: str
control_col: str
geoid_col: str
[docs]
class EmploymentModuleConfig(BaseModel):
simultaneous_calibration_config: Optional[SimultaneousCalibrationConfig] = None
enter_model_calibration_procedure: Optional[CalibrationConfig] = None
exit_model_calibration_procedure: Optional[CalibrationConfig] = None
@model_validator(mode="after")
def check_calibration_config_exclusivity(self):
sim_cal = self.simultaneous_calibration_config is not None
enter_cal = self.enter_model_calibration_procedure is not None
exit_cal = self.exit_model_calibration_procedure is not None
if sim_cal and (enter_cal or exit_cal):
raise ValueError(
f"Simultaneous calibration cannot be used at the same time as "
+ f"individual model calibration. Simultaneous selected: {sim_cal}, "
+ f"EnterModel selected: {enter_cal}, ExitModel selected: {exit_cal}"
)
return self
[docs]
class HHReorgModuleConfig(BaseModel):
simultaneous_calibration_config: Optional[SimultaneousCalibrationConfig] = None
geoid_col: Optional[str] = None
[docs]
class MortalityModuleConfig(BaseModel):
calibration_procedure: Optional[CalibrationConfig] = None
[docs]
class BirthModuleConfig(BaseModel):
calibration_procedure: Optional[CalibrationConfig] = None
[docs]
class KidsMovingModuleConfig(BaseModel):
geoid_col: str
calibration_target_share: float = 0.12
calibration_tolerance: float = 0.001
max_iter: int = 100
[docs]
class AgingModuleConfig(BaseModel):
#: Age at which a person qualifies as senior
senior_age: int = 65
[docs]
class DEMOSConfig(BaseModel):
"""
Global configuration for DEMOS. Individual fields in this class control the configuration of each module.
"""
random_seed: int
#: Year represented in synthetic population input
base_year: int
#: Last year of simulation
forecast_year: int = 2020
#: Path to DEMOS outputs
output_dir: str = "../data/output"
#: Name of output HDF5 file. Defaults to `demos_output_{forecast_year}.h5`.
output_fname: str = None
#: List of orca tables to include in output
output_tables: list[str] = None
#: Path to directory with calibration models
calibrated_models_dir: str = None
#: Behavior of inconsistent `persons` input table
inconsistent_persons_table_behavior: Literal["error", "fix", "ignore"] = "error"
#: Name of tables to be initialized as empty
initialize_empty_tables: list[str] = None
#: List of tables to be loaded into orca
tables: Optional[list[DataSourceModel]] = None
#: List of modules to be run
modules: Optional[list[str]] = None
# Module-specific config
aging_module_config: AgingModuleConfig = Field(default_factory=AgingModuleConfig)
employment_module_config: EmploymentModuleConfig = Field(
default_factory=EmploymentModuleConfig
)
hh_reorg_module_config: HHReorgModuleConfig = Field(
default_factory=HHReorgModuleConfig
)
mortality_module_config: MortalityModuleConfig = Field(
default_factory=MortalityModuleConfig
)
birth_module_config: BirthModuleConfig = Field(default_factory=BirthModuleConfig)
hh_rebalancing_module_config: HHRebalancingModuleConfig = Field(
default_factory=HHRebalancingModuleConfig
)
kids_moving_module_config: KidsMovingModuleConfig = Field(
default_factory=KidsMovingModuleConfig
)
def model_post_init(self, __context) -> None:
if self.output_fname is None:
self.output_fname = (
f"{self.output_dir}/demos_output_{self.forecast_year}.h5"
)
os.makedirs(self.output_dir, exist_ok=True)
logger.info(f"Output file set to default: {self.output_fname}")
if self.output_tables is None:
self.output_tables = []
if self.initialize_empty_tables is None:
self.initialize_empty_tables = []
# Load all table datasources
for t in self.tables:
t.load_into_orca()
for n in self.initialize_empty_tables:
orca.add_table(n, pd.DataFrame())
if self.modules is None:
self.modules = [
"aging",
"laborforce_model",
"households_reorg",
"kids_moving_model",
"fatality_model",
"birth_model",
"education_model",
"household_rebalancing",
"update_income",
]
@model_validator(mode="after")
def require_persons_and_households(self):
loaded_table_names = [t.table_name for t in self.tables]
if (
"persons" not in loaded_table_names
or "households" not in loaded_table_names
):
raise ValueError(
f"Both 'persons' and 'households' tables are required. Tables defined: {loaded_table_names}"
)
return self
@model_validator(mode="after")
def require_lcm_county_column(self):
loaded_table_names = [t.table_name for t in self.tables]
if "lcm_county_id" not in orca.get_table("households").columns:
raise ValueError(
f"`lcm_county_id` (County FIPS) is required in the households table"
)
return self
def load_config_file(dir: str) -> DEMOSConfig:
global CONFIG
CONFIG = DEMOSConfig(**toml.load(dir))
def get_config():
global CONFIG
if CONFIG is None:
CONFIG = DEMOSConfig()
return CONFIG