float AnalyzeResponseUniformity::getFitBoundary(std::string &strInputExp, std::shared_ptr<TH1F> hInput, TSpectrum &specInput){
    //Variable Declaration
    map<string, float> map_key2Val;
    
    //Search the input expression for each of the supported keywords
    //Store these Keywords with their values
    for (int i=0; i < vec_strSupportedKeywords.size(); ++i) { //Loop Through Supported Keywords
        if ( strInputExp.find( vec_strSupportedKeywords[i] ) != std::string::npos ) { //Case: Keyword Found!
            map_key2Val[vec_strSupportedKeywords[i]] = getValByKeyword( vec_strSupportedKeywords[i], hInput, specInput );
        } //End Case: Keyword Found!
    } //End Loop Through Supported Keywords
    
    //Check if map_key2Val has any entries, if so user requested complex expression; parse!
    //If map_key2Val is empty, user has a numeric input; convert to float!
    if (map_key2Val.size() > 0) { //Case: Complex Expression!
        //Setup the expression parser
        symbol_table_t symbol_table;    //Stores the variables in expression and maps them to C++ objects
        expression_t expression;        //Stores the actual expression & the symbol table
        parser_t parser;                //Parses the information for evaluation
        
        //Load all found keywords into the symbol table
        for (auto iterMap = map_key2Val.begin(); iterMap != map_key2Val.end(); ++iterMap) {
            symbol_table.add_variable( (*iterMap).first, (*iterMap).second);
        }
        
        //Give the expression the variables it should have
        expression.register_symbol_table(symbol_table);
        
        //Compile the parsing
        parser.compile(strInputExp, expression);
        
        //Return value to the user
        return expression.value();
    } //End Case: Complex Expression!
    else{ //Case: Numeric Input
        return stofSafe( strInputExp );
    } //End Case: Numeric Input
} //End AnalyzeResponseUniformity::getFitBoundary()
//Uniformity
//Loads parameters defined in file read by inputFileStream and sets tehm to the aSetupUniformity
//Note this should only be called within the Uniformity heading if the user has configured the file correctly
void ParameterLoaderAnaysis::loadAnalysisParametersUniformity(ifstream &inputFileStream, AnalysisSetupUniformity &aSetupUniformity){
    //Variable Declaration
    bool bExitSuccess = false;
    
    pair<string,string> pair_strParam; //Input file is setup in <Field, Value> pairs; not used here yet but placeholder
    
    //string strField = "";   //From input file we have <Field,Value> pairs
    string strLine = "";    //Line taken from the input file
    //string strHeading = ""; //For storing detector heading
    
    vector<string> vec_strList; //For storing char separated input; not used here yet but placeholder
    
    if (bVerboseMode_IO) { //Case: User Requested Verbose Error Messages - I/O
        printClassMethodMsg("ParameterLoaderAnaysis","loadAnalysisParametersUniformity", "Found Uniformity Heading");
    } //End Case: User Requested Verbose Error Messages - I/O
    
    while ( getlineNoSpaces(inputFileStream, strLine) ) {
        //Does the user want to comment out this line?
        if ( 0 == strLine.compare(0,1,"#") ) continue;
        
        //Do we reach the end of the section?
        if ( 0 == strLine.compare(strSecEnd_Uniformity ) ) break;
        
        //Should we be storing histogram/fit setup parameters?
        if ( 0 == strLine.compare(strSecBegin_Uniformity_Fit) ) { //Case: Fit Setup
            loadAnalysisParametersFits(inputFileStream, aSetupUniformity.histoSetup_clustADC);
	    continue; //Tell it to move to the next loop iteration (e.g. line in file)
        } //End Case: Fit Setup
        else if( 0 == strLine.compare(strSecBegin_Uniformity_Histo) ){ //Case: Histo Setup
            loadAnalysisParametersHistograms(inputFileStream, aSetupUniformity);
	    continue; //Tell it to move to the next loop iteration (e.g. line in file)
        } //End Case: Histo Setup
        
        //Debugging
        cout<<"strLine: = " << strLine.c_str() << endl;
        
        //Parse the line
        pair_strParam = getParsedLine(strLine,bExitSuccess);
        
        if (bExitSuccess) { //Case: Parameter Fetched Correctly
            //transform(pair_strParam.first.begin(), pair_strParam.second.end(),pair_strParam.first.begin(),toupper);
            
            string strTmp = pair_strParam.first;
            transform(strTmp.begin(), strTmp.end(), strTmp.begin(), toupper);
            
            pair_strParam.first = strTmp;
            
            //cout<<pair_strParam.first<<"\t"<<pair_strParam.second;

            if ( 0 == pair_strParam.first.compare("CUT_ADC_MIN") ) {
                aSetupUniformity.selClust.iCut_ADCNoise = stoiSafe(pair_strParam.first,pair_strParam.second);
                //cout<<"\t"<<aSetupUniformity.selClust.iCut_ADCNoise<<endl;
            } //End Case: Minimum ADC Value
            else if( 0 == pair_strParam.first.compare("CUT_CLUSTERMULTI_MIN") ){ //Case: Min Cluster Multiplicity
                aSetupUniformity.selClust.iCut_MultiMin = stoiSafe(pair_strParam.first,pair_strParam.second);
            } //End Case: Max Cluster Multiplicity
            else if( 0 == pair_strParam.first.compare("CUT_CLUSTERMULTI_MAX") ){
                aSetupUniformity.selClust.iCut_MultiMax = stoiSafe(pair_strParam.first,pair_strParam.second);
            } //End Case:
            else if( 0 == pair_strParam.first.compare("CUT_CLUSTERSIZE_MIN") ) {
                aSetupUniformity.selClust.iCut_SizeMin = stoiSafe(pair_strParam.first,pair_strParam.second);
                //cout<<"\t"<<aSetupUniformity.selClust.iCut_SizeMin<<endl;
            } //End Case: Min Cluster Size
            else if( 0 == pair_strParam.first.compare("CUT_CLUSTERSIZE_MAX") ) {
                aSetupUniformity.selClust.iCut_SizeMax = stoiSafe(pair_strParam.first,pair_strParam.second);
                //cout<<"\t"<<aSetupUniformity.selClust.iCut_SizeMax<<endl;
            } //End Case: Max Cluster Size
            else if( 0 == pair_strParam.first.compare("CUT_CLUSTERTIME_MIN") ) {
                aSetupUniformity.selClust.iCut_TimeMin = stoiSafe(pair_strParam.first,pair_strParam.second);
                //cout<<"\t"<<aSetupUniformity.selClust.iCut_TimeMin<<endl;
            } //End Case: Min Cluster Time
            else if( 0 == pair_strParam.first.compare("CUT_CLUSTERTIME_MAX") ) {
                aSetupUniformity.selClust.iCut_TimeMax = stoiSafe(pair_strParam.first,pair_strParam.second);
                //cout<<"\t"<<aSetupUniformity.selClust.iCut_TimeMax<<endl;
            } //End Case: Max Cluster Time
            if( 0 == pair_strParam.first.compare("EVENT_FIRST") ){ //Case: ADC Spectrum Fit Equation
                aSetupUniformity.iEvt_First = stoiSafe(pair_strParam.second);
            } //End Case: ADC Spectrum Fit Equation
            else if( 0 == pair_strParam.first.compare("EVENT_TOTAL") ){ //Case: ADC Spectrum Fit Equation
                aSetupUniformity.iEvt_Total = stoiSafe(pair_strParam.second);
            } //End Case: ADC Spectrum Fit Equation
            else if( 0 == pair_strParam.first.compare("UNIFORMITY_GRANULARITY") ){ //Case: Uniformity Granularity
                aSetupUniformity.iUniformityGranularity = stoiSafe(pair_strParam.first,pair_strParam.second);
            } //End Case: Uniformity Granularity
            else if( 0 == pair_strParam.first.compare("UNIFORMITY_TOLERANCE") ){ //Case: Uniformity Granularity
                aSetupUniformity.fUniformityTolerance = stofSafe(pair_strParam.first,pair_strParam.second);
            } //End Case: Uniformity Granularity
            else{ //Case: Parameter Not Recognized
                printClassMethodMsg("ParameterLoaderAnaysis","loadAnalysisParametersUniformity","Error!!! Parameter Not Recognizd:\n");
                printClassMethodMsg("ParameterLoaderAnaysis","loadAnalysisParametersUniformity",( "\tField = " + pair_strParam.first + "\n" ).c_str() );
                printClassMethodMsg("ParameterLoaderAnaysis","loadAnalysisParametersUniformity",( "\tValue = " + pair_strParam.second + "\n" ).c_str() );
            } //End Case: Parameter Not Recognized
        } //End Case: Parameter Fetched Correctly
        else{ //Case: Parameter Failed to fetch correctly
            printClassMethodMsg("ParameterLoaderAnaysis","loadAnalysisParametersUniformity","Error!!!  I didn't parse parameter correctly\n");
            printClassMethodMsg("ParameterLoaderAnaysis","loadAnalysisParametersUniformity",("\tCurrent line: " + strLine).c_str() );
        } //End Case: Parameter Failed to fetch correctly
    } //End Loop through Uniformity Heading
    
    return;
} //End ParameterLoaderAnaysis::loadAnalysisParametersUniformity()
TF1 AnalyzeResponseUniformity::getFit(int iEta, int iPhi, int iSlice, HistoSetup & setupHisto, shared_ptr<TH1F> hInput, TSpectrum &specInput ){
    //Variable Declaration
    float fLimit_Max = setupHisto.fHisto_xUpper, fLimit_Min = setupHisto.fHisto_xLower;
    
    vector<string>::const_iterator iterVec_IGuess; //Iterator to use for setting initial guess of fit
    vector<float> vec_fFitRange;
    
    for (auto iterRange = aSetup.histoSetup_clustADC.vec_strFit_Range.begin(); iterRange != aSetup.histoSetup_clustADC.vec_strFit_Range.end(); ++iterRange) { //Loop Over Fit Range
        vec_fFitRange.push_back( getFitBoundary( (*iterRange), hInput, specInput ) );
    } //End Loop Over Fit Range
    
    if (vec_fFitRange.size() > 1) {
        fLimit_Min = (*std::min_element(vec_fFitRange.begin(), vec_fFitRange.end() ) );
        fLimit_Max = (*std::max_element(vec_fFitRange.begin(), vec_fFitRange.end() ) );
    }
    
    TF1 ret_Func( getNameByIndex(iEta, iPhi, iSlice, "fit", setupHisto.strHisto_Name).c_str(), setupHisto.strFit_Formula.c_str(), fLimit_Min, fLimit_Max);
    
    //Check to see if the number of parameters in the TF1 meets the expectation
    if ( ret_Func.GetNpar() < setupHisto.vec_strFit_ParamIGuess.size() || ret_Func.GetNpar() < setupHisto.vec_strFit_ParamLimit_Min.size() || ret_Func.GetNpar() < setupHisto.vec_strFit_ParamLimit_Max.size() ) { //Case: Set points for initial parameters do not meet expectations
        
        printClassMethodMsg("AnalyzeResponseUniformity","getFit","Error! Number of Parameters in Function Less Than Requested Initial Guess Parameters!");
        printClassMethodMsg("AnalyzeResponseUniformity","getFit", ("\tNum Parameter: " + getString( ret_Func.GetNpar() ) ).c_str() );
        printClassMethodMsg("AnalyzeResponseUniformity","getFit", ("\tNum Initial Guesses: " + getString( setupHisto.vec_strFit_ParamIGuess.size() ) ).c_str() );
        printClassMethodMsg("AnalyzeResponseUniformity","getFit", ("\tNum Initial Guess Limits (Min): " + getString( setupHisto.vec_strFit_ParamLimit_Min.size() ) ).c_str() );
        printClassMethodMsg("AnalyzeResponseUniformity","getFit", ("\tNum Initial Guess Limits (Max): " + getString( setupHisto.vec_strFit_ParamLimit_Max.size() ) ).c_str() );
        printClassMethodMsg("AnalyzeResponseUniformity","getFit", "No Initial Parameters Have Been Set! Please Cross-Check Input Analysis Config File" );
        
        return ret_Func;
    } //End Case: Set points for initial parameters do not meet expectations
    
    //Set Fit Parameters - Initial Value
    //------------------------------------------------------
    //Keywords are AMPLITUDE, MEAN, PEAK, SIGMA
    for (int i=0; i<setupHisto.vec_strFit_ParamIGuess.size(); ++i) { //Loop over parameters - Initial Guess
        iterVec_IGuess = std::find(vec_strSupportedKeywords.begin(), vec_strSupportedKeywords.end(), setupHisto.vec_strFit_ParamIGuess[i]);
        
        if ( iterVec_IGuess == vec_strSupportedKeywords.end() ) { //Case: No Keyword Found; Try to set a Numeric Value
            ret_Func.SetParameter(i, stofSafe( setupHisto.vec_strFit_ParamIGuess[i] ) );
        } //End Case: No Keyword Found; Try to set a Numeric Value
        else{ //Case: Keyword Found; Set Value based on Keyword
            ret_Func.SetParameter(i, getValByKeyword( (*iterVec_IGuess), hInput, specInput ) );
        } //End Case: Keyword Found; Set Value based on Keyword
    } //End Loop over parameters - Initial Guess
    
    //Set Fit Parameters - Boundaries
    //------------------------------------------------------
    if (setupHisto.vec_strFit_ParamLimit_Min.size() == setupHisto.vec_strFit_ParamLimit_Max.size() ) { //Check: Stored Parameter Limits Match

        //Here we use vec_strFit_ParamLimit_Min but we know it has the same number of parameters as vec_strFit_ParamLimit_Max
        //For each fit parameter, set the boundary
        for (int i=0; i<setupHisto.vec_strFit_ParamLimit_Min.size(); ++i) { //Loop over boundary parameters
            fLimit_Min = getFitBoundary(setupHisto.vec_strFit_ParamLimit_Min[i], hInput, specInput);
            fLimit_Max = getFitBoundary(setupHisto.vec_strFit_ParamLimit_Max[i], hInput, specInput);
            
		//cout<<"(fLimit_Min, fLimit_Max) = (" << fLimit_Min << ", " << fLimit_Max << ")\n";

            (fLimit_Max > fLimit_Min) ? ret_Func.SetParLimits(i, fLimit_Min, fLimit_Max ) : ret_Func.SetParLimits(i, fLimit_Max, fLimit_Min );
        } //End Loop over boundary parameters
    } //End Check: Stored Parameter Limits Match
    
    //Set Fit Parameters - Fixed?
    //------------------------------------------------------
    
    //Placeholder; maybe we add functionality in the future
    
    //Set Other Fit Data Members
    //------------------------------------------------------
    ret_Func.SetLineColor(kRed);
    ret_Func.SetLineWidth(3);
    
    //Delete Pointers
    //delete iterVec_IGuess;
    
    //Return fit
    //------------------------------------------------------
    return ret_Func;
} //End AnalyzeResponseUniformity::getFit()