Exemple #1
0
 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
Exemple #2
0
 def __init__(self,
              initialArgument,
              inputQ,
              outputQ,
              isException=False,
              logger=Logger()):
     super().__init__(initialArgument, inputQ, outputQ, logger=Logger())
     self.isException = isException
Exemple #3
0
 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)
Exemple #4
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)
Exemple #5
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
Exemple #6
0
 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))
Exemple #7
0
 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()
Exemple #8
0
    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)
Exemple #9
0
 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)
Exemple #10
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
Exemple #11
0
    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
Exemple #12
0
 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()
Exemple #13
0
    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))
Exemple #14
0
 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
Exemple #15
0
 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())
Exemple #16
0
 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
Exemple #17
0
    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)
Exemple #18
0
 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]
Exemple #19
0
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)
Exemple #20
0
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])
Exemple #21
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)
Exemple #22
0
 def rpRevise(self):
     """
     Overrides rpickler.
     """
     if "logger" not in self.__dict__.keys():
         self.logger = Logger()
Exemple #23
0
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))
Exemple #24
0
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
Exemple #25
0
    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
Exemple #26
0
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):
Exemple #27
0
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,
Exemple #28
0
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
Exemple #29
0
 def setUp(self):
     self.remove()
     self.logger = Logger(toFile=LOG_PATH,
                          logPerformance=True,
                          logLevel=logs.LEVEL_MAX)
Exemple #30
0
    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