diff --git a/modestpy/estim/ga_parallel/ga_parallel.py b/modestpy/estim/ga_parallel/ga_parallel.py index 9c17238..d5b5218 100644 --- a/modestpy/estim/ga_parallel/ga_parallel.py +++ b/modestpy/estim/ga_parallel/ga_parallel.py @@ -6,11 +6,11 @@ """ import logging import os -import pandas as pd -import numpy as np from random import random -from multiprocessing import Manager -from multiprocessing.managers import BaseManager + +import numpy as np +import pandas as pd + from modestga import minimize from modestpy.fmi.model import Model from modestpy.estim.estpar import EstPar @@ -21,9 +21,20 @@ class ObjectiveFun: + """Objective function for paralell GA. + + This class is callable and it is a wrapper + containing FMU initialization and simulation. + + Note: + If FMU simulation fails, the returned error is 1e8 + (attribute `err_sim_failed`) and the FMU is + reinitialized before next simulation. + """ def __init__(self, fmu_path, inp, known, est, ideal, ftype='RMSE'): self.logger = logging.getLogger(type(self).__name__) self.model = None + self.sim_failed = False self.fmu_path = fmu_path self.inp = inp @@ -41,6 +52,7 @@ def __init__(self, fmu_path, inp, known, est, ideal, ftype='RMSE'): self.output_names = [var for var in ideal] self.ftype = ftype self.best_err = 1e7 + self.err_sim_failed = 1e8 self.res = pd.DataFrame() self.logger.debug(f"fmu_path = {fmu_path}") @@ -54,12 +66,17 @@ def rescale(self, v, lo, hi): return lo + v * (hi - lo) def __call__(self, x, *args): - # Instantiate the model + self.logger.debug(f"x = {x}") - if self.model is None: + + # Instantiate the model if + # - there's no model yet + # - previous simulation failed + if (self.model is None) or (self.sim_failed is True): self.model = self._get_model_instance( self.fmu_path, self.inp, self.known, self.est, self.output_names ) + self.sim_failed = False logging.debug(f"Model instance returned: {self.model}") # Updated parameters are stored in x. Need to update the model. @@ -69,15 +86,25 @@ def __call__(self, x, *args): parameters[ep.name] = self.rescale(v, ep.lo, ep.hi) except TypeError as e: raise e + self.logger.debug(f"parameters = {parameters}") + self.model.parameters_from_df(parameters) + self.logger.debug(f"est: {self.est}") self.logger.debug(f"parameters: {parameters}") self.logger.debug(f"model: {self.model}") self.logger.debug("Calling simulation...") - result = self.model.simulate() - self.logger.debug(f"result: {result}") - err = calc_err(result, self.ideal, ftype=self.ftype)['tot'] + + try: + result = self.model.simulate() + self.logger.debug(f"result: {result}") + err = calc_err(result, self.ideal, ftype=self.ftype)['tot'] + except Exception as e: + self.model_failed = True + err = self.err_sim_failed + self.logger.error(str(e)) + # Update best error and result if err < self.best_err: self.best_err = err @@ -92,19 +119,31 @@ def _get_model_instance(self, fmu_path, inputs, known_pars, est, output_names): self.logger.debug(f"est = {est}") self.logger.debug(f"estpars_2_df(est) = {estpars_2_df(est)}") self.logger.debug(f"output_names = {output_names}") - model = Model(fmu_path) - model.inputs_from_df(inputs) - model.parameters_from_df(known_pars) - model.parameters_from_df(estpars_2_df(est)) - model.specify_outputs(output_names) + + try: + model = Model(fmu_path) + model.inputs_from_df(inputs) + model.parameters_from_df(known_pars) + model.parameters_from_df(estpars_2_df(est)) + model.specify_outputs(output_names) + res = model.simulate() + except Exception as e: + self.logger.error(str(e)) + msg = "Can't initialize FMU with given parameters. Will try with defaults." + self.logger.error(msg) + model = Model(fmu_path) + model.inputs_from_df(inputs) + model.specify_outputs(output_names) + res = model.simulate() + self.logger.debug(f"Model instance initialized: {model}") self.logger.debug(f"Model instance initialized: {model.model}") - res = model.simulate() self.logger.debug(f"test result: {res}") + return model -class MODESTGA(object): +class MODESTGA: """ Parallel Genetic Algorithm based on modestga (https://github.com/krzysztofarendt/modestga). diff --git a/modestpy/test/run.py b/modestpy/test/run.py index 56c1b30..edcf110 100644 --- a/modestpy/test/run.py +++ b/modestpy/test/run.py @@ -7,6 +7,7 @@ import unittest from modestpy.test import test_fmpy from modestpy.test import test_ga +from modestpy.test import test_ga_parallel from modestpy.test import test_ps from modestpy.test import test_scipy from modestpy.test import test_estimation diff --git a/modestpy/test/test_ga_parallel.py b/modestpy/test/test_ga_parallel.py index 8216521..c9b517f 100644 --- a/modestpy/test/test_ga_parallel.py +++ b/modestpy/test/test_ga_parallel.py @@ -70,6 +70,18 @@ def test_modestga_default(self): par_df = ga.estimate() assert type(par_df) is pd.DataFrame + def test_modestga_simulation_fail(self): + est_fail = { + "R1": (-99, -100., 0.), + "R2": (-1e6, -1e7, -1e5), + "C": (0., -1e-10, 1e-10) + } + gen = 3 + ga = MODESTGA(self.fmu_path, self.inp, self.known, est_fail, self.ideal, + generations=gen) + par_df = ga.estimate() + assert type(par_df) is pd.DataFrame + def test_modestga_1_worker(self): ga = MODESTGA(self.fmu_path, self.inp, self.known, self.est, self.ideal, generations=self.gen, @@ -107,6 +119,7 @@ def test_modestga_2_workers_large_pop(self): def suite(): suite = unittest.TestSuite() suite.addTest(TestMODESTGA('test_modestga_default')) + suite.addTest(TestMODESTGA('test_modestga_simulation_fail')) suite.addTest(TestMODESTGA('test_modestga_1_worker')) suite.addTest(TestMODESTGA('test_modestga_2_workers_small_pop')) suite.addTest(TestMODESTGA('test_modestga_2_workers_large_pop')) @@ -116,4 +129,3 @@ def suite(): if __name__ == '__main__': config_logger(filename='unit_tests.log', level='DEBUG') unittest.main() - diff --git a/setup.py b/setup.py index 43a255b..fb3edca 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='modestpy', - version='0.1', + version='0.1.1', description='FMI-compliant model identification package', url='https://github.com/sdu-cfei/modest-py', keywords='fmi fmu optimization model identification estimation',