CMS 3D CMS Logo

/data/refman/pasoursint/CMSSW_5_3_0/src/SimCalorimetry/EcalSelectiveReadoutProducers/src/EcalSelectiveReadoutProducer.cc

Go to the documentation of this file.
00001 #include "SimCalorimetry/EcalSelectiveReadoutProducers/interface/EcalSelectiveReadoutProducer.h"
00002 #include "FWCore/Framework/interface/Event.h"
00003 #include "FWCore/Framework/interface/EventSetup.h"
00004 #include "Geometry/Records/interface/CaloGeometryRecord.h"
00005 #include "Geometry/EcalMapping/interface/EcalMappingRcd.h"
00006 #include "FWCore/Framework/interface/ESHandle.h"
00007 #include "FWCore/MessageLogger/interface/MessageLogger.h"
00008 #include "CondFormats/DataRecord/interface/EcalSRSettingsRcd.h"
00009 //#include "DataFormats/EcalDigi/interface/EcalMGPASample.h"
00010 
00011 #include "SimCalorimetry/EcalSelectiveReadoutProducers/interface/EcalSRCondTools.h"
00012 
00013 #include <memory>
00014 #include <fstream>
00015 
00016 
00017 using namespace std;
00018 
00019 EcalSelectiveReadoutProducer::EcalSelectiveReadoutProducer(const edm::ParameterSet& params)
00020   : params_(params)
00021 {
00022   //settings:
00023   //  settings which are only in python config files:
00024   digiProducer_ = params.getParameter<string>("digiProducer");
00025   ebdigiCollection_ = params.getParameter<std::string>("EBdigiCollection");
00026   eedigiCollection_ = params.getParameter<std::string>("EEdigiCollection");
00027   ebSRPdigiCollection_ = params.getParameter<std::string>("EBSRPdigiCollection");
00028   eeSRPdigiCollection_ = params.getParameter<std::string>("EESRPdigiCollection");
00029   ebSrFlagCollection_ = params.getParameter<std::string>("EBSrFlagCollection");
00030   eeSrFlagCollection_ = params.getParameter<std::string>("EESrFlagCollection");
00031   trigPrimProducer_ = params.getParameter<string>("trigPrimProducer");
00032   trigPrimCollection_ = params.getParameter<string>("trigPrimCollection");
00033   trigPrimBypass_ = params.getParameter<bool>("trigPrimBypass");
00034   trigPrimBypassMode_ = params.getParameter<int>("trigPrimBypassMode");
00035   dumpFlags_ = params.getUntrackedParameter<int>("dumpFlags", 0);
00036   writeSrFlags_ = params.getUntrackedParameter<bool>("writeSrFlags", false);
00037   produceDigis_ = params.getUntrackedParameter<bool>("produceDigis", true);
00038   //   settings which can come from either condition database or python configuration file:
00039   useCondDb_ = false;
00040   try{
00041     if(params.getParameter<bool>("configFromCondDB")){
00042       useCondDb_ = true;
00043     }
00044   } catch(cms::Exception){
00045     /* pameter not found */
00046     edm::LogWarning("EcalSelectiveReadout") << "Parameter configFromCondDB of EcalSelectiveReadout module not found. "
00047       "Selective readout configuration will be read from python file.";
00048   }
00049   if(!useCondDb_){
00050     settingsFromFile_ = auto_ptr<EcalSRSettings>(new EcalSRSettings());
00051     EcalSRCondTools::importParameterSet(*settingsFromFile_, params);
00052     settings_ = settingsFromFile_.get();
00053   }
00054 
00055   //declares the products made by this producer:
00056   if(produceDigis_){
00057     produces<EBDigiCollection>(ebSRPdigiCollection_);
00058     produces<EEDigiCollection>(eeSRPdigiCollection_);
00059   }
00060 
00061   if (writeSrFlags_) {
00062     produces<EBSrFlagCollection>(ebSrFlagCollection_);
00063     produces<EESrFlagCollection>(eeSrFlagCollection_);
00064   }
00065 
00066   theGeometry = 0;
00067   theTriggerTowerMap = 0;
00068   theElecMap = 0;
00069 }
00070 
00071 
00072 
00073 EcalSelectiveReadoutProducer::~EcalSelectiveReadoutProducer()
00074 { }
00075 
00076 
00077 void
00078 EcalSelectiveReadoutProducer::produce(edm::Event& event, const edm::EventSetup& eventSetup)
00079 {
00080   if(useCondDb_){
00081     //getting selective readout configuration:
00082     edm::ESHandle<EcalSRSettings> hSr;
00083     eventSetup.get<EcalSRSettingsRcd>().get(hSr);
00084     settings_ = hSr.product();
00085   }
00086   
00087   //gets the trigger primitives:
00088   EcalTrigPrimDigiCollection emptyTPColl;
00089   const EcalTrigPrimDigiCollection* trigPrims =
00090     (trigPrimBypass_ && trigPrimBypassMode_==0)?&emptyTPColl:getTrigPrims(event);
00091 
00092 
00093   //gets the digis from the events:
00094   EBDigiCollection dummyEbDigiColl;
00095   EEDigiCollection dummyEeDigiColl;
00096 
00097   const EBDigiCollection* ebDigis = produceDigis_?getEBDigis(event)
00098     :&dummyEbDigiColl;
00099   const EEDigiCollection* eeDigis = produceDigis_?getEEDigis(event)
00100     :&dummyEeDigiColl;
00101 
00102   //runs the selective readout algorithm:
00103   auto_ptr<EBDigiCollection> selectedEBDigis;
00104   auto_ptr<EEDigiCollection> selectedEEDigis;
00105   auto_ptr<EBSrFlagCollection> ebSrFlags;
00106   auto_ptr<EESrFlagCollection> eeSrFlags;
00107 
00108   if(produceDigis_){
00109     selectedEBDigis = auto_ptr<EBDigiCollection>(new EBDigiCollection);
00110     selectedEEDigis = auto_ptr<EEDigiCollection>(new EEDigiCollection);
00111   }
00112 
00113   if(writeSrFlags_){
00114     ebSrFlags = auto_ptr<EBSrFlagCollection>(new EBSrFlagCollection);
00115     eeSrFlags = auto_ptr<EESrFlagCollection>(new EESrFlagCollection);
00116   }
00117 
00118   if(suppressor_.get() == 0){
00119     //Check the validity of EcalSRSettings
00120     checkValidity(*settings_);
00121     
00122     //instantiates the selective readout algorithm:
00123     suppressor_ = auto_ptr<EcalSelectiveReadoutSuppressor>(new EcalSelectiveReadoutSuppressor(params_, settings_));
00124 
00125     // check that everything is up-to-date
00126     checkGeometry(eventSetup);
00127     checkTriggerMap(eventSetup);
00128     checkElecMap(eventSetup);
00129   }
00130   
00131   suppressor_->run(eventSetup, *trigPrims, *ebDigis, *eeDigis,
00132                    selectedEBDigis.get(), selectedEEDigis.get(),
00133                    ebSrFlags.get(), eeSrFlags.get());
00134 
00135   static int iEvent = 1;
00136   if(dumpFlags_>=iEvent){
00137     ofstream ttfFile("TTF.txt", (iEvent==1?ios::trunc:ios::app));
00138     suppressor_->printTTFlags(ttfFile, iEvent,
00139                               iEvent==1?true:false);
00140 
00141     ofstream srfFile("SRF.txt", (iEvent==1?ios::trunc:ios::app));
00142     if(iEvent==1){
00143       suppressor_->getEcalSelectiveReadout()->printHeader(srfFile);
00144     }
00145     srfFile << "# Event " << iEvent << "\n";
00146     suppressor_->getEcalSelectiveReadout()->print(srfFile);
00147     srfFile << "\n";
00148 
00149     ofstream afFile("AF.txt", (iEvent==1?ios::trunc:ios::app));
00150     printSrFlags(afFile, *ebSrFlags, *eeSrFlags, iEvent,
00151                  iEvent==1?true:false);
00152   }
00153 
00154   ++iEvent; //event counter
00155 
00156   if(produceDigis_){
00157     //puts the selected digis into the event:
00158     event.put(selectedEBDigis, ebSRPdigiCollection_);
00159     event.put(selectedEEDigis, eeSRPdigiCollection_);
00160   }
00161 
00162   //puts the SR flags into the event:
00163   if(writeSrFlags_) {
00164     event.put(ebSrFlags, ebSrFlagCollection_);
00165     event.put(eeSrFlags, eeSrFlagCollection_);
00166   }
00167 }
00168 
00169 const EBDigiCollection*
00170 EcalSelectiveReadoutProducer::getEBDigis(edm::Event& event) const
00171 {
00172   edm::Handle<EBDigiCollection> hEBDigis;
00173   event.getByLabel(digiProducer_, ebdigiCollection_, hEBDigis);
00174   //product() method is called before id() in order to get an exception
00175   //if the handle is not available (check not done by id() method).
00176   const EBDigiCollection* result = hEBDigis.product();
00177   static bool firstCall= true;
00178   if(firstCall){
00179     checkWeights(event, hEBDigis.id());
00180     firstCall = false;
00181   }
00182   return result;
00183 }
00184 
00185 const EEDigiCollection*
00186 EcalSelectiveReadoutProducer::getEEDigis(edm::Event& event) const
00187 {
00188   edm::Handle<EEDigiCollection> hEEDigis;
00189   event.getByLabel(digiProducer_, eedigiCollection_, hEEDigis);
00190   //product() method is called before id() in order to get an exception
00191   //if the handle is not available (check not done by id() method).
00192   const EEDigiCollection* result = hEEDigis.product();
00193   static bool firstCall = true;
00194   if(firstCall){
00195     checkWeights(event, hEEDigis.id());
00196     firstCall = false;
00197   }
00198   return result;
00199 }
00200 
00201 const EcalTrigPrimDigiCollection*
00202 EcalSelectiveReadoutProducer::getTrigPrims(edm::Event& event) const
00203 {
00204   edm::Handle<EcalTrigPrimDigiCollection> hTPDigis;
00205   event.getByLabel(trigPrimProducer_, trigPrimCollection_, hTPDigis);
00206   return hTPDigis.product();
00207 }
00208 
00209 
00210 void EcalSelectiveReadoutProducer::checkGeometry(const edm::EventSetup & eventSetup)
00211 {
00212   edm::ESHandle<CaloGeometry> hGeometry;
00213   eventSetup.get<CaloGeometryRecord>().get(hGeometry);
00214 
00215   const CaloGeometry * pGeometry = &*hGeometry;
00216 
00217   // see if we need to update
00218   if(pGeometry != theGeometry) {
00219     theGeometry = pGeometry;
00220     suppressor_->setGeometry(theGeometry);
00221   }
00222 }
00223 
00224 
00225 void EcalSelectiveReadoutProducer::checkTriggerMap(const edm::EventSetup & eventSetup)
00226 {
00227 
00228    edm::ESHandle<EcalTrigTowerConstituentsMap> eTTmap;
00229    eventSetup.get<IdealGeometryRecord>().get(eTTmap);
00230 
00231    const EcalTrigTowerConstituentsMap * pMap = &*eTTmap;
00232 
00233   // see if we need to update
00234   if(pMap!= theTriggerTowerMap) {
00235     theTriggerTowerMap = pMap;
00236     suppressor_->setTriggerMap(theTriggerTowerMap);
00237   }
00238 }
00239 
00240 
00241 void EcalSelectiveReadoutProducer::checkElecMap(const edm::EventSetup & eventSetup)
00242 {
00243 
00244    edm::ESHandle<EcalElectronicsMapping> eElecmap;
00245    eventSetup.get<EcalMappingRcd>().get(eElecmap);
00246 
00247    const EcalElectronicsMapping * pMap = &*eElecmap;
00248 
00249   // see if we need to update
00250   if(pMap!= theElecMap) {
00251     theElecMap = pMap;
00252     suppressor_->setElecMap(theElecMap);
00253   }
00254 }
00255 
00256 
00257 void EcalSelectiveReadoutProducer::printTTFlags(const EcalTrigPrimDigiCollection& tp, ostream& os) const{
00258   const char tccFlagMarker[] = { 'x', '.', 'S', '?', 'C', 'E', 'E', 'E', 'E'};
00259   const int nEta = EcalSelectiveReadout::nTriggerTowersInEta;
00260   const int nPhi = EcalSelectiveReadout::nTriggerTowersInPhi;
00261 
00262   //static bool firstCall=true;
00263   //  if(firstCall){
00264   //  firstCall=false;
00265   os << "# TCC flag map\n#\n"
00266     "# +-->Phi            " << tccFlagMarker[0+1] << ": 000 (low interest)\n"
00267     "# |                  " << tccFlagMarker[1+1] << ": 001 (mid interest)\n"
00268     "# |                  " << tccFlagMarker[2+1] << ": 010 (not valid)\n"
00269     "# V Eta              " << tccFlagMarker[3+1] << ": 011 (high interest)\n"
00270     "#                    " << tccFlagMarker[4+1] << ": 1xx forced readout (Hw error)\n"
00271     "#\n";
00272   //}
00273 
00274   vector<vector<int> > ttf(nEta, vector<int>(nPhi, -1));
00275   for(EcalTrigPrimDigiCollection::const_iterator it = tp.begin();
00276       it != tp.end(); ++it){
00277     const EcalTriggerPrimitiveDigi& trigPrim = *it;
00278     if(trigPrim.size()>0){
00279       int iEta = trigPrim.id().ieta();
00280       int iEta0 = iEta<0?iEta+nEta/2:iEta+nEta/2-1;
00281       int iPhi0 = trigPrim.id().iphi() - 1;
00282       ttf[iEta0][iPhi0] = trigPrim.ttFlag();
00283     }
00284   }
00285   for(int iEta=0; iEta<nEta; ++iEta){
00286     for(int iPhi=0; iPhi<nPhi; ++iPhi){
00287       os << tccFlagMarker[ttf[iEta][iPhi]+1];
00288     }
00289     os << "\n";
00290   }
00291 }
00292 
00293 void EcalSelectiveReadoutProducer::checkWeights(const edm::Event& evt,
00294                                                 const edm::ProductID& noZsDigiId) const{
00295   const vector<float> & weights = settings_->dccNormalizedWeights_[0]; //params_.getParameter<vector<double> >("dccNormalizedWeights");
00296   int nFIRTaps = EcalSelectiveReadoutSuppressor::getFIRTapCount();
00297   static bool warnWeightCnt = true;
00298   if((int)weights.size() > nFIRTaps && warnWeightCnt){
00299       edm::LogWarning("Configuration") << "The list of DCC zero suppression FIR "
00300         "weights given in parameter dccNormalizedWeights is longer "
00301         "than the expected depth of the FIR filter :(" << nFIRTaps << "). "
00302         "The last weights will be discarded.";
00303       warnWeightCnt = false; //it's not needed to repeat the warning.
00304   }
00305 
00306   if(weights.size()>0){
00307     int iMaxWeight = 0;
00308     double maxWeight = weights[iMaxWeight];
00309     //looks for index of maximum weight
00310     for(unsigned i=0; i<weights.size(); ++i){
00311       if(weights[i]>maxWeight){
00312         iMaxWeight = i;
00313         maxWeight = weights[iMaxWeight];
00314       }
00315     }
00316 
00317     //position of time sample whose maximum weight is applied:
00318     int maxWeightBin = settings_->ecalDccZs1stSample_[0] //params_.getParameter<int>("ecalDccZs1stSample")
00319       + iMaxWeight;
00320     
00321     //gets the bin of maximum (in case of raw data it will not exist)
00322     int binOfMax = 0;
00323     bool rc = getBinOfMax(evt, noZsDigiId, binOfMax);
00324 
00325     if(rc && maxWeightBin!=binOfMax){
00326       edm::LogWarning("Configuration")
00327         << "The maximum weight of DCC zero suppression FIR filter is not "
00328         "applied to the expected maximum sample(" << binOfMax
00329         << (binOfMax==1?"st":(binOfMax==2?"nd":(binOfMax==3?"rd":"th")))
00330         << " time sample). This may indicate faulty 'dccNormalizedWeights' "
00331         "or 'ecalDccZs1sSample' parameters.";
00332     }
00333   }
00334 }
00335 
00336 bool
00337 EcalSelectiveReadoutProducer::getBinOfMax(const edm::Event& evt,
00338                                           const edm::ProductID& noZsDigiId,
00339                                           int& binOfMax) const{
00340   bool rc;
00341   const edm::Provenance p=evt.getProvenance(noZsDigiId);
00342   edm::ParameterSet result = getParameterSet(p.psetID());
00343   vector<string> ebDigiParamList = result.getParameterNames();
00344   string bofm("binOfMaximum");
00345   if(find(ebDigiParamList.begin(), ebDigiParamList.end(), bofm)
00346      != ebDigiParamList.end()){//bofm found
00347     binOfMax=result.getParameter<int>("binOfMaximum");
00348     rc = true;
00349   } else{
00350     rc = false;
00351   }
00352   return rc;
00353 }
00354 
00355 void
00356 EcalSelectiveReadoutProducer::printSrFlags(ostream& os,
00357                                            const EBSrFlagCollection& ebSrFlags,
00358                                            const EESrFlagCollection& eeSrFlags,
00359                                            int iEvent,
00360                                            bool withHeader){
00361   const char srpFlagMarker[] = {'.', 'z', 'Z', 'F', '4','5','6','7'};
00362   if(withHeader){
00363     time_t t;
00364     time(&t);
00365     const char* date = ctime(&t);
00366     os << "#SRP flag map\n#\n"
00367       "# Generatied on: " << date << "\n#\n"
00368       "# +-->Phi/Y " << srpFlagMarker[0] << ": suppressed\n"
00369       "# |         " << srpFlagMarker[1] << ": ZS 1\n"
00370       "# |         " << srpFlagMarker[2] << ": ZS 2\n"
00371       "# V Eta/X   " << srpFlagMarker[3] << ": full readout\n"
00372       "#\n";
00373   }
00374 
00375   //EE-,EB,EE+ map wil be written onto file in following format:
00376   //
00377   //      72
00378   // <-------------->
00379   //  20
00380   // <--->
00381   //  EEE                A             +-----> Y
00382   // EEEEE               |             |
00383   // EE EE               | 20   EE-    |
00384   // EEEEE               |             |
00385   //  EEE                V             V X
00386   // BBBBBBBBBBBBBBBBB   A
00387   // BBBBBBBBBBBBBBBBB   |             +-----> Phi
00388   // BBBBBBBBBBBBBBBBB   |             |
00389   // BBBBBBBBBBBBBBBBB   | 34  EB      |
00390   // BBBBBBBBBBBBBBBBB   |             |
00391   // BBBBBBBBBBBBBBBBB   |             V Eta
00392   // BBBBBBBBBBBBBBBBB   |
00393   // BBBBBBBBBBBBBBBBB   |
00394   // BBBBBBBBBBBBBBBBB   V
00395   //  EEE                A             +-----> Y
00396   // EEEEE               |             |
00397   // EE EE               | 20 EE+      |
00398   // EEEEE               |             |
00399   //  EEE                V             V X
00400   //
00401   //
00402   //
00403   //
00404   //event header:
00405   if(iEvent>=0){
00406     os << "# Event " << iEvent << "\n";
00407   }
00408 
00409   //retrieve flags:
00410   const int nEndcaps = 2;
00411   const int nScX = 20;
00412   const int nScY = 20;
00413   int eeSrf[nEndcaps][nScX][nScY];
00414   for(size_t i=0; i < sizeof(eeSrf)/sizeof(int); ((int*)eeSrf)[i++] = -1){};
00415   for(EESrFlagCollection::const_iterator it = eeSrFlags.begin();
00416       it != eeSrFlags.end(); ++it){
00417     const EESrFlag& flag = *it;
00418     int iZ0 = flag.id().zside()>0?1:0;
00419     int iX0 = flag.id().ix()-1;
00420     int iY0 = flag.id().iy()-1;
00421     assert(iZ0>=0 && iZ0<nEndcaps);
00422     assert(iX0>=0 && iX0<nScX);
00423     assert(iY0>=0 && iY0<nScY);
00424     eeSrf[iZ0][iX0][iY0] = flag.value();
00425   }
00426   const int nEbTtEta = 34;
00427   const int nEeTtEta = 11;
00428   const int nTtEta = nEeTtEta*2+nEbTtEta;
00429   const int nTtPhi = 72;
00430   int ebSrf[nEbTtEta][nTtPhi];
00431   for(size_t i=0; i<sizeof(ebSrf)/sizeof(int); ((int*)ebSrf)[i++] = -1){};
00432   for(EBSrFlagCollection::const_iterator it = ebSrFlags.begin();
00433       it != ebSrFlags.end(); ++it){
00434 
00435     const EBSrFlag& flag = *it;
00436     int iEta = flag.id().ieta();
00437     int iEta0 = iEta + nTtEta/2 - (iEta>=0?1:0); //0->55 from eta=-3 to eta=3
00438     int iEbEta0 = iEta0 - nEeTtEta;//0->33 from eta=-1.48 to eta=1.48
00439     int iPhi0 = flag.id().iphi() - 1;
00440     assert(iEbEta0>=0 && iEbEta0<nEbTtEta);
00441     assert(iPhi0>=0 && iPhi0<nTtPhi);
00442 
00443 //     cout << __FILE__ << ":" << __LINE__ << ": "
00444 //       <<  iEta << "\t" << flag.id().iphi() << " -> "
00445 //       << iEbEta0 << "\t" << iPhi0
00446 //       << "... Flag: " << flag.value() << "\n";
00447 
00448 
00449     ebSrf[iEbEta0][iPhi0] = flag.value();
00450   }
00451 
00452 
00453   //print flags:
00454 
00455   //EE-
00456   for(int iX0=0; iX0<nScX; ++iX0){
00457     for(int iY0=0; iY0<nScY; ++iY0){
00458       int srFlag = eeSrf[0][iX0][iY0];
00459       assert(srFlag>=-1
00460              && srFlag<(int)(sizeof(srpFlagMarker)/sizeof(srpFlagMarker[0])));
00461       os << (srFlag==-1?' ':srpFlagMarker[srFlag]);
00462     }
00463     os << "\n"; //one Y supercystal column per line
00464   } //next supercrystal X-index
00465 
00466   //EB
00467   for(int iEta0 = 0;
00468       iEta0 < nEbTtEta;
00469       ++iEta0){
00470     for(int iPhi0 = 0; iPhi0 < nTtPhi; ++iPhi0){
00471       int srFlag = ebSrf[iEta0][iPhi0];
00472       assert(srFlag>=-1
00473              && srFlag<(int)(sizeof(srpFlagMarker)/sizeof(srpFlagMarker[0])));
00474       os << (srFlag==-1?'?':srpFlagMarker[srFlag]);
00475     }
00476     os << "\n"; //one phi per line
00477   }
00478 
00479   //EE+
00480   for(int iX0=0; iX0<nScX; ++iX0){
00481     for(int iY0=0; iY0<nScY; ++iY0){
00482       int srFlag = eeSrf[1][iX0][iY0];
00483       assert(srFlag>=-1
00484              && srFlag<(int)(sizeof(srpFlagMarker)/sizeof(srpFlagMarker[0])));
00485       os << (srFlag==-1?' ':srpFlagMarker[srFlag]);
00486     }
00487     os << "\n"; //one Y supercystal column per line
00488   } //next supercrystal X-index
00489 
00490   //event trailer:
00491   os << "\n";
00492 }
00493 
00494 void EcalSelectiveReadoutProducer::checkValidity(const EcalSRSettings& settings){
00495   if(settings.dccNormalizedWeights_.size() != 1){
00496     throw cms::Exception("Configuration") << "Selective readout emulator, EcalSelectiveReadout, supports only single set of ZS weights. "
00497       "while the configuration contains " << settings.dccNormalizedWeights_.size() << " set(s)\n";
00498   }
00499   
00500 //   if(settings.dccNormalizedWeights_.size() != 1
00501 //      && settings.dccNormalizedWeights_.size() != 2
00502 //      && settings.dccNormalizedWeights_.size() != 54
00503 //      && settings.dccNormalizedWeights_.size() != 75848){
00504 //     throw cms::Exception("Configuration") << "Invalid number of DCC weight set (" << settings.dccNormalizedWeights_.size()
00505 //                                        << ") in condition object EcalSRSetting::dccNormalizedWeights_. "
00506 //                                        << "Valid counts are: 1 (single set), 2 (EB and EE), 54 (one per DCC) and 75848 "
00507 //       "(one per crystal)\n";
00508 //   }
00509   
00510   if(settings.dccNormalizedWeights_.size() != settings.ecalDccZs1stSample_.size()){
00511     throw cms::Exception("Configuration") << "Inconsistency between number of weigth sets ("
00512                                           << settings.dccNormalizedWeights_.size() << ") and "
00513                                           << "number of ecalDccZs1Sample values ("
00514                                           << settings.ecalDccZs1stSample_.size() << ").";
00515   }  
00516 }