LArSoft  v09_90_00
Liquid Argon Software toolkit - https://larsoft.org/
CosmicRayTaggingMonitoringTool.cc
Go to the documentation of this file.
1 
9 #include "Pandora/AlgorithmHeaders.h"
10 #include "Pandora/PdgTable.h"
11 
13 
16 
18 
19 using namespace pandora;
20 
21 namespace lar_content
22 {
23 
24 CosmicRayTaggingMonitoringTool::CosmicRayTaggingMonitoringTool() :
25  m_minHitsToConsiderTagging(15),
26  m_minPurity(0.95),
27  m_minImpurity(0.95),
28  m_minSignificance(0.1)
29 {
30 }
31 
32 //------------------------------------------------------------------------------------------------------------------------------------------
33 
34 void CosmicRayTaggingMonitoringTool::FindAmbiguousPfos(const PfoList &parentCosmicRayPfos, PfoList &ambiguousPfos, const MasterAlgorithm *const pAlgorithm)
35 {
36  if (this->GetPandora().GetSettings()->ShouldDisplayAlgorithmInfo())
37  std::cout << "----> Running Algorithm Tool: " << this->GetInstanceName() << ", " << this->GetType() << std::endl;
38 
39  const MCParticleList *pMCParticleList = nullptr;
40  PANDORA_THROW_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::GetCurrentList(*pAlgorithm, pMCParticleList));
41 
42  const CaloHitList *pCaloHitList = nullptr;
43  PANDORA_THROW_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::GetList(*pAlgorithm, m_caloHitList2D, pCaloHitList));
44 
45  // Identify reconstructable MCParticles, and get mappings to their good hits
46  LArMCParticleHelper::MCContributionMap nuMCParticlesToGoodHitsMap;
47  LArMCParticleHelper::MCContributionMap beamMCParticlesToGoodHitsMap;
48  LArMCParticleHelper::MCContributionMap crMCParticlesToGoodHitsMap;
49 
51  pMCParticleList, pCaloHitList, m_parameters, LArMCParticleHelper::IsBeamNeutrinoFinalState, nuMCParticlesToGoodHitsMap);
53  pMCParticleList, pCaloHitList, m_parameters, LArMCParticleHelper::IsBeamParticle, beamMCParticlesToGoodHitsMap);
55  pMCParticleList, pCaloHitList, m_parameters, LArMCParticleHelper::IsCosmicRay, crMCParticlesToGoodHitsMap);
56 
57  // Get the hit sharing maps between Pfos and reconstructable MCParticles
58  LArMCParticleHelper::MCContributionMapVector mcParticlesToGoodHitsMaps(
59  {nuMCParticlesToGoodHitsMap, beamMCParticlesToGoodHitsMap, crMCParticlesToGoodHitsMap});
60 
61  LArMCParticleHelper::PfoContributionMap pfoToReconstructable2DHitsMap;
63  parentCosmicRayPfos, mcParticlesToGoodHitsMaps, pfoToReconstructable2DHitsMap, m_parameters.m_foldBackHierarchy);
64 
65  LArMCParticleHelper::PfoToMCParticleHitSharingMap pfoToMCParticleHitSharingMap;
66  LArMCParticleHelper::MCParticleToPfoHitSharingMap mcParticleToPfoHitSharingMap;
68  pfoToReconstructable2DHitsMap, mcParticlesToGoodHitsMaps, pfoToMCParticleHitSharingMap, mcParticleToPfoHitSharingMap);
69 
70  // Calculate the purity and significane and classification of each Pfo
71  PfoToFloatMap pfoSignificanceMap;
72  PfoToFloatMap pfoPurityMap;
73  PfoClassificationMap pfoClassificationMap;
74  LArMCParticleHelper::MCContributionMapVector targetsToGoodHitsMaps({nuMCParticlesToGoodHitsMap, beamMCParticlesToGoodHitsMap});
75  this->CalculatePfoMetrics(pfoToMCParticleHitSharingMap, pfoToReconstructable2DHitsMap, targetsToGoodHitsMaps, pfoSignificanceMap,
76  pfoPurityMap, pfoClassificationMap);
77 
78  // -------------------------------------------------------------------------------------------------------------------------------------
79 
80  // Print the monte-carlo information for this event
81  MCParticleVector orderedMCParticleVector;
82  LArMonitoringHelper::GetOrderedMCParticleVector(mcParticlesToGoodHitsMaps, orderedMCParticleVector);
83 
84  LArFormattingHelper::PrintHeader("MC : Reconstructable neutrino final state particles");
85  LArMonitoringHelper::PrintMCParticleTable(nuMCParticlesToGoodHitsMap, orderedMCParticleVector);
86 
87  LArFormattingHelper::PrintHeader("MC : Reconstructable primary beam particles");
88  LArMonitoringHelper::PrintMCParticleTable(beamMCParticlesToGoodHitsMap, orderedMCParticleVector);
89 
90  LArFormattingHelper::PrintHeader("MC : Reconstructable primary cosmic-rays");
91  LArMonitoringHelper::PrintMCParticleTable(crMCParticlesToGoodHitsMap, orderedMCParticleVector);
92 
93  LArFormattingHelper::PrintHeader("Reco : Primary cosmic-ray candidates");
94  std::cout << "Columns with headers [n] are the number of shared hits between the Pfo and the target MCParticle with ID n." << std::endl;
95 
96  PfoVector orderedPfoVector;
97  LArMonitoringHelper::GetOrderedPfoVector(pfoToReconstructable2DHitsMap, orderedPfoVector);
98  this->PrintPfoTable(orderedPfoVector, pfoToReconstructable2DHitsMap, pfoPurityMap, pfoSignificanceMap, pfoClassificationMap, ambiguousPfos);
99 }
100 
101 //------------------------------------------------------------------------------------------------------------------------------------------
102 
104  const LArMCParticleHelper::PfoContributionMap &pfoToCaloHitListMap, const LArMCParticleHelper::MCContributionMapVector &targetsToGoodHitsMaps,
105  PfoToFloatMap &pfoSignificanceMap, PfoToFloatMap &pfoPurityMap, PfoClassificationMap &pfoClassificationMap) const
106 {
107  PfoVector sortedPfos;
108  for (const auto &mapEntry : hitSharingMap)
109  sortedPfos.push_back(mapEntry.first);
110  std::sort(sortedPfos.begin(), sortedPfos.end(), LArPfoHelper::SortByNHits);
111 
112  for (const ParticleFlowObject *const pPfo : sortedPfos)
113  {
114  if (pfoToCaloHitListMap.find(pPfo) == pfoToCaloHitListMap.end())
115  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
116 
117  const unsigned int n2DHits(pfoToCaloHitListMap.at(pPfo).size());
118  float significance(0);
119  float purity(0);
120 
121  if (n2DHits != 0)
122  {
123  // Sum over all target/shared hits pairs
124  for (const LArMCParticleHelper::MCParticleCaloHitListPair &targetHitsShared : hitSharingMap.at(pPfo))
125  {
126  bool foundTarget(false);
127  unsigned int nMCHits(std::numeric_limits<unsigned int>::max());
128 
129  // ATTN This map is unordered, but this does not impact search for specific target hit
130  for (const LArMCParticleHelper::MCContributionMap &mcContributionMap : targetsToGoodHitsMaps)
131  {
132  if (mcContributionMap.find(targetHitsShared.first) != mcContributionMap.end())
133  {
134  foundTarget = true;
135  nMCHits = mcContributionMap.at(targetHitsShared.first).size();
136  break;
137  }
138  }
139 
140  if (!foundTarget)
141  continue;
142 
143  significance += static_cast<float>(targetHitsShared.second.size()) / static_cast<float>(nMCHits);
144  purity += static_cast<float>(targetHitsShared.second.size()) / static_cast<float>(n2DHits);
145  }
146  }
147 
148  if (!pfoSignificanceMap.insert(PfoToFloatMap::value_type(pPfo, significance)).second)
149  throw StatusCodeException(STATUS_CODE_ALREADY_PRESENT);
150 
151  if (!pfoPurityMap.insert(PfoToFloatMap::value_type(pPfo, purity)).second)
152  throw StatusCodeException(STATUS_CODE_ALREADY_PRESENT);
153 
154  Classification classification(this->ClassifyPfo(n2DHits, significance, purity, this->IsMainMCParticleMuon(pPfo)));
155  if (!pfoClassificationMap.insert(PfoClassificationMap::value_type(pPfo, classification)).second)
156  throw StatusCodeException(STATUS_CODE_ALREADY_PRESENT);
157  }
158 }
159 
160 //------------------------------------------------------------------------------------------------------------------------------------------
161 
162 bool CosmicRayTaggingMonitoringTool::IsMainMCParticleMuon(const ParticleFlowObject *const /*pPfo*/) const
163 {
164  bool isMuon(false);
165  try
166  {
167  isMuon = false; // TODO Local treatment is being developed, specific to this tool (std::abs(LArMCParticleHelper::GetMainMCParticle(pPfo)->GetParticleId()) == MU_MINUS);
168  }
169  catch (const StatusCodeException &)
170  {
171  }
172 
173  return isMuon;
174 }
175 
176 //------------------------------------------------------------------------------------------------------------------------------------------
177 
179  const unsigned int &nHits, const float &significance, const float &purity, const bool isMuon) const
180 {
181  if (nHits < m_minHitsToConsiderTagging)
182  return SPARSE;
183 
184  bool isPure(purity > m_minPurity);
185  bool isImpure((1 - purity) > m_minImpurity);
186  bool isSignificant(significance > m_minSignificance);
187 
188  if (!isPure && !isImpure)
189  return MIXED;
190 
191  if (isPure && isSignificant)
192  return TARGET;
193 
194  if (isPure && !isSignificant)
195  return FRAGMENTED;
196 
197  if (!isPure && isSignificant)
198  return ABSORBED;
199 
200  if (!isPure && !isSignificant && isMuon)
201  return CR_MUON;
202 
203  // !isPure && !isSignificant && !isMuon
204  return CR_OTHER;
205 }
206 
207 //------------------------------------------------------------------------------------------------------------------------------------------
208 
209 void CosmicRayTaggingMonitoringTool::PrintPfoTable(const PfoVector &orderedPfoVector,
210  const LArMCParticleHelper::PfoContributionMap &pfoToReconstructable2DHitsMap, const PfoToFloatMap &pfoPurityMap,
211  const PfoToFloatMap &pfoSignificanceMap, const PfoClassificationMap &pfoClassificationMap, const PfoList &ambiguousPfos) const
212 {
213  if (orderedPfoVector.empty())
214  {
215  std::cout << "No Pfos supplied." << std::endl;
216  return;
217  }
218 
220  {"ID", "PID", "", "nHits", "U", "V", "W", "", "nGoodHits", "U", "V", "W", "", "Purity", "Significance", "Classification", "", "Tagged?"});
221 
222  for (unsigned int id = 0; id < orderedPfoVector.size(); ++id)
223  {
224  const ParticleFlowObject *const pPfo(orderedPfoVector.at(id));
225 
226  LArMCParticleHelper::PfoContributionMap::const_iterator it = pfoToReconstructable2DHitsMap.find(pPfo);
227  if (pfoToReconstructable2DHitsMap.end() == it)
228  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
229 
230  if (pfoPurityMap.end() == pfoPurityMap.find(pPfo))
231  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
232 
233  if (pfoSignificanceMap.end() == pfoSignificanceMap.find(pPfo))
234  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
235 
236  if (pfoClassificationMap.end() == pfoClassificationMap.find(pPfo))
237  throw StatusCodeException(STATUS_CODE_NOT_FOUND);
238 
239  table.AddElement(id);
240  table.AddElement(pPfo->GetParticleId());
241 
242  CaloHitList all2DCaloHits;
243  LArPfoHelper::GetCaloHits(pPfo, TPC_VIEW_U, all2DCaloHits);
244  LArPfoHelper::GetCaloHits(pPfo, TPC_VIEW_V, all2DCaloHits);
245  LArPfoHelper::GetCaloHits(pPfo, TPC_VIEW_W, all2DCaloHits);
246 
247  table.AddElement(all2DCaloHits.size());
248  table.AddElement(LArMonitoringHelper::CountHitsByType(TPC_VIEW_U, all2DCaloHits));
249  table.AddElement(LArMonitoringHelper::CountHitsByType(TPC_VIEW_V, all2DCaloHits));
250  table.AddElement(LArMonitoringHelper::CountHitsByType(TPC_VIEW_W, all2DCaloHits));
251 
252  table.AddElement(it->second.size());
253  table.AddElement(LArMonitoringHelper::CountHitsByType(TPC_VIEW_U, it->second));
254  table.AddElement(LArMonitoringHelper::CountHitsByType(TPC_VIEW_V, it->second));
255  table.AddElement(LArMonitoringHelper::CountHitsByType(TPC_VIEW_W, it->second));
256 
257  table.AddElement(pfoPurityMap.at(pPfo));
258  table.AddElement(pfoSignificanceMap.at(pPfo));
259 
260  const Classification classification(pfoClassificationMap.at(pPfo));
261  table.AddElement(this->GetClassificationName(classification), LArFormattingHelper::INVERTED, this->GetClassificationColor(classification));
262 
263  const bool isTagged(std::find(ambiguousPfos.begin(), ambiguousPfos.end(), pPfo) == ambiguousPfos.end());
264  const bool isGoodTag(isTagged && (classification == CR_MUON || classification == CR_OTHER));
265  const bool isBadTag(isTagged && (classification == TARGET));
266  const LArFormattingHelper::Color tagColor(
268  table.AddElement(isTagged ? "yes" : "no", LArFormattingHelper::INVERTED, tagColor);
269  }
270 
271  table.Print();
272 }
273 
274 //------------------------------------------------------------------------------------------------------------------------------------------
275 
277 {
278  switch (classification)
279  {
280  case TARGET:
282  case CR_MUON:
284  case CR_OTHER:
286  case FRAGMENTED:
288  case ABSORBED:
290  case MIXED:
292  default:
294  }
295 }
296 
297 //------------------------------------------------------------------------------------------------------------------------------------------
298 
300 {
301  switch (classification)
302  {
303  case TARGET:
304  return "TARGET";
305  case CR_MUON:
306  return "CR_MUON";
307  case CR_OTHER:
308  return "CR_OTHER";
309  case FRAGMENTED:
310  return "FRAGMENTED";
311  case ABSORBED:
312  return "ABSORBED";
313  case MIXED:
314  return "MIXED";
315  case SPARSE:
316  return "SPARSE";
317  default:
318  return "UNCLASSIFIED";
319  }
320 }
321 
322 //------------------------------------------------------------------------------------------------------------------------------------------
323 
324 StatusCode CosmicRayTaggingMonitoringTool::ReadSettings(const TiXmlHandle xmlHandle)
325 {
326  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, XmlHelper::ReadValue(xmlHandle, "CaloHitList2D", m_caloHitList2D));
327 
328  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=,
329  XmlHelper::ReadValue(xmlHandle, "MinPrimaryGoodHits", m_parameters.m_minPrimaryGoodHits));
330 
331  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=,
332  XmlHelper::ReadValue(xmlHandle, "MinHitsForGoodView", m_parameters.m_minHitsForGoodView));
333 
334  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=,
335  XmlHelper::ReadValue(xmlHandle, "MinPrimaryGoodViews", m_parameters.m_minPrimaryGoodViews));
336 
337  PANDORA_RETURN_RESULT_IF_AND_IF(
338  STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle, "SelectInputHits", m_parameters.m_selectInputHits));
339 
340  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=,
341  XmlHelper::ReadValue(xmlHandle, "MaxPhotonPropagation", m_parameters.m_maxPhotonPropagation));
342 
343  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=,
344  XmlHelper::ReadValue(xmlHandle, "MinHitSharingFraction", m_parameters.m_minHitSharingFraction));
345 
346  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=,
347  XmlHelper::ReadValue(xmlHandle, "MinHitsToConsiderTagging", m_minHitsToConsiderTagging));
348 
349  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle, "MinPurity", m_minPurity));
350 
351  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle, "MinImpurity", m_minImpurity));
352 
353  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle, "MinSignificance", m_minSignificance));
354 
355  return STATUS_CODE_SUCCESS;
356 }
357 
358 } // namespace lar_content
void CalculatePfoMetrics(const LArMCParticleHelper::PfoToMCParticleHitSharingMap &hitSharingMap, const LArMCParticleHelper::PfoContributionMap &pfoToCaloHitListMap, const LArMCParticleHelper::MCContributionMapVector &targetsToGoodHitsMaps, PfoToFloatMap &pfoSignificanceMap, PfoToFloatMap &pfoPurityMap, PfoClassificationMap &pfoClassificationMap) const
Calculate metrics to classify Pfos based on the target reconstructable MCParticles with which they sh...
Classification ClassifyPfo(const unsigned int &nHits, const float &significance, const float &purity, const bool isMuon) const
Classify a pfo given some metrics.
unsigned int m_minPrimaryGoodViews
the minimum number of primary good views
static bool SortByNHits(const pandora::ParticleFlowObject *const pLhs, const pandora::ParticleFlowObject *const pRhs)
Sort pfos by number of constituent hits.
Header file for the pfo helper class.
LArFormattingHelper::Color GetClassificationColor(const Classification &classification) const
Returns a unique color for each possible Pfo classification.
std::unordered_map< const pandora::MCParticle *, pandora::CaloHitList > MCContributionMap
bool m_selectInputHits
whether to select input hits
std::pair< const pandora::MCParticle *, pandora::CaloHitList > MCParticleCaloHitListPair
static void PrintMCParticleTable(const LArMCParticleHelper::MCContributionMap &selectedMCParticleToGoodHitsMaps, const pandora::MCParticleVector &orderedMCParticleVector)
Print details of selected MCParticles to the terminal in a table.
Header file for the cosmic-ray tagging monitoring tool class.
Header file for the lar calo hit class.
unsigned int m_minPrimaryGoodHits
the minimum number of primary good Hits
void FindAmbiguousPfos(const pandora::PfoList &parentCosmicRayPfos, pandora::PfoList &ambiguousPfos, const MasterAlgorithm *const pAlgorithm)
Find the list of ambiguous pfos (could represent cosmic-ray muons or neutrinos)
static void GetPfoToReconstructable2DHitsMap(const pandora::PfoList &pfoList, const MCContributionMap &selectedMCParticleToHitsMap, PfoContributionMap &pfoToReconstructable2DHitsMap, const bool foldBackHierarchy)
Get mapping from Pfo to reconstructable 2D hits (=good hits belonging to a selected reconstructable M...
static void GetPfoMCParticleHitSharingMaps(const PfoContributionMap &pfoToReconstructable2DHitsMap, const MCContributionMapVector &selectedMCParticleToHitsMaps, PfoToMCParticleHitSharingMap &pfoToMCParticleHitSharingMap, MCParticleToPfoHitSharingMap &mcParticleToPfoHitSharingMap)
Get the mappings from Pfo -> pair (reconstructable MCparticles, number of reconstructable 2D hits sha...
std::map< const pandora::MCParticle *, PfoToSharedHitsVector > MCParticleToPfoHitSharingMap
intermediate_table::const_iterator const_iterator
bool m_foldBackHierarchy
whether to fold the hierarchy back to the primary (neutrino) or leading particles (test beam) ...
LArMCParticleHelper::PrimaryParameters m_parameters
Parameters used to decide when an MCParticle is reconstructable.
std::string GetClassificationName(const Classification &classification) const
Returns a string for each classification.
Header file for the lar monitoring helper helper class.
unsigned int m_minHitsForGoodView
the minimum number of Hits for a good view
float m_maxPhotonPropagation
the maximum photon propagation length
std::map< const pandora::ParticleFlowObject *, float > PfoToFloatMap
std::map< const pandora::ParticleFlowObject *, Classification > PfoClassificationMap
static void GetOrderedMCParticleVector(const LArMCParticleHelper::MCContributionMapVector &selectedMCParticleToGoodHitsMaps, pandora::MCParticleVector &orderedMCParticleVector)
Order input MCParticles by their number of hits.
static bool IsCosmicRay(const pandora::MCParticle *const pMCParticle)
Return true if passed a primary cosmic ray MCParticle.
static void SelectReconstructableMCParticles(const pandora::MCParticleList *pMCParticleList, const pandora::CaloHitList *pCaloHitList, const PrimaryParameters &parameters, std::function< bool(const pandora::MCParticle *const)> fCriteria, MCContributionMap &selectedMCParticlesToHitsMap)
Select target, reconstructable mc particles that match given criteria.
pandora::StatusCode ReadSettings(const pandora::TiXmlHandle xmlHandle)
Read settings.
static bool IsBeamParticle(const pandora::MCParticle *const pMCParticle)
Returns true if passed a primary beam MCParticle.
unsigned int m_minHitsToConsiderTagging
The minimum number of hits to consider a Pfo for tagging.
static void PrintHeader(const std::string &title="", const unsigned int width=140)
Print a header line of a given width.
std::vector< art::Ptr< simb::MCParticle > > MCParticleVector
static void GetOrderedPfoVector(const LArMCParticleHelper::PfoContributionMap &pfoToReconstructable2DHitsMap, pandora::PfoVector &orderedPfoVector)
Order input Pfos by their number of hits.
float m_minHitSharingFraction
the minimum Hit sharing fraction
float m_minSignificance
The minimum significance to consider a Pfo as "significant".
std::vector< MCContributionMap > MCContributionMapVector
MasterAlgorithm class.
float m_minImpurity
The minimum impurity to consider a Pfo as "impure".
float m_minPurity
The minimum purity to consider a Pfo as "pure".
std::unordered_map< const pandora::ParticleFlowObject *, pandora::CaloHitList > PfoContributionMap
static void GetCaloHits(const pandora::PfoList &pfoList, const pandora::HitType &hitType, pandora::CaloHitList &caloHitList)
Get a list of calo hits of a particular hit type from a list of pfos.
bool IsMainMCParticleMuon(const pandora::ParticleFlowObject *const pPfo) const
Returns true if the main MCParticle of the supplied Pfo is a muon.
static bool IsBeamNeutrinoFinalState(const pandora::MCParticle *const pMCParticle)
Returns true if passed a primary neutrino final state MCParticle.
static unsigned int CountHitsByType(const pandora::HitType hitType, const pandora::CaloHitList &caloHitList)
Count the number of calo hits, in a provided list, of a specified type.
std::map< const pandora::ParticleFlowObject *, MCParticleToSharedHitsVector > PfoToMCParticleHitSharingMap
void PrintPfoTable(const pandora::PfoVector &orderedPfoVector, const LArMCParticleHelper::PfoContributionMap &pfoToReconstructable2DHitsMap, const PfoToFloatMap &pfoPurityMap, const PfoToFloatMap &pfoSignificanceMap, const PfoClassificationMap &pfoClassificationMap, const pandora::PfoList &ambiguousPfos) const
Prints a table detailing all input Pfos and their classifications.