diff --git a/include/MReadOutAssembly.h b/include/MReadOutAssembly.h index a0aa8da..724c5f6 100644 --- a/include/MReadOutAssembly.h +++ b/include/MReadOutAssembly.h @@ -262,12 +262,17 @@ class MReadOutAssembly : public MReadOutSequence //! Get the energy resolution calibration incomplete flag bool IsEnergyResolutionCalibrationIncomplete() const { return m_EnergyResolutionCalibrationIncomplete; } - //! set the Strip Hit Below Threshold flag - void SetStripHitBelowThreshold(bool Flag = true, MString Text = ""){ - m_StripHitBelowThreshold = Flag; m_StripHitBelowThreshold = Text;} - //! get the Strip Hit Below Threshold flag - bool IsStripHitBelowThreshold() const { return m_StripHitBelowThreshold; } - + //! Set the Strip Hit Below Threshold quality flag + void SetStripHitBelowThreshold_QualityFlag(bool Flag = true, MString Text = ""){ m_StripHitBelowThreshold_QualityFlag = Flag; m_StripHitBelowThresholdString_QualityFlag = Text;} + //! Get the Strip Hit Below Threshold quality flag + bool IsStripHitBelowThreshold_QualityFlag() const { return m_StripHitBelowThreshold_QualityFlag; } + + //! Track the energy and number of strip hits removed through the threshold cut + void AddStripHitBelowThreshold(double Energy) { m_StripHitBelowThreshold_Energy = m_StripHitBelowThreshold_Energy + Energy; m_StripHitBelowThreshold_Number = m_StripHitBelowThreshold_Number + 1; } + //! Get total energy removed through threshold cut + double GetStripHitBelowThreshold_Energy() const { return m_StripHitBelowThreshold_Energy; } + //! Get total number of strip hits removed through threshold cut + int GetStripHitBelowThreshold_Number() const { return m_StripHitBelowThreshold_Number;} //! Set the strip-pairing-incomplete flag void SetStripPairingIncomplete(bool Flag = true, MString Text = "") { m_StripPairingIncomplete = Flag; m_StripPairingIncompleteString = Text; } @@ -448,7 +453,7 @@ class MReadOutAssembly : public MReadOutSequence //! Reduced Chi^2 of the Strip Paired Event double m_ReducedChiSquare; - // Flags indicating the quality of the event + // Flags indicating the quality of the event: incomplete calibration bool m_AspectIncomplete; MString m_AspectIncompleteString; @@ -473,9 +478,12 @@ class MReadOutAssembly : public MReadOutSequence MString m_DepthCalibrationIncompleteString; bool m_DepthCalibration_OutofRange; MString m_DepthCalibration_OutofRangeString; - - bool m_StripHitBelowThreshold; - MString m_StripHitBelowThresholdString; + + // Flags indicating the quality of the event: quality warning, but not to be filtered out + bool m_StripHitBelowThreshold_QualityFlag; + MString m_StripHitBelowThresholdString_QualityFlag; + double m_StripHitBelowThreshold_Energy; + int m_StripHitBelowThreshold_Number; bool m_EventReconstructionIncomplete; MString m_EventReconstructionIncompleteString; diff --git a/src/MModuleEnergyCalibrationUniversal.cxx b/src/MModuleEnergyCalibrationUniversal.cxx index 5cf37bf..000a714 100644 --- a/src/MModuleEnergyCalibrationUniversal.cxx +++ b/src/MModuleEnergyCalibrationUniversal.cxx @@ -63,38 +63,38 @@ ClassImp(MModuleEnergyCalibrationUniversal) MModuleEnergyCalibrationUniversal::MModuleEnergyCalibrationUniversal() : MModule() { // Construct an instance of MModuleEnergyCalibrationUniversal - + // Set all module relevant information - + // Set the module name --- has to be unique m_Name = "Universal energy calibrator"; - + // Set the XML tag --- has to be unique --- no spaces allowed m_XmlTag = "EnergyCalibrationUniversal"; - + // Set all modules, which have to be done before this module AddPreceedingModuleType(MAssembly::c_EventLoader); // AddPreceedingModuleType(MAssembly::c_TACcut); - + // Set all types this modules handles AddModuleType(MAssembly::c_EnergyCalibration); - + // Set all modules, which can follow this module AddSucceedingModuleType(MAssembly::c_TACcut); - + // Set if this module has an options GUI m_HasOptionsGUI = true; - + // Allow the use of multiple threads and instances m_AllowMultiThreading = true; m_AllowMultipleInstances = true; - // + // Initiate the Slow Threshold Cut variables m_SlowThresholdCutMode = MSlowThresholdCutModes::e_Ignore; - m_SlowThresholdCutFixedValue = 35; + m_SlowThresholdCutFixedValue = 15; m_SlowThresholdCutFileName = ""; -} +} //////////////////////////////////////////////////////////////////////////////// @@ -111,12 +111,14 @@ MModuleEnergyCalibrationUniversal::~MModuleEnergyCalibrationUniversal() void MModuleEnergyCalibrationUniversal::CreateExpos() { // If they are already created, return - if (m_Expos.size() != 0) return; - + if (m_Expos.size() != 0) { + return; + } + // Set the histogram display m_ExpoEnergyCalibration = new MGUIExpoEnergyCalibration(this); m_ExpoEnergyCalibration->SetEnergyHistogramParameters(200, 0, 2000); - m_Expos.push_back(m_ExpoEnergyCalibration); + m_Expos.push_back(m_ExpoEnergyCalibration); } @@ -126,11 +128,13 @@ void MModuleEnergyCalibrationUniversal::CreateExpos() bool MModuleEnergyCalibrationUniversal::Initialize() { // Initialize the module - + //Parse the Energy Calibration file MParser Parser; if (Parser.Open(m_FileName, MFile::c_Read) == false) { - if (g_Verbosity >= c_Error) cout<= c_Error) { + cout << m_XmlTag << ": Unable to open calibration file " << m_FileName << endl; + } return false; } //Parse the slow Threshold file @@ -139,30 +143,27 @@ bool MModuleEnergyCalibrationUniversal::Initialize() if (Parser_Threshold.Open(m_SlowThresholdCutFileName, MFile::c_Read) == false) { if (g_Verbosity >= c_Error) cout< Tokens = Line.Tokenize(","); // for each line, Create tokens seperated by commas if (Tokens.size() == 6) { int IndexOffset = Tokens.size() % 6; //index counter - int DetID = Tokens[1+IndexOffset].ToInt(); // Detector ID - MString Side = Tokens[2+IndexOffset].ToString(); // side is a string, either 'l' or 'h' - int StripID = Tokens[3+IndexOffset].ToInt(); // stripID - int ThresholdADC = Tokens[4+IndexOffset].ToInt(); // energy threshold in ADC - double ThresholdKeVFile = Tokens[5+IndexOffset].ToDouble(); //energy threshold in keV - + int DetID = Tokens[1 + IndexOffset].ToInt(); // Detector ID + MString Side = Tokens[2 + IndexOffset].ToString(); // side is a string, either 'l' or 'h' + int StripID = Tokens[3 + IndexOffset].ToInt(); // stripID + int ThresholdADC = Tokens[4 + IndexOffset].ToInt(); // energy threshold in ADC + double ThresholdKeVFile = Tokens[5 + IndexOffset].ToDouble(); //energy threshold in keV + MReadOutElementDoubleStrip R; R.SetDetectorID(DetID); R.SetStripID(StripID); R.IsLowVoltageStrip(Side == "l"); - + // map detectorID, strip number, and voltage side to the threshold (keV) m_ThresholdMap[R] = ThresholdKeVFile; - } - } } } @@ -175,11 +176,13 @@ bool MModuleEnergyCalibrationUniversal::Initialize() //tokenize ecal file for (unsigned int i = 0; i < Parser.GetNLines(); ++i) { unsigned int NTokens = Parser.GetTokenizerAt(i)->GetNTokens(); - if (NTokens < 2) continue; + if (NTokens < 2) { + continue; + } if (Parser.GetTokenizerAt(i)->IsTokenAt(0, "CP") == true || - Parser.GetTokenizerAt(i)->IsTokenAt(0, "CM") == true || Parser.GetTokenizerAt(i)->IsTokenAt(0,"CR") == true) { + Parser.GetTokenizerAt(i)->IsTokenAt(0, "CM") == true || Parser.GetTokenizerAt(i)->IsTokenAt(0, "CR") == true) { if (Parser.GetTokenizerAt(i)->IsTokenAt(1, "dss") == true) { - + // input token values to map MReadOutElementDoubleStrip R; R.SetDetectorID(Parser.GetTokenizerAt(i)->GetTokenAtAsUnsignedInt(2)); @@ -193,90 +196,96 @@ bool MModuleEnergyCalibrationUniversal::Initialize() CR_ROEToLine[R] = i; } } else { - if (g_Verbosity >= c_Error) cout<GetTokenAt(1)<<")"<= c_Error) { + cout << m_XmlTag << ": Line parser: Unknown read-out element (" << Parser.GetTokenizerAt(i)->GetTokenAt(1) << ")" << endl; + } return false; } } } - for (auto CM: CM_ROEToLine) { + for (auto CM : CM_ROEToLine) { // If we have at least three data points, we store the calibration - + if (CP_ROEToLine.find(CM.first) != CP_ROEToLine.end()) { unsigned int i = CP_ROEToLine[CM.first]; if (Parser.GetTokenizerAt(i)->IsTokenAt(5, "pakw") == false) { - if (g_Verbosity >= c_Warning) cout<GetTokenAt(5)<= c_Warning) { + cout << m_XmlTag << ": Unknown calibration point descriptor found: " << Parser.GetTokenizerAt(i)->GetTokenAt(5) << endl; + } continue; } } else { - if (g_Verbosity >= c_Warning) cout<= c_Warning) { + cout << m_XmlTag << ": No good calibration for the following strip found: " << CM.first << endl; + } continue; } - + // Read the calibrator, i.e. read the fit function from the .ecal Melinator file. - + unsigned int Pos = 5; MString CalibratorType = Parser.GetTokenizerAt(CM.second)->GetTokenAtAsString(Pos); CalibratorType.ToLower(); - + // Below inclusion of poly1zero written by J. Beechert on 2019/11/15 if (CalibratorType == "poly1zero") { double a0 = Parser.GetTokenizerAt(CM.second)->GetTokenAtAsDouble(++Pos); - + //From the fit parameters I just extracted from the .ecal file, I can define a function - TF1* melinatorfit = new TF1("poly1zero","0. + [0]*x", 0., 8191.); + TF1* melinatorfit = new TF1("poly1zero", "0. + [0]*x", 0., 8191.); melinatorfit->FixParameter(0, a0); - + //Define the map by saving the fit function I just created as a map to the current ReadOutElement m_Calibration[CM.first] = melinatorfit; - - } - + + } + // Below inclusion of poly1 and poly2 written by J. Beechert on 2019/10/24 else if (CalibratorType == "poly1") { double a0 = Parser.GetTokenizerAt(CM.second)->GetTokenAtAsDouble(++Pos); double a1 = Parser.GetTokenizerAt(CM.second)->GetTokenAtAsDouble(++Pos); - + //From the fit parameters I just extracted from the .ecal file, I can define a function - TF1* melinatorfit = new TF1("poly1","[0] + [1]*x", 0., 8191.); + TF1* melinatorfit = new TF1("poly1", "[0] + [1]*x", 0., 8191.); melinatorfit->FixParameter(0, a0); melinatorfit->FixParameter(1, a1); - + //Define the map by saving the fit function I just created as a map to the current ReadOutElement m_Calibration[CM.first] = melinatorfit; - + } else if (CalibratorType == "poly2") { double a0 = Parser.GetTokenizerAt(CM.second)->GetTokenAtAsDouble(++Pos); double a1 = Parser.GetTokenizerAt(CM.second)->GetTokenAtAsDouble(++Pos); double a2 = Parser.GetTokenizerAt(CM.second)->GetTokenAtAsDouble(++Pos); - + //From the fit parameters I just extracted from the .ecal file, I can define a function - TF1* melinatorfit = new TF1("poly2","[0] + [1]*x + [2]*x^2", 0., 8191.); + TF1* melinatorfit = new TF1("poly2", "[0] + [1]*x + [2]*x^2", 0., 8191.); melinatorfit->FixParameter(0, a0); melinatorfit->FixParameter(1, a1); melinatorfit->FixParameter(2, a2); - + //Define the map by saving the fit function I just created as a map to the current ReadOutElement m_Calibration[CM.first] = melinatorfit; - - } - //Eventually, I'll be including other possible fits, but for now, we've just include poly3 and poly4 - else if (CalibratorType == "poly3") { + + } + //Eventually, I'll be including other possible fits, but for now, we've just include poly3 and poly4 + else if (CalibratorType == "poly3") { double a0 = Parser.GetTokenizerAt(CM.second)->GetTokenAtAsDouble(++Pos); double a1 = Parser.GetTokenizerAt(CM.second)->GetTokenAtAsDouble(++Pos); double a2 = Parser.GetTokenizerAt(CM.second)->GetTokenAtAsDouble(++Pos); double a3 = Parser.GetTokenizerAt(CM.second)->GetTokenAtAsDouble(++Pos); - + //From the fit parameters I just extracted from the .ecal file, I can define a function - TF1* melinatorfit = new TF1("poly3","[0] + [1]*x + [2]*x^2 + [3]*x^3", 0., 8191.); + TF1* melinatorfit = new TF1("poly3", "[0] + [1]*x + [2]*x^2 + [3]*x^3", 0., 8191.); melinatorfit->FixParameter(0, a0); melinatorfit->FixParameter(1, a1); melinatorfit->FixParameter(2, a2); melinatorfit->FixParameter(3, a3); - + //Define the map by saving the fit function I just created as a map to the current ReadOutElement m_Calibration[CM.first] = melinatorfit; - + } else if (CalibratorType == "poly4") { double a0 = Parser.GetTokenizerAt(CM.second)->GetTokenAtAsDouble(++Pos); double a1 = Parser.GetTokenizerAt(CM.second)->GetTokenAtAsDouble(++Pos); @@ -285,7 +294,7 @@ bool MModuleEnergyCalibrationUniversal::Initialize() double a4 = Parser.GetTokenizerAt(CM.second)->GetTokenAtAsDouble(++Pos); //From the fit parameters I just extracted from the .ecal file, I can define a function - TF1 * melinatorfit = new TF1("poly4","[0] + [1]*x + [2]*x^2 + [3]*x^3 + [4]*x^4", 0., 8191.); + TF1* melinatorfit = new TF1("poly4", "[0] + [1]*x + [2]*x^2 + [3]*x^3 + [4]*x^4", 0., 8191.); melinatorfit->FixParameter(0, a0); melinatorfit->FixParameter(1, a1); melinatorfit->FixParameter(2, a2); @@ -296,12 +305,14 @@ bool MModuleEnergyCalibrationUniversal::Initialize() m_Calibration[CM.first] = melinatorfit; } else { - if (g_Verbosity >= c_Error) cout<= c_Error) { + cout << m_XmlTag << ": Line parser: Unknown calibrator type (" << CalibratorType << ") for strip" << CM.first << endl; + } continue; } } - - for (auto CR: CR_ROEToLine) { + + for (auto CR : CR_ROEToLine) { unsigned int Pos = 5; MString CalibratorType = Parser.GetTokenizerAt(CR.second)->GetTokenAtAsString(Pos); @@ -309,97 +320,121 @@ bool MModuleEnergyCalibrationUniversal::Initialize() if (CalibratorType == "p1" || CalibratorType == "poly1") { double f0 = Parser.GetTokenizerAt(CR.second)->GetTokenAtAsDouble(++Pos); double f1 = Parser.GetTokenizerAt(CR.second)->GetTokenAtAsDouble(++Pos); - TF1* resolutionfit = new TF1("P1","([0]+[1]*x) / 2.355",0.,2000.); - resolutionfit->FixParameter(0,f0); - resolutionfit->FixParameter(1,f1); + TF1* resolutionfit = new TF1("P1", "([0]+[1]*x) / 2.355", 0., 2000.); + resolutionfit->FixParameter(0, f0); + resolutionfit->FixParameter(1, f1); m_ResolutionCalibration[CR.first] = resolutionfit; - } - else if (CalibratorType == "p2" || CalibratorType == "poly2") { + } else if (CalibratorType == "p2" || CalibratorType == "poly2") { double f0 = Parser.GetTokenizerAt(CR.second)->GetTokenAtAsDouble(++Pos); double f1 = Parser.GetTokenizerAt(CR.second)->GetTokenAtAsDouble(++Pos); double f2 = Parser.GetTokenizerAt(CR.second)->GetTokenAtAsDouble(++Pos); - TF1* resolutionfit = new TF1("P2","([0]+[1]*x+[2]*x*x) / 2.355",0.,2000.); - resolutionfit->FixParameter(0,f0); - resolutionfit->FixParameter(1,f1); - resolutionfit->FixParameter(2,f2); + TF1* resolutionfit = new TF1("P2", "([0]+[1]*x+[2]*x*x) / 2.355", 0., 2000.); + resolutionfit->FixParameter(0, f0); + resolutionfit->FixParameter(1, f1); + resolutionfit->FixParameter(2, f2); m_ResolutionCalibration[CR.first] = resolutionfit; } else { - if (g_Verbosity >= c_Error) cout<= c_Error) { + cout << m_XmlTag << ": Line parser: Unknown resolution calibrator type (" << CalibratorType << ") for strip" << CR.first << endl; + } continue; } } return MModule::Initialize(); - } //////////////////////////////////////////////////////////////////////////////// -bool MModuleEnergyCalibrationUniversal::AnalyzeEvent(MReadOutAssembly* Event) +bool MModuleEnergyCalibrationUniversal::AnalyzeEvent(MReadOutAssembly* Event) { // Main data analysis routine, which updates the event to a new level, i.e. takes the raw ADC value from the .roa file loaded through nuclearizer and converts it into energy units. - - for (unsigned int i = 0; i < Event->GetNStripHits(); ++i) { + + for (unsigned int i = 0; i < Event->GetNStripHits();) { + MStripHit* SH = Event->GetStripHit(i); MReadOutElementDoubleStrip R = *dynamic_cast(SH->GetReadOutElement()); - + TF1* Fit = m_Calibration[R]; TF1* FitRes = m_ResolutionCalibration[R]; double ADCMod, newADC; if (Fit == nullptr) { - if (g_Verbosity >= c_Error) cout<= c_Error) { + cout << m_XmlTag << ": Error: Energy-fit not found for read-out element " << R << endl; + } Event->SetEnergyCalibrationIncomplete_BadStrip(true); - } else { + ++i; // iterate to next SH + continue; + + } else { double Energy = 0; // declare energy variable + double Threshold = 0; // 0 means ignore Energy = Fit->Eval(SH->GetADCUnits()); - double Threshold = 0; // 0 means ignore + + if (Energy < 0) { + Energy = 0; // If the calibrated energy is less than 0, force it to be 0. + } if (m_SlowThresholdCutMode == MSlowThresholdCutModes::e_Fixed) { //check if user input threshold is enabled (one value applied to all strips) Threshold = m_SlowThresholdCutFixedValue; } else if (m_SlowThresholdCutMode == MSlowThresholdCutModes::e_File) { //check if threshold file is enabled (unique value applied to each strip) - double Threshold_map = m_ThresholdMap[R]; // if file enabled, declare value from map - - if (Threshold_map == 0) { - if (g_Verbosity >= c_Error) cout<= c_Error) { + cout << m_XmlTag << ": Error: Threshold not found for read-out element " << R << endl; + } + const double DefaultSlowThreshold = 15.0; + Threshold = DefaultSlowThreshold; // set default threshold if threshold not found } else { - Threshold = Threshold_map; // set threshold variable to value found in map + Threshold = ThresholdFromFile; // set threshold variable to value found in map } } - - //! Remove SH for any energy value below the established threshold + + //! Remove SH for any energy value below the established threshold (0 is default) if (Energy < Threshold) { + if (g_Verbosity >= c_Warning) { + cout << m_XmlTag << ": Strip Hit below threshold, deleting SH with Energy " << Energy << " keV " << endl; + } + Event->AddStripHitBelowThreshold(Energy); + Event->SetStripHitBelowThreshold_QualityFlag(true, to_string(Event->GetStripHitBelowThreshold_Number()) + " SH(s) with total energy " + to_string(Event->GetStripHitBelowThreshold_Energy())); Event->RemoveStripHit(i); - if (g_Verbosity >= c_Error) cout<SetStripHitBelowThreshold(true); - return false; - } - - SH->SetEnergy(Energy); - if (FitRes == nullptr) { - if (g_Verbosity >= c_Error) cout<SetEnergyResolutionCalibrationIncomplete(true); + delete SH; + continue; // continue to next SH without iterating i } else { - double EnergyResolution = FitRes->Eval(Energy); - SH->SetEnergyResolution(EnergyResolution); - } - if (R.IsLowVoltageStrip() == true) { // check voltage side - if (HasExpos() == true) { - m_ExpoEnergyCalibration->AddEnergy(Energy); + + // if the energy isn't filtered out with the threshold, then assign the energy to the SH + ++i; // iterate to next SH + SH->SetEnergy(Energy); + + if (FitRes == nullptr) { + if (g_Verbosity >= c_Error) { + cout << m_XmlTag << ": Error: Energy Resolution fit not found for read-out element " << R << endl; + } + Event->SetEnergyResolutionCalibrationIncomplete(true); + } else { + double EnergyResolution = FitRes->Eval(Energy); + SH->SetEnergyResolution(EnergyResolution); + } + if (R.IsLowVoltageStrip() == true) { // check voltage side to plot only LV hits to the Expo histogram + if (HasExpos() == true) { + m_ExpoEnergyCalibration->AddEnergy(Energy); + } + } + if (g_Verbosity >= c_Info) { + cout << m_XmlTag << ": Energy: " << SH->GetADCUnits() << " adc --> " << Energy << " keV" << endl; } } - if (g_Verbosity >= c_Info) cout<GetADCUnits()<<" adu --> "<SetAnalysisProgress(MAssembly::c_EnergyCalibration); - + return true; } @@ -419,7 +454,7 @@ double MModuleEnergyCalibrationUniversal::GetEnergy(MReadOutElementDoubleStrip R Energy = 0.0; } } else { - cout<GetX(Energy); } else { - cout<Create(); gClient->WaitForUnmap(Options); @@ -479,7 +518,7 @@ void MModuleEnergyCalibrationUniversal::ShowOptionsGUI() bool MModuleEnergyCalibrationUniversal::ReadXmlConfiguration(MXmlNode* Node) { //! Read the configuration data from an XML node - + MXmlNode* FileNameNode = Node->GetNode("FileName"); if (FileNameNode != nullptr) { m_FileName = FileNameNode->GetValue(); @@ -497,7 +536,7 @@ bool MModuleEnergyCalibrationUniversal::ReadXmlConfiguration(MXmlNode* Node) if (SlowThresholdCutFileNameNode != nullptr) { m_SlowThresholdCutFileName = SlowThresholdCutFileNameNode->GetValue(); } - + return true; } @@ -505,10 +544,10 @@ bool MModuleEnergyCalibrationUniversal::ReadXmlConfiguration(MXmlNode* Node) //////////////////////////////////////////////////////////////////////////////// -MXmlNode* MModuleEnergyCalibrationUniversal::CreateXmlConfiguration() +MXmlNode* MModuleEnergyCalibrationUniversal::CreateXmlConfiguration() { //! Create an XML node tree from the configuration - + MXmlNode* Node = new MXmlNode(0, m_XmlTag); new MXmlNode(Node, "FileName", m_FileName); new MXmlNode(Node, "SlowThresholdCutMode", static_cast(m_SlowThresholdCutMode)); @@ -527,12 +566,12 @@ double MModuleEnergyCalibrationUniversal::LookupEnergyResolution(MStripHit* SH, MReadOutElementDoubleStrip* ROE = dynamic_cast(SH->GetReadOutElement()); if (ROE == nullptr) { - cout<Eval(Energy); @@ -540,6 +579,5 @@ double MModuleEnergyCalibrationUniversal::LookupEnergyResolution(MStripHit* SH, } - // MModuleEnergyCalibrationUniversal.cxx: the end... //////////////////////////////////////////////////////////////////////////////// diff --git a/src/MReadOutAssembly.cxx b/src/MReadOutAssembly.cxx index 1d0558a..e59be23 100644 --- a/src/MReadOutAssembly.cxx +++ b/src/MReadOutAssembly.cxx @@ -128,6 +128,7 @@ void MReadOutAssembly::Clear() m_EventTimeUTC = 0; m_MJD = 0.0; m_ReducedChiSquare = -1; + m_StripHitBelowThreshold_Energy = 0; m_ShieldVeto = false; m_GuardRingVeto = false; @@ -699,6 +700,11 @@ void MReadOutAssembly::StreamBDFlags(ostream& S) if (m_EnergyResolutionCalibrationIncompleteString != "") S<<" ("<