def __init__(self, function, initialParams, methods, logger=None, isCollect=False): """ Parameters ---------- function: Funtion Arguments lmfit.parameters isInitialze (bool). True on first call the isGetBest (bool). True to retrieve best parameters returns residuals (if bool arguments are false) initialParams: lmfit.parameters methods: list-_helpers.OptimizerMethod isCollect: bool Collects performance statistcs """ self._function = function self._methods = methods self._initialParams = initialParams self._isCollect = isCollect self.logger = logger if self.logger is None: self.logger = Logger() # Outputs self.performanceStats = [] # list of performance results self.qualityStats = [] # relative rssq self.params = None self.minimizerResult = None self.rssq = None
def __init__(self, initialArgument, inputQ, outputQ, isException=False, logger=Logger()): super().__init__(initialArgument, inputQ, outputQ, logger=Logger()) self.isException = isException
def testNoLogPerformance(self): if IGNORE_TEST: return logger = Logger(toFile=LOG_PATH, logPerformance=False, logLevel=logs.LEVEL_MAX) guid = logger.startBlock(BLOCK1) self.assertEqual(len(self.logger.blockDct), 0) logger.endBlock(guid) self.assertEqual(len(self.logger.blockDct), 0)
def test(numBlock, sleepTime): logger = Logger(logPerformance=True) for idx in range(numBlock): block = "blk_%d" % idx guid = logger.startBlock(block) time.sleep(sleepTime) logger.endBlock(guid) df = logger.performanceDF self.assertLess(np.abs(sleepTime - df["mean"].mean()), sleepTime) self.assertEqual(df["count"].mean(), 1.0)
def _residuals(self, params) -> np.ndarray: """ Compute the residuals between objective and experimental data Handle nan values in observedTS. This internal-only method is implemented to maximize efficieency. Parameters ---------- kwargs: dict arguments for simulation Instance Variables Updated -------------------------- self.residualsTS Returns ------- 1-d ndarray of residuals """ block = Logger.join(self._loggerPrefix, "fitModel._residuals") guid = self.logger.startBlock(block) ##V self.roadrunnerModel.reset() self._setupModel(params) # roadrunnerBlock = Logger.join(block, "roadrunner") roadrunnerGuid = self.logger.startBlock(roadrunnerBlock) ## V # data = self.roadrunnerModel.simulate(self.observedTS.start, self.observedTS.end, len(self.observedTS), self.selectedColumns) ## ^ self.logger.endBlock(roadrunnerGuid) # tailBlock = Logger.join(block, "tail") tailGuid = self.logger.startBlock(tailBlock) ## V residualsArr = self._observedArr - data.flatten() residualsArr = np.nan_to_num(residualsArr) ## ^ self.logger.endBlock(tailGuid) ##^ self.logger.endBlock(guid) # # Used for detailed debugging if False: self.logger.details("_residuals/std(residuals): %f" % np.std(residualsArr)) self.logger.details("_residuals/params: %s" % str(params)) return residualsArr
def _testApi(self, method, logLevel): if IGNORE_TEST: return logger = Logger(toFile=LOG_PATH, logLevel=logLevel) stmt = "logger.%s(MSG)" % method exec(stmt) line1s = self._checkMsg(MSG) # logger = Logger(toFile=LOG_PATH, logLevel=0) stmt = "logger.%s(MSG)" % method exec(stmt) line2s = self.read() self.assertEqual(len(line1s), len(line2s))
def __init__(self, firstModel: int = 210, numModel: int = 2, pclPath=PCL_FILE, figPath=FIG_PATH, useExistingData: bool = False, reportInterval: int = 10, isPlot=IS_PLOT, **kwargDct): """ Parameters ---------- firstModel: first model to use numModel: number of models to use pclPath: file to which results are saved reportInterval: how frequently report progress useExistingData: use data in existing PCL file """ self.useExistingData = useExistingData and os.path.isfile(pclPath) # Recover previously saved results if desired if self.useExistingData: self.restore(pclPath=pclPath) else: # Initialize based on type of context variable for name in CONTEXT: if name[-1:] == "s": self.__setattr__(name, []) elif name[-3:] == "Dct": self.__setattr__(name, {}) elif name[-4:] == "Path": self.__setattr__(name, None) elif name[0:2] == "is": self.__setattr__(name, False) else: self.__setattr__(name, 0) # Initialize to parameters for this instantiation self.firstModel = firstModel self.numModel = numModel self.pclPath = pclPath self.figPath = figPath self.reportInterval = reportInterval self.kwargDct = kwargDct self.isPlot = isPlot self.useExistingData = useExistingData # if LOGGER in kwargDct.keys(): self.logger = kwargDct[LOGGER] else: self.logger = Logger() kwargDct[LOGGER] = self.logger self.save()
def mkParameters( cls, parametersToFit: list, logger: Logger = Logger(), parameterLowerBound: float = cn.PARAMETER_LOWER_BOUND, parameterUpperBound: float = cn.PARAMETER_UPPER_BOUND ) -> lmfit.Parameters: """ Constructs lmfit parameters based on specifications. Parameters ---------- parametersToFit: list-Parameter/list-str logger: error logger parameterLowerBound: lower value of range for parameters parameterUpperBound: upper value of range for parameters Returns ------- lmfit.Parameters """ if len(parametersToFit) == 0: raise RuntimeError("Must specify at least one parameter.") if logger is None: logger = logger() # Process each parameter elements = [] for element in parametersToFit: # Get the lower bound, upper bound, and initial value for the parameter if not isinstance(element, SBstoat.Parameter): element = SBstoat.Parameter(element, lower=parameterLowerBound, upper=parameterUpperBound) elements.append(element) return SBstoat.Parameter.mkParameters(elements)
def testJoin(self): if IGNORE_TEST: return NAMES = ["aa", "bbb", "z"] result = Logger.join(*NAMES) for name in NAMES: self.assertGreaterEqual(result.index(name), 0)
def __init__(self, modelFitters, modelNames=None, modelWeights=None, fitterMethods=None, numRestart=0, isParallel=False, logger=Logger()): """ Parameters ---------- modelFitters: list-modelFiter modelWeights: list-float how models are weighted in least squares modelNames: list-str fitterMethods: list-optimization methods numRestart: int number of times the minimization is restarted with random initial values for parameters to fit. isParallel: bool runs each fitter in parallel logger: Logger Raises ------ ValueError: len(modelNames) == len(modelFitters) """ self.numModel = len(modelFitters) self.modelWeights = modelWeights if self.modelWeights is None: self.modelWeights = np.repeat(1, self.numModel) self.modelNames = modelNames if self.modelNames is None: self.modelNames = [str(v) for v in range(len(modelSpecifications))] self._numRestart = numRestart self._isParallel = isParallel self.logger = logger # Validation checks if self.numModel != len(self.modelNames): msg = "Length of modelNames must be same as number of modelFitters." raise ValueError(msg) # self.fitterDct = {n: f for n, f in zip(modelNames, modelFitters)} # Construct tha parameters for each model self.parametersCollection = [f.params for f in self.fitterDct.values()] self.parameterManager = _ParameterManager(self.modelNames, self.parametersCollection) self._fitterMethods = ModelFitter.makeMethods( fitterMethods, cn.METHOD_FITTER_DEFAULTS) # Residuals calculations self.residualsServers = [ ResidualsServer(f, None, None, logger=self.logger) for f in self.fitterDct.values() ] self.manager = None # Results self.optimizer = None
def merge(cls, bootstrapResults, fitter=None): """ Combines a list of BootstrapResult. Sets a fitter. Parameter --------- bootstrapResults: list-BootstrapResult fitter: ModelFitter Return ------ BootstrapResult """ if len(bootstrapResults) == 0: raise ValueError("Must provide a non-empty list") parameterDct = {p: [] for p in bootstrapResults[0].parameterDct} numIteration = sum([r.numIteration for r in bootstrapResults]) bootstrapError = sum([b.bootstrapError for b in bootstrapResults]) fittedStatistic = None if numIteration > 0: # Merge the logs logger = Logger.merge([b.logger for b in bootstrapResults]) # Merge the statistics for fitted timeseries fittedStatistics = [ b.fittedStatistic for b in bootstrapResults if b.fittedStatistic is not None ] fittedStatistic = TimeseriesStatistic.merge(fittedStatistics) # Accumulate the results for bootstrapResult in bootstrapResults: for parameter in parameterDct.keys(): parameterDct[parameter].extend( bootstrapResult.parameterDct[parameter]) # fitter.logger = Logger.merge([fitter.logger, logger]) bootstrapResult = BootstrapResult(fitter, numIteration, parameterDct, fittedStatistic, bootstrapError=bootstrapError) bootstrapResult.setFitter(fitter) return bootstrapResult
def testBug(self): if IGNORE_TEST: return runner = Runner(firstModel=607, numModel=1, useExistingData=False, figPath=FIG_PATH, pclPath=PCL_PATH, isPlot=IS_PLOT, logger=Logger()) runner.run()
def __init__(self, runnerArgument): """ Parameters ---------- runnerArgument: RunnerArgument Notes ----- 1. Uses METHOD_LEASTSQ for fitModel iterations. """ super().__init__() # self.lastErr = "" self.fitter = runnerArgument.fitter self.numIteration = runnerArgument.numIteration self.kwargs = runnerArgument.kwargs self.synthesizerClass = runnerArgument.synthesizerClass if "logger" in self.fitter.__dict__.keys(): self.logger = self.fitter.logger else: self.logger = Logger() self._isDone = not self._fitInitial() self.columns = self.fitter.selectedColumns # Initializations for bootstrap loop if not self.isDone: fittedTS = self.fitter.fittedTS.subsetColumns(self.columns, isCopy=False) self.synthesizer = self.synthesizerClass( observedTS=self.fitter.observedTS.subsetColumns(self.columns, isCopy=False), fittedTS=fittedTS, **self.kwargs) self.numSuccessIteration = 0 if self.fitter.minimizerResult is None: self.fitter.fitModel() self.baseChisq = self.fitter.minimizerResult.redchi self.curIteration = 0 self.fd = self.logger.getFileDescriptor() self.baseFittedStatistic = TimeseriesStatistic( self.fitter.observedTS.subsetColumns( self.fitter.selectedColumns, isCopy=False))
def __init__(self, fitter, inputQ, outputQ, logger=Logger()): """ Parameters ---------- fitter: ModelFitter cannot have swig objects (e.g., roadrunner) inputQ: multiprocessing.queue outputQ: multiprocessing.queue logger: Logger """ super().__init__(fitter, inputQ, outputQ, logger=logger) self.fitter = fitter
def testWolfBug(self): if IGNORE_TEST: return fullDct = { #"J1_n": (1, 1, 8), # 4 #"J4_kp": (3600, 36000, 150000), #76411 #"J5_k": (10, 10, 160), # 80 #"J6_k": (1, 1, 10), # 9.7 "J9_k": (1, 50, 50), # 28 } for parameter in fullDct.keys(): logger = Logger(logLevel=LEVEL_MAX) logger = Logger() ts = NamedTimeseries(csvPath=WOLF_DATA) parameterDct = {parameter: fullDct[parameter]} fitter = ModelFitter(WOLF_MODEL, ts[0:100], parameterDct=parameterDct, logger=logger, fitterMethods=[ "differential_evolution", "leastsq"]) fitter.fitModel() self.assertTrue("J9_k" in fitter.reportFit())
def __init__(self, initialArgument, inputQ, outputQ, logger=Logger()): """ Parameters ---------- initialArgument: Object used by RunFunction inputQ: multiprocessing.queue outputQ: multiprocessing.queue logger: Logger """ self.initialArgument = initialArgument multiprocessing.Process.__init__(self) self.inputQ = inputQ self.outputQ = outputQ self.logger = logger
def setupModel(cls, roadrunner, parameters, logger=Logger()): """ Sets up the model for use based on the parameter parameters Parameters ---------- roadrunner: ExtendedRoadRunner parameters: lmfit.Parameters logger Logger """ pp = parameters.valuesdict() for parameter in pp.keys(): try: roadrunner.model[parameter] = pp[parameter] except Exception as err: msg = "_modelFitterCore.setupModel: Could not set value for %s" \ % parameter logger.error(msg, err)
def __init__(self, cls, initialArguments, logger=Logger(), **kwargs): """ Parameters ---------- cls: AbstractServer initialArguments: list kwargs: dict optional arguments for cls constructor """ self.cls = cls self.logger = logger self.numProcess = len(initialArguments) # Create the servers self.inputQs = [multiprocessing.Queue() for _ in range(self.numProcess)] self.outputQs = [multiprocessing.Queue() for _ in range(self.numProcess)] self.servers = [self.cls(initialArguments[i], self.inputQs[i], self.outputQs[i], logger=self.logger, **kwargs) for i in range(self.numProcess)] _ = [s.start() for s in self.servers]
def mkSuiteFitter(modelSpecifications, datasets, parametersCol, modelNames=None, modelWeights=None, fitterMethods=None, numRestart=0, isParallel=False, logger=Logger(), **kwargs): """ Constructs a SuiteFitterCore with fitters that have similar structure. Parameters ---------- modelSpecifications: list-modelSpecification as in ModelFitter datasets: list-observedData as in ModelFitter paramersCol: list-parametersToFit as in ModelFitter modelNames: list-str modelWeights: list-float how models are weighted in least squares fitterMethods: list-optimization methods numRestart: int number of times the minimization is restarted with random initial values for parameters to fit. isParallel: bool run fits in parallel for each fitter logger: Logger kwargs: dict keyword arguments for ModelFitter Returns ------- SuiteFitter """ modelFitters = [] for modelSpecification, dataset, parametersToFit in \ zip(modelSpecifications, datasets, parametersCol): modelFitter = ModelFitter(modelSpecification, dataset, parametersToFit=parametersToFit, logger=logger, **kwargs) modelFitters.append(modelFitter) return SuiteFitter(modelFitters, modelNames=modelNames, modelWeights=modelWeights, fitterMethods=fitterMethods, numRestart=numRestart, isParallel=isParallel, logger=logger)
class Runner(object): """Runs tests on biomodels.""" def __init__(self, firstModel: int = 210, numModel: int = 2, pclPath=PCL_FILE, figPath=FIG_PATH, useExistingData: bool = False, isPlot=IS_PLOT, **kwargDct): """ Parameters ---------- firstModel: first model to use numModel: number of models to use pclPath: file to which results are saved useExistingData: use data in existing PCL file """ self.useExistingData = useExistingData and os.path.isfile(pclPath) # Recover previously saved results if desired if self.useExistingData: self.restore(pclPath=pclPath) else: # Initialize based on type of context variable for name in CONTEXT: if name[-1:] == "s": self.__setattr__(name, []) elif name[-3:] == "Dct": self.__setattr__(name, {}) elif name[-4:] == "Path": self.__setattr__(name, None) elif name[0:2] == "is": self.__setattr__(name, False) else: self.__setattr__(name, 0) # Initialize to parameters for this instantiation self.firstModel = firstModel self.numModel = numModel self.pclPath = pclPath self.figPath = figPath self.kwargDct = kwargDct self.isPlot = isPlot self.useExistingData = useExistingData # if LOGGER in kwargDct.keys(): self.logger = kwargDct[LOGGER] else: self.logger = Logger() kwargDct[LOGGER] = self.logger self.save() def _isListSame(self, list1, list2): diff = set(list1).symmetric_difference(list2) return len(diff) == 0 def equals(self, other): selfKeys = list(self.__dict__.keys()) otherKeys = list(other.__dict__.keys()) if not self._isListSame(selfKeys, otherKeys): return False # for key, value in self.__dict__.items(): if isinstance(value, list): isEqual = self._isListSame(value, other.__getattribute__(key)) if not isEqual: return False elif any([isinstance(value, t) for t in [int, str, float, bool]]): if self.__getattribute__(key) != other.__getattribute__(key): return False else: pass # return True def run(self): """ Runs the tests. Saves state after each tests. """ # Processing models modelNums = self.firstModel + np.array(range(self.numModel)) for modelNum in modelNums: if (modelNum in self.processedModels) and self.useExistingData: continue else: self.processedModels.append(modelNum) input_path = PATH_PAT % modelNum msg = "Model %s" % input_path self.logger.activity(msg) try: harness = TestHarness(input_path, **self.kwargDct) if len(harness.parametersToFit) == 0: self.logger.result("No fitable parameters in model.") self.save() continue harness.evaluate(stdResiduals=1.0, fractionParameterDeviation=1.0, relError=2.0) except Exception as err: self.erroredModels.append(modelNum) self.logger.error("TestHarness failed", err) self.save() continue # Parameters for model self.modelParameterDct[modelNum] = \ list(harness.fitModelResult.parameterRelErrorDct.keys()) # Relative error in initial fit values = [ v for v in harness.fitModelResult.parameterRelErrorDct.values() ] self.fitModelRelerrors.extend(values) # Relative error in bootstrap values = [ v for v in harness.bootstrapResult.parameterRelErrorDct.values() ] self.bootstrapRelerrors.extend(values) # Count models without exceptions self.nonErroredModels.append(modelNum) self.numNoError = len(self.nonErroredModels) self.save() # Check for plot if self.isPlot: self.plot() def save(self): """ Saves state. Maintain in sync with self.restore(). """ if self.pclPath is not None: data = [self.__getattribute__(n) for n in CONTEXT] with (open(self.pclPath, "wb")) as fd: pickle.dump(data, fd) def restore(self, pclPath=None): """ Restores state. Maintain in sync with self.save(). """ if pclPath is None: pclPath = self.pclPath if os.path.isfile(pclPath): with (open(pclPath, "rb")) as fd: data = pickle.load(fd) [self.__setattr__(n, v) for n, v in zip(CONTEXT, data)] else: raise ValueError("***Restart file %s does not exist" % self.pclPath) @staticmethod def _pruneRelativeErrors(relativeErrors, maxError=MAX_RELATIVE_ERROR): """ Deletes Nans. Removes very large values. Parameters ---------- list: relative errors maxError: maximum relative error considered Returns ------- list: pruned errors float: fraction pruned from non-nan values """ noNanErrors = [v for v in relativeErrors if not np.isnan(v)] prunedErrors = [v for v in noNanErrors if v <= maxError] prunedFrc = 1 - len(prunedErrors) / len(noNanErrors) return prunedErrors, prunedFrc def plot(self): """ Does all plots. """ _, axes = plt.subplots(1, 2) prunedModelErrors, modelPrunedFrc = \ self._pruneRelativeErrors(self.fitModelRelerrors) prunedBootstrapErrors, bootstrapPrunedFrc = \ self._pruneRelativeErrors(self.bootstrapRelerrors) maxBin1 = self._plotRelativeErrors(axes[0], prunedModelErrors, FIT_MODEL, modelPrunedFrc) maxBin2 = self._plotRelativeErrors(axes[1], prunedBootstrapErrors, BOOTSTRAP, bootstrapPrunedFrc, isYLabel=False) maxBin = max(maxBin1, maxBin2) if maxBin > 0: axes[0].set_ylim([0, maxBin]) axes[1].set_ylim([0, maxBin]) # if len(self.processedModels) == 0: frac = 0.0 else: frac = 1.0 * self.numNoError / len(self.processedModels) suptitle = "Models %d-%d. Fraction non-errored: %2.3f" lastModel = self.firstModel + len(self.processedModels) - 1 suptitle = suptitle % (self.firstModel, lastModel, frac) plt.suptitle(suptitle) plt.show() plt.savefig(self.figPath) def _plotRelativeErrors(self, ax, relErrors, title, prunedFrc, isYLabel=True): """ Plots histogram of relative errors. Parameters ---------- ax: Matplotlib.axes relErrors: list-float title: str prunedFrc: float isYlabel: bool Returns ------- float: maximum number in a bin """ rr = ax.hist(relErrors) fullTitle = "%s. Frc Pruned: %2.2f" % (title, prunedFrc) ax.set_title(fullTitle) ax.set_xlabel("relative error") if isYLabel: ax.set_ylabel("number parameters") ax.set_xlim([0, 1]) return max(rr[0])
def __init__(self, modelSpecification, dataSources, dirStudyPath=None, instanceNames=None, logger=Logger(), useSerialized=True, doSerialize=True, isPlot=True, **kwargs): """ Parameters --------- modelSpecification: ExtendedRoadRunner/str roadrunner model or antimony model dataSources: list-NamedTimeseries/list-str or dict of either str: path to CSV file dirStudyPath: str Path to the output directory containing the serialized fitters for the study. instanceNames: list-str Names of study instances corresponds to the list of dataSources useSerialized: bool Use the serialization of each ModelFitter, if it exists doSerialized: bool Serialize each ModelFitter isPlot: bool Do plots kwargs: dict arguments passed to ModelFitter """ self.dirStudyPath = dirStudyPath # Path to the directory serialized ModelFitter if self.dirStudyPath is None: length = len(inspect.stack()) absPath = os.path.abspath((inspect.stack()[length - 1])[1]) dirCaller = os.path.dirname(absPath) self.dirStudyPath = os.path.join(dirCaller, DIR_NAME) self.isPlot = isPlot self.doSerialize = doSerialize self.useSerialized = useSerialized self.instanceNames, self.dataSourceDct = self._mkInstanceData( instanceNames, dataSources) self.fitterPathDct = { } # Path to serialized fitters; key is instanceName self.fitterDct = {} # Fitters: key is instanceName self.logger = logger # Ensure that the directory exists if not os.path.isdir(self.dirStudyPath): os.makedirs(self.dirStudyPath) # Construct the fitters for name, dataSource in self.dataSourceDct.items(): filePath = self._getSerializePath(name) self.fitterPathDct[name] = filePath if os.path.isfile(filePath) and useSerialized: self.fitterDct[name] = ModelFitter.deserialize(filePath) else: self.fitterDct[name] = ModelFitter(modelSpecification, dataSource, logger=self.logger, isPlot=self.isPlot, **kwargs) self._serializeFitter(name)
def rpRevise(self): """ Overrides rpickler. """ if "logger" not in self.__dict__.keys(): self.logger = Logger()
class TestLogger(unittest.TestCase): def setUp(self): self.remove() self.logger = Logger(toFile=LOG_PATH, logPerformance=True, logLevel=logs.LEVEL_MAX) def tearDown(self): self.remove() def remove(self): for ffile in FILES: if os.path.isfile(ffile): os.remove(ffile) def isFile(self): return os.path.isfile(LOG_PATH) def read(self): if not self.isFile(): raise RuntimeError("Missing log file.") with open(LOG_PATH, "r") as fd: lines = fd.readlines() return lines def testConstructor(self): if IGNORE_TEST: return self.assertFalse(self.isFile()) self.assertEqual(self.logger.logLevel, logs.LEVEL_MAX) def testFileDescriptor(self): if IGNORE_TEST: return fd = self.logger.getFileDescriptor() self.assertIsInstance(fd, io.TextIOWrapper) fd.close() def _checkMsg(self, msg): lines = self.read() true = any([MSG in t for t in lines]) self.assertTrue(true) return lines def testWrite(self): if IGNORE_TEST: return self.logger._write(MSG, 0) _ = self._checkMsg(MSG) def _testApi(self, method, logLevel): if IGNORE_TEST: return logger = Logger(toFile=LOG_PATH, logLevel=logLevel) stmt = "logger.%s(MSG)" % method exec(stmt) line1s = self._checkMsg(MSG) # logger = Logger(toFile=LOG_PATH, logLevel=0) stmt = "logger.%s(MSG)" % method exec(stmt) line2s = self.read() self.assertEqual(len(line1s), len(line2s)) def testActivity(self): if IGNORE_TEST: return self._testApi("activity", logs.LEVEL_ACTIVITY) def testResult(self): if IGNORE_TEST: return self._testApi("result", logs.LEVEL_RESULT) def testStatus(self): if IGNORE_TEST: return self._testApi("status", logs.LEVEL_STATUS) def testException(self): if IGNORE_TEST: return self._testApi("status", logs.LEVEL_EXCEPTION) def testStartBlock(self): if IGNORE_TEST: return guid = self.logger.startBlock(BLOCK1) self.assertLess(guid, BlockSpecification.guid) self.assertEqual(len(self.logger.blockDct), 1) def testEndBlock(self): if IGNORE_TEST: return guid1 = self.logger.startBlock(BLOCK1) guid2 = self.logger.startBlock(BLOCK2) self.logger.endBlock(guid2) self.logger.endBlock(guid1) self.assertGreater(self.logger.statisticDct[BLOCK1].total, self.logger.statisticDct[BLOCK2].total) def testNoLogPerformance(self): if IGNORE_TEST: return logger = Logger(toFile=LOG_PATH, logPerformance=False, logLevel=logs.LEVEL_MAX) guid = logger.startBlock(BLOCK1) self.assertEqual(len(self.logger.blockDct), 0) logger.endBlock(guid) self.assertEqual(len(self.logger.blockDct), 0) def testPerformanceReport(self): if IGNORE_TEST: return def test(numBlock, sleepTime): logger = Logger(logPerformance=True) for idx in range(numBlock): block = "blk_%d" % idx guid = logger.startBlock(block) time.sleep(sleepTime) logger.endBlock(guid) df = logger.performanceDF self.assertLess(np.abs(sleepTime - df["mean"].mean()), sleepTime) self.assertEqual(df["count"].mean(), 1.0) # test(3, 0.1) test(30, 0.1) def testJoin(self): if IGNORE_TEST: return NAMES = ["aa", "bbb", "z"] result = Logger.join(*NAMES) for name in NAMES: self.assertGreaterEqual(result.index(name), 0) def testCopy(self): if IGNORE_TEST: return newLogger = self.logger.copy() self.assertTrue(self.logger.equals(newLogger))
class ModelFitterCore(rpickle.RPickler): def __init__( self, modelSpecification, observedData, # The following must be kept in sync with ModelFitterBootstrap.bootstrap parametersToFit=None, # Must be first kw for backwards compatibility bootstrapMethods=None, endTime=None, fitterMethods=None, isPlot=True, logger=Logger(), _loggerPrefix="", maxProcess: int = None, numFitRepeat=1, numIteration: int = 10, numPoint=None, numRestart=0, parameterLowerBound=cn.PARAMETER_LOWER_BOUND, parameterUpperBound=cn.PARAMETER_UPPER_BOUND, selectedColumns=None, serializePath: str = None, isParallel=True, isProgressBar=True, ): """ Constructs estimates of parameter values. Parameters ---------- endTime: float end time for the simulation modelSpecification: ExtendedRoadRunner/str roadrunner model or antimony model observedData: NamedTimeseries/str str: path to CSV file parametersToFit: list-str/SBstoat.Parameter/None parameters in the model that you want to fit if None, no parameters are fit selectedColumns: list-str species names you wish use to fit the model default: all columns in observedData parameterLowerBound: float lower bound for the fitting parameters parameterUpperBound: float upper bound for the fitting parameters logger: Logger fitterMethods: str/list-str/list-OptimizerMethod method used for minimization in fitModel numFitRepeat: int number of times fitting is repeated for a method bootstrapMethods: str/list-str/list-OptimizerMethod method used for minimization in bootstrap numIteration: number of bootstrap iterations numPoint: int number of time points in the simulation maxProcess: Maximum number of processes to use. Default: numCPU serializePath: Where to serialize the fitter after bootstrap numRestart: int number of times the minimization is restarted with random initial values for parameters to fit. isParallel: bool run in parallel where possible isProgressBar: bool display the progress bar Usage ----- parametersToFit = [SBstoat.Parameter("k1", lower=1, upper=10, value=5), SBstoat.Parameter("k2", lower=2, upper=6, value=3), ] ftter = ModelFitter(roadrunnerModel, "observed.csv", parametersToFit=parametersToFit) fitter.fitModel() # Do the fit fitter.bootstrap() # Estimate parameter variance with bootstrap """ if modelSpecification is not None: # Not the default constructor self._numIteration = numIteration # Save for copy constructor self._serializePath = serializePath # Save for copy constructor self._loggerPrefix = _loggerPrefix self.modelSpecification = modelSpecification self.observedData = observedData self.parametersToFit = parametersToFit self.parameterLowerBound = parameterLowerBound self.parameterUpperBound = parameterUpperBound self._maxProcess = maxProcess self.bootstrapKwargs = dict( numIteration=numIteration, serializePath=serializePath, ) self._numFitRepeat = numFitRepeat self.selectedColumns = selectedColumns self.observedTS, self.selectedColumns = self._updateObservedTS( mkNamedTimeseries(self.observedData)) # Check for nan in observedTS self._isObservedNan = np.isnan(np.sum(self.observedTS.flatten())) # self.selectedColumns = [c.strip() for c in self.selectedColumns] self.numPoint = numPoint if self.numPoint is None: self.numPoint = len(self.observedTS) self.endTime = endTime if self.endTime is None: self.endTime = self.observedTS.end # Other internal state self._fitterMethods = ModelFitterCore.makeMethods( fitterMethods, cn.METHOD_FITTER_DEFAULTS) self._bootstrapMethods = ModelFitterCore.makeMethods( bootstrapMethods, cn.METHOD_BOOTSTRAP_DEFAULTS) if isinstance(self._bootstrapMethods, str): self._bootstrapMethods = [self._bootstrapMethods] self._isPlot = isPlot self._plotter = tp.TimeseriesPlotter(isPlot=self._isPlot) self.logger = logger self._numRestart = numRestart self._isParallel = isParallel self._isProgressBar = isProgressBar self._selectedIdxs = None self._params = self.mkParams() # The following are calculated during fitting self.roadrunnerModel = None self.minimizerResult = None # Results of minimization self.fittedTS = self.observedTS.copy( isInitialize=True) # Initialize self.residualsTS = None # Residuals for selectedColumns self.bootstrapResult = None # Result from bootstrapping self.optimizer = None self.suiteFitterParams = None # Result from a suite fitter # else: pass def copy(self, isKeepLogger=False, isKeepOptimizer=False, **kwargs): """ Creates a copy of the model fitter, overridding any argument that is not None. Preserves the user-specified settings and the results of bootstrapping. Parameters ---------- isKeepLogger: bool isKeepOptimizer: bool kwargs: dict arguments to override in copy Returns ------- ModelFitter """ def setValues(names): """ Sets the value for a list of names. Parameters ---------- names: list-str Returns ------- list-object """ results = [] for name in names: altName = "_%s" % name if name in kwargs.keys(): results.append(kwargs[name]) elif name in self.__dict__.keys(): results.append(self.__dict__[name]) elif altName in self.__dict__.keys(): results.append(self.__dict__[altName]) else: raise RuntimeError("%s: not found" % name) return results # # Get the positional and keyword arguments inspectResult = inspect.getfullargspec(ModelFitterCore.__init__) allArgNames = inspectResult.args[1:] # Ignore 'self' if inspectResult.defaults is None: numKwarg = 0 else: numKwarg = len(inspectResult.defaults) numParg = len(allArgNames) - numKwarg # positional arguments pargNames = allArgNames[:numParg] kwargNames = allArgNames[numParg:] # Construct the arguments callPargs = setValues(pargNames) callKwargValues = setValues(kwargNames) callKwargs = {n: v for n, v in zip(kwargNames, callKwargValues)} if numKwarg > 0: # Adjust model specification modelSpecificationIdx = pargNames.index("modelSpecification") observedDataIdx = pargNames.index("observedData") modelSpecification = callPargs[modelSpecificationIdx] observedTS = NamedTimeseries(callPargs[observedDataIdx]) selectedColumns = callKwargs["selectedColumns"] if not isinstance(modelSpecification, str): try: modelSpecification = self.modelSpecification.getAntimony() callPargs[modelSpecificationIdx] = modelSpecification observedTS, selectedColumns = self._adjustNames( modelSpecification, observedTS) callPargs[observedDataIdx] = observedTS callKwargs["selectedColumns"] = selectedColumns except Exception as err: self.logger.error( "Problem wth conversion to Antimony. Details:", err) raise ValueError("Cannot proceed.") # if isKeepLogger: callKwargs["logger"] = self.logger if numParg < 2: callPargs = [None, None] newModelFitter = self.__class__(*callPargs, **callKwargs) if self.optimizer is not None: if isKeepOptimizer: newModelFitter.optimizer = self.optimizer.copyResults() if self.bootstrapResult is not None: newModelFitter.bootstrapResult = self.bootstrapResult.copy() return newModelFitter def _updateSelectedIdxs(self): resultTS = self.simulate() if resultTS is not None: self._selectedIdxs = \ ModelFitterCore.selectCompatibleIndices(resultTS[TIME], self.observedTS[TIME]) else: self._selectedIdxs = list(range(self.numPoint)) def _updateObservedTS(self, newObservedTS, isCheck=True): """ Changes just the observed timeseries. The new timeseries must have the same shape as the old one. Also used on initialization, in which case, the check should be disabled. Parameters ---------- newObservedTS: NamedTimeseries isCheck: Bool verify that new timeseries as the same shape as the old one Returns ------- NamedTimeseries ObservedTS list-str selectedColumns """ if isCheck: isError = False if not isinstance(newObservedTS, NamedTimeseries): isError = True if "observedTS" in self.__dict__.keys(): isError = isError \ or (not self.observedTS.equalSchema(newObservedTS)) if isError: raise RuntimeError( "Timeseries argument: incorrect type or shape.") # self.observedTS = newObservedTS if (self.selectedColumns is None) and (self.observedTS is not None): self.selectedColumns = self.observedTS.colnames if self.observedTS is None: self._observedArr = None else: self._observedArr = self.observedTS[self.selectedColumns].flatten() # return self.observedTS, self.selectedColumns @property def params(self): params = None # if params is None: if self.suiteFitterParams is not None: params = self.suiteFitterParams if self.bootstrapResult is not None: if self.bootstrapResult.params is not None: params = self.bootstrapResult.params if params is None: if self.optimizer is not None: params = self.optimizer.params else: params = self._params return params @staticmethod def makeMethods(methods, default): """ Creates a method dictionary. Parameters ---------- methods: str/list-str/dict method used for minimization in fitModel dict: key-method, value-optional parameters Returns ------- list-OptimizerMethod key: method name value: dict of optional parameters """ if methods is None: methods = default if isinstance(methods, str): if methods == cn.METHOD_BOTH: methods = cn.METHOD_FITTER_DEFAULTS else: methods = [methods] if isinstance(methods, list): if isinstance(methods[0], str): results = [ _helpers.OptimizerMethod(method=m, kwargs={}) for m in methods ] else: results = methods else: raise RuntimeError("Must be a list") trues = [isinstance(m, _helpers.OptimizerMethod) for m in results] if not all(trues): raise ValueError("Invalid methods: %s" % str(methods)) return results @classmethod def mkParameters( cls, parametersToFit: list, logger: Logger = Logger(), parameterLowerBound: float = cn.PARAMETER_LOWER_BOUND, parameterUpperBound: float = cn.PARAMETER_UPPER_BOUND ) -> lmfit.Parameters: """ Constructs lmfit parameters based on specifications. Parameters ---------- parametersToFit: list-Parameter/list-str logger: error logger parameterLowerBound: lower value of range for parameters parameterUpperBound: upper value of range for parameters Returns ------- lmfit.Parameters """ if len(parametersToFit) == 0: raise RuntimeError("Must specify at least one parameter.") if logger is None: logger = logger() # Process each parameter elements = [] for element in parametersToFit: # Get the lower bound, upper bound, and initial value for the parameter if not isinstance(element, SBstoat.Parameter): element = SBstoat.Parameter(element, lower=parameterLowerBound, upper=parameterUpperBound) elements.append(element) return SBstoat.Parameter.mkParameters(elements) @classmethod def initializeRoadrunnerModel(cls, modelSpecification): """ Sets self.roadrunnerModel. Parameters ---------- modelSpecification: ExtendedRoadRunner/str Returns ------- ExtendedRoadRunner """ if isinstance(modelSpecification, te.roadrunner.extended_roadrunner.ExtendedRoadRunner): roadrunnerModel = modelSpecification elif isinstance(modelSpecification, str): roadrunnerModel = te.loada(modelSpecification) else: msg = 'Invalid model.' msg = msg + "\nA model must either be a Roadrunner model " msg = msg + "an Antimony model." raise ValueError(msg) return roadrunnerModel @classmethod def setupModel(cls, roadrunner, parameters, logger=Logger()): """ Sets up the model for use based on the parameter parameters Parameters ---------- roadrunner: ExtendedRoadRunner parameters: lmfit.Parameters logger Logger """ pp = parameters.valuesdict() for parameter in pp.keys(): try: roadrunner.model[parameter] = pp[parameter] except Exception as err: msg = "_modelFitterCore.setupModel: Could not set value for %s" \ % parameter logger.error(msg, err) @classmethod def runSimulation(cls, **kwargs): """ Runs a simulation. Defaults to parameter values in the simulation. Returns a NamedTimeseries. Return ------ NamedTimeseries (or None if fail to converge) """ return NamedTimeseries(namedArray=cls.runSimulationNumpy(**kwargs)) @classmethod def runSimulationDF(cls, **kwargs): """ Runs a simulation. Defaults to parameter values in the simulation. Returns a NamedTimeseries. Return ------ pd.DataFrame """ fittedTS = NamedTimeseries(namedArray=cls.runSimulationArr(**kwargs)) return fittedTS.to_dataframe() @classmethod def runSimulationNumpy( cls, parameters=None, modelSpecification=None, startTime=0, endTime=5, numPoint=30, selectedColumns=None, _logger=Logger(), _loggerPrefix="", ): """ Runs a simulation. Defaults to parameter values in the simulation. Returns a NamedArray Parameters ---------- modelSpecification: ExtendedRoadRunner/str Roadrunner model parameters: lmfit.Parameters lmfit parameters startTime: float start time for the simulation endTime: float end time for the simulation numPoint: int number of points in the simulation selectedColumns: list-str output columns in simulation _logger: Logger _loggerPrefix: str Return ------ NamedArray (or None if fail to converge) """ roadrunnerModel = modelSpecification if isinstance(modelSpecification, str): roadrunnerModel = cls.initializeRoadrunnerModel(roadrunnerModel) else: roadrunnerModel.reset() if parameters is not None: # Parameters have been specified cls.setupModel(roadrunnerModel, parameters, logger=_logger) # Do the simulation if selectedColumns is not None: newSelectedColumns = list(selectedColumns) if TIME not in newSelectedColumns: newSelectedColumns.insert(0, TIME) try: dataArr = roadrunnerModel.simulate(startTime, endTime, numPoint, newSelectedColumns) except Exception as err: _logger.error("Roadrunner exception: ", err) dataArr = None else: try: dataArr = roadrunnerModel.simulate(startTime, endTime, numPoint) except Exception as err: _logger.exception("Roadrunner exception: %s" % err) dataArr = None return dataArr @classmethod def rpConstruct(cls): """ Overrides rpickler.rpConstruct to create a method that constructs an instance without arguments. Returns ------- Instance of cls """ return cls(None, None, None) def rpRevise(self): """ Overrides rpickler. """ if "logger" not in self.__dict__.keys(): self.logger = Logger() def _adjustNames(self, antimonyModel:str, observedTS:NamedTimeseries) \ ->typing.Tuple[NamedTimeseries, list]: """ Antimony exports can change the names of floating species by adding a "_" at the end. Check for this and adjust the names in observedTS. Return ------ NamedTimeseries: newObservedTS list: newSelectedColumns """ rr = te.loada(antimonyModel) dataNames = rr.simulate().colnames names = ["[%s]" % n for n in observedTS.colnames] missingNames = [n[1:-1] for n in set(names).difference(dataNames)] newSelectedColumns = list(self.selectedColumns) if len(missingNames) > 0: newObservedTS = observedTS.copy() self.logger.exception("Missing names in antimony export: %s" % str(missingNames)) for name in observedTS.colnames: missingName = "%s_" % name if name in missingNames: newObservedTS = newObservedTS.rename(name, missingName) newSelectedColumns.remove(name) newSelectedColumns.append(missingName) else: newObservedTS = observedTS return newObservedTS, newSelectedColumns def clean(self): """ Cleans the object so that it can be pickled. """ self.roadrunnerModel = None return self def initializeRoadRunnerModel(self): """ Sets self.roadrunnerModel. """ if self.roadrunnerModel is None: self.roadrunnerModel = ModelFitterCore.initializeRoadrunnerModel( self.modelSpecification) def getDefaultParameterValues(self): """ Obtain the original values of parameters. Returns ------- list-SBstoat.Parameter """ dct = {} self.initializeRoadRunnerModel() self.roadrunnerModel.reset() for parameterName in self.parametersToFit: dct[parameterName] = self.roadrunnerModel.model[parameterName] return dct def simulate(self, **kwargs): """ Runs a simulation. Defaults to parameter values in the simulation. Parameters ---------- params: lmfit.Parameters startTime: float endTime: float numPoint: int Return ------ NamedTimeseries """ fixedArr = self._simulateNumpy(**kwargs) return NamedTimeseries(namedArray=fixedArr) def _simulateNumpy(self, params=None, startTime=None, endTime=None, numPoint=None): """ Runs a simulation. Defaults to parameter values in the simulation. Parameters ---------- params: lmfit.Parameters startTime: float endTime: float numPoint: int Return ------ NamedTimeseries """ def setValue(default, parameter): # Sets to default if parameter unspecified if parameter is None: return default return parameter # startTime = setValue(self.observedTS.start, startTime) endTime = setValue(self.endTime, endTime) numPoint = setValue(self.numPoint, numPoint) # if self.roadrunnerModel is None: self.initializeRoadRunnerModel() # return self.runSimulationNumpy(parameters=params, modelSpecification=self.roadrunnerModel, startTime=startTime, endTime=endTime, numPoint=numPoint, selectedColumns=self.selectedColumns, _logger=self.logger, _loggerPrefix=self._loggerPrefix) def updateFittedAndResiduals(self, **kwargs) -> np.ndarray: """ Updates values of self.fittedTS and self.residualsTS based on self.params. Parameters ---------- kwargs: dict arguments for simulation Instance Variables Updated -------------------------- self.fittedTS self.residualsTS Returns ------- 1-d ndarray of residuals """ self.fittedTS = self.simulate(**kwargs) # Updates self.fittedTS if self._selectedIdxs is None: self._updateSelectedIdxs() self.fittedTS = self.fittedTS[self._selectedIdxs] residualsArr = self.calcResiduals(self.params) numRow = len(self.fittedTS) numCol = len(residualsArr) // numRow residualsArr = np.reshape(residualsArr, (numRow, numCol)) cols = self.selectedColumns if self.residualsTS is None: self.residualsTS = self.observedTS.subsetColumns(cols) self.residualsTS[cols] = residualsArr @staticmethod def selectCompatibleIndices(bigTimes, smallTimes): """ Finds the indices such that smallTimes[n] is close to bigTimes[indices[n]] Parameters ---------- bigTimes: np.ndarray smalltimes: np.ndarray Returns np.ndarray """ indices = [] for idx, _ in enumerate(smallTimes): distances = (bigTimes - smallTimes[idx])**2 def getValue(k): return distances[k] thisIndices = sorted(range(len(distances)), key=getValue) indices.append(thisIndices[0]) return np.array(indices) def calcResiduals(self, params) -> np.ndarray: """ Compute the residuals between objective and experimental data Handle nan values in observedTS. This internal-only method is implemented to maximize efficieency. Parameters ---------- params: lmfit.Parameters arguments for simulation Returns ------- 1-d ndarray of residuals """ if self._selectedIdxs is None: self._updateSelectedIdxs() dataArr = ModelFitterCore.runSimulationNumpy( parameters=params, modelSpecification=self.roadrunnerModel, startTime=self.observedTS.start, endTime=self.endTime, numPoint=self.numPoint, selectedColumns=self.selectedColumns, _logger=self.logger, _loggerPrefix=self._loggerPrefix) if dataArr is None: residualsArr = np.repeat(LARGE_RESIDUAL, len(self._observedArr)) else: truncatedArr = dataArr[self._selectedIdxs, 1:] truncatedArr = truncatedArr.flatten() residualsArr = self._observedArr - truncatedArr if self._isObservedNan: residualsArr = np.nan_to_num(residualsArr) return residualsArr def fitModel(self, params: lmfit.Parameters = None): """ Fits the model by adjusting values of parameters based on differences between simulated and provided values of floating species. Parameters ---------- params: lmfit.parameters starting values of parameters Example ------- f.fitModel() """ if params is None: params = self.params self.initializeRoadRunnerModel() if self.parametersToFit is not None: self.optimizer = Optimizer.optimize(self.calcResiduals, params, self._fitterMethods, logger=self.logger, numRestart=self._numRestart) self.minimizerResult = self.optimizer.minimizerResult # Ensure that residualsTS and fittedTS match the parameters self.updateFittedAndResiduals(params=self.params) def getFittedModel(self): """ Provides the roadrunner model with fitted parameters Returns ------- ExtendedRoadrunner """ self._checkFit() self.roadrunnerModel.reset() self._setupModel(self.params) return self.roadrunnerModel def _setupModel(self, parameters): """ Sets up the model for use based on the parameter parameters Parameters ---------- parameters: lmfit.Parameters """ ModelFitterCore.setupModel(self.roadrunnerModel, parameters, logger=self.logger) def mkParams(self, parametersToFit: list = None) -> lmfit.Parameters: """ Constructs lmfit parameters based on specifications. Parameters ---------- parametersToFit: list-Parameter Returns ------- lmfit.Parameters """ if parametersToFit is None: parametersToFit = self.parametersToFit if parametersToFit is not None: return ModelFitterCore.mkParameters( parametersToFit, logger=self.logger, parameterLowerBound=self.parameterLowerBound, parameterUpperBound=self.parameterUpperBound) else: return None def _checkFit(self): if self.params is None: raise ValueError("Must use fitModel before using this method.") def serialize(self, path): """ Serialize the model to a path. Parameters ---------- path: str File path """ newModelFitter = self.copy() with open(path, "wb") as fd: rpickle.dump(newModelFitter, fd) @classmethod def deserialize(cls, path): """ Deserialize the model from a path. Parameters ---------- path: str File path Return ------ ModelFitter Model is initialized. """ with open(path, "rb") as fd: fitter = rpickle.load(fd) fitter.initializeRoadRunnerModel() return fitter
def __init__( self, modelSpecification, observedData, # The following must be kept in sync with ModelFitterBootstrap.bootstrap parametersToFit=None, # Must be first kw for backwards compatibility bootstrapMethods=None, endTime=None, fitterMethods=None, isPlot=True, logger=Logger(), _loggerPrefix="", maxProcess: int = None, numFitRepeat=1, numIteration: int = 10, numPoint=None, numRestart=0, parameterLowerBound=cn.PARAMETER_LOWER_BOUND, parameterUpperBound=cn.PARAMETER_UPPER_BOUND, selectedColumns=None, serializePath: str = None, isParallel=True, isProgressBar=True, ): """ Constructs estimates of parameter values. Parameters ---------- endTime: float end time for the simulation modelSpecification: ExtendedRoadRunner/str roadrunner model or antimony model observedData: NamedTimeseries/str str: path to CSV file parametersToFit: list-str/SBstoat.Parameter/None parameters in the model that you want to fit if None, no parameters are fit selectedColumns: list-str species names you wish use to fit the model default: all columns in observedData parameterLowerBound: float lower bound for the fitting parameters parameterUpperBound: float upper bound for the fitting parameters logger: Logger fitterMethods: str/list-str/list-OptimizerMethod method used for minimization in fitModel numFitRepeat: int number of times fitting is repeated for a method bootstrapMethods: str/list-str/list-OptimizerMethod method used for minimization in bootstrap numIteration: number of bootstrap iterations numPoint: int number of time points in the simulation maxProcess: Maximum number of processes to use. Default: numCPU serializePath: Where to serialize the fitter after bootstrap numRestart: int number of times the minimization is restarted with random initial values for parameters to fit. isParallel: bool run in parallel where possible isProgressBar: bool display the progress bar Usage ----- parametersToFit = [SBstoat.Parameter("k1", lower=1, upper=10, value=5), SBstoat.Parameter("k2", lower=2, upper=6, value=3), ] ftter = ModelFitter(roadrunnerModel, "observed.csv", parametersToFit=parametersToFit) fitter.fitModel() # Do the fit fitter.bootstrap() # Estimate parameter variance with bootstrap """ if modelSpecification is not None: # Not the default constructor self._numIteration = numIteration # Save for copy constructor self._serializePath = serializePath # Save for copy constructor self._loggerPrefix = _loggerPrefix self.modelSpecification = modelSpecification self.observedData = observedData self.parametersToFit = parametersToFit self.parameterLowerBound = parameterLowerBound self.parameterUpperBound = parameterUpperBound self._maxProcess = maxProcess self.bootstrapKwargs = dict( numIteration=numIteration, serializePath=serializePath, ) self._numFitRepeat = numFitRepeat self.selectedColumns = selectedColumns self.observedTS, self.selectedColumns = self._updateObservedTS( mkNamedTimeseries(self.observedData)) # Check for nan in observedTS self._isObservedNan = np.isnan(np.sum(self.observedTS.flatten())) # self.selectedColumns = [c.strip() for c in self.selectedColumns] self.numPoint = numPoint if self.numPoint is None: self.numPoint = len(self.observedTS) self.endTime = endTime if self.endTime is None: self.endTime = self.observedTS.end # Other internal state self._fitterMethods = ModelFitterCore.makeMethods( fitterMethods, cn.METHOD_FITTER_DEFAULTS) self._bootstrapMethods = ModelFitterCore.makeMethods( bootstrapMethods, cn.METHOD_BOOTSTRAP_DEFAULTS) if isinstance(self._bootstrapMethods, str): self._bootstrapMethods = [self._bootstrapMethods] self._isPlot = isPlot self._plotter = tp.TimeseriesPlotter(isPlot=self._isPlot) self.logger = logger self._numRestart = numRestart self._isParallel = isParallel self._isProgressBar = isProgressBar self._selectedIdxs = None self._params = self.mkParams() # The following are calculated during fitting self.roadrunnerModel = None self.minimizerResult = None # Results of minimization self.fittedTS = self.observedTS.copy( isInitialize=True) # Initialize self.residualsTS = None # Residuals for selectedColumns self.bootstrapResult = None # Result from bootstrapping self.optimizer = None self.suiteFitterParams = None # Result from a suite fitter # else: pass
import numpy as np import os import unittest IGNORE_TEST = False IS_PLOT = False DIR = os.path.dirname(os.path.abspath(__file__)) LOG_PATH = os.path.join(DIR, "testTestHarness.log") DATA_DIR = os.path.join(os.path.dirname(DIR), "biomodels") PATH_PAT = os.path.join(DATA_DIR, "BIOMD0000000%03d.xml") INPUT_PATH = PATH_PAT % 339 VARIABLE_NAMES = ["Va_Xa", "IIa_Tmod", "VIIa_TF"] PARAMETER_NAMES = ["r27_c", "r28_c", "r29_c"] VARIABLE_NAMES = ["Pk", "VK"] PARAMETER_NAMES = ["d_Pk", "d_VK"] LOGGER = Logger() if os.path.isfile(LOG_PATH): os.remove(LOG_PATH) class TestFunctions(unittest.TestCase): def setUp(self): if IGNORE_TEST: return self.harness = TestHarness(INPUT_PATH, PARAMETER_NAMES, VARIABLE_NAMES, logger=LOGGER) def testConstructor(self):
import unittest import matplotlib import matplotlib.pyplot as plt IGNORE_TEST = False IS_PLOT = False DIR = os.path.dirname(os.path.abspath(__file__)) PCL_PATH = os.path.join(DIR, "testMainTestHarness.pcl") FIG_PATH = os.path.join(DIR, "testMainTestHarness.png") FILES = [PCL_PATH, FIG_PATH] FIRST_MODEL = 200 NUM_MODEL = 1 DIR = os.path.dirname(os.path.abspath(__file__)) LOG_FILE = os.path.join(DIR, "testMainTestHarness.log") if IGNORE_TEST: LOGGER = Logger() else: LOGGER = Logger(toFile=LOG_FILE) if os.path.isfile(LOG_FILE): os.remove(LOG_FILE) class TestRunner(unittest.TestCase): def setUp(self): self._remove() self.runner = Runner(firstModel=FIRST_MODEL, numModel=NUM_MODEL, useExistingData=False, figPath=FIG_PATH, pclPath=PCL_PATH,
class BootstrapRunner(AbstractRunner): def __init__(self, runnerArgument): """ Parameters ---------- runnerArgument: RunnerArgument Notes ----- 1. Uses METHOD_LEASTSQ for fitModel iterations. """ super().__init__() # self.lastErr = "" self.fitter = runnerArgument.fitter self.numIteration = runnerArgument.numIteration self.kwargs = runnerArgument.kwargs self.synthesizerClass = runnerArgument.synthesizerClass if "logger" in self.fitter.__dict__.keys(): self.logger = self.fitter.logger else: self.logger = Logger() self._isDone = not self._fitInitial() self.columns = self.fitter.selectedColumns # Initializations for bootstrap loop if not self.isDone: fittedTS = self.fitter.fittedTS.subsetColumns(self.columns, isCopy=False) self.synthesizer = self.synthesizerClass( observedTS=self.fitter.observedTS.subsetColumns(self.columns, isCopy=False), fittedTS=fittedTS, **self.kwargs) self.numSuccessIteration = 0 if self.fitter.minimizerResult is None: self.fitter.fitModel() self.baseChisq = self.fitter.minimizerResult.redchi self.curIteration = 0 self.fd = self.logger.getFileDescriptor() self.baseFittedStatistic = TimeseriesStatistic( self.fitter.observedTS.subsetColumns( self.fitter.selectedColumns, isCopy=False)) def report(self, id=None): if True: return if id is None: self._startTime = time.time() else: elapsed = time.time() - self._startTime print("%s: %2.3f" % (id, elapsed)) @property def numWorkUnit(self): return self.numIteration @property def isDone(self): return self._isDone def run(self): """ Runs the bootstrap. Returns ------- BootstrapResult """ def mkNullResult(): fittedStatistic = TimeseriesStatistic( self.fitter.observedTS[self.fitter.selectedColumns]) return BootstrapResult(self.fitter, 0, {}, fittedStatistic) # if self.isDone: return # Set up logging for this run if self.fd is not None: sys.stderr = self.fd sys.stdout = self.fd isSuccess = False bootstrapError = 0 self.report() for _ in range(ITERATION_MULTIPLIER): newObservedTS = self.synthesizer.calculate() self.report("newObservedTS") # Update fitter to use the new observed data _ = self.fitter._updateObservedTS(newObservedTS, isCheck=False) self.report("updated fitter") # Try fitting try: self.fitter.fitModel(params=self.fitter.params) self.report("fitter.fit") except Exception as err: # Problem with the fit. msg = "modelFitterBootstrap. Fit failed on iteration %d." \ % iteration self.logger.error(msg, err) bootstrapError += 1 continue # Verify that there is a result if self.fitter.minimizerResult is None: continue # Check if the fit is of sufficient quality if self.fitter.minimizerResult.redchi > MAX_CHISQ_MULT * self.baseChisq: continue if self.fitter.params is None: continue isSuccess = True self.report("break") break # Create the result if isSuccess: self.numSuccessIteration += 1 parameterDct = { k: [v] for k, v in self.fitter.params.valuesdict().items() } fittedStatistic = self.baseFittedStatistic.copy() fittedStatistic.accumulate( self.fitter.fittedTS.subsetColumns(self.fitter.selectedColumns, isCopy=False)) bootstrapResult = BootstrapResult(self.fitter, self.numSuccessIteration, parameterDct, fittedStatistic, bootstrapError=bootstrapError) else: bootstrapResult = mkNullResult() self._isDone = True # Close the logging file if self.fd is not None: if not self.fd.closed: self.fd.close() # See if completed work if self.numSuccessIteration >= self.numIteration: self._isDone = True return bootstrapResult def _fitInitial(self): """ Do the initial fit. Returns ------- bool successful fit """ isSuccess = False for _ in range(MAX_TRIES): try: self.fitter.fitModel() # Initialize model isSuccess = True break except Exception as err: self.lastErr = err msg = "Could not do initial fit" self.logger.error(msg, err) return isSuccess
def setUp(self): self.remove() self.logger = Logger(toFile=LOG_PATH, logPerformance=True, logLevel=logs.LEVEL_MAX)
def runSimulationNumpy( cls, parameters=None, modelSpecification=None, startTime=0, endTime=5, numPoint=30, selectedColumns=None, _logger=Logger(), _loggerPrefix="", ): """ Runs a simulation. Defaults to parameter values in the simulation. Returns a NamedArray Parameters ---------- modelSpecification: ExtendedRoadRunner/str Roadrunner model parameters: lmfit.Parameters lmfit parameters startTime: float start time for the simulation endTime: float end time for the simulation numPoint: int number of points in the simulation selectedColumns: list-str output columns in simulation _logger: Logger _loggerPrefix: str Return ------ NamedArray (or None if fail to converge) """ roadrunnerModel = modelSpecification if isinstance(modelSpecification, str): roadrunnerModel = cls.initializeRoadrunnerModel(roadrunnerModel) else: roadrunnerModel.reset() if parameters is not None: # Parameters have been specified cls.setupModel(roadrunnerModel, parameters, logger=_logger) # Do the simulation if selectedColumns is not None: newSelectedColumns = list(selectedColumns) if TIME not in newSelectedColumns: newSelectedColumns.insert(0, TIME) try: dataArr = roadrunnerModel.simulate(startTime, endTime, numPoint, newSelectedColumns) except Exception as err: _logger.error("Roadrunner exception: ", err) dataArr = None else: try: dataArr = roadrunnerModel.simulate(startTime, endTime, numPoint) except Exception as err: _logger.exception("Roadrunner exception: %s" % err) dataArr = None return dataArr