R/dMod-import.R

Defines functions importPEtabSBML_indiv pdIndiv_getBaseScales pdIndiv_getParameters_conditionGrid pdIndiv_initializeGridlist getTrafoType updateParscalesToBaseTrafo sbmlImport_getInitialsFromSBML sbmlImport_getCompartmentsFromSBML sbmlImport_getParametersFromSBML sbmlImport_getEventsFromTrueEvents TransformEvents sbmlImport_getEventStrings sbmlImport_getFunctionInfo smblImport_replaceFunctions sbmlImport_getReactionDetails sanitizePowers plotPEtabSBML getDataPEtabSBML getReactionsSBML getParametersSBML getObservablesSBML getInitialsSBML getConditionsSBML testPEtabSBML fitModelPEtabSBML importPEtabSBML

Documented in fitModelPEtabSBML getConditionsSBML getDataPEtabSBML getInitialsSBML getObservablesSBML getParametersSBML getReactionsSBML getTrafoType importPEtabSBML importPEtabSBML_indiv pdIndiv_getBaseScales pdIndiv_getParameters_conditionGrid pdIndiv_initializeGridlist plotPEtabSBML sanitizePowers sbmlImport_getCompartmentsFromSBML sbmlImport_getEventsFromTrueEvents sbmlImport_getFunctionInfo sbmlImport_getInitialsFromSBML sbmlImport_getParametersFromSBML sbmlImport_getReactionDetails smblImport_replaceFunctions testPEtabSBML updateParscalesToBaseTrafo

#' Import an SBML model and corresponding PEtab objects
#'
#' @description This function imports an SBML model and corresponding PEtab files, e.g. from the Benchmark collection.
#'
#' @param modelname name of folder containing all PEtab files of the model to be imported. NULL if file paths are defined separately (see below).
#' @param path2model path to model folder
#' @param TestCases TRUE to load feature test cases
#' @param path2TestCases path to feature test case folder
#' @param compile if FALSE, g, ODEmodel and err are loaded from .RData (if present) and compilation time is saved
#' @param SBML_file SBML model as .xml
#' @param observable_file PEtab observable file as .tsv
#' @param condition_file PEtab condition file as .tsv
#' @param data_file PEtab data file as .tsv
#' @param parameter_file PEtab parameter file as .tsv
#'
#' @details Objects such as model equations, parameters or data are automatically assigned to the following standard variables and written to your current working directory (via <<-):
#' reactions, observables, errors, g, x, p0, err, obj, mydata, ODEmodel, condition.grid, trafoL, pouter, times.
#' Compiled objects (g, ODEmodel and err) are saved in .RData.
#'
#' @return name of imported model
#'
#' @author Marcus Rosenblatt and Svenja Kemmer
#'
#' @export
#'
importPEtabSBML <- function(modelname = "Boehm_JProteomeRes2014",
                            path2model = "BenchmarkModels/",
                            testCases = FALSE,
                            path2TestCases = "PEtabTests/",
                            compile = TRUE,
                            SBML_file = NULL,
                            observable_file = NULL,
                            condition_file = NULL,
                            data_file = NULL,
                            parameter_file = NULL
)
{
  ## load required packages
  
  require(libSBML)
  require(dplyr)
  require(rlang)
  require(stringr)
  require(deSolve)
  require(ggplot2)
  require(trust)
  ## Define path to SBML and PEtab files --------------------
  
  starttime <- Sys.time()
  if(testCases == FALSE){
    if(is.null(SBML_file))       SBML_file       <- paste0(path2model, modelname, "/model_", modelname, ".xml")
    if(is.null(observable_file)) observable_file <- paste0(path2model, modelname, "/observables_", modelname, ".tsv")
    if(is.null(condition_file))  condition_file  <- paste0(path2model, modelname, "/experimentalCondition_", modelname, ".tsv")
    if(is.null(data_file))       data_file       <- paste0(path2model, modelname, "/measurementData_", modelname, ".tsv")
    if(is.null(parameter_file))  parameter_file  <- paste0(path2model, modelname, "/parameters_", modelname, ".tsv")
  } else{
    SBML_file       <- paste0(path2TestCases, modelname, "/_model.xml")
    observable_file <- paste0(path2TestCases, modelname, "/_observables.tsv")
    condition_file  <- paste0(path2TestCases, modelname, "/_conditions.tsv")
    data_file       <- paste0(path2TestCases, modelname, "/_measurements.tsv")
    parameter_file  <- paste0(path2TestCases, modelname, "/_parameters.tsv")
  }
  mywd <- getwd()
  if(!file.exists(SBML_file)){       cat(paste0("The file ",mywd,SBML_file, " does not exist. Please check spelling or provide the file name via the SBML_file argument.")); return(NULL)}
  if(!file.exists(observable_file)){ cat(paste0("The file ",mywd,observable_file, " does not exist. Please check spelling or provide the file name via the observable_file argument.")); return(NULL)}
  if(!file.exists(condition_file)){  cat(paste0("The file ",mywd,condition_file, " does not exist. Please check spelling or provide the file name via the condition_file argument.")); return(NULL)}
  if(!file.exists(data_file)){       cat(paste0("The file ",mywd,data_file, " does not exist. Please check spelling or provide the file name via the data_file argument.")); return(NULL)}
  if(!file.exists(parameter_file)){  cat(paste0("The file ",mywd,parameter_file, " does not exist. Please check spelling or provide the file name via the parameter_file argument.")); return(NULL)}
  
  if(is.null(modelname)) modelname <- "mymodel"
  ## Load shared objects --------------------
  
  dir.create(paste0(mywd,"/CompiledObjects/"), showWarnings = FALSE)
  setwd(paste0(mywd,"/CompiledObjects/"))
  files_loaded <- FALSE
  if(compile == FALSE & file.exists(paste0(modelname,".RData"))){
    load(paste0(modelname,".RData"))
    files_loaded <- TRUE
  }
  setwd(mywd)
  
  ## Model Definition - Equations --------------------
  
  cat("Reading SBML file ...\n")
  mylist <- getReactionsSBML(SBML_file, condition_file)
  myreactions <- mylist$reactions
  myreactions_orig <- mylist$reactions_orig
  myevents <- mylist$events
  mypreeqEvents <- mylist$preeqEvents
  mystates <- mylist$mystates
  reactions <<- myreactions
  
  
  ## g - Model Definition - Observables --------------------
  
  cat("Reading observables ...\n")
  myobservables <- getObservablesSBML(observable_file)
  observables <<- myobservables
  
  
  cat("Compiling observable function ...\n")
  if(!files_loaded) {
    setwd(paste0(mywd,"/CompiledObjects/"))
    myg <- Y(myobservables, myreactions, compile=TRUE, modelname=paste0("g_",modelname))
    setwd(mywd)
  }
  g <<- myg
  
  
  ## data - Get Data ------------
  
  cat("Reading data file ...\n")
  mydataSBML <- getDataPEtabSBML(data_file, observable_file)
  mydata <- mydataSBML$data
  mydata <<- mydata
  
  
  ## x - Model Generation ---------------------
  
  cat("Compiling ODE model ...\n")
  
  if(!files_loaded) {
    setwd(paste0(mywd,"/CompiledObjects/"))
    myodemodel <- odemodel(myreactions, forcings = NULL, events = myevents, fixed=NULL, modelname = paste0("odemodel_", modelname), jacobian = "inz.lsodes", compile = TRUE)
    setwd(mywd)
  }
  ODEmodel <<- myodemodel
  
  
  ## err -  Check and define error model ------------
  
  cat("Check and compile error model ...\n")
  myerrors <- mydataSBML$errors
  errors <<- myerrors
  
  myerr <- NULL
  if(!files_loaded) {
    if(!is_empty(getSymbols(myerrors))){
      setwd(paste0(mywd,"/CompiledObjects/"))
      myerr <- Y(myerrors, f = c(as.eqnvec(myreactions), myobservables), states = names(myobservables), attach.input = FALSE, compile = TRUE, modelname = paste0("errfn_", modelname))
      setwd(mywd)
    }
  }
  err <<- myerr
  
  ## Define constraints, initials, parameters and compartments --------------
  
  cat("Reading parameters and initials ...\n")
  myparameters <- getParametersSBML(parameter_file, SBML_file)
  myconstraints <- myparameters$constraints
  SBMLfixedpars <- myparameters$SBMLfixedpars
  myfit_values <- myparameters$pouter
  myinitialsSBML <- getInitialsSBML(SBML_file, condition_file)
  mycompartments <- myinitialsSBML$compartments
  myinitials <- myinitialsSBML$initials
  
  ## p - Parameter transformations -----------
  
  # Generate condition.grid
  grid <- getConditionsSBML(conditions = condition_file, data = data_file, observables_file = observable_file)
  mypreeqCons <- grid$preeqCons
  mycondition.grid <- grid$condition_grid
  
  if(!is.null(SBMLfixedpars)){
    for (i in 1:length(SBMLfixedpars)) {
      if(!names(SBMLfixedpars)[i] %in% names(mycondition.grid))  mycondition.grid[names(SBMLfixedpars)[i]] <- SBMLfixedpars[i]
    }
  }
  condi_pars <- names(mycondition.grid)[!names(mycondition.grid) %in% c("conditionName","conditionId")]
  condition.grid <<- mycondition.grid
  
  cat("Generate parameter transformations ...\n")
  myinnerpars <- unique(c(getParameters(myodemodel), getParameters(myg), getSymbols(myerrors)))
  names(myinnerpars) <- myinnerpars
  trafo <- as.eqnvec(myinnerpars, names = myinnerpars)
  trafo <- replaceSymbols(names(mycompartments), mycompartments, trafo)
  # only overwrite intial if it's not defined in condition.grid
  for (i in 1:length(myinitials)) {
    if(!names(myinitials)[i] %in% names(mycondition.grid)){
      trafo <- replaceSymbols(names(myinitials)[i], myinitials[i], trafo)
    }
  }
  trafo <- replaceSymbols(names(myconstraints), myconstraints, trafo)
  
  # branch trafo for different conditions
  mytrafoL <- branch(trafo, table=mycondition.grid)
  # set preequilibration event initials to corresponding values
  if(!is.null(mypreeqEvents)){
    mypreeqEvents2replace <- filter(mypreeqEvents, !var%in%mystates)
    mytrafoL <- repar("x~y", mytrafoL , x = unique(mypreeqEvents2replace$var), y = attr(mypreeqEvents2replace, "initials"))
  }
  # set remaining event initial to 0
  mytrafoL <- repar("x~0", mytrafoL , x = setdiff(unique(myevents$var), unique(mypreeqEvents$var)))
  
  # condition-specific assignment of parameters from condition grid
  if(length(condi_pars) > 0){
    for (j in 1:length(names(mytrafoL))) {
      for (i in 1:length(condi_pars)) {
        mytrafoL[[j]] <- repar(x~y, mytrafoL[[j]], x=condi_pars[i], y=mycondition.grid[j,condi_pars[i]])
      }
    }
  }
  
  
  # transform parameters according to scale defined in the parameter PEtab file
  parscales <- attr(myfit_values,"parscale")
  mynames <- names(parscales)
  for(i in 1:length(parscales)){
    par <- parscales[i]
    par[par=="lin"] <- ""
    par[par=="log10"] <- "10**"
    par[par=="log"] <- "exp"
    parameter <- mynames[i]
    mytrafoL <- repar(paste0("x~",par,"(x)"), mytrafoL, x = parameter)
  }
  trafoL <<- mytrafoL
  
  
  ## Specify prediction functions ------
  
  # Get numeric steady state for preequilibration conditions
  if(!is.null(mypreeqCons)){
    myf <- as.eqnvec(myreactions_orig)[mystates]
    cq <- conservedQuantities(myreactions_orig$smatrix)
    if(!is.null(cq)){
      for(i in 1:nrow(cq)){
        myf[getSymbols(cq)[1]] <- paste0(as.character(conservedQuantities(myreactions_orig$smatrix)[1,]),"-1")
      }
    }
    setwd(paste0(mywd,"/CompiledObjects/"))
    pSS <- P(myf, condition = "c0", method = "implicit", compile = TRUE, modelname = paste0("preeq_", modelname))
    setwd(mywd)
  } else pSS <- NULL
  
  cat("Generate prediction function ...\n")
  tolerances <- 1e-7
  myp0 <- myx <- NULL
  for (C in names(mytrafoL)) {
    if(C%in%mypreeqCons){
      myp0 <- myp0 + pSS*P(mytrafoL[[C]], condition = C)
    } else {
      myp0 <- myp0 + P(mytrafoL[[C]], condition = C)
    }
    myx <- myx + Xs(myodemodel, optionsOde = list(method = "lsoda", rtol = tolerances, atol = tolerances, maxsteps = 5000),
                    optionsSens = list(method = "lsodes", lrw=200000, rtol = tolerances, atol = tolerances),
                    condition = C)
  }
  
  p0 <<- myp0
  x <<- myx
  
  ## Generate objective function and initial parameter set -------
  
  myouterpars <- getParameters(myp0)
  mypouter <- structure(rep(NA,length(myouterpars)), names = myouterpars)
  
  common <- intersect(names(mypouter),names(myfit_values))
  mypouter[common] <- myfit_values[common]
  attr(mypouter, "parscales") <- parscales[which(names(myfit_values)%in%names(mypouter))]
  attr(mypouter, "lowerBound") <- attr(myfit_values,"lowerBound")[which(names(myfit_values)%in%names(mypouter))]
  attr(mypouter, "upperBound") <- attr(myfit_values,"upperBound")[which(names(myfit_values)%in%names(mypouter))]
  pouter <<- mypouter
  
  
  ## Define objective function -------
  
  cat("Generate objective function ...\n")
  if(!is.null(myerrors)){
    myobj <- normL2(mydata, myg*myx*myp0, errmodel = myerr) #+ constraintL2(prior, sigma=16)
  } else myobj <- normL2(mydata, myg*myx*myp0)
  obj <<- myobj
  
  mytimes <- seq(0,max(do.call(c, lapply(1:length(mydata), function(i) max(mydata[[i]]$time)))), len=501)
  times <<- mytimes
  
  if(!files_loaded){
    setwd(paste0(mywd,"/CompiledObjects/"))
    save(list = c("myg","myodemodel","myerr"),file = paste0(modelname,".RData"))
    setwd(mywd)
  }
  
  model_name <<- modelname
  
  endtime <- Sys.time()
  mytimediff <- as.numeric(difftime(endtime, starttime, unit="secs"))
  if(mytimediff > 3600) cat(paste0(modelname, " imported in ",as.character(format(as.numeric(difftime(endtime, starttime, unit="hours")), digits=3)), " hours.\n")) else
    if(mytimediff > 60) cat(paste0(modelname, " imported in ",as.character(format(as.numeric(difftime(endtime, starttime, unit="mins")), digits=3)), " minutes.\n")) else
      cat(paste0(modelname, " imported in ",as.character(format(as.numeric(difftime(endtime, starttime, unit="secs")), digits=3)), " seconds.\n"))
  # return(modelname)
  
  
  
  # .. Collect list -----
  symbolicEquations <- list(
    reactions   = myreactions,
    events      = myevents,
    observables = myobservables,
    errors      = myerrors,
    trafo       = trafoL)
  fns <- list(
    g = myg,
    x = myx,
    p0 = myp0
  )
  prd <- (myg*myx*myp0)
  obj_data <- normL2(mydata, prd, errmodel = myerr,
                     times = seq(0,max(as.data.frame(mydata)$time), len=501))
  
  filenameParts = list(modelname = modelname, .currentFolder = mywd,
                       .compiledFolder = "CompiledObjects",type = "classic")
  pe <- readPetab(filename = file.path(path2model, modelname))
  # .. Collect final list -----
  pd <- list(
    # petab
    pe                 = pe,
    # Basic dMod elements
    dModAtoms          = list(
      # [ ] add events!
      symbolicEquations  = symbolicEquations,
      odemodel           = myodemodel,
      data               = mydata,
      gridlist           = NULL,
      e                  = myerr,
      fns                = fns
    ),
    # other components: Dump your stuff here
    filenameParts = filenameParts,
    # Parameters + Time
    pars               = dMod::unclass_parvec(myfit_values),
    times              = dMod::predtimes(pe$measurementData$time, Nobjtimes = 200),
    # High-level functions
    prd                = prd,
    obj_data           = obj_data
  )
  
  
  
  pd
}



