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 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 fitModel(self, params: lmfit.Parameters = None, max_nfev: int = 100): """ Fits the model by adjusting values of parameters based on differences between simulated and provided values of floating species. Parameters ---------- params: starting values of parameters max_nfev: maximum number of function evaluations Example ------- f.fitModel() """ ParameterDescriptor = collections.namedtuple( "ParameterDescriptor", "params method std minimizer minimizerResult") block = Logger.join(self._loggerPrefix, "fitModel") guid = self.logger.startBlock(block) self._initializeRoadrunnerModel() if self.parametersToFit is None: # Compute fit and residuals for base model self.params = None else: if params is None: params = self.mkParams() # Fit the model to the data using one or more methods. # Choose the result with the lowest residual standard deviation paramDct = {} for method in self._fitterMethods: for _ in range(self._numFitRepeat): minimizer = lmfit.Minimizer(self._residuals, params, max_nfev=max_nfev) try: minimizerResult = minimizer.minimize(method=method, max_nfev=max_nfev) except Exception as excp: msg = "Error minimizing for method: %s" % method self.logger.error(msg, excp) continue params = minimizerResult.params std = np.std(self._residuals(params)) if method in paramDct.keys(): if std >= paramDct[method].std: continue paramDct[method] = ParameterDescriptor( params=params.copy(), method=method, std=std, minimizer=minimizer, minimizerResult=minimizerResult, ) if len(paramDct) == 0: msg = "*** Minimizer failed for this model and data." raise ValueError(msg) # Select the result that has the smallest residuals sortedMethods = sorted(paramDct.keys(), key=lambda m: paramDct[m].std) bestMethod = sortedMethods[0] self.params = paramDct[bestMethod].params self.minimizer = paramDct[bestMethod].minimizer self.minimizerResult = paramDct[bestMethod].minimizerResult # Ensure that residualsTS and fittedTS match the parameters self.updateFittedAndResiduals(params=self.params) self.logger.endBlock(guid)
def simulate(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 set(default, parameter): # Sets to default if parameter unspecified if parameter is None: return default else: return parameter ##V block = Logger.join(self._loggerPrefix, "fitModel.simulate") guid = self.logger.startBlock(block) ## V sub1Block = Logger.join(block, "sub1") sub1Guid = self.logger.startBlock(sub1Block) startTime = set(self.observedTS.start, startTime) endTime = set(self.observedTS.end, endTime) numPoint = set(len(self.observedTS), numPoint) ## V sub1aBlock = Logger.join(sub1Block, "sub1a") sub1aGuid = self.logger.startBlock(sub1aBlock) if self.roadrunnerModel is None: self._initializeRoadrunnerModel() self.roadrunnerModel.reset() ## ^ self.logger.endBlock(sub1aGuid) ## V sub1bBlock = Logger.join(sub1Block, "sub1b") sub1bGuid = self.logger.startBlock(sub1bBlock) if params is not None: # Parameters have been specified self._setupModel(params) ## ^ self.logger.endBlock(sub1bGuid) # Do the simulation selectedColumns = list(self.selectedColumns) if not TIME in selectedColumns: selectedColumns.insert(0, TIME) ## ^ self.logger.endBlock(sub1Guid) ## V roadrunnerBlock = Logger.join(block, "roadrunner") roadrunnerGuid = self.logger.startBlock(roadrunnerBlock) data = self.roadrunnerModel.simulate(startTime, endTime, numPoint, selectedColumns) self.logger.endBlock(roadrunnerGuid) ## ^ # Select the required columns ## V sub2Block = Logger.join(block, "sub2") sub2Guid = self.logger.startBlock(sub2Block) fittedTS = NamedTimeseries(namedArray=data) self.logger.endBlock(sub2Guid) ## ^ self.logger.endBlock(guid) ##^ return fittedTS
def _runBootstrap(arguments: _Arguments, queue=None) -> BootstrapResult: """ Executes bootstrapping. Parameters ---------- arguments: inputs to bootstrap queue: multiprocessing.Queue Notes ----- 1. Only the first process generates progress reports. 2. Uses METHOD_LEASTSQ for fitModel iterations. """ fitter = arguments.fitter logger = fitter.logger mainBlock = Logger.join(arguments._loggerPrefix, "_runBootstrap") mainGuid = logger.startBlock(mainBlock) # Unapack arguments isSuccess = False lastErr = "" # Do an initial fit for _ in range(MAX_TRIES): try: fitter.fitModel() # Initialize model isSuccess = True break except Exception as err: lastErr = err # Set up logging for this process fd = logger.getFileDescriptor() processIdx = arguments.processIdx if fd is not None: sys.stderr = logger.getFileDescriptor() sys.stdout = logger.getFileDescriptor() iterationGuid = None if not isSuccess: msg = "Process %d/modelFitterBootstrip/_runBootstrap" % processIdx logger.error(msg, lastErr) fittedStatistic = TimeseriesStatistic(fitter.observedTS, percentiles=[]) bootstrapResult = BootstrapResult(fitter, 0, {}, fittedStatistic) else: numIteration = arguments.numIteration reportInterval = arguments.reportInterval processingRate = min(arguments.numProcess, multiprocessing.cpu_count()) cols = fitter.selectedColumns synthesizer = arguments.synthesizerClass( observedTS=fitter.observedTS.subsetColumns(cols), fittedTS=fitter.fittedTS.subsetColumns(cols), **arguments.kwargs) # Initialize parameterDct = {p: [] for p in fitter.parametersToFit} numSuccessIteration = 0 lastReport = 0 if fitter.minimizerResult is None: fitter.fitModel() baseChisq = fitter.minimizerResult.redchi # Do the bootstrap iterations bootstrapError = 0 iterationBlock = Logger.join(mainBlock, "Iteration") for iteration in range(numIteration * ITERATION_MULTIPLIER): if iterationGuid is not None: logger.endBlock(iterationGuid) iterationGuid = logger.startBlock(iterationBlock) newObservedTS = synthesizer.calculate() fittingSetupBlock = Logger.join(iterationBlock, "fittingSetup") fittingSetupGuid = logger.startBlock(fittingSetupBlock) newFitter = ModelFitterBootstrap( fitter.roadrunnerModel, newObservedTS, fitter.parametersToFit, selectedColumns=fitter.selectedColumns, # Use bootstrap methods for fitting fitterMethods=fitter._bootstrapMethods, parameterLowerBound=fitter.lowerBound, parameterUpperBound=fitter.upperBound, fittedDataTransformDct=fitter.fittedDataTransformDct, logger=logger, _loggerPrefix=iterationBlock, isPlot=fitter._isPlot) fittedStatistic = TimeseriesStatistic(newFitter.observedTS, percentiles=[]) logger.endBlock(fittingSetupGuid) try: if (iteration > 0) and (iteration != lastReport) \ and (processIdx == 0): totalSuccessIteration = numSuccessIteration * processingRate totalIteration = iteration * processingRate if totalIteration % reportInterval == 0: msg = "Bootstrap completed %d total iterations " msg += "with %d successes." msg = msg % (totalIteration, totalSuccessIteration) fitter.logger.status(msg) lastReport = numSuccessIteration if numSuccessIteration >= numIteration: # Performed the iterations break tryBlock = Logger.join(iterationBlock, "try") tryGuid = logger.startBlock(tryBlock) try: tryFitterBlock = Logger.join(tryBlock, "Fitter") tryFitterGuid = logger.startBlock(tryFitterBlock) newFitter.fitModel(params=fitter.params) logger.endBlock(tryFitterGuid) except Exception as err: # Problem with the fit. Don't numSuccessIteration it. msg = "Process %d/modelFitterBootstrap" % processIdx msg += " Fit failed on iteration %d." % iteration fitter.logger.error(msg, err) logger.endBlock(tryGuid) continue if newFitter.minimizerResult.redchi > MAX_CHISQ_MULT * baseChisq: if IS_REPORT: msg = "Process %d: Fit has high chisq: %2.2f on iteration %d." fitter.logger.exception( msg % (processIdx, newFitter.minimizerResult.redchi, iteration)) logger.endBlock(tryGuid) continue if newFitter.params is None: continue numSuccessIteration += 1 dct = newFitter.params.valuesdict() [ parameterDct[p].append(dct[p]) for p in fitter.parametersToFit ] cols = newFitter.fittedTS.colnames fittedStatistic.accumulate(newFitter.fittedTS) newFitter.observedTS = synthesizer.calculate() logger.endBlock(tryGuid) except Exception as err: msg = "Process %d/modelFitterBootstrap" % processIdx msg += " Error on iteration %d." % iteration fitter.logger.error(msg, err) bootstrapError += 1 fitter.logger.status("Process %d: completed bootstrap." % (processIdx + 1)) bootstrapResult = BootstrapResult(fitter, numSuccessIteration, parameterDct, fittedStatistic, bootstrapError=bootstrapError) if iterationGuid is not None: logger.endBlock(iterationGuid) logger.endBlock(mainGuid) if fd is not None: if not fd.closed: fd.close() if queue is None: return bootstrapResult else: queue.put(bootstrapResult)
def fitModel(self, params: lmfit.Parameters = None, max_nfev=100): """ Fits the model by adjusting values of parameters based on differences between simulated and provided values of floating species. Parameters ---------- params: starting values of parameters Example ------- f.fitModel() """ ParameterDescriptor = collections.namedtuple( "ParameterDescriptor", "params method rssq kwargs minimizer minimizerResult") MAX_NFEV = "max_nfev" block = Logger.join(self._loggerPrefix, "fitModel") guid = self.logger.startBlock(block) self.initializeRoadRunnerModel() self.params = None if self.parametersToFit is not None: if params is None: params = self.mkParams() # Fit the model to the data using one or more methods. # Choose the result with the lowest residual standard deviation paramResults = [] lastExcp = None for idx, optimizerMethod in enumerate(self._fitterMethods): method = optimizerMethod.method kwargs = optimizerMethod.kwargs if MAX_NFEV not in kwargs: kwargs[MAX_NFEV] = max_nfev for _ in range(self._numFitRepeat): self._bestParameters = _BestParameters(params=None, rssq=None) minimizer = lmfit.Minimizer(self._residuals, params) try: minimizerResult = minimizer.minimize(method=method, **kwargs) except Exception as excp: lastExcp = excp msg = "Error minimizing for method: %s" % method self.logger.error(msg, excp) continue params = self._bestParameters.params.copy() rssq = np.sum(self._residuals(params)**2) if len(paramResults) > idx: if rssq >= paramResults[idx].rssq: continue parameterDescriptor = ParameterDescriptor( params=params, method=method, rssq=rssq, kwargs=dict(kwargs), minimizer=minimizer, minimizerResult=minimizerResult, ) paramResults.append(parameterDescriptor) if len(paramResults) == 0: msg = "*** Minimizer failed for this model and data." self.logger.error(msg, lastExcp) else: # Select the result that has the smallest residuals sortedMethods = sorted(paramResults, key=lambda r: r.rssq) bestMethod = sortedMethods[0] self.params = bestMethod.params self.minimizer = bestMethod.minimizer self.minimizerResult = bestMethod.minimizerResult # Ensure that residualsTS and fittedTS match the parameters self.updateFittedAndResiduals(params=self.params) self.logger.endBlock(guid)