From aa112200d1490e9e64992d26e9dae90f209f1167 Mon Sep 17 00:00:00 2001 From: "patrick.henkel" Date: Fri, 6 Feb 2026 12:00:39 +0100 Subject: [PATCH 1/5] Added logging print levels and verbosity --- .../bestest_hydronic_heat_pump/P_hp.py | 2 +- physXAI/evaluation/metrics.py | 12 +++---- .../recursive_feature_elimination.py | 17 +++++----- physXAI/models/ann/ann_design.py | 16 +++++---- .../ann/model_construction/ann_models.py | 4 --- .../ann/model_construction/rbf_models.py | 5 ++- .../ann/model_construction/residual_models.py | 2 -- physXAI/models/models.py | 23 ++++++++----- physXAI/preprocessing/constructed.py | 3 +- physXAI/utils/logging.py | 33 +++++++++++++++++-- 10 files changed, 72 insertions(+), 45 deletions(-) diff --git a/executables/bestest_hydronic_heat_pump/P_hp.py b/executables/bestest_hydronic_heat_pump/P_hp.py index 97bf7ec..6164a42 100644 --- a/executables/bestest_hydronic_heat_pump/P_hp.py +++ b/executables/bestest_hydronic_heat_pump/P_hp.py @@ -12,7 +12,7 @@ """ # Setup up logger for saving -Logger.setup_logger(folder_name='P_hp', override=True) +Logger.setup_logger(folder_name='P_hp', override=True, print_level='warning') # File path to data file_path = r"data/bestest_hydronic_heat_pump/pid_data.csv" diff --git a/physXAI/evaluation/metrics.py b/physXAI/evaluation/metrics.py index 7d73901..650c20f 100644 --- a/physXAI/evaluation/metrics.py +++ b/physXAI/evaluation/metrics.py @@ -2,6 +2,7 @@ import numpy as np from sklearn.metrics import mean_squared_error, r2_score from physXAI.preprocessing.training_data import TrainingData, TrainingDataMultiStep, TrainingDataGeneric +from physXAI.utils.logging import Logger class Metrics: @@ -10,8 +11,6 @@ class Metrics: for training, validation, and test datasets. """ - print_evaluate = True - def __init__(self, td: TrainingDataGeneric): """ Initializes the Metrics object by calculating metrics for train, validation (if available), @@ -52,10 +51,9 @@ def evaluate(y_true: np.ndarray, y_pred: np.ndarray, label: str = '') -> dict[st kpis['RMSE' + ' ' + label] = rmse kpis['R2' + ' ' + label] = r2 - if Metrics.print_evaluate: - # print(f"{label} MSE: {mse:.2f}") - print(f"{label} RMSE: {rmse:.2f}") - print(f"{label} R2: {r2:.2f}") + Logger.print(f"{label} MSE: {mse:.2f}", 'debug') + Logger.print(f"{label} RMSE: {rmse:.2f}", 'info') + Logger.print(f"{label} R2: {r2:.2f}", 'info') return kpis @@ -130,7 +128,7 @@ def evaluate(y_true: np.ndarray, y_pred: np.ndarray, label: str = '', **kwargs) for loss in kwargs['pinn_losses']: val = float(loss(y_true, y_pred)) kpis[loss.__name__ + ' ' + label] = val - print(f"{loss.__name__ + ' ' + label}: {val:.2f}") + Logger.print(f"{loss.__name__ + ' ' + label}: {val:.2f}", 'info') return kpis diff --git a/physXAI/feature_selection/recursive_feature_elimination.py b/physXAI/feature_selection/recursive_feature_elimination.py index 718f1f4..1e6350c 100644 --- a/physXAI/feature_selection/recursive_feature_elimination.py +++ b/physXAI/feature_selection/recursive_feature_elimination.py @@ -35,12 +35,12 @@ def search_best_features(runs: dict, multi_step: bool, use_multi_step_error: boo except ValueError: max_features = np.inf - print('Selected features:') + Logger.print('Selected features:', 'info') if max_features == np.inf: inputs = sorted_kpis[min_index]['inputs'] else: inputs = sorted_kpis[max_features]['inputs'] - print(inputs) + Logger.print(inputs, 'info') return inputs @@ -53,12 +53,11 @@ def recursive_feature_elimination(file_path: str, preprocessing: PreprocessingDa if fixed_inputs is None: fixed_inputs = list() - print('Feature Selection') - Metrics.print_evaluate = False - if Logger._logger is None: Logger.setup_logger() + Logger.print('Feature Selection', 'info') + org_inputs = preprocessing.inputs inputs = preprocessing.inputs input_length = len(inputs) @@ -83,8 +82,8 @@ def recursive_feature_elimination(file_path: str, preprocessing: PreprocessingDa # Recursive feature elimination for j in range(input_length - 1, 0, -1): - print(f'Features {j + 1}') - print(inputs) + Logger.print(f'Features {j + 1}', 'info') + Logger.print(inputs, 'info') # Reduced input features new_inputs = list() @@ -137,8 +136,8 @@ def recursive_feature_elimination(file_path: str, preprocessing: PreprocessingDa key_filter = int(min(kpis, key=kpis.get)) inputs = new_inputs[key_filter] runs[j] = run - print(f'Features {1}') - print(inputs) + Logger.print(f'Features {1}', 'info') + Logger.print(inputs, 'info') preprocessing.inputs = org_inputs diff --git a/physXAI/models/ann/ann_design.py b/physXAI/models/ann/ann_design.py index 4097ead..e42a5be 100644 --- a/physXAI/models/ann/ann_design.py +++ b/physXAI/models/ann/ann_design.py @@ -80,7 +80,7 @@ def fit_model(self, model, td: TrainingDataGeneric): callbacks = list() if self.early_stopping_epochs is not None: es = keras.callbacks.EarlyStopping(monitor='val_loss', mode='min', patience=self.early_stopping_epochs, - restore_best_weights=True, verbose=1) + restore_best_weights=True, verbose=Logger.verbosity()) callbacks.append(es) # Fit model, track training time @@ -111,14 +111,16 @@ def fit_model(self, model, td: TrainingDataGeneric): training_history = model.fit(train_ds, validation_data=val_ds, epochs=self.epochs, - callbacks=callbacks) + callbacks=callbacks, + verbose=Logger.verbosity()) stop_time = time.perf_counter() # Add metrics to training data td.add_training_time(stop_time - start_time) td.add_training_record(training_history) - model.summary() + if Logger.check_print_level('info'): + model.summary() def plot(self, td: TrainingDataGeneric): """ @@ -587,10 +589,10 @@ def evaluate(self, model, td: TrainingDataGeneric): td (TrainingData): The training data """ - y_pred_train = model.predict(td.X_train_single) - y_pred_test = model.predict(td.X_test_single) + y_pred_train = model.predict(td.X_train_single, verbose=Logger.verbosity()) + y_pred_test = model.predict(td.X_test_single, verbose=Logger.verbosity()) if td.X_val is not None: - y_pred_val = model.predict(td.X_val_single) + y_pred_val = model.predict(td.X_val_single, verbose=Logger.verbosity()) else: y_pred_val = None td.add_predictions(y_pred_train, y_pred_val, y_pred_test) @@ -703,7 +705,7 @@ def fit_model(self, model, td: TrainingDataMultiStep): callbacks = list() if self.early_stopping_epochs is not None: es = keras.callbacks.EarlyStopping(monitor='val_loss', mode='min', patience=self.early_stopping_epochs, - restore_best_weights=True, verbose=1) + restore_best_weights=True, verbose=Logger.verbosity()) callbacks.append(es) # Fit model, track training time diff --git a/physXAI/models/ann/model_construction/ann_models.py b/physXAI/models/ann/model_construction/ann_models.py index 97c2563..0e5bae6 100644 --- a/physXAI/models/ann/model_construction/ann_models.py +++ b/physXAI/models/ann/model_construction/ann_models.py @@ -67,8 +67,6 @@ def ClassicalANNConstruction(config: dict, td: TrainingDataGeneric): if config['rescale_output']: model.add(keras.layers.Rescaling(scale=rescale_sigma, offset=rescale_mean)) - model.summary() - return model @@ -182,6 +180,4 @@ def CMNNModelConstruction(config: dict, td: TrainingDataGeneric): model = keras.models.Model(inputs=input_layer, outputs=x) - model.summary() - return model diff --git a/physXAI/models/ann/model_construction/rbf_models.py b/physXAI/models/ann/model_construction/rbf_models.py index 65b763d..8de36db 100644 --- a/physXAI/models/ann/model_construction/rbf_models.py +++ b/physXAI/models/ann/model_construction/rbf_models.py @@ -5,6 +5,7 @@ from physXAI.preprocessing.training_data import TrainingDataGeneric from physXAI.models.ann.configs.ann_model_configs import RBFConstruction_config from physXAI.models.ann.keras_models.keras_models import RBFLayer +from physXAI.utils.logging import Logger def gamma_init(centers, overlap=0.5) -> float: @@ -26,7 +27,7 @@ def gamma_init(centers, overlap=0.5) -> float: return 1.0 # Fallback gamma = -np.log(overlap) / avg_dist_sq - # print(f"Calculated Gamma: {gamma}") + Logger.print(f"Calculated Gamma: {gamma}", 'info') return gamma @@ -111,6 +112,4 @@ def RBFModelConstruction(config: dict, td: TrainingDataGeneric): model = keras.Model(inputs=input_layer, outputs=x) - model.summary() - return model diff --git a/physXAI/models/ann/model_construction/residual_models.py b/physXAI/models/ann/model_construction/residual_models.py index 70ca9ad..0314595 100644 --- a/physXAI/models/ann/model_construction/residual_models.py +++ b/physXAI/models/ann/model_construction/residual_models.py @@ -57,6 +57,4 @@ def LinResidualANNConstruction(config: dict, td: TrainingDataGeneric, lin_model: lin.set_weights([lin_model.coef_.reshape(-1, 1), np.array(lin_model.intercept_)]) lin.trainable = False - model.summary() - return model diff --git a/physXAI/models/models.py b/physXAI/models/models.py index 14b40c0..a7037f1 100644 --- a/physXAI/models/models.py +++ b/physXAI/models/models.py @@ -1,3 +1,4 @@ +import os import time from abc import ABC, abstractmethod from typing import Type @@ -10,6 +11,9 @@ from physXAI.evaluation.metrics import Metrics, MetricsMultiStep from physXAI.plotting.plotting import (plot_prediction_correlation, plot_metrics_table, subplots, plot_predictions, plot_multi_rmse) +os.environ['TF_CPP_MIN_LOG_LEVEL'] = '1' +import keras +os.environ['TF_CPP_MIN_LOG_LEVEL'] = '0' MODEL_CLASS_REGISTRY: dict[str, Type['AbstractModel']] = dict() @@ -199,7 +203,7 @@ def register_model(cls): # pragma: no cover The class is registered using its __name__. """ if cls.__name__ in MODEL_CLASS_REGISTRY: - print(f"Warning: Class '{cls.__name__}' is already registered. Overwriting.") + Logger.print(f"Warning: Class '{cls.__name__}' is already registered. Overwriting.", 'warning') MODEL_CLASS_REGISTRY[cls.__name__] = cls return cls # Decorators must return the class (or a replacement) @@ -224,11 +228,14 @@ def evaluate(model, td: TrainingDataGeneric): model: The trained model instance. td (TrainingDataGeneric): The TrainingData object containing datasets and for storing results. """ + kwargs = {} + if isinstance(model, keras.Model): + kwargs['verbose'] = Logger.verbosity() - y_pred_train = model.predict(td.X_train_single) - y_pred_test = model.predict(td.X_test_single) + y_pred_train = model.predict(td.X_train_single, **kwargs) + y_pred_test = model.predict(td.X_test_single, **kwargs) if td.X_val_single is not None: - y_pred_val = model.predict(td.X_val_single) + y_pred_val = model.predict(td.X_val_single, **kwargs) else: y_pred_val = None td.add_predictions(y_pred_train, y_pred_val, y_pred_test) @@ -307,7 +314,7 @@ def _evaluate_multi_inner_loop(model, X: np.ndarray, y: np.ndarray, X_columns: l current_val = X[:, 0, index].reshape(-1, 1) current_true_val = current_val.copy() for t in range(X.shape[1]): - pred = model.predict(X[:, t, :], verbose=0) + pred = model.predict(X[:, t, :], verbose=Logger.verbosity()) if delta_prediction: current_val += pred current_true_val += y[:, t, 0].reshape(-1, 1) @@ -463,12 +470,12 @@ def evaluate(model, td: TrainingDataMultiStep): td (TrainingDataMultistep): The TrainingDataMultiStep object containing datasets and for storing results. """ - y_pred_train = model.predict(td.X_train) + y_pred_train = model.predict(td.X_train, verbose=Logger.verbosity()) if td.X_val is not None: - y_pred_val = model.predict(td.X_val) + y_pred_val = model.predict(td.X_val, verbose=Logger.verbosity()) else: y_pred_val = None - y_pred_test = model.predict(td.X_test) + y_pred_test = model.predict(td.X_test, verbose=Logger.verbosity()) td.add_predictions(y_pred_train, y_pred_val, y_pred_test) metrics = MetricsMultiStep(td) diff --git a/physXAI/preprocessing/constructed.py b/physXAI/preprocessing/constructed.py index cd7e429..b1292bd 100644 --- a/physXAI/preprocessing/constructed.py +++ b/physXAI/preprocessing/constructed.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from logging import Logger from typing import Type, Union import numpy as np from pandas import DataFrame, Series @@ -135,7 +136,7 @@ def register_feature(cls): The class is registered using its __name__. """ if cls.__name__ in CONSTRUCTED_CLASS_REGISTRY: # pragma: no cover - print(f"Warning: Class '{cls.__name__}' is already registered. Overwriting.") # pragma: no cover + Logger.print(f"Warning: Class '{cls.__name__}' is already registered. Overwriting.", 'warning') # pragma: no cover CONSTRUCTED_CLASS_REGISTRY[cls.__name__] = cls return cls # Decorators must return the class (or a replacement) diff --git a/physXAI/utils/logging.py b/physXAI/utils/logging.py index 7f873d7..272d56d 100644 --- a/physXAI/utils/logging.py +++ b/physXAI/utils/logging.py @@ -28,10 +28,10 @@ def get_parent_working_directory() -> str: git_root = repo.working_tree_dir return git_root except git.InvalidGitRepositoryError: # pragma: no cover - print(f"Error: Cannot find git root directory.") # pragma: no cover + Logger.print(f"Error: Cannot find git root directory.", 'error') # pragma: no cover return '' # pragma: no cover except Exception as e: # pragma: no cover - print(f"Error: An unexpected error occurred when searching for parent directory: {e}") # pragma: no cover + Logger.print(f"Error: An unexpected error occurred when searching for parent directory: {e}", 'error') # pragma: no cover return '' # pragma: no cover @@ -110,9 +110,33 @@ class Logger: save_name_model: str = 'model' save_name_model_online_learning: str = 'model_ol' + print_level: str = 'info' # options: 'debug', 'info', 'warning', 'error' + _print_levels = ['debug', 'info', 'warning', 'error'] + _logger = None _override = False + @staticmethod + def print(message: str, print_level: str = 'info'): + if Logger.check_print_level(print_level): + print(message) + + @staticmethod + def check_print_level(print_level: str) -> bool: + if print_level not in Logger._print_levels: + raise ValueError(f"Invalid print level: {print_level}. Valid options are: {Logger._print_levels}") + if Logger._print_levels.index(print_level) >= Logger._print_levels.index(Logger.print_level): + return True + else: + return False + + @staticmethod + def verbosity() -> git.Union[int, str]: + if Logger._print_levels.index(Logger.print_level) >= Logger._print_levels.index('warning'): + return 0 + else: + return "auto" + @staticmethod def override_question(path: str): # pragma: no cover if os.path.exists(path) and not Logger._override: @@ -138,7 +162,7 @@ def already_exists_question(path: str): # pragma: no cover raise e @staticmethod - def setup_logger(folder_name: str = None, override: bool = False, base_path: str = None): + def setup_logger(folder_name: str = None, override: bool = False, base_path: str = None, print_level: str = None): if base_path is None: base_path = Logger.base_path if folder_name is None: @@ -153,6 +177,9 @@ def setup_logger(folder_name: str = None, override: bool = False, base_path: str Logger._logger = path Logger._override = override + + if print_level is not None: + Logger.print_level = print_level @staticmethod def log_setup(preprocessing=None, model=None, save_name_preprocessing=None, save_name_model=None, From 5879fdc513f8b7c05c8e14a5c01e103b7ce440e4 Mon Sep 17 00:00:00 2001 From: "patrick.henkel" Date: Fri, 6 Feb 2026 14:05:42 +0100 Subject: [PATCH 2/5] Bug fix --- physXAI/models/ann/ann_design.py | 4 ++-- physXAI/preprocessing/constructed.py | 2 +- physXAI/utils/logging.py | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/physXAI/models/ann/ann_design.py b/physXAI/models/ann/ann_design.py index e42a5be..f03d925 100644 --- a/physXAI/models/ann/ann_design.py +++ b/physXAI/models/ann/ann_design.py @@ -80,7 +80,7 @@ def fit_model(self, model, td: TrainingDataGeneric): callbacks = list() if self.early_stopping_epochs is not None: es = keras.callbacks.EarlyStopping(monitor='val_loss', mode='min', patience=self.early_stopping_epochs, - restore_best_weights=True, verbose=Logger.verbosity()) + restore_best_weights=True, verbose=Logger.verbosity_float()) callbacks.append(es) # Fit model, track training time @@ -705,7 +705,7 @@ def fit_model(self, model, td: TrainingDataMultiStep): callbacks = list() if self.early_stopping_epochs is not None: es = keras.callbacks.EarlyStopping(monitor='val_loss', mode='min', patience=self.early_stopping_epochs, - restore_best_weights=True, verbose=Logger.verbosity()) + restore_best_weights=True, verbose=Logger.verbosity_float()) callbacks.append(es) # Fit model, track training time diff --git a/physXAI/preprocessing/constructed.py b/physXAI/preprocessing/constructed.py index b1292bd..f01fd1d 100644 --- a/physXAI/preprocessing/constructed.py +++ b/physXAI/preprocessing/constructed.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from logging import Logger +from physXAI.utils.logging import Logger from typing import Type, Union import numpy as np from pandas import DataFrame, Series diff --git a/physXAI/utils/logging.py b/physXAI/utils/logging.py index 272d56d..6684b66 100644 --- a/physXAI/utils/logging.py +++ b/physXAI/utils/logging.py @@ -136,6 +136,13 @@ def verbosity() -> git.Union[int, str]: return 0 else: return "auto" + + @staticmethod + def verbosity_float() -> git.Union[int, str]: + if Logger._print_levels.index(Logger.print_level) >= Logger._print_levels.index('warning'): + return 0 + else: + return 1 @staticmethod def override_question(path: str): # pragma: no cover From 35fde92f11c067113cab57f8f00608a790789f63 Mon Sep 17 00:00:00 2001 From: "patrick.henkel" Date: Fri, 6 Feb 2026 14:09:25 +0100 Subject: [PATCH 3/5] Bug fix --- physXAI/models/ann/ann_design.py | 4 ++-- physXAI/utils/logging.py | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/physXAI/models/ann/ann_design.py b/physXAI/models/ann/ann_design.py index f03d925..a5b90da 100644 --- a/physXAI/models/ann/ann_design.py +++ b/physXAI/models/ann/ann_design.py @@ -80,7 +80,7 @@ def fit_model(self, model, td: TrainingDataGeneric): callbacks = list() if self.early_stopping_epochs is not None: es = keras.callbacks.EarlyStopping(monitor='val_loss', mode='min', patience=self.early_stopping_epochs, - restore_best_weights=True, verbose=Logger.verbosity_float()) + restore_best_weights=True, verbose=Logger.verbosity_int()) callbacks.append(es) # Fit model, track training time @@ -705,7 +705,7 @@ def fit_model(self, model, td: TrainingDataMultiStep): callbacks = list() if self.early_stopping_epochs is not None: es = keras.callbacks.EarlyStopping(monitor='val_loss', mode='min', patience=self.early_stopping_epochs, - restore_best_weights=True, verbose=Logger.verbosity_float()) + restore_best_weights=True, verbose=Logger.verbosity_int()) callbacks.append(es) # Fit model, track training time diff --git a/physXAI/utils/logging.py b/physXAI/utils/logging.py index 6684b66..fb2bde6 100644 --- a/physXAI/utils/logging.py +++ b/physXAI/utils/logging.py @@ -3,11 +3,10 @@ import os import shutil from datetime import datetime +from typing import Union import git from physXAI.preprocessing.constructed import FeatureConstruction import pickle -from pathlib import Path - from physXAI.preprocessing.training_data import TrainingDataMultiStep @@ -131,14 +130,14 @@ def check_print_level(print_level: str) -> bool: return False @staticmethod - def verbosity() -> git.Union[int, str]: + def verbosity() -> Union[int, str]: if Logger._print_levels.index(Logger.print_level) >= Logger._print_levels.index('warning'): return 0 else: return "auto" @staticmethod - def verbosity_float() -> git.Union[int, str]: + def verbosity_int() -> int: if Logger._print_levels.index(Logger.print_level) >= Logger._print_levels.index('warning'): return 0 else: From 7a55bd653f31b96b3fa2c21bc9c260b213ac7666 Mon Sep 17 00:00:00 2001 From: "patrick.henkel" Date: Fri, 6 Feb 2026 14:12:41 +0100 Subject: [PATCH 4/5] Bug fix --- physXAI/utils/logging.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/physXAI/utils/logging.py b/physXAI/utils/logging.py index fb2bde6..2b4a26f 100644 --- a/physXAI/utils/logging.py +++ b/physXAI/utils/logging.py @@ -122,9 +122,9 @@ def print(message: str, print_level: str = 'info'): @staticmethod def check_print_level(print_level: str) -> bool: - if print_level not in Logger._print_levels: - raise ValueError(f"Invalid print level: {print_level}. Valid options are: {Logger._print_levels}") - if Logger._print_levels.index(print_level) >= Logger._print_levels.index(Logger.print_level): + if str(print_level).lower() not in Logger._print_levels: + raise ValueError(f"Invalid print level: {str(print_level).lower()}. Valid options are: {Logger._print_levels}") + if Logger._print_levels.index(str(print_level).lower()) >= Logger._print_levels.index(Logger.print_level): return True else: return False @@ -185,7 +185,9 @@ def setup_logger(folder_name: str = None, override: bool = False, base_path: str Logger._override = override if print_level is not None: - Logger.print_level = print_level + if str(print_level).lower() not in Logger._print_levels: + raise ValueError(f"Invalid print level: {str(print_level).lower()}. Valid options are: {Logger._print_levels}") + Logger.print_level = str(print_level).lower() @staticmethod def log_setup(preprocessing=None, model=None, save_name_preprocessing=None, save_name_model=None, From 76c15016ec7e932de92151c5a9ee6ee51e3e75b8 Mon Sep 17 00:00:00 2001 From: "patrick.henkel" Date: Fri, 6 Feb 2026 14:15:57 +0100 Subject: [PATCH 5/5] Bug fix --- physXAI/utils/logging.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/physXAI/utils/logging.py b/physXAI/utils/logging.py index 2b4a26f..65ee02a 100644 --- a/physXAI/utils/logging.py +++ b/physXAI/utils/logging.py @@ -5,9 +5,7 @@ from datetime import datetime from typing import Union import git -from physXAI.preprocessing.constructed import FeatureConstruction import pickle -from physXAI.preprocessing.training_data import TrainingDataMultiStep def get_parent_working_directory() -> str: @@ -208,6 +206,7 @@ def log_setup(preprocessing=None, model=None, save_name_preprocessing=None, save with open(path, "w") as f: json.dump(preprocessing_dict, f, indent=4) + from physXAI.preprocessing.constructed import FeatureConstruction constructed_config = FeatureConstruction.get_config() if len(constructed_config) > 0: if save_name_constructed is None: @@ -256,6 +255,7 @@ def save_training_data(training_data, path: str = None): with open(p, "w") as f: json.dump(td_dict, f, indent=4) + from physXAI.preprocessing.training_data import TrainingDataMultiStep if isinstance(training_data, TrainingDataMultiStep): training_data = copy.copy(training_data) training_data.train_ds = None