#' Fit a model imported via importPEtabSBML
#'
#' @description A wrapper function to use \link{mstrust} with imported PEtabSBML models. Some reasonable standard arguments for mstrust are used. Results of mstrust are written to Results folder.
#'
#' @param objfun Objective function to be minimized as created by \link{importPEtabSBML}.
#' @param nrfits numeric, Number of fits to be performed
#' @param nrcores numeric, Number of cores to be used
#' @param useBounds  boolean, if TRUE, parameter bounds are taken as provided in PEtab format, if FALSE no parameter bounds are applied
#'
#' @return parframe with the parameter estimated of the multi-start optimization
#'
#' @author Marcus Rosenblatt and Svenja Kemmer
#'
#' @export
#' @importFrom dMod mstrust msParframe
#'
fitModelPEtabSBML <- function(objfun=obj, nrfits=4, nrcores=4, useBounds=TRUE){
  prior <- structure(rep(0,length(pouter)))
  names(prior) <- names(pouter)
  mywd <- getwd()
  dir.create(paste0(mywd,"/Test/mstrust/"), showWarnings = FALSE)
  if(useBounds) out <- dMod::mstrust(objfun=objfun, center=dMod::msParframe(prior, n = nrfits+1, seed=47)[-1,], studyname=model_name, rinit = 0.1, rmax = 10,
                                     fits = nrfits, cores = nrcores, samplefun = "rnorm", resultPath = "Test/mstrust/",
                                     parlower = attr(pouter, "lowerBound"), parupper=attr(pouter, "upperBound"),
                                     stats = FALSE, narrowing = NULL, iterlim=400, sd = 3)
  else out <- dMod::mstrust(objfun=objfun, center=dMod::msParframe(prior, n = nrfits, seed=47), studyname=model_name, rinit = 0.1, rmax = 10,
                            fits = nrfits, cores = nrcores, samplefun = "rnorm", resultPath = "Test/mstrust/",
                            stats = FALSE, narrowing = NULL, iterlim=400, sd = 3)
  if(any(lapply(out, function(fgh) fgh$converged)==TRUE)) return(as.parframe(out)) else {cat("No fit converged."); return(NULL)}
}



#' Test PEtabSBML import
#'
#' @description This function imports, evaluates and tests the PEtab model.
#'
#' @param models model names to test
#'
#' @return evaluation data.frame
#'
#' @author Marcus Rosenblatt and Svenja Kemmer
#'
#' @export
#'
testPEtabSBML <- function(models = c(
  #"Boehm_JProteomeRes2014"
  # "Fujita_SciSignal2010",
  # "Borghans_BiophysChem1997",
  # "Elowitz_Nature2000",
  # "Sneyd_PNAS2002",
  # "Crauste_CellSystems2017",
  # "Schwen_PONE2014",
  # "Raia_CancerResearch2011",
  # "Zheng_PNAS2012",
  # "Beer_MolBioSystems2014",
  # "Brannmark_JBC2010",
  # "Bruno_JExpBio2016",
  # "Chen_MSB2009",
  # "Fiedler_BMC2016",
  # "Weber_BMC2015",
  # "Swameye_PNAS2003"
  # "Bachmann_MSB2011"
  # "Lucarelli_CellSystems2018",
  "0001",
  "0002",
  "0003",
  "0004",
  "0005",
  "0006",
  "0007",
  "0008",
  "0009",
  "0010",
  "0011",
  "0012",
  "0013",
  "0014",
  "0015",
  "0016"
), testFit = TRUE, timelimit = 5000, testCases = TRUE) {
  try_with_time_limit <- function(expr, cpu = Inf, elapsed = Inf) {
    y <- try(
      {
        setTimeLimit(cpu, elapsed)
        expr
      },
      silent = TRUE
    )
    if (inherits(y, "try-error")) NULL else y
  }
  require(crayon)
  require(trust)
  require(deSolve)
  require(ggplot2)
  cat(green("Start test function...\n"))
  mywd <- getwd()
  teststarttime <- Sys.time()
  output <- NULL
  predictions <- NULL
  for (model in models) {
    setwd(mywd)
    importtest <- F
    plottest <- F
    bestfit <- NA
    cat(blue(paste0("Testing ", model, "\n")))
    fgh <- try_with_time_limit(
      {
        test <- try(importPEtabSBML(model, compile = T, testCases = testCases), silent = T)
        if (inherits(test, "try-error")) "import error" else test
      },
      timelimit
    )
    if (fgh == "import error") {
      cat(yellow("Import error or time limit exceeded for", model, "\n\n\n"))
      output <- rbind(output, data.frame(
        modelname = model, import = importtest,
        fitting_time = NA, plot = plottest, chi2 = NA, LL = NA, bestfit = NA, difference = NA
      ))
    } else {
      importtest <- T
      testobj <- try(obj(pouter))
      if (inherits(testobj, "try-error")) {
        cat(red("Warning: Error in calculation of objective function.\n"))
        output <- rbind(output, data.frame(
          modelname = model, import = importtest,
          fitting_time = NA, plot = plottest, chi2 = NA, LL = NA, bestfit = NA, difference = NA
        ))
      } else {
        if (is.numeric(testobj$value)) {
          cat(green("Calculation of objective function successful.\n"))
          if (testCases){
            # calculate predictions for trajectory comparison
            mysimulations <- read.csv(paste0("PEtabTests/", model, "/_simulations.tsv"), sep = "\t")
            simu_time <- unique(mysimulations$time)
            prediction <- (g*x*p0)(simu_time, pouter)
            predictions <- rbind(predictions, data.frame(
              modelname = model, pred = prediction, obs.transformation = NA
            ))
            # append observable scale to predictions
            for (i in 1:length(observables)) {
              scale <- attr(observables, "obsscales")[i]
              predictions <- predictions %>% mutate(obs.transformation = ifelse(modelname == model & pred.name == names(observables)[i], scale, obs.transformation))
            }
          }
        } else {
          cat(red("Warning: obj(pouter) is not numeric.\n"))
        }
        # objLL <- mynormL2(mydata, g * x * p0, outputLL = T)
        # testLL <- try(-0.5 * objLL(pouter)$value)
        # if (inherits(testLL, "try-error")) testLL <- NA
        if (testFit) {
          fitstarttime <- Sys.time()
          myframe <- fitModelPEtabSBML(nrfits = 20)
          fitendtime <- Sys.time()
          if (is.parframe(myframe) & !is.null(myframe)) {
            if (is.numeric(obj(myframe[1, ])$value)) {
              cat(green("Fit test successful.\n"))
              bestfit <- obj(myframe[1, ])$value
            } else {
              cat(red("Warning: obj(myframe) is not numeric.\n"))
            }
          } else {
            cat(red("Warning: Fit test not successful..\n"))
          }
          mytimediff <- as.numeric(difftime(fitendtime, fitstarttime, unit = "secs"))
          if (mytimediff > 3600) {
            cat(green(paste0("Fitting done in ", as.character(format(as.numeric(difftime(fitendtime, fitstarttime, unit = "hours")), digits = 3)), " hours.\n")))
          } else
            if (mytimediff > 60) {
              cat(green(paste0("Fitting done in ", as.character(format(as.numeric(difftime(fitendtime, fitstarttime, unit = "mins")), digits = 3)), " minutes.\n")))
            } else {
              cat(green(paste0("Fitting done in ", as.character(format(as.numeric(difftime(fitendtime, fitstarttime, unit = "secs")), digits = 3)), " seconds.\n")))
            }
        }
        pdf(file = paste0("Test/", model, "_plotAll.pdf"))
        plotPEtabSBML()
        dev.off()
        pdf(file = paste0("Test/", model, "_plotTargetsObserved.pdf"))
        plotPEtabSBML(name %in% names(observables))
        dev.off()
        pdf(file = paste0("Test/", model, "_plotConditionsObserved.pdf"))
        plotPEtabSBML(condition %in% names(mydata))
        dev.off()
        plottest <- T
        cat(green("Import and plot test for ", fgh, " successful!\n\n\n"))
        
        output <- rbind(output, data.frame(
          modelname = model, import = importtest,
          # fitting_time = format(as.numeric(difftime(fitendtime, fitstarttime, unit = "mins")), digits = 3),
          plot = plottest, chi2 = attr(testobj,"chisquare"), LL = -0.5*testobj$value
          # , bestfit = bestfit, difference = bestfit - testobj$value
        ))
      }
    }
    if (testCases){
      sharedObjects <- paste0("CompiledObjects/",
                              c(paste0("g_",model,".so"),
                                paste0("g_",model,"_deriv.so"),
                                paste0("odemodel_",model,".so"),
                                paste0("odemodel_",model,"_s.so"),
                                paste0("errfn_",model,".so"),
                                paste0("errfn_",model,"_s.so")))
      for (file in sharedObjects) if(file.exists(file)) try(dyn.unload(file))
    }
  }
  if (testCases) {
    simu_output <- output[1]
    output <- cbind(output, chi2_sol = NA, tol_chi2_sol = NA, LL_sol = NA, tol_LL_sol = NA)
    for (model in models) {
      mysolution <- read_yaml(paste0("PEtabTests/", model, "/_", model, "_solution.yaml"))
      output[which(output$modelname == model), "chi2_sol"] <- mysolution$chi2
      output[which(output$modelname == model), "tol_chi2_sol"] <- mysolution$tol_chi2
      output[which(output$modelname == model), "LL_sol"] <- mysolution$llh
      output[which(output$modelname == model), "tol_LL_sol"] <- mysolution$tol_llh
      
      # extract simulation values
      simu_output[which(simu_output$modelname == model), "tol_simus_sol"] <- mysolution$tol_simulations
      mysimulations <- read.csv(paste0("PEtabTests/", model, "/_simulations.tsv"), sep = "\t")
      simu_prediction <- subset(predictions, modelname == model)
      
      # iterate through simulation points
      for (nrow in 1:nrow(mysimulations)) {
        simu_row <- mysimulations[nrow,]
        simu_time <- simu_row$time
        simu_obs <- simu_row$observableId %>% as.character()
        simu_condi <- simu_row$simulationConditionId %>% as.character()
        simu_obspars <- simu_row$observableParameters
        
        if(!is.null(simu_obspars) & length(unique(simu_prediction$pred.condition)) > 1){
          simu_condi <- paste0(simu_condi, "_", simu_obspars)
        }
        pred_row <- subset(simu_prediction, pred.time == simu_time & pred.name == simu_obs & pred.condition == simu_condi)
        # retransform simulation value according to observable transformation
        if(pred_row$obs.transformation == "log10") pred_row$pred.value <- 10**pred_row$pred.value
        if(pred_row$obs.transformation == "log") pred_row$pred.value <- exp(pred_row$pred.value)
        simu_value <- pred_row$pred.value
        
        simu_output[which(simu_output$modelname == model), paste0("simu_", nrow)] <- simu_value
        simu_output[which(simu_output$modelname == model), paste0("simu_", nrow,"_sol")] <- mysimulations$simulation[nrow]
      }
    }
  }
  
  testendtime <- Sys.time()
  mytimediff <- as.numeric(difftime(testendtime, teststarttime, unit = "secs"))
  if (mytimediff > 3600) {
    cat(green(paste0("Test done in ", as.character(format(as.numeric(difftime(testendtime, teststarttime, unit = "hours")), digits = 3)), " hours.\n")))
  } else
    if (mytimediff > 60) {
      cat(green(paste0("Test done in ", as.character(format(as.numeric(difftime(testendtime, teststarttime, unit = "mins")), digits = 3)), " minutes.\n")))
    } else {
      cat(green(paste0("Test done in ", as.character(format(as.numeric(difftime(testendtime, teststarttime, unit = "secs")), digits = 3)), " seconds.\n")))
    }
  
  if (testCases) {
    
    # check simulations
    for (model in models) {
      correctORnot <- NULL
      modelrow <- subset(simu_output, modelname == model)
      modelrow_woNA <- modelrow[colSums(!is.na(modelrow)) > 0]
      for (ncol in seq(3,(ncol(modelrow_woNA)),2)) {
        simuCompare <- abs(modelrow_woNA[[ncol]]-modelrow_woNA[[ncol+1]]) < modelrow_woNA$tol_simus_sol
        correctORnot <- c(correctORnot, simuCompare)
      }
      if(length(unique(correctORnot)) == 1){
        SimuPassed <- unique(correctORnot)
      } else SimuPassed <- FALSE
      simu_output[which(simu_output$modelname == model), "Passed"] <- SimuPassed
    }
    
    output <- cbind(output,
                    X2Passed = (abs(output$chi2 - output$chi2_sol) < output$tol_chi2_sol),
                    LLPassed = (abs(output$LL - output$LL_sol) < output$tol_LL_sol)
    )
  }
  
  if (!testCases) simu_output <- NULL
  return(list(output = output,simu_output = simu_output))
}



