Source code for GUIBRUSHR.General_Constants.FunctionsAndConstants.Constant_Variables

"""
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 }