"""
Constants and configuration management module for GUIBRUSHR.
This module handles all constant variables, configuration loading, and UI-related constants
used throughout the GUIBRUSHR application. It provides centralized access to configuration
parameters, paths, and UI settings.
"""
import os
import warnings
from pathlib import Path
from typing import Dict, List, Any, Union
import yaml
from pandas import read_csv
from screeninfo import get_monitors
import matplotlib.colors as mcolors
def _load_yaml_config(file_path: str) -> Dict[str, Any]:
"""Load and parse YAML configuration file."""
try:
with open(file_path, 'r') as f:
return yaml.safe_load(f)
except Exception as e:
raise RuntimeError(f"Failed to load YAML configuration {file_path}: {str(e)}")
def _load_configuration(path_default) -> tuple[Path, List[str]]:
"""Load configuration paths from CSV file."""
if os.environ.get('SPHINX_BUILD'):
return Path('/dummy/prt/path'), ['/dummy/path1', '/dummy/path2']
try:
df = read_csv(str(Path(path_default, "GUIBRUSHR", "Files",
"Configuration_Path", "configuration.csv")))
paths = df["Path"]
if not Path(str(paths[0])).exists():
warnings.warn(f"PRT path does not exist: {paths[0]}. Please check the configuration.", RuntimeWarning)
exit()
if not Path(str(paths[1])).exists():
warnings.warn(f"Target path does not exist: {paths[1]}. Please check the configuration.", RuntimeWarning)
exit()
return Path(str(df["Path"][0])), df["Path"].tolist()
except Exception as e:
raise RuntimeError(f"Failed to load configuration paths: {str(e)}")
def _get_screen_dimensions() -> tuple[int, int]:
"""Get screen dimensions with fallback values."""
if os.environ.get('SPHINX_BUILD'):
return 2200, 1000
try:
monitor = get_monitors()[0]
return monitor.width, monitor.height
except Exception as e:
print(f"Warning: Using default screen dimensions due to: {e}")
return 2200, 1000
def _load_molecular_configs(path_petitradtrans, PARAMS_LIST, LIST_ELEMENT_FOR_HYBRID, condensed_yaml) -> tuple[List[str], List[str], int, int, int, Dict[str, Dict], Dict[str, str], List[str], Dict[str, str]]:
"""Load molecular and condensed species configurations and update parameter lists.
Args:
path_petitradtrans: Path to petitRADTRANS installation
PARAMS_LIST: List of existing parameters
LIST_ELEMENT_FOR_HYBRID: List of elements for hybrid calculations
condensed_yaml: Condensed species configurations
Returns:
tuple containing:
- List of molecular species
- Updated parameter list
- Start index for molecular species
- Start index for elements
- Start index for condensed
- Dictionary of condensed species
- Dictionary mapping condensed species names to keys
- List of condensed species names
- Dictionary of condensed species labels
"""
if os.environ.get('SPHINX_BUILD'):
return [], PARAMS_LIST, 0, 0, 0, {}, {}, [], {}
try:
# Load molecular configurations
paths = Path(path_petitradtrans, "opacities", "lines", "line_by_line")
molecs = sorted([d.name for d in paths.iterdir() if d.is_dir()])
# Load condensed species configurations
all_condensed_molecs_dict = condensed_yaml
all_condensed_molec = {val["name"]: key for key, val in all_condensed_molecs_dict.items()}
paths_condensed = Path(path_petitradtrans, "opacities", "continuum", "clouds")
condensed_names = [cloud.name.removesuffix(".cotable.petitRADTRANS.h5")
for cloud in sorted(paths_condensed.glob('**/*.h5'))]
dict_labels_condensed_molec = {key: val["label"]
for key, val in all_condensed_molecs_dict.items()}
start_molecs = len(PARAMS_LIST)
params_list = PARAMS_LIST + molecs
start_elements = len(params_list)
params_list += LIST_ELEMENT_FOR_HYBRID
start_condensed_molecs = len(params_list)
params_list += all_condensed_molec
# print(f"MOLECULES: {molecs}")
# print(f"HYBRID ELEMENTS: {LIST_ELEMENT_FOR_HYBRID}")
# print(f"CONDENSED: {all_condensed_molec}")
# print(f"PARAMETERS LIST: {params_list}")
return (molecs, params_list, start_molecs, start_elements, start_condensed_molecs,
all_condensed_molecs_dict, all_condensed_molec,
condensed_names, dict_labels_condensed_molec)
except Exception as e:
raise RuntimeError(f"Failed to load molecular configurations: {str(e)}")
[docs]
class ConstantVariables:
"""
Central configuration and constants management class.
This class handles loading and managing all constant variables, configuration parameters,
and UI settings used throughout the application. It provides a single source of truth
for all constant values and configuration parameters.
"""
# Base paths and configuration loading
path_default: Path = Path(__file__).parent.parent.parent.parent.resolve()
ncore = os.cpu_count() - 3 # Reserve 3 cores: 1 for the GUI + 2 for retrieval plots
# Load configurations
path_petitradtrans, paths = _load_configuration(path_default)
configuration_yaml_path = str(Path(path_default, "GUIBRUSHR", "Files", "Configuration_Yaml"))
parameters_yaml = _load_yaml_config(str(Path(configuration_yaml_path, "parameters.yaml")))
molecules_yaml = _load_yaml_config(str(Path(configuration_yaml_path, "molecules.yaml")))
hybrid_yaml = _load_yaml_config(str(Path(configuration_yaml_path, "hybrid_elements.yaml")))
general_yaml = _load_yaml_config(str(Path(configuration_yaml_path, "general.yaml")))
condensed_yaml = _load_yaml_config(str(Path(configuration_yaml_path, "condensed.yaml")))
personalization_file = _load_yaml_config(str(Path(path_default, "GUIBRUSHR", "General_Constants/personalization.yaml")))
# UI Configuration
WINDOW_WIDTH: int = 800
WINDOW_HEIGHT: int = 600
screen_width, screen_height = _get_screen_dimensions()
position_x = (screen_width - WINDOW_WIDTH) // 2
position_y = (screen_height - WINDOW_HEIGHT) // 2
geometry = f"{WINDOW_WIDTH}x{WINDOW_HEIGHT}+{position_x}+{position_y}"
# Database and Table Configuration
COLUMN_WITH_MULTIPLE_VALUES: List[str] = ["instruments"]
NEW_COLUMN_RETRIEVAL_DB: List[str] = ["instruments"]
NEW_COLUMN_RETRIEVAL_DB_TYPE: List[str] = ["character varying(200)"]
# Application Constants
HEIGHT_TABLE_FILTER: int = 8
# Chemical Species Configuration
NOT_VMR_METALS: str = general_yaml["not_vmr_metals"]
HCNO_NEUTRALS: str = general_yaml['HCNO_neutrals']
IONS: str = general_yaml['ions']
ALKALI: str = general_yaml['alkali']
METALS: str = general_yaml['metals']
METAL_OXIDES: str = general_yaml['metal_oxides']
LIST_ELEMENT_FOR_HYBRID: str = sorted(general_yaml["list_element_for_hybrid"].split())
CLIGHT = float(general_yaml["clight"])
SOLAR_TO_JUPITER_MASSES = float(general_yaml["solar_to_jupiter_masses"])
AU = float(general_yaml["au"])
R_JUP_MEAN = float(general_yaml["r_jup_mean"])
R_JUP = float(general_yaml["r_jup"])
M_JUP = float(general_yaml["m_jup"])
R_SUN = float(general_yaml["r_sun"])
M_SUN = float(general_yaml["m_sun"])
G = float(general_yaml["G"])
RATIO_RSUN_RJUP = R_SUN / R_JUP
RATIO_RSUN_RJUP_MEAN = R_SUN / R_JUP_MEAN
# Molecular and Element Configuration
ALL_MOLECS_DICT: Dict[str, Dict] = molecules_yaml
ALL_MOLEC: List[str] = list(ALL_MOLECS_DICT.keys())
DICT_LABELS_MOLEC: Dict[str, str] = {key: val["label"] for key, val in ALL_MOLECS_DICT.items()}
ELEMENTS_DICT: Dict[str, Dict] = hybrid_yaml
ALL_ELEMENT: List[str] = list(ELEMENTS_DICT.keys())
DICT_LABELS_ELEMENTS: Dict[str, str] = {key: val["label"] for key, val in ELEMENTS_DICT.items()}
# Parameters Configuration
PARAMS_DICT: Dict[str, Dict] = parameters_yaml
PARAMS_LIST: List[str] = list(PARAMS_DICT.keys())
LIST_MULTIPLE_PARAM: List[str] = [key for key, details in PARAMS_DICT.items() if details["multi"] >= 1]
LIST_DERIVATIVE_PARAMS: List[str] = [key for key, details in PARAMS_DICT.items() if details["multi"] == 2]
LIST_MOLECULAR_PARAMS: List[str] = [key for key, details in PARAMS_DICT.items() if details["multi"] == 3]
LIST_INSTRUMENTAL_PARAMS: List[str] = [key for key, details in PARAMS_DICT.items() if details["multi"] == 4]
# Load molecular and condensed configurations
(molecs, params_list, start_molecs, start_elements, start_condensed_molecs,
ALL_CONDENSED_MOLECS_DICT, ALL_CONDENSED_MOLEC,
CONDENSED_NAMES, DICT_LABELS_CONDENSED_MOLEC) = _load_molecular_configs(
path_petitradtrans, PARAMS_LIST, LIST_ELEMENT_FOR_HYBRID, condensed_yaml)
# Update DICT_LABELS with all species
DICT_LABELS: Dict[str, str] = {key: val["label"] for key, val in PARAMS_DICT.items()}
DICT_LABELS.update(DICT_LABELS_MOLEC)
DICT_LABELS.update(DICT_LABELS_ELEMENTS)
DICT_LABELS.update(DICT_LABELS_CONDENSED_MOLEC)
# UI Color Configuration
# COLOR_MACRO: str = "#757575" # Material Gray 600
# COLOR_SUB_NOTEBOOK: str = "#9E9E9E" # Material Gray 500
# COLOR_SUB_NOTEBOOK_2: str = "#BDBDBD" # Material Gray 400
# COLOR_SUB_NOTEBOOK_3: str = "#E0E0E0" # Material Gray 300
COLOR_MACRO: str = "#E8B824"
COLOR_SUB_NOTEBOOK: str = "#FFD842"
COLOR_SUB_NOTEBOOK_2: str = "#FFE166"
COLOR_SUB_NOTEBOOK_3: str = "#FFEA8A"
# Colormap Configuration for Cross-Correlation Plots
# 32 hues ordered as rainbow spectrum + grey + matplotlib's inferno
HUES: List[tuple] = [
('#FF0000', 'Red'), ('#FF2200', 'Scarlet'), ('#FF4400', 'Vermillion'),
('#FF6600', 'Orange'), ('#FF8800', 'Amber'), ('#FFAA00', 'Gold'),
('#FFCC00', 'Yellow'), ('#FFFF00', 'Lemon'), ('#CCFF00', 'Chartreuse'),
('#99FF00', 'LimeGreen'), ('#66FF00', 'SpringGreen'), ('#33FF00', 'Green'),
('#00FF33', 'Emerald'), ('#00FF66', 'Aquamarine'), ('#00FF99', 'Turquoise'),
('#00FFCC', 'Cyan'), ('#00CCFF', 'SkyBlue'), ('#0099FF', 'Azure'),
('#0066FF', 'Blue'), ('#0033FF', 'Cobalt'), ('#0000FF', 'Indigo'),
('#3300FF', 'Violet'), ('#6600FF', 'Purple'), ('#9900FF', 'Amethyst'),
('#CC00FF', 'Magenta'), ('#FF00CC', 'Fuchsia'), ('#FF0099', 'Rose'),
('#FF0066', 'Cerise'), ('#FF0033', 'Crimson'), ('#FF69B4', 'HotPink'),
('#808080', 'Grey'), ("inferno", "inferno")
]
# Tab Configuration
LIST_TAB_MACRO = [
["General configuration", COLOR_MACRO],
["Run Retrievals", COLOR_MACRO],
["Retrieval Analysis and Cross Correlation", COLOR_MACRO],
["Telluric Removal", COLOR_MACRO],
["Forward Model", COLOR_MACRO],
["Cross Correlation Night-Forward", COLOR_MACRO],
["Create simulated HR data", COLOR_MACRO],
["DB and Data Interactions", COLOR_MACRO],
]
list_tab_analysis = [
["Posterior distribution", COLOR_SUB_NOTEBOOK],
["Model Plot", COLOR_SUB_NOTEBOOK],
["Cross Correlation Retrievals", COLOR_SUB_NOTEBOOK],
["Resume/Delete Retrievals", COLOR_SUB_NOTEBOOK],
]
list_tab_frame_parameter = [
["Planet + Data", COLOR_SUB_NOTEBOOK],
["Clouds/Haze", COLOR_SUB_NOTEBOOK],
["Temperatures", COLOR_SUB_NOTEBOOK],
["Temperatures2", COLOR_SUB_NOTEBOOK],
["VMR", COLOR_SUB_NOTEBOOK],
["Equilibrium", COLOR_SUB_NOTEBOOK],
["Molecules", COLOR_SUB_NOTEBOOK],
["Condensed elements", COLOR_SUB_NOTEBOOK],
]
list_tab_cross_correlation_1 = [
["Planetary Trace", COLOR_SUB_NOTEBOOK_2],
["Cross Correlation map", COLOR_SUB_NOTEBOOK_2],
]
list_tab_cross_correlation_2 = [
["Planetary Trace", COLOR_SUB_NOTEBOOK_2],
["Cross Correlation map", COLOR_SUB_NOTEBOOK_2],
["Likelihood map", COLOR_SUB_NOTEBOOK_2],
]
list_tab_simulated_hr = [
["Basic Configuration", COLOR_SUB_NOTEBOOK],
["Advanced Parameters", COLOR_SUB_NOTEBOOK],
]
list_tab_simulated_hr_advanced = [
["Atmospheric", COLOR_SUB_NOTEBOOK_2],
["Observational", COLOR_SUB_NOTEBOOK_2],
["Wavelength", COLOR_SUB_NOTEBOOK_2],
["Observatory", COLOR_SUB_NOTEBOOK_2],
]
# Filter Configuration
LIST_TELL_TABLE_FILTER: List[str] = ["None", "PCA_Scikit_Learn", "Eigenvalues"]
LIST_CHEMISTRY_TABLE_FILTER: List[str] = ["None", "Equilibrium", "Free Chemistry"]
LIST_PT_PROFILE_TABLE_FILTER: List[str] = ["None", "isot", "madhu", "guillot", "personalized"]
LIST_SCATTERING_TABLE_FILTER: List[str] = ["None", "True", "False"]
LIST_RESOLUTION_TABLE_FILTER: List[str] = ["None", "High", "High+Low", "Low"]
LIST_ECC_OPI_TABLE_FILTER: List[str] = ["None", "True", "False"]
LIST_RETRIEVAL_MODE_FILTER: List[str] = ["None", "petitRADTRANS", "PyratBay"]
LIST_RAD_MODE_FILTER: List[str] = ["None", "Transmission", "Emission"]
LIST_SCATTERING_TABLE: List[str] = ["True", "False"]
LIST_ECC_OPI_TABLE: List[str] = ["True", "False"]
LIST_CORRELATION_METHODS = ["Numpy", "Normal", "Weighted"]
LIST_RAD_MODE = ["Transmission", "Emission"]
LIST_CHEMISTRY_TABLE = ["Equilibrium", "Free Chemistry", "Hybrid Chemical Equilibrium"]
LIST_PT_PROFILE_TABLE = ["isot", "madhu", "guillot", "personalized", "FourNodeSpline"] + personalization_file["temperature_profile_list"]
LIST_MODEL_REPRO = ["hard", "soft"]
LIST_STANDARDIZE_PCA = ["True", "False", "From pkl"]
LIST_LIMIT_PHASES = ["T14", "T23"]
LIST_TELL_TABLE = ["PCA_Scikit_Learn"] # , "Eigenvalues"]
LIST_RESOLUTION_TABLE = ["High", "High+Low", "Low"]
LIST_RETRIEVAL_MODE = ["petitRADTRANS", "PyratBay"]
LIST_RESOLUTION_DB = ["HR", "LR"]
# DataFrame Configuration
COLUMN_DF_GENERAL_INFO: List[str] = ["Variable", "Value"]
COLUMN_DF_PARAMETERS: List[str] = [
'name', 'is_present', 'molec', 'value', 'scale', 'range_min', 'range_max',
'rayleigh_species', 'in_bestpars', 'mass', 'sigma_prior',
"molec_formula", "constant_vmr", "isotope", "opacity_name_lr"
]
TYPES_DF_PARAMETERS: List[type] = [str, bool, str, float, float, float, float, bool, bool, float, float, str, bool, str, str]
# Folder Configuration
CC_FOLDER: str = "Models"
RESULTS_FOLDER: str = "Retrievals"
HR_INSTRUMENTS: str = "HR_Instruments"
LR_INSTRUMENTS: str = "LR_Instruments"
# Filter Table Configuration
DICT_FILTER_TABLE: Dict[str, Dict[str, Union[str, int, type]]] = {
"ID": {"name": "ID", "index": 0, "type": str},
"Fitted Params": {"name": "Fitted Params", "index": 1, "type": str},
"Priors": {"name": "Priors", "index": 2, "type": str},
"Order_Sel": {"name": "Order_Sel", "index": 3, "type": str},
"Nights": {"name": "Nights and instruments", "index": 4, "type": list},
"Method": {"name": "Method", "index": 5, "type": str},
"Rad Mode": {"name": "Rad Mode", "index": 6, "type": str},
"Exist": {"name": "Exist", "index": 7, "type": str},
"#modes": {"name": "#modes", "index": 8, "type": str},
"#atmos": {"name": "#atmos", "index": 9, "type": str},
"#scatterings": {"name": "#scatterings", "index": 10, "type": bool},
"#eccs": {"name": "#eccs", "index": 11, "type": bool},
"#resolutions": {"name": "#resolutions", "index": 12, "type": str},
"#Params": {"name": "#Params", "index": 13, "type": str},
"#Fixed Params": {"name": "#Fixed Params", "index": 14, "type": str},
"#Molec_EQ": {"name": "#Molec_EQ", "index": 15, "type": str},
"#PriorsComplete": {"name": "#PriorsComplete", "index": 16, "type": str},
"#Instruments": {"name": "#Instruments", "index": 17, "type": str}
}
LIST_FILTER_TABLE_COLUMNS: List[str] = [details["name"] for key, details in DICT_FILTER_TABLE.items()]
LIST_FILTER_TABLE_COLUMN_TYPES: List[type] = [details["type"] for key, details in DICT_FILTER_TABLE.items()]
LIST_OUTPUT_TABLE_COLUMN: tuple[str, ...] = ["PID", "Target", "Time", "ID", "Output"]
# Create colormap dictionary at module level (after class definition to avoid circular imports)
def _make_smooth_mono_cmap(color: str, name: str = 'custom'):
"""
Create a smooth monochromatic colormap with 5 control points.
This is a local copy to avoid circular import with general_functions.
"""
rgb = mcolors.to_rgb(color)
dark = tuple(c * 0.4 for c in rgb)
light = tuple(1 - (1 - c) * 0.4 for c in rgb)
return mcolors.LinearSegmentedColormap.from_list(
name,
['black', dark, color, light, 'white'],
N=256
)
# Generate colormaps from HUES, excluding "inferno" (which uses matplotlib's built-in)
ConstantVariables.CMAPS = {
h[1]: _make_smooth_mono_cmap(h[0], h[1])
for h in ConstantVariables.HUES
if "inferno" not in h
}