#' Import condition.grid from PEtab
#'
#' @description This function imports the experimental conditions from the PEtab condition file as a gondition.grid.
#'
#' @param conditions PEtab condition file as .tsv
#'
#' @return condition.grid as data frame.
#'
#' @author Marcus Rosenblatt and Svenja Kemmer
#'
#' @export
#'
#' @importFrom dplyr inner_join filter mutate
#' @importFrom stringr str_detect
getConditionsSBML <- function(conditions,data, observables_file, FLAGnormalImport = TRUE){
  condition.grid_orig <- read.csv(file = conditions, sep = "\t")
  mydata              <- read.csv(file = data, sep = "\t")
  myobservables       <- read.csv(file = observables_file, sep = "\t")
  
  # handle preequilibration conditions
  myCons <- condition.grid_orig$conditionId
  mypreeqCons <- NULL
  for (con in myCons){if(paste0("preeq_", con)%in%myCons) mypreeqCons <- c(mypreeqCons, con)}
  condition.grid_orig <- dplyr::filter(condition.grid_orig, !conditionId%in%mypreeqCons)
  condition.grid_orig$conditionId <- sub("preeq_", "", condition.grid_orig$conditionId)
  
  # check which conditions are observed
  condis_obs <- mydata$simulationConditionId %>% unique()
  # check which observables exist
  observables <- mydata$observableId %>% unique()
  
  # replace "" by NA
  if(!is.null(mydata$observableParameters)){
    mydata$observableParameters <- mydata$observableParameters %>% as.character()
    mydata <- mydata %>% dplyr::mutate(observableParameters = ifelse(observableParameters == "",NA,observableParameters))
  }
  if(!is.null(mydata$noiseParameters)){
    mydata$noiseParameters <- mydata$noiseParameters %>% as.character()
    mydata <- mydata %>% dplyr::mutate(noiseParameters = ifelse(noiseParameters == "",NA,noiseParameters))
  }
  # generate columns for observableParameters
  if(!is.numeric(mydata$observableParameters) & !is.null(mydata$observableParameters)){
    condition.grid_obs <- data.frame(conditionId = condis_obs)
    for (obs in observables){
      data_obs <- subset(mydata, observableId == obs)
      for (condition in condis_obs){
        if(condition %in% data_obs$simulationConditionId){
          row_pars <- NULL
          obs_par <- subset(data_obs, simulationConditionId == condition)$observableParameters %>% unique() %>% as.character()
          if(length(obs_par)==1){
            if(!is.na(obs_par)){
              # one or more observable parameters?
              if(stringr::str_detect(obs_par,";")){
                myobspars <- strsplit(obs_par,";")[[1]]
                for(i in 1:length(myobspars)) {
                  row_pars <- c(row_pars, myobspars[i])
                }
              } else row_pars <- c(row_pars, obs_par)
            }
            if(!is.null(row_pars)) for (par in 1:length(row_pars)) {
              col_name <- paste0("observableParameter",par,"_",obs)
              condition.grid_obs[which(condition.grid_obs$conditionId==condition),col_name] <- row_pars[par]
            }
          } else {
            col_name <- paste0("observableParameter1_",obs)
            add <- NULL
            for(j in 2:length(obs_par)){
              add <- rbind(add, subset(condition.grid_obs, conditionId==condition))
            }
            condition.grid_obs <- rbind(condition.grid_obs, add)
            condition.grid_obs[which(condition.grid_obs$conditionId==condition),col_name] <- obs_par
            condition.grid_obs$conditionId <- as.character(condition.grid_obs$conditionId)
            condition.grid_obs$conditionId[which(condition.grid_obs$conditionId==condition)] <- paste0(condition,"_", obs_par)
            
            condition.grid_orig <- rbind(condition.grid_orig, add)
            condition.grid_orig$conditionId <- as.character(condition.grid_orig$conditionId)
            condition.grid_orig$conditionId[which(condition.grid_orig$conditionId==condition)] <- paste0(condition,"_", obs_par)
          }
        }
      }
    }
    mycondition.grid <- suppressWarnings(dplyr::inner_join(condition.grid_orig,condition.grid_obs, by = "conditionId"))
    # avoid warning if not all conditions are observed
  } else mycondition.grid <- condition.grid_orig
  
  
  # Error parameters: Need to cover the four cases
  # 1. err = noiseParameter1_obs, noiseParameter1_obs = c(1)              => No columns in condition.grid
  # 2. err = noiseParameter1_obs, noiseParameter1_obs = c("sigma1")       => Need Column in condition.grid
  # 3. err = noiseParameter1_obs * obs, noiseParameter1_obs = c(1)        => Need Column in condition.grid
  # 4. err = noiseParameter1_obs * obs, noiseParameter1_obs = c("sigma1") => Need Column in condition.grid
  # Check if noise parameters need to be generated or the error model is simply "sigma"
  err_simple <- !is.na(as.numeric(myobservables$noiseFormula)) | myobservables$noiseFormula == paste0("noiseParameter1_", myobservables$observableId)
  # Check if there are any symbolic error parameters
  err_symbols <- getSymbols(mydata$noiseParameters)
  # generate columns for noiseParameters
  if((FLAGnormalImport && is.character(mydata$noiseParameters)) | (any(!err_simple) | length(err_symbols) > 0) & !is.null(mydata$noiseParameters))
  {
    if(exists("mycondition.grid")) {condition.grid_orig <- mycondition.grid}
    condition.grid_noise <- data.frame(conditionId = condis_obs)
    for (obs in observables[!err_simple | FLAGnormalImport]) # `| FLAGnormalImport` for backwards compatibility
    {
      data_obs <- subset(mydata, observableId == obs)
      for (condition in condis_obs)
      {
        if(condition %in% data_obs$simulationConditionId){
          row_pars <- NULL
          noise_par <- subset(data_obs, simulationConditionId == condition)$noiseParameters %>% unique() %>% as.character()
          if(!is.na(noise_par)){
            # one or more observable parameters?
            if(stringr::str_detect(noise_par,";")){
              myobspars <- strsplit(noise_par,";")[[1]]
              for(i in 1:length(myobspars)) {
                row_pars <- c(row_pars, myobspars[i])
              }
            } else row_pars <- c(row_pars, noise_par)
          }
          if(!is.null(row_pars)) for (par in 1:length(row_pars)) {
            col_name <- paste0("noiseParameter",par,"_",obs)
            condition.grid_noise[which(condition.grid_noise$conditionId==condition),col_name] <- row_pars[par]
          }
        }
      }
      
    }
    mycondition.grid <- suppressWarnings(dplyr::inner_join(condition.grid_orig,condition.grid_noise, by = "conditionId"))
    # avoid warning if not all conditions are observed
  }
  
  if(!exists("mycondition.grid")) mycondition.grid <- condition.grid_orig
  rownames(mycondition.grid) <- mycondition.grid$conditionId
  # mycondition.grid$conditionId <- NULL ## we need this column in cases with just one condition!
  
  # check if all conditions are observed
  if(nrow(mycondition.grid) < nrow(condition.grid_orig)) print("There exist non-observed conditions!")
  
  for(i in 1:nrow(mycondition.grid)){
    for(j in 1:ncol(mycondition.grid)){
      if(is.na(mycondition.grid[i,j])) mycondition.grid[i,j] <- "1"
    }
  }
  
  return(list(condition_grid=mycondition.grid, preeqCons=mypreeqCons))
}



