# Function.py -- Pamhyr # Copyright (C) 2023-2026 INRAE # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -*- coding: utf-8 -*- import struct import logging import itertools import numpy as np from tools import logger_exception from Model.Except import NotImplementedMethodeError from Model.Tools.PamhyrDB import SQLSubModel from Model.Scenario import Scenario logger = logging.getLogger() class Function(SQLSubModel): _sub_classes = [] def __init__(self, id: int = -1, name: str = "", script: str = "", status=None): super(Function, self).__init__( id=id, status=status, owner_scenario=0 ) self._name = name self._type = "generic" self._script = script @classmethod def _db_create(cls, execute, ext=""): execute(f""" CREATE TABLE ensemble_function{ext} ( {cls.create_db_add_pamhyr_id()}, deleted BOOLEAN NOT NULL DEFAULT FALSE, name TEXT NOT NULL, script TEXT NOT NULL, PRIMARY KEY(pamhyr_id) ) """) if ext == "_tmp": return True return cls._create_submodel(execute) @classmethod def _db_update(cls, execute, version, data=None): major, minor, release = version.strip().split(".") if major == "0" and int(minor) < 2: cls._db_create(execute) elif major == "0" and int(minor) == 2: if int(release) < 8: cls._db_create(execute) return cls._update_submodel(execute, version, data) @classmethod def _db_load(cls, execute, data=None): new = [] scenario = data["scenario"] loaded = data['loaded_pid'] table = execute( "SELECT pamhyr_id, deleted, name, script " + "FROM ensemble_function " + f"WHERE pamhyr_id NOT IN ({', '.join(map(str, loaded))})" ) for row in table: it = iter(row) id = next(it) deleted = (next(it) == 1) name = next(it) script = next(it) new_function = cls( id, name=name, script=script, status=data["status"] ) if deleted: new_function.set_as_deleted() loaded.add(id) new.append(new_function) return new def _db_save(self, execute, data=None): # Only save custom function if self._type != "custom": return True if not self.must_be_saved(): return True execute( "INSERT INTO " + "ensemble_function(pamhyr_id, deleted, " + " name, script) " + "VALUES (?, ?, ?, ?)", self.pamhyr_id, self.is_deleted(), self._name, self._script ) return True def _data_traversal(self, predicate=lambda obj, data: True, modifier=lambda obj, data: None, data={}): if predicate(self, data): modifier(self, data) def get_sample(self, r): raise NotImplementedMethodeError(cls, cls._load) ########################### # FUNCTION IMPLEMENTATION # ########################### class Uniform(Function): _sub_classes = [] def __init__(self, id: int = -1, name: str = "uniform", script: str = "", status=None): super(Uniform, self).__init__( id=id, status=status, name=name, script=script ) def get_sample(self, ens_range, nb): b = ens_range[0] t = ens_range[1] step = (t - b) / nb return np.arange(b, t, step) class Custom(Function): _sub_classes = [] def __init__(self, id: int = -1, name: str = "", script: str = "", status=None): super(Custom, self).__init__( id=id, status=status, name=name, script=script ) self._type = "custom" def get_sample(self, ens_range, nb): try: # Run script to (re)define the sample function f = eval(self._script) return f(ens_range, nb) except Exception as e: logger_exception(e) return None # TODO: Raise helpful exception