#' Import initials from SBML.
#'
#' @description This function imports initial values or equations describing the same from SBML and writes them in a named vector.
#'
#' @param model SBML file as .xml
#'
#' @return Named vector of initials.
#'
#' @author Marcus Rosenblatt, Svenja Kemmer and Frank Bergmann
#' importFrom libSBML readSBML formulaToL3String
#' @export
#'
getInitialsSBML <- function(model, conditions){
  
  condition.grid <- read.csv(file = conditions, sep = "\t")
  model = readSBML(model)$getModel()
  # model = libSBML::readSBML(model)$getModel()
  initials <- NULL
  species <- NULL
  
  # now we can go through all species
  for ( i in 0:(model$getNumSpecies()-1) ){
    current <- model$getSpecies(i)
    
    # now the species can have several cases that determine
    # their initial value
    
    # CASE 1: it could be that the species is fully determined by an assignment rule
    # (that apply at all times), so we have to check rules first
    rule <- model$getRule(current$getId())
    if (!is.null(rule))
    {
      # ok there is a rule for this species so lets figure out what its type
      # is as that determines whether it applies at t0
      rule_type <- rule$getTypeCode()
      type_name <- SBMLTypeCode_toString(rule_type, 'core')
      
      # CASE 1.1: Assignment rule
      if (type_name == "AssignmentRule"){
        # the initial value is determined by the formula
        math <- rule$getMath()
        if (!is.null(math)){
          formula <- formulaToL3String(math)
          # print(paste('Species: ', current$getId(), ' is determined at all times by formula: ', formula))
          initials <- c(initials,formula)
          species <- c(species,current$getId())
          
          # no need to look at other values so continue with next species
          next
        }
      }
      
      # CASE 1.2: Rate rule
      if (type_name == "RateRule")
      {
        math <- rule$getMath()
        if (!is.null(math))
        {
          formula <- formulaToL3String(math)
          # print(paste('Species: ', current$getId(), ' has an ode rule with formula: ', formula))
          initials <- c(initials,formula)
          species <- c(species,current$getId())
          
          # there is an ODE attached to the species, its initial value is needed
        }
      }
    }
    
    
    # CASE 2 (or subcase of 1?): Initial assignment
    
    # it could have an initial assignment
    ia <- model$getInitialAssignment(current$getId())
    if (!is.null(ia))
    {
      math <- ia$getMath()
      if (!is.null(math))
      {
        formula <- formulaToL3String(math)
        # formula <- libSBML::formulaToL3String(math)
        # print(paste("Species: ", current$getId(), " has an initial assignment with formula: ", formula))
        initials <- c(initials,formula)
        species <- c(species,current$getId())
        
        # as soon as you have that formula, no initial concentration / amount applies
        # so we don't have to look at anything else for this species
        next
      }
    }
    
    # CASE 3 (or subcase of 1?): Inital amount
    # it could have an initial amount
    if (current$isSetInitialAmount())
    {
      # print (paste("Species: ", current$getId(), "has initial amount: ", current$getInitialAmount()))
      initials <- c(initials,current$getInitialAmount())
      species <- c(species,current$getId())
    }
    
    # CASE 4 (or subcase of 1?): Inital Concentration
    # it could have an initial concentration
    if (current$isSetInitialConcentration())
    {
      # print (paste("Species: ", current$getId(), "has initial concentration: ", current$getInitialConcentration()))
      initials <- c(initials,current$getInitialConcentration())
      species <- c(species,current$getId())
    }
  }
  names(initials) <- species
  
  
  ## extract compartments
  # check if compartments exist
  if(model$getNumCompartments()>0) {
    
    #initialize vectors
    comp_name <- NULL
    comp_size <- NULL
    
    for ( i in 0:(model$getNumCompartments()-1) ) {
      
      # get compartment name and size
      
      compartmentId <- model$getCompartment(i)$getId()
      if(compartmentId %in% names(condition.grid)){
        size <- condition.grid[compartmentId] %>% as.numeric()
      } else size <- model$getCompartment(i)$getSize()
      
      comp_name <- c(comp_name,compartmentId)
      comp_size <- c(comp_size,size)
      
    }
    compartments <- comp_size
    names(compartments) <- comp_name
  }
  
  # Get compartments
  if(model$getNumCompartments()>0) {
    
    #initialize vectors
    comp_name <- NULL
    comp_size <- NULL
    
    for ( i in 0:(model$getNumCompartments()-1) ) {
      
      # get compartment name and size
      
      compartmentId <- model$getCompartment(i)$getId()
      if(compartmentId %in% names(condition.grid)){
        size <- condition.grid[compartmentId] %>% as.numeric()
      } else size <- model$getCompartment(i)$getSize()
      
      comp_name <- c(comp_name,compartmentId)
      comp_size <- c(comp_size,size)
      
    }
    compartments <- comp_size
    names(compartments) <- comp_name
  }  
  return(list(initials = initials, compartments = compartments))
}





#' Import observables from PEtab.
#'
#' @description This function imports observables from the PEtab observable file.
#'
#' @param observables PEtab observable file as .tsv
#'
#' @return Eqnvec of observables.
#'
#' @author Marcus Rosenblatt and Svenja Kemmer
#'
#' @export
#'
getObservablesSBML <- function(observables){
  ## Load observables
  myobs <- read.csv(file = observables, sep = "\t") %>% as.data.frame()
  obsNames <- myobs$observableId %>% as.character()
  
  # # rename observables with _obs
  # obsNames <- paste0(obsNames,"_obs")
  
  obsFormula <- myobs$observableFormula %>% as.character()
  obsFormula[which(myobs$observableTransformation=="log")] <- paste0("log(", obsFormula[which(myobs$observableTransformation=="log")], ")")
  obsFormula[which(myobs$observableTransformation=="log10")] <- paste0("log10(", obsFormula[which(myobs$observableTransformation=="log10")], ")")
  names(obsFormula) <- obsNames
  observables <- obsFormula %>% as.eqnvec()
  
  # collect observable transformations as attribute
  if(!is.null(myobs$observableTransformation)){
    obsscales <- myobs$observableTransformation %>% as.character()
  } else obsscales <- rep("lin", length(obsNames))
  
  attr(observables,"obsscales") <- obsscales
  return(observables)
}


#' Import Parameters from PEtab
#'
#' @description This function imports fixed and fitted parameters from the PEtab parameter file as named vectors.
#'
#' @param parameters PEtab parameter file as .tsv
#'
#' @return constraints and pouter as list of named vectros.
#'
#' @author Marcus Rosenblatt and Svenja Kemmer
#' @importFrom dplyr filter "%>%"
#' @export
#'
getParametersSBML <- function(parameters, model){
  mypars <- read.csv(file = parameters, sep = "\t")
  fixed <- mypars %>% dplyr::filter(estimate == 0)
  constraints <- NULL
  if(nrow(fixed)>0){
    for(i in 1:length(fixed$parameterScale)) {
      parscale <- fixed$parameterScale[i]
      par <- fixed$parameterId[i] %>% as.character()
      value <- fixed$nominalValue[i]
      if(parscale == "lin") constraints <- c(constraints, value)
      if(parscale == "log10") constraints <- c(constraints, log10(value))
      if(parscale == "log") constraints <- c(constraints, log(value))
      else paste("This type of parameterScale is not supported.")
      names(constraints)[i] <- par
    }
    parscales <- fixed$parameterScale %>% as.character()
    pars <- fixed$parameterId %>% as.character()
    names(parscales) <- pars
    attr(constraints,"parscale") <- parscales
  }
  estimated <- mypars %>% filter(estimate == 1)
  pouter <- NULL
  parlower <- NULL
  parupper <- NULL
  if(nrow(estimated)>0){
    for(i in 1:length(estimated$parameterScale)) {
      parscale <- estimated$parameterScale[i]
      par <- estimated$parameterId[i] %>% as.character()
      value <- estimated$nominalValue[i]
      lowervalue <- estimated$lowerBound[i]
      uppervalue <- estimated$upperBound[i]
      if(parscale == "lin"){
        pouter <- c(pouter, value)
        parlower <- c(parlower, lowervalue)
        parupper <- c(parupper, uppervalue)
      } else if(parscale == "log10"){
        pouter <- c(pouter, log10(value))
        parlower <- c(parlower, log10(lowervalue))
        parupper <- c(parupper, log10(uppervalue))
      } else if(parscale == "log"){
        pouter <- c(pouter, log(value))
        parlower <- c(parlower, log(lowervalue))
        parupper <- c(parupper, log(uppervalue))
      } else paste("This type of parameterScale is not supported.")
      names(pouter)[i] <- par
      names(parlower)[i] <- par
      names(parupper)[i] <- par
    }
    parscales <- estimated$parameterScale %>% as.character()
    pars <- estimated$parameterId %>% as.character()
    names(parscales) <- pars
    attr(pouter,"parscale") <- parscales
    attr(pouter,"lowerBound") <- parlower
    attr(pouter,"upperBound") <- parupper
  }
  
  # check if additional parameters exist in SBML file
  model = readSBML(model)$getModel()
  n_pars <- model$getNumParameters()
  SBMLfixedpars <- NULL
  count <- 1
  for (i in (seq_len(n_pars)-1)) {
    mypar <- model$getParameter(i)$getId()
    if(!mypar %in% names(pouter) & !mypar %in% names(constraints)){
      value <- model$getParameter(i)$getValue()
      SBMLfixedpars <- c(SBMLfixedpars, value)
      names(SBMLfixedpars)[count] <- mypar
      count <- count + 1
    }
  }
  
  return(list(constraints=constraints, pouter=pouter, SBMLfixedpars = SBMLfixedpars))
}

#' Import reactions from SBML.
#'
#' @description This function imports reactions from SBML. Reactions are written to an eqnlist object.
#' Assignment rules for input functions are substituted in the rate column. Time is introduced as an additionel state t.
#'
#' @param model SBML file as .xml
#'
#' @return Eqnlist of reactions.
#'
#' @author Marcus Rosenblatt and Svenja Kemmer
#' importFrom libSBML readSBML
#' @importFrom cOde replaceSymbols
#' @importFrom dMod addReaction
#' @export
#'
getReactionsSBML <- function(model, conditions, rates){
  # m = libSBML::readSBML(model)$getModel()
  m = readSBML(model)$getModel()
  
  # .. Initialization -----
  reactions <- NULL
  events <- NULL
  compartments <- NULL
  
  # .. import compartments -----
  N_species <- m$getNumSpecies()
  compartments <- do.call(c, lapply(0:(N_species-1), function(i){ m$getSpecies(i)$getCompartment()}))
  names_compartments <- do.call(c, lapply(0:(N_species-1), function(i){ m$getSpecies(i)$getId() }))
  names(compartments) <- names_compartments
  
  # if(unique(compartments)[1]=="default") compartments <- NULL
  
  # .. import reactions -----
  N_reactions <- m$getNumReactions()
  for (reaction in 0:(N_reactions-1)){
    reactionDetails <- sbmlImport_getReactionDetails(m, reaction, compartments)
    reactions <- do.call(dMod::addReaction, c(list(eqnlist = reactions), reactionDetails))
  }
  
  
  # .. import functions -----
  N_fundefs <- m$getNumFunctionDefinitions()
  if (N_fundefs > 0){
    for (fun in 0:(N_fundefs-1)){
      reactions$rates <- smblImport_replaceFunctions(m = m, fun = fun, rates = reactions$rates)
      # substitute m$getRule(0)$getVariable() by m$getRule(0)$getFormula()
      #print(formulaToL3String(mymath$getChild(mymath$getNumChildren()-1)))
    }
  }
  
  # .. import inputs -----
  N_rules <- m$getNumRules()
  if (N_rules > 0){
    for (rule in 0:(N_rules-1)){
      # substitute m$getRule(0)$getVariable() by m$getRule(0)$getFormula()
      reactions$rates <- cOde::replaceSymbols(m$getRule(rule)$getVariable(),
                                              paste0("(",m$getRule(rule)$getFormula(), ")"), reactions$rates)
    }
  }
  reactions$rates <- gsub(" ","",reactions$rates)
  
  # .. Events by piecewise functions -----
  # replace function based inputs by events (done in reactions)
  events <- sbmlImport_getEventStrings(reactions, events = events)
  if(!is.null(events)) for(i in 1:length(events)){
    replace <- gsub("\\(", "\\\\\\(", events[i])
    replace <- gsub("\\*", "\\\\\\*", replace)
    replace <- gsub("\\+", "\\\\\\+", replace)
    #replace <- gsub("\\)", "\\\\\\)", replace)
    reactions$rates <- gsub(replace, paste0("event", i), reactions$rates)
    reactions <- reactions %>% dMod::addReaction("", paste0("event", i), "0")
  }
  # replace mathematical expressions
  reactions$rates <- replaceSymbols(c("t", "TIME", "T"), "time", reactions$rates)
  events <- TransformEvents(events)
  
  # .. ## check for preequilibration conditions and handle them via events -----
  preeqEvents <- NULL
  myconditions <- read.csv(file = conditions, sep = "\t")
  myCons <- myconditions$conditionId
  mypreeqCons <- NULL
  attrib <- NULL
  for (con in myCons){if(paste0("preeq_", con)%in%myCons) mypreeqCons <- c(mypreeqCons, con)}
  if(!is.null(mypreeqCons)){
    for (con in mypreeqCons){
      mycongrid <- filter(myconditions, conditionId==con | conditionId==paste0("preeq_", con))
      if(ncol(mycongrid)>1){
        for(i in 2:ncol(mycongrid)){
          preeqEvents <- addEvent(preeqEvents,
                                  var=names(mycongrid)[i],
                                  time=0,
                                  value=mycongrid[[which(mycongrid$conditionId==con),i]],
                                  root=NA,
                                  method="replace")
          attrib <- c(attrib, mycongrid[[which(mycongrid$conditionId==paste0("preeq_",con)),i]])
        }
      }
    }
  }
  mystates <- reactions$states
  reactions_orig <- reactions
  attr(preeqEvents, "initials") <- attrib
  if(!is.null(preeqEvents)) for(i in 1:nrow(preeqEvents)){
    events <- rbind(events, preeqEvents[i,])
    reactions <- reactions %>% dMod::addReaction("", preeqEvents[[i,"var"]], "0")
  }
  
  
  # for(i in 1:length(reactions$rates)){
  #   reaction <- reactions$rates[i]
  #   if(stringr::str_detect(reaction, "pow")){
  #     reaction_new <- gsub("pow", "", reaction)
  #     # reaction_new <- gsub(", ", "**", reaction_new)
  #     reaction_new <- gsub(",", "**", reaction_new)
  #     reactions$rates[i] <- reaction_new
  #   } else reactions$rates[i] <- reaction
  # }
  
  mydata <- as.data.frame(reactions)
  reactions <- as.eqnlist(mydata, compartments)
  
  return(list(reactions=reactions, events=events, reactions_orig=reactions_orig, preeqEvents=preeqEvents, mystates=mystates))
}

#' Import Data from PEtab
#'
#' @description This function imports data from the PEtab data file as a data list and defines errors if an error model is required.
#'
#' @param data PEtab data file as .tsv
#' @param observables observables as eqnvec
#'
#' @return data as data list and errors (if required) as eqnvec.
#'
#' @author Marcus Rosenblatt and Svenja Kemmer
#'
#' @export
#'
getDataPEtabSBML <- function(data, observables){
  mydata <- read.csv(file = data, sep = "\t")
  myobs <- read.csv(file = observables, sep = "\t") %>% as.data.frame()
  obs <- myobs$observableId %>% as.character()
  errors <- NULL
  
  # if(!is.null(mydata$noiseParameters) & !is.null(myobs$noiseFormula)) cat(red("Warning: errors specified in data and observable file.\n"))
  if(!mydata$noiseParameters %>% is.numeric) {
    # define errors
    errors <- myobs$noiseFormula %>% as.character()
    which_err <- c(1:length(obs))
    if(length(errors) != length(obs)) errors <- rep(errors,length(obs))
    names(errors) <- obs[which_err]
    errors <- as.eqnvec(errors)
    # set fixed sigmas
    if(is.null(mydata$noiseParameters)){
      mydata$noiseParameters <- errors %>% as.numeric()
    } else mydata$noiseParameters <- NA
  }
  
  # # rename observables with _obs
  # obs <- mydata$observableId %>% as.character() %>% paste0("_obs")
  # mydata$observableId <- obs
  # if(!is.null(errors)) names(errors) <- paste0(names(errors), "_obs")
  mydata$simulationConditionId <- as.character(mydata$simulationConditionId)
  if(!is.null(mydata$observableParameters)){
    for (observable in unique(mydata$observableId)){
      for(condition in unique(mydata$simulationConditionId)){
        sub <- subset(mydata, simulationConditionId==condition & observableId==observable)
        if(nrow(sub) > 0){
          if(length(unique(sub$observableParameters)) > 1){
            index <- which(mydata$simulationConditionId==condition & mydata$observableId==observable)
            mydata$simulationConditionId[index] <- paste0(mydata$simulationConditionId[index], "_",mydata$observableParameters[index])
          }
        }
      }
    }
  }
  
  # select necessary data columns
  data <- data.frame(name = mydata$observableId, time = mydata$time,
                     value = mydata$measurement, sigma = mydata$noiseParameters,
                     condition = mydata$simulationConditionId,
                     lloq = if (!is.null(mydata$lloq)) mydata$lloq else -Inf)
  obs2log <- myobs$observableId[which(myobs$observableTransformation=="log")]
  data$value[which(data$name%in%obs2log)] <- log(data$value[which(data$name%in%obs2log)])
  obs2log10 <- myobs$observableId[which(myobs$observableTransformation=="log10")]
  data$value[which(data$name%in%obs2log10)] <- log10(data$value[which(data$name%in%obs2log10)])
  data <- data %>% as.datalist()
  
  return(list(data=data,errors=errors))
}


#' Plot observables and states of an SBML/PEtab model
#'
#' @description This function plots data and fits of an SBML/PEtab model after the import.
#' Note: Certain objects generated during the model import by importPEtabSBML have to be present in the global environment and are used as default variables if not specified differntly.
#'
#' @param g observation function as obsfn
#' @param x prediction function as prdfn
#' @param p parameter function as parfn
#' @param mydata data as datalist
#' @param pars parameter as vector
#' @param times times as vector
#' @param ... further arguments going to \link{plotCombined}
#'
#' @return NULL
#'
#' @author Marcus Rosenblatt and Svenja Kemmer
#' @importFrom dMod theme_dMod scale_color_dMod scale_fill_dMod
#' @export
#'
plotPEtabSBML <- function(..., g1 = g,
                          x1 = x,
                          p1 = p0,
                          mydata1 = mydata,
                          pouter1 = pouter,
                          times1 = times,
                          errfn = err){
  if(!is.null(errfn)){
    prediction <- as.data.frame((g1*x1*p1)(times1, pouter1), errfn = err)
    data <- as.data.frame(mydata1)
    P1 <- ggplot() + geom_line(data=subset(prediction, ...), aes(x=time, y=value, color=condition)) +
      geom_ribbon(data=subset(prediction, ...), aes(x=time, ymin=value-sigma, ymax=value+sigma, fill=condition), color=NA, alpha=0.25) +
      geom_point(data=subset(data, ...), aes(x=time, y=value, color=condition)) +
      theme_dMod() + scale_color_dMod() + scale_fill_dMod() +
      facet_wrap(~name, scales="free")
  } else {
    prediction <- (g1*x1*p1)(times1, pouter1)
    P1 <- plotCombined(prediction, mydata1, ...)
  }
  print(P1)
  # plotPrediction(prediction)
  
  # return(modelname)
}


# -------------------------------------------------------------------------#
# Refactoring sbml import ----
# -------------------------------------------------------------------------#

#' Sanitize pow(x,y) equations
#'
#' @param rate 
#'
#' @return
#' @author Daniel Lill (daniel.lill@physik.uni-freiburg.de)
#' @md
#' @family sbmlImport
#'
#' @examples
#' sanitizePowers("EMAX * pow(Ac / Vc + eps, hill) / (pow(Ac / Vc + eps, hill) + pow(EC50, hill)) * cytoplasm")
#' rate <- "EMAX * pow(Ac /   Vc, hill+2)"
#' sanitizePowers(rate)
sanitizePowers <- function(rate) {
  parseD <- getParseData(parse(text = rate))
  pow_ids <- parseD[which(parseD$text == "pow")-1,"id"]
  for (pow_id in pow_ids){
    child_ids <- parseD[parseD$parent == pow_id & !parseD$text %in% c("(",")",","), "id"][-1]
    texts <- getParseText(parseD, child_ids)
    rate <- gsub(paste0("pow(",texts[1], ", ", texts[2], ")"), paste0("(",texts[1],")**(", texts[2], ")"),rate, fixed = TRUE)
  }
  rate
}


#' Title
#'
#' import reactions and adjust by means of compartments
#'
#' @param m SBML model
#' @param reaction index of reaction
#' @param compartments names of compartments
#'
#' @return
#' @export
#'
#' @examples
sbmlImport_getReactionDetails <- function(m, reaction, compartments) {
  Reactantstring <- ""
  Productstring <- ""
  eq <- m$getReaction(reaction)
  Reactantnr <- eq$getNumReactants(reaction)
  if(Reactantnr > 0) Reactantstring <- paste0( eq$getReactant(0)$getStoichiometry(), "*", eq$getReactant(0)$getSpecies())
  if(Reactantnr > 1) for (s in 1:(Reactantnr-1)) {
    Reactantstring <- paste0(Reactantstring, " + ",
                             paste0(eq$getReactant(s)$getStoichiometry(), "*", eq$getReactant(s)$getSpecies()))
  }
  Productnr <- eq$getNumProducts(reaction)
  if(Productnr > 0) Productstring <- paste0( eq$getProduct(0)$getStoichiometry(), "*", eq$getProduct(0)$getSpecies())
  if(Productnr > 1) for (s in 1:(Productnr-1)) {
    Productstring <- paste0(Productstring, " + ",
                            paste0(eq$getProduct(s)$getStoichiometry(), "*", eq$getProduct(s)$getSpecies()))
  }
  rate <- eq$getKineticLaw()$getFormula()
  if (stringr::str_detect(rate, "pow")) {
    rate <- sanitizePowers(rate)
  }
  if(!is.null(compartments)){
    if(Reduce("|", stringr::str_detect(rate, unique(compartments)))){
      rate <- cOde::replaceSymbols(unique(compartments), rep("1", length(unique(compartments))), rate)
    }
  }
  
  rate <- gsub(" ", "", rate)
  
  list(from = Reactantstring, to = Productstring,rate = rate)
}

#' Replace function calls by their mathematical expressions
#'
#' @param m sbml model
#' @param fun an integer with a function id (badly refactored)
#' @param rates reaction rate formulas
#'
#' @return modified rates
#' @author Daniel Lill (daniel.lill@physik.uni-freiburg.de)
#' @md
#' @family SBML import
#' @importFrom cOde replaceSymbols
smblImport_replaceFunctions <- function(m, fun, rates) {
  mymath <- m$getFunctionDefinition(fun)$getMath()
  fun <- m$getFunctionDefinition(fun)
  funInfo <- sbmlImport_getFunctionInfo(fun)
  
  idx <- grep(funInfo$funName, rates)
  for (i in idx) {
    # 1. Replace function arguments by supplied arguments in rate, see petab_select/testcase0001 SBML for nontrivial examples
    # https://stackoverflow.com/questions/546433/regular-expression-to-match-balanced-parentheses
    regexExtractArguments <- paste0(".*", funInfo$funName, "(", "\\((?:[^)(]*(?R)?)*+\\)", ").*")
    argsInRateEqn <- gsub(regexExtractArguments, "\\1", rates[i], perl = TRUE)
    argsInRateEqn <- gsub("^\\(|\\)$", "", argsInRateEqn)
    argsInRateEqn <- strsplit(argsInRateEqn, ",", T)[[1]]
    funBody <- cOde::replaceSymbols(funInfo$funArgs, argsInRateEqn,funInfo$funBody)
    
    # 2. Replace function call in rate by funBody
    regexMatchFunCall <- paste0( "(", funInfo$funName, "\\((?:[^)(]*(?R)?)*+\\)", ")")
    cat("Replacing ", unique(gsub(paste0(".*",regexMatchFunCall, ".*"), "\\1", rates[i], perl = TRUE)), " by ", funBody, "\n")
    rates[i] <- gsub(regexMatchFunCall, funBody, rates[i], perl = TRUE)
  }
  
  rates
}



#' Get function info
#' 
#' function adapted from Frank
#'
#' @param fun some sbml-thingy representation of a function
#'
#' @return list with infos about the funciton
#' @author Daniel Lill (daniel.lill@physik.uni-freiburg.de)
#' @md
#' @family SBML import
#' importFrom libSBML formulaToL3String
sbmlImport_getFunctionInfo <- function(fun) {
  if (is.null(fun)) return;
  id <- fun$getId()
  
  math <- fun$getMath()
  if (is.null(math)) return;
  
  num_children <- math$getNumChildren()
  
  funInfo <- list(
    funName = id,
    funArgs = vapply(seq_len(num_children-1), function(i) formulaToL3String(math$getChild(i-1)), FUN.VALUE = "a"),
    funBody = formulaToL3String(math$getChild(num_children - 1))
  )
  funInfo
}


sbmlImport_getEventStrings <- function(reactions, events = NULL) {
  for(fun in c("piecewise")){
    for(reaction in reactions$rates){
      if(stringr::str_detect(reaction, fun)){
        split <- stringr::str_split(reaction, fun)[[1]][2]
        count_bracket <- 0
        done <- F
        for(z in 1:nchar(split)){
          if(substr(split, z, z)=="(") count_bracket <- count_bracket+1
          if(substr(split, z, z)==")") count_bracket <- count_bracket-1
          if(count_bracket==0 & !done) {done <- T; pos <- z}
        }
        #pos <- which(strsplit(split, "")[[1]]==")")[2]
        event <- paste0(fun, substr(split, 1, pos))
        events <- c(events, event)
      }
    }
  }
  unique(events)
}


TransformEvents <- function(events){
  if(!is.null(events)){
    do.call(rbind, lapply(1:length(events), function(i){
      myevent <- events[i]
      if(stringr::str_detect(myevent, "piecewise") & (stringr::str_detect(myevent, "leq") | stringr::str_detect(myevent, "lt"))){
        expr1 <- strsplit(myevent, ",")[[1]][2]
        expr1 <- gsub(paste0(strsplit(expr1, "\\(")[[1]][1],"\\("), "", expr1)
        expr2 <- strsplit(strsplit(myevent, ",")[[1]][3], ")")[[1]][1]
        if(expr1=="time") timepoint <- expr2 else
          if(stringr::str_detect(expr1, "time-")) timepoint <- gsub("time-", "", expr1) else cat("Warning: Event not yet supported.")
        first <- strsplit(strsplit(myevent, "\\(")[[1]][2], ",")[[1]][1]
        second <- strsplit(strsplit(myevent, ",")[[1]][4], ")")[[1]][1]
        if(!is.na(suppressWarnings(as.numeric(timepoint)))) timepoint <- as.numeric(timepoint) # avoid warning if variable is not numeric
        if(!is.na(suppressWarnings(as.numeric(first)))) first <- as.numeric(first)
        if(!is.na(suppressWarnings(as.numeric(second)))) second <- as.numeric(second)
        return(data.frame(var=paste0("event",i), time=c(0,timepoint), value=c(first, second),root=NA, method="replace"))
      } else {cat("Warning: Event not yet supported"); return(myevent)}
    }))
  } else return(NULL)
}

#' Get Events from "events" entries in SBML
#' 
#' The difficulty is that triggers can take arbitrary expressions which doesn't play 
#' well with dMod, which has a rather stiff input format for events.
#' Therefore, this function is tailored to my own export ONLY
#'
#' @param modelfile 
#'
#' @return
#' @export
#'
#' @examples
sbmlImport_getEventsFromTrueEvents <- function(modelfile) {
  model = readSBML(modelfile)$getModel()
  nx <- Model_getNumEvents(model)
  
  eventList <- NULL
  for (i in seq_len(nx)-1) {
    ev <- model$getEvent(i)
    eventTime <- ev$getTrigger()
    eventTime <- eventTime$getMath()
    eventTime <- formulaToL3String(eventTime)
    if (!grepl("time >= ", eventTime)) stop("Event Trigger not of form 'time >= eventtime'. \nEvent will not be imported properly. \n Event trigger has value:\n", eventTime, "\nPlease add a parser for such events to petab::sbmlImport_getEventsFromTrueEvents")
    eventTime <- gsub("time >= ", "", eventTime) # hard coded for my own export which has always the same string. error prone for other models, but I can't capture all possible cases anyway
    eventTime <- as.numeric(eventTime)
    
    ea <- ev$getEventAssignment(0)              # hard coded for my own export which assumes only one eventAssignment per trigger
    eventSpecies <- ea$getVariable()
    eventValue <- ea$getMath()
    eventValue <- formulaToL3String(eventValue)
    # eventValue <- as.numeric(eventValue)  
    
    eventList <- addEvent(event = eventList, var = eventSpecies, time = eventTime, value = eventValue,
                          root = NA, method = "replace") # Again hard coded
  }
  eventList
}

#' Extract parameters from SBML
#' 
#' Assumes the parameters are given numerically
#' 
#' @param modelfile Path to SBML file
#'
#' @return [petab_parameters()] with scale = "lin" and estimate = 0
#' @export
#' @author Daniel Lill (daniel.lill@physik.uni-freiburg.de)
#' @md
#' @family SBML import
#'
#' @examples
#' modelfile <- file.path(petab_examplePath("01", "pe"), "model_petab.xml")
#' sbmlImport_getCompartmentsFromSBML(modelfile)
sbmlImport_getParametersFromSBML <- function(modelfile) {
  model = readSBML(modelfile)$getModel()
  nx <- model$getNumParameters()
  
  pars <- NULL
  for (i in (seq_len(nx)-1)) {
    mypar <- model$getParameter(i)$getId()
    value <- model$getParameter(i)$getValue()
    pars <- c(pars, setNames(value, mypar))
  }
  
  petab_parameters(parameterId = names(pars), 
                   parameterScale = "lin",
                   lowerBound = 1e-10, upperBound = 1e10,
                   nominalValue = pars,
                   estimate = 0)
}

#' Extract compartments from SBML
#' 
#' Assumes the compartments are given numerically
#'
#' @param modelfile Path to SBML file
#'
#' @return [petab_parameters()] with scale = "lin" and estimate = 0
#' @export
#' @author Daniel Lill (daniel.lill@physik.uni-freiburg.de)
#' @md
#' @family SBML import
#'
#' @examples
#' modelfile <- file.path(petab_examplePath("01", "pe"), "model_petab.xml")
#' sbmlImport_getCompartmentsFromSBML(modelfile)
sbmlImport_getCompartmentsFromSBML <- function(modelfile) {
  
  model <- readSBML(modelfile)$getModel()
  nx <- model$getNumCompartments()
  
  comp_name <- comp_size <- NULL
  for ( i in (seq_len(nx)-1) ) {
    # get compartment name and size
    compartmentId <- model$getCompartment(i)$getId()
    size          <- model$getCompartment(i)$getSize()
    comp_name <- c(comp_name,compartmentId)
    comp_size <- c(comp_size,size)
  }
  
  petab_parameters(parameterId = comp_name, 
                   parameterScale = "lin",
                   lowerBound = 1e-10, upperBound = 1e10,
                   nominalValue = comp_size,
                   estimate = 0)
}



#' Import initials from SBML.
#'
#' @description This function imports initial values or equations describing the same from SBML and writes them in a named vector.
#'
#' @param model SBML file as .xml
#'
#' @return Named vector of initials.
#'
#' @author Marcus Rosenblatt, Svenja Kemmer and Frank Bergmann
#' importFrom libSBML readSBML formulaToL3String
#' @export
#'
sbmlImport_getInitialsFromSBML <- function(modelfile){
  
  model = readSBML(modelfile)$getModel()
  
  initials <- species <- NULL
  nx <- model$getNumSpecies()
  # now we can go through all species
  for (i in (seq_len(nx)-1) ){
    current <- model$getSpecies(i)
    species <- c(species,current$getId())
    # now the species can have several cases that determine their initial value
    
    # CASE 1: it could be that the species is fully determined by an assignment rule
    # (that apply at all times), so we have to check rules first
    rule <- model$getRule(current$getId())
    if (!is.null(rule)) {
      # ok there is a rule for this species so lets figure out what its type
      # is as that determines whether it applies at t0
      rule_type <- rule$getTypeCode()
      type_name <- SBMLTypeCode_toString(rule_type, 'core')
      # CASE 1.1: Assignment rule
      if (type_name == "AssignmentRule"){
        # the initial value is determined by the formula
        math <- rule$getMath()
        if (!is.null(math)){
          formula <- formulaToL3String(math)
          # print(paste('Species: ', current$getId(), ' is determined at all times by formula: ', formula))
          initials <- c(initials,formula)
          # no need to look at other values so continue with next species
          next
        }
      }
      
      # CASE 1.2: Rate rule
      if (type_name == "RateRule") {
        math <- rule$getMath()
        if (!is.null(math)) {
          formula <- formulaToL3String(math)
          # print(paste('Species: ', current$getId(), ' has an ode rule with formula: ', formula))
          initials <- c(initials,formula)
          # there is an ODE attached to the species, its initial value is needed
        }
      }
    }
    
    # CASE 2 (or subcase of 1?): Initial assignment
    # it could have an initial assignment
    ia <- model$getInitialAssignment(current$getId())
    if (!is.null(ia)) {
      math <- ia$getMath()
      if (!is.null(math)) {
        formula <- formulaToL3String(math)
        # formula <- libSBML::formulaToL3String(math)
        # print(paste("Species: ", current$getId(), " has an initial assignment with formula: ", formula))
        initials <- c(initials,formula)
        # as soon as you have that formula, no initial concentration / amount applies
        # so we don't have to look at anything else for this species
        next
      }
    }
    
    # CASE 3 (or subcase of 1?): Inital amount
    # it could have an initial amount
    if (current$isSetInitialAmount()) {
      # print (paste("Species: ", current$getId(), "has initial amount: ", current$getInitialAmount()))
      initials <- c(initials,current$getInitialAmount())
    }
    
    # CASE 4 (or subcase of 1?): Inital Concentration
    # it could have an initial concentration
    if (current$isSetInitialConcentration())
    {
      # print (paste("Species: ", current$getId(), "has initial concentration: ", current$getInitialConcentration()))
      initials <- c(initials,current$getInitialConcentration())
    }
  }
  names(initials) <- species
  
  
  
  
  
  # Post process initials: Divide into symbolic and numeric
  # initials <- c(a = "1", b = "2")
  # initials <- c(a = "1", b = "a+f")
  # initials <- c(a = "c+d", b = "a+f")
  
  inits_sym <- suppressWarnings(initials[ is.na(as.numeric(initials))])
  inits_sym <- data.table(parameterId = names(inits_sym),
                          parameterFormula = inits_sym)
  
  inits_num <- suppressWarnings(initials[!is.na(as.numeric(initials))])
  inits_num <- suppressWarnings(petab_parameters(parameterId = names(inits_num), 
                                                 parameterScale = "lin",
                                                 lowerBound = 1e-10, upperBound = 1e10,
                                                 nominalValue = inits_num,
                                                 estimate = 0))
  inits_num <- inits_num[!is.na(parameterId)] # To catch the case that no numeric inits are given
  
  list(inits_sym = inits_sym, inits_num = inits_num)
}




# get parameters function: Logic:
# get 

# -------------------------------------------------------------------------#
# PETabIndiv ----
# -------------------------------------------------------------------------#

#' Rename parscales to the names needed in the base trafo
#'
#' @param parscales setNames(PETABPars$parameterScale, PETABPars$parameterId)
#' @param est.grid data.table
#'
#' @return
#' @author Daniel Lill (daniel.lill@physik.uni-freiburg.de)
#' @md
#' @export
#'
#' @examples
#' # Example 1: k1_A and k1_B are duplicated to two inner parameters 
#' est.grid <- data.frame(ID = 1:2,
#'                        condition = c("A", "B"),
#'                        k1 = c("k1_A", "k1_B"),
#'                        k1DUPE = c("k1_A", "k1_B"),
#'                        k3 = c("k3outer", NA),
#'                        stringsAsFactors = FALSE)
#' scales_outer <- c(k1_A = "log", k1_B = "log", k3outer = "lin")
#' updateParscalesToBaseTrafo(scales_outer, est.grid)
#' 
#' # Example 2: SHOULD FAIL k1_A and k1_B map to same inner parameter, but have idfferent scales
#' est.grid <- data.frame(ID = 1:2,
#'                        condition = c("A", "B"),
#'                        k1 = c("k1_A", "k1_B"),
#'                        k1DUPE = c("k1_A", "k1_B"),
#'                        k3 = c("k3outer", NA),
#'                        stringsAsFactors = FALSE)
#' scales_outer <- c(k1_A = "log", k1_B = "log10", k3outer = "lin")
#' updateParscalesToBaseTrafo(scales_outer, est.grid)
#' 
#' # Example 3: k4 is a parameter not in est.grid. It might be a parameter in fix.grid and should be returned as is
#' est.grid <- data.frame(ID = 1:2,
#'                        condition = c("A", "B"),
#'                        k1 = c("k1_A", "k1_B"),
#'                        k1DUPE = c("k1_A", "k1_B"),
#'                        k3 = c("k3outer", NA),
#'                        stringsAsFactors = FALSE)
#' scales_outer <- c(k1_A = "log", k1_B = "log", k3outer = "lin", k4 = "log")
#' updateParscalesToBaseTrafo(scales_outer, est.grid)
updateParscalesToBaseTrafo <- function(scales_outer, est.grid) {
  # parscales = c(outername = scaleouter)
  
  # Get name mapping between est.grid pars and outer pars
  pars_inner_outer <- getEstGridParameterMapping(est.grid) # c(inner = outer)
  pars_inner_outer <- pars_inner_outer[!is.na(pars_inner_outer)]
  pars_outer_inner <- setNames(names(pars_inner_outer), pars_inner_outer) # c(outer = inner)
  
  # Get scales for inner pars
  scales_inner <- setNames(scales_outer[pars_inner_outer], names(pars_inner_outer))
  
  # Determine if there are outer parameters mapping to the same inner parameter with different scales
  dupes <- names(scales_inner)[duplicated(names(scales_inner))]
  dupes <- unique(dupes)
  for (d in dupes) {
    if (length(unique(scales_inner[names(scales_inner) == d])) > 1)
      stop("The following parameter refers to the same structural model parameter, but has different ",
           "scales in different conditions. This is not allowed. \n",
           "Parameter: ", d , "\n",
           "Outer pars: ", paste0(names(pars_outer_inner[pars_outer_inner == d]), collapse = ", "))
  }
  
  # Remove the duplicates and return updated parscales
  scales_inner <- scales_inner[!duplicated(names(scales_inner))]
  
  # Append scales which did not appear in est.grid
  scales_fix <- scales_outer[setdiff(names(scales_outer),names(pars_outer_inner))]
  
  c(scales_inner, scales_fix)
}

#' Determine the type of trafo for each element of a vector
#'
#' @param trafo_string named character
#'
#' @return character(lenth(trafo_string)):
#' TRAFO = functional trafo
#' SYMBOL = Parameter name mapping
#' NUMBER = fixed value
#' @export
#' @author Daniel Lill (daniel.lill@physik.uni-freiburg.de)
#' @md
#'
#' @examples
#' trafo_string <- c(STAT5A = "207.6 * ratio", STAT5B = "207.6 - 207.6 * ratio",
#'                   pApB = "alpha", pApA = "beta", pBpB = "pBpB", nucpApA = "0.1", nucpApB = "0",
#'                   nucpBpB = "0")
#' getTrafoType(trafo_string)
getTrafoType <- function(trafo_string) {
  opt.keep.source <- getOption("keep.source") # important for Rscript --vanilla
  options(keep.source = TRUE)
  
  out <- vapply(names(trafo_string), function(nm) {
    ts <- trafo_string[nm]
    pd <- getParseData(parse(text = ts))
    if (nrow(pd) > 2) return("TRAFO")
    if (pd[1,"token"] == "SYMBOL") {
      if (pd[1,"text"] == nm) return("SYMBOL")
      else return("TRAFO")
    }
    if (pd[1,"token"] == "NUM_CONST") return("NUMBER")
    stop("Unkown trafo type: ", ts)
  }, FUN.VALUE = "TYPE")
  
  options(keep.source = opt.keep.source)
  
  out
}

#' Title
#'
#' @param mycondition.grid 
#'
#' @return
#' @export
#' @author Daniel Lill (daniel.lill@physik.uni-freiburg.de)
#' @md
#' @family SBML Import
#' @importFrom data.table as.data.table setnames setcolorder copy
#'
#' @examples
pdIndiv_initializeGridlist <- function(mycondition.grid) {
  # Copy condition.grid, take unique identifying column only
  cg <- copy(mycondition.grid)
  cg <- lapply(cg, as.character)
  cg <- data.table::as.data.table(cg)
  cg <- cg[,!"conditionName"]
  data.table::setnames(cg, "conditionId", "condition")
  data.table::setcolorder(cg, "condition")
  # Determine which columns contain values and/or parameter names
  is_string  <- suppressWarnings(vapply(cg[,-1], function(x) any(is.na(as.numeric(x))), FUN.VALUE = TRUE))
  is_string  <- which(is_string) + 1
  is_numeric <- suppressWarnings(vapply(cg[,-1], function(x) any(!is.na(as.numeric(x))), FUN.VALUE = TRUE))
  is_numeric <- which(is_numeric) + 1
  
  # Initialize fix.grid and est.grid
  # For mixed columns (string & numeric), need NA in the respective places
  fix.grid <- data.table::copy(cg)
  fix.grid <- fix.grid[,.SD,.SD = c(1, is_numeric)]
  suppressWarnings(fix.grid[,(names(fix.grid)[-1]) := lapply(.SD, as.numeric), .SDcols = -1])
  fix.grid[,`:=`(ID = 1:.N)]
  
  est.grid <- data.table::copy(cg)
  est.grid <- est.grid[,.SD,.SD = c(1, is_string)]
  suppressWarnings(est.grid[,(names(est.grid)[-1]) := lapply(.SD, function(x) {replace(x, !is.na(as.numeric(x)), NA)}), .SDcols = -1])
  est.grid[,`:=`(ID = 1:.N)]
  
  gl <- list(est.grid = copy(est.grid), fix.grid = copy(fix.grid))
  gl
}


#' Title
#'
#' @param x 
#'
#' @return
#' @export
#' @author Daniel Lill (daniel.lill@physik.uni-freiburg.de)
#' @md
#' @family SBML Import
#'
#' @examples
pdIndiv_getParameters_conditionGrid <- function(x) {
  x <- suppressWarnings(x[,!c("conditionId", "conditionName", "ID")])
  x <- unlist(x, use.names = FALSE)
  x <- unique(x)
  x <- suppressWarnings(x[is.na(as.numeric(x))])
  x
}


#' Title
#'
#' @param cg condition.grid
#' @param scalesOuter vector of scales of outer parameters c(CS_name = scale) 
#'
#' @return vector of scales of base parameters c(BASE_name = scale) 
#' @export
#' @author Daniel Lill (daniel.lill@physik.uni-freiburg.de)
#' @md
#' @family SBML Import
#'
#' @examples
#' cg <- data.table(structure(list(conditionId = c("C1", "C2"), conditionName = c("C1", "C2"), kcat = c("kcat_C1", "kcat_C2"), kon = c("kon_C1", "kon_C2"), koff = c(0.1, 0.2), observableParameter1_obsE = c("offset_E", "offset_E"), observableParameter1_obsS = c("offset_S", "offset_S_C2"), noiseParameter1_obsE = c("sigmaRel_obsE", "sigmaRel_obsE"), noiseParameter1_obsES = c("sigma_obsES", "sigma_obsES"), noiseParameter1_obsP = c("sigma_obsP", "sigma_obsP"), noiseParameter1_obsS = c("sigma_obsS", "sigma_obsS_C2"), noiseParameter2_obsE = c("sigmaAbs_obsE", "sigmaAbs_obsE"), E = c("E", "E"), ES = c("ES", "ES"), P = c("P", "P"), S = c("S", "S"), cytoplasm = c("cytoplasm", "cytoplasm")), row.names = c(NA, -2L), class = c("data.frame")))
#' scalesOuter <- c(E = "log10", ES = "log10", kcat_C1 = "log10", kcat_C2 = "log10", offset_E = "log10", offset_S = "log10", offset_S_C2 = "log10", P = "log10", S = "log10", sigma_obsES = "log10", sigma_obsP = "log10", sigma_obsS = "log10", sigma_obsS_C2 = "log10", sigmaAbs_obsE = "log10", sigmaRel_obsE = "log10", kon_C1 = "log10", kon_C2 = "log10", cytoplasm = "lin")
#' pdIndiv_getBaseScales(cg, scalesOuter)
pdIndiv_getBaseScales <- function(cg, scalesOuter) {
  parametersBase <- setdiff(names(cg), c("conditionId", "conditionName", "ID"))
  scalesBase <- vapply(setNames(nm = parametersBase), function(par) {
    px <- cg[[par]]
    px <- suppressWarnings(px[is.na(as.numeric(px))])
    
    if (!length(px)) 
      return("lin")
    
    sx <- scalesOuter[px]
    if (length(unique(sx)) > 1)
      stop("The following parameters refer to the same structural model parameter, but have different ",
           "scales in different conditions. This is not allowed, please fix manually. \n",
           "Base parameter: ", par , "\n",
           "Condition specific parameters: ", paste0(paste0(names(sx), " (", sx, ")"), collapse = ", "), "\n")
    unique(sx)
  }, "lin")
  scalesBase
}



#' Import an SBML model and corresponding PEtab objects
#'
#' @description This function imports an SBML model and corresponding PEtab files, e.g. from the Benchmark collection.
#'
#' @param filename "path/to/modelname.petab". Will look for files like
#'        "path/to/modelname/model_modelname.xml"
#' @param TestCases TRUE to load feature test cases
#' @param path2TestCases path to feature test case folder
#' @param compile if FALSE, g, ODEmodel and err are loaded from .RData (if present) and compilation time is saved
#'
#' @details Objects such as model equations, parameters or data are automatically assigned to the following standard variables and written to your current working directory (via <<-):
#' reactions, observables, errors, g, x, p0, err, obj, mydata, ODEmodel, condition.grid, trafoL, pouter, times.
#' Compiled objects (g, ODEmodel and err) are saved in .RData.
#'
#' @return list of dMod model objects with entries
#' * symbolicEquations
#' * odemodel
#' * data
#' * gridlist
#' * e
#' * fns
#' * prd
#' * obj_data
#' * pars
#'
#' @author Daniel Lill (daniel.lill@physik.uni-freiburg.de) building on the original function of Marcus and Svenja
#' @md
#' @importFrom dMod repar as.parframe
#' @importFrom cOde getSymbols
#'
#' @export
importPEtabSBML_indiv <- function(filename = "enzymeKinetics/enzymeKinetics.yaml",
                                  testCases = FALSE,
                                  path2TestCases = "PEtabTests/",
                                  .compiledFolder = file.path("CompiledObjects"),
                                  NFLAGcompile = c(Auto = 3, Recompile = 0, RebuildGrids = 1, LoadPrevious = 2)[3],
                                  SFLAGbrowser = c("0None", "1Beginning", "2BuildGrids", "3Compilation", "4CollectList",
                                                   "5Scales", "6ParameterFormulaInjection")[1]
)
{
  if (grepl(SFLAGbrowser,"1Beginning")) browser()
  # .. Set exit behaviour -----
  mywd <- getwd()
  on.exit({setwd(mywd)})
  
  # .. Define path to SBML, PEtab and pd files -----
  filename      <- path.expand(filename)
  modelname     <- petab_modelname_path(filename)$modelname
  files         <- petab_files(filename)
  filenameParts <- list(modelname = modelname, .currentFolder = mywd, .compiledFolder = .compiledFolder, 
                        type = "indiv", petabYaml = if(grepl("yaml", filename)) filename else NULL,
                        .resultsFolder = file.path(dirname(.compiledFolder), "Results"),
                        .projectFolder = dirname(.compiledFolder)
  )
  rdsfile       <- pd_files(filenameParts)$rdsfile
  
  # .. Read PEtab tables -----
  pe <- readPetab(filename)
  
  # .. Read previously imported files -----
  dir.create(.compiledFolder, showWarnings = FALSE)
  if (NFLAGcompile == 3){
    if (file.exists(rdsfile)) {
      peOld <- readRDS(rdsfile)$pe
      NFLAGcompile <- as.numeric(petab_hash(pe) == petab_hash(peOld)) * 2 # Um die Ecke wegen suboptimaler NFLAGcompile Definition
    } else {
      NFLAGcompile <- 0 
    }
  }
  
  if(NFLAGcompile > 0) {
    pd <- readPd(rdsfile)
    if (NFLAGcompile == 2) return(pd)
  }
  
  if(NFLAGcompile == 0) {
    .resultsFolder <- filenameParts$.resultsFolder
    results <- list.files(.resultsFolder, recursive = TRUE)
    if (any(grepl("mstrust.rds|profile|L1", results))) {
      cat(paste0("Deleting the following results: ", 
                 paste0(grep("mstrust.rds|profile|L1", results, value = TRUE), collapse = ",\n")))
      if (readline(" Are you sure? (type yes)") != "yes") stop("import stopped")}
    unlink(.resultsFolder,recursive = TRUE)
  }
  
  # check this for some coming versions before deprecating
  if (!is.null(pe$meta$events)) {
    stop("pe$meta$events is deprecated as of version 0.4.1. Please specify your events in pe$model :)")
  }
  
  ## load required packages
  require(libSBML) # => Not very nice, better explicitly import the required functions
  
  # .. Model Definition - Equations -----
  dummy            <- getReactionsSBML(files$modelXML, files$experimentalCondition)
  myreactions      <- dummy$reactions
  myreactions_orig <- dummy$reactions_orig
  myevents         <- rbind(dummy$events, sbmlImport_getEventsFromTrueEvents(files$modelXML)) # hack because events can take many forms in sbml
  mypreeqEvents    <- dummy$preeqEvents
  myobservables    <- getObservablesSBML(files$observables)
  
  dummy    <- getDataPEtabSBML(files$measurementData, files$observables)
  mydata   <- dummy$data
  myerrors <- dummy$errors
  
  
  # [ ] Pre-Equi Events
  if(!is.null(mypreeqEvents))
    stop("Pre-Equilibration is not yet implemented")
  # [ ] Need example for preeqEvents
  
  if (!is.null(myevents)){
    # set remaining event initials to 0. <= From Original import. Don't know what this means
    inits_events <- setdiff(unique(myevents$var), unique(mypreeqEvents$var))
    inits_events <- setNames(rep(0, length(inits_events)), inits_events)
  }
  
  # .. Initialize gridlist and trafo -----
  if (grepl(SFLAGbrowser, "2BuildGrids")) browser()
  cg <- copy(pe$experimentalCondition)
  
  # Get parameter information from all possible sources
  # Estimated parameters. Possible sources
  # * pe$parameters
  parsEst <- pe$parameters[estimate == 1] # replaces myfit_values
  parsEst[,`:=`(estValue = eval(parse(text = paste0(parameterScale, "(", nominalValue, ")")))),by = 1:nrow(parsEst)]
  
  # Fixed parameters. 
  # Possible sources 
  # * pe$parameters
  # * SBML parameters
  # * SBML compartments
  # * SBML inits
  # * Events
  parsFix <- pe$parameters[estimate == 0] # replaces myconstraints part1
  parsFix_SBMLPars <- sbmlImport_getParametersFromSBML(files$modelXML)   # replaces myconstraints part2
  parsFix_SBMLPars <- parsFix_SBMLPars[!parameterId %in% c(names(cg), parsFix$parameterId, parsEst$parameterId)] 
  parsFix_SBMLComp <- sbmlImport_getCompartmentsFromSBML(files$modelXML) # replaces myconstraints part3
  parsFix_SBMLComp <- parsFix_SBMLComp[!parameterId %in% c(names(cg), parsFix$parameterId, parsEst$parameterId)] 
  parsFix_SBMLInit <- sbmlImport_getInitialsFromSBML(files$modelXML)$inits_num # Should be handled as well
  parsFix_SBMLInit <- parsFix_SBMLInit[!parameterId %in% c(names(cg), parsFix$parameterId, parsEst$parameterId)] 
  parsFix <- rbindlist(list(parsFix, parsFix_SBMLPars, parsFix_SBMLComp, parsFix_SBMLInit))
  parsFix[,`:=`(estValue = eval(parse(text = paste0(parameterScale, "(", nominalValue, ")")))),by = 1:nrow(parsFix)]
  
  # Possible sinks => Remove from fix
  # * pe$meta$parameterFormulaInjection
  parsFix <- parsFix[!parameterId %in% pe$meta$parameterFormulaInjection$parameterId]
  
  # .. Add measurement parameters -----
  obsParMapping <- petab_getMeasurementParsMapping(pe, column = "observableParameters")
  cg <- merge(cg, obsParMapping, by = "conditionId", all.x = TRUE)
  
  errParMapping <- petab_getMeasurementParsMapping(pe, column = "noiseParameters")
  cg <- merge(cg, errParMapping, by = "conditionId", all.x = TRUE)
  
  # obsErrPars were already added => remove
  parametersObsErr <- petab_getParameterType(pe)
  parametersObsErr <- parametersObsErr[parameterType %in% c("observableParameters", "noiseParameters"), parameterId]
  parsEstNoObsErr <- parsEst[!parameterId %in% parametersObsErr]
  parsFixNoObsErr <- parsFix[!parameterId %in% parametersObsErr]
  
  # .. Add estPars and fixPars symbolically -----
  # Determine parameters already specified in cg. Assume they are fully mapped to inner parameters => remove
  parsEstNoObsErrNoCS <- parsEstNoObsErr[!parameterId %in% pdIndiv_getParameters_conditionGrid(cg)]
  # Add remaining parsEst globally
  parametersEstGlobal <- as.data.table(as.list(setNames(nm = parsEstNoObsErrNoCS$parameterId)))
  cg <- data.table(cg, parametersEstGlobal)
  
  # Add fixPars symbolically, they will later be replaced by actual values
  # Determine parameters already specified in cg. Assume they are fully mapped to inner parameters => remove
  parsFixNoObsErrNoCS <- parsFixNoObsErr[!parameterId %in% pdIndiv_getParameters_conditionGrid(cg)]
  parametersFixGlobal <- as.data.table(as.list(setNames(nm = parsFixNoObsErrNoCS$parameterId)))
  cg <- data.table(cg, parametersFixGlobal)
  
  # .. Parameter scales -----
  # Ensure that parameter scales are consistent for each CS <-> BASE mapping
  scalesOuter <- c(setNames(parsEst$parameterScale, parsEst$parameterId), setNames(parsFix$parameterScale, parsFix$parameterId))
  scalesBase <- pdIndiv_getBaseScales(cg, scalesOuter)
  
  # .. Replace symbolic fixPars by their values (on est scale) -----
  cg <- as.matrix(cg)
  for (par in parsFix$parameterId) cg[cg == par] <- parsFix[parameterId == par, estValue]
  cg <- as.data.table(cg)
  
  # .. Split into gridlist -----
  gl <- petab::pdIndiv_initializeGridlist(cg)
  
  # .. Build trafo -----
  # Initialize
  trafo <- setNames(nm = unique(c(getParameters(myreactions),
                                  getSymbols(myobservables),
                                  setdiff(getSymbols(myerrors), names(myobservables)),
                                  getSymbols(as.character(myevents$value)),
                                  getSymbols(pe$meta$parameterFormulaInjection$parameterFormula))))
  trafo <- trafo[trafo != "time"]
  
  # Insert inits
  trafo_inits <- sbmlImport_getInitialsFromSBML(files$modelXML)$inits_sym
  trafo[trafo_inits$parameterId] <- trafo_inits$parameterFormula
  trafo_assignmentRules <- NULL # [] Todo, if there is anything that needs to be done...
  
  # Insert scales
  trafo <- repar("x ~ 10**(x)", trafo = trafo, x = names(which(scalesBase=="log10")))
  trafo <- repar("x ~ exp(x)" , trafo = trafo, x = names(which(scalesBase=="log")))
  
  if (grepl(SFLAGbrowser,"5InspectTrafo")) browser()
  
  
  # .. ParameterFormulaInjection -----
  if (grepl(SFLAGbrowser, "6ParameterFormulaInjection")) browser()
  trafoInjected <- NULL
  pfi <- pe$meta$parameterFormulaInjection
  if (!is.null(pfi)) {
    # Probably over-cautios: This shouldn't happen. Can probably be removed
    check_pfiEstimated <- intersect(pfi$parameterId, names(gl$est.grid))
    if (length(check_pfiEstimated)) stop("These trafoInjected parameters are in est.grid: ", paste0(check_pfiEstimated, collapse = ", "))
    # Remove injected Parameter from est-trafo
    trafo <- trafo[setdiff(names(trafo), pfi$parameterId)]
    for (pid in intersect(names(gl$fix.grid), pfi$parameterId)) gl$fix.grid[,(pid) := NULL,]
    # trafoInjected
    trafoInjected <- setNames(pfi$parameterFormula, pfi$parameterId)
  }
  
  # -------------------------------------------------------------------------#
  # .. Model Compilation -----
  # -------------------------------------------------------------------------#
  if (grepl(SFLAGbrowser, "3Compilation")) browser()
  if (NFLAGcompile == 0) {
    setwd(.compiledFolder)
    cat("Compiling g\n")
    myg <- dMod::Y(myobservables, myreactions, compile=TRUE, modelname=paste0("g_",modelname))
    
    cat("Compiling odemodel\n")
    myodemodel <- dMod::odemodel(myreactions, forcings = NULL, events = myevents, fixed=NULL,
                                 estimate = c(dMod::getParametersToEstimate(est.grid = gl$est.grid,
                                                                            trafo = c(trafo, trafoInjected),
                                                                            reactions = myreactions),
                                              pe$meta$parameterFormulaInjection$parameterId),
                                 modelname = paste0("odemodel_", modelname),
                                 jacobian = "inz.lsodes", compile = TRUE)
    
    myx <- dMod::Xs(myodemodel,
                    optionsOde = list(method = "lsoda", rtol = 1e-10, atol = 1e-10, maxsteps = 5000),
                    optionsSens = list(method = "lsodes", lrw=200000, rtol = 1e-10, atol = 1e-10))
    
    cat("Compiling errormodel\n")
    myerr <- NULL
    if(length(cOde::getSymbols(myerrors)))
      myerr <- dMod::Y(myerrors, f = c(as.eqnvec(myreactions), myobservables), states = names(myobservables),
                       attach.input = FALSE, compile = TRUE, modelname = paste0("errfn_", modelname))
    
    # [ ] Pre-equilibration
    # mypSS <- Id()
    
    p1 <- dMod::Id()
    if (length(trafoInjected)){
      cat("Compiling pInjected\n")
      p1 <- dMod::P(trafoInjected, compile = TRUE, modelname = paste0("PInjected_", modelname),
                    attach.input = TRUE)
    }
    
    cat("Compiling p\n")
    p0 <- dMod::P(trafo, compile = TRUE, modelname = paste0("P_", modelname))
    setwd(mywd)
    
  } else if (NFLAGcompile == 1) {
    myg        <- pd$dModAtoms$fns$g
    myx        <- pd$dModAtoms$fns$x
    p1         <- pd$dModAtoms$fns$p1
    p0         <- pd$dModAtoms$fns$p0
    myodemodel <- pd$dModAtoms$odemodel
    myerr      <- pd$dModAtoms$e
  }
  
  # -------------------------------------------------------------------------#
  # Collect list ----
  # -------------------------------------------------------------------------#
  if (grepl(SFLAGbrowser, "4CollectList")) browser()
  fns <- list(
    g = myg,
    x = myx,
    # p1 = mypSS, # [ ] Pre-Equilibration
    p1 = p1,
    p0 = p0
  )
  symbolicEquations <- list(
    reactions = myreactions,
    observables = myobservables,
    errors  = myerrors,
    trafo = trafo,
    trafoInjected = trafoInjected)
  
  pars <- setNames(parsEst$estValue, parsEst$parameterId)
  pars <- pars[setdiff(names(pars), names(trafoInjected))]
  
  times <- c(
    pe$measurementData$time,
    pe$meta$presimTimes
  )
  
  # .. Collect final list -----
  pd <- list(
    # petab
    pe                 = pe,
    # Basic dMod elements
    dModAtoms          = list(
      # [ ] add events!
      symbolicEquations  = symbolicEquations,
      odemodel           = myodemodel,
      data               = mydata,
      gridlist           = gl,
      e                  = myerr,
      fns                = fns
    ),
    # other components: Dump your stuff here
    filenameParts = filenameParts,
    # Parameters + Time
    pars               = pars,
    times              = predtimes(times, Nobjtimes = 200)
  )
  # High level prediction function
  pd <- pdIndiv_rebuildPrdObj(pd = pd,Nobjtimes = 100)
  
  
  # .. Save and return -----
  saveRDS(pd, rdsfile)
  
  if (grepl(SFLAGbrowser, "7evaluateObj")) browser()
  # Try to evaluate obj for first time
  cat("Evaluating obj ... ")
  value_base <- tryCatch(pd$obj(pd$pars)$value, error = function(x) NA)
  cat("objective value: ", value_base, "\n")
  # suggestion for the future: Include parameterSetId from the beginning
  # parf_base <- dMod::parframe(data.frame(parameterSetId = "Base", value = value_base, index = 1, converged = FALSE, iterations = 1, pd$pars),parameters = names(pd$pars)) 
  # old solution
  parf_base <- dMod::as.parframe(structure(list(list(value = value_base, index = 1, converged = FALSE, iterations = 1, argument = pd$pars)), class = c("parlist", "list")))
  dMod_saveMstrust(parf_base, dirname(dirname(rdsfile)), identifier = "base", FLAGoverwrite = TRUE)
  
  # return pd
  readPd(rdsfile)
}

# .. pd_parameterNames -----
#' @export
#' @author Daniel Lill (daniel.lill@physik.uni-freiburg.de)
#' @md
#' @importFrom data.table data.table
#' @importFrom tibble tribble
pd_parameterNames <- data.table::data.table(tibble::tribble(
  ~STAGEID , ~STAGENAME,        
  "CS"     , "Condition specific, on est-scale"    ,
  "BASE"   , "Not condition specific, on est-scale",
  "NOMINAL", "Base parameters on nominal scale"    ,
  "INNER"  , "Base parameters after parameter formula injections"
))
dlill/petab documentation built on Oct. 9, 2022, 3:07 p.m.