Skip to content

Commit 8dc47c0

Browse files
authored
Merge pull request #11 from rest-for-physics/alvaroezq_Volume
Functionality for setting nuclei targets abundances in mol (volume) and as chemical compound
2 parents 2da916d + 93039e3 commit 8dc47c0

8 files changed

+272
-6
lines changed

examples/WIMP.rml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<parameter name="escapeVelocity" value="544"/>
1111
<parameter name="exposure" value="116.8"/>
1212
<parameter name="background" value="1"/>
13-
<parameter name="energySpectra" value="(0,2)"/>
13+
<parameter name="energySpectra" value="(0,4)"/>
1414
<parameter name="energySpectraStep" value="0.01"/>
1515
<parameter name="energyRange" value="(0.1,1.1)"/>
1616
<parameter name="useQuenchingFactor" value="true"/>

examples/WIMP_compound_1.rml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+
<wimp>
3+
<TRestWimpSensitivity name="WIMPSensitivity" title="WIMP Sensitivity" verboseLevel="info">
4+
<addElement nucleusName="Ar" anum="39.948" znum="18" abundanceInMol="0.99"/>
5+
<addCompound compoundName="C4H10" abundanceInMol="0.01">
6+
<addElement nucleusName="C" anum="12.0107" znum="6" />
7+
<addElement nucleusName="H" anum="1.00784" znum="1" />
8+
</addCompound>
9+
<parameter name="wimpDensity" value="0.3"/>
10+
<parameter name="labVelocity" value="232"/>
11+
<parameter name="rmsVelocity" value="220"/>
12+
<parameter name="escapeVelocity" value="544"/>
13+
<parameter name="exposure" value="116.8"/>
14+
<parameter name="background" value="100"/>
15+
<parameter name="energySpectra" value="(0,8)"/>
16+
<parameter name="energySpectraStep" value="0.01"/>
17+
<parameter name="energyRange" value="(0.05,1.05)"/>
18+
<parameter name="useQuenchingFactor" value="true"/>
19+
</TRestWimpSensitivity>
20+
</wimp>

examples/WIMP_compound_2.rml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+
<wimp>
3+
<TRestWimpSensitivity name="WIMPSensitivity" title="WIMP Sensitivity" verboseLevel="info">
4+
<addCompound compoundName="Ar99(C4H10)1" abundanceInMol="1">
5+
<addElement nucleusName="Ar" anum="39.948" znum="18"/>
6+
<addElement nucleusName="C" anum="12.0107" znum="6" />
7+
<addElement nucleusName="H" anum="1.00784" znum="1" />
8+
</addCompound>
9+
<parameter name="wimpDensity" value="0.3"/>
10+
<parameter name="labVelocity" value="232"/>
11+
<parameter name="rmsVelocity" value="220"/>
12+
<parameter name="escapeVelocity" value="544"/>
13+
<parameter name="exposure" value="116.8"/>
14+
<parameter name="background" value="100"/>
15+
<parameter name="energySpectra" value="(0,8)"/>
16+
<parameter name="energySpectraStep" value="0.01"/>
17+
<parameter name="energyRange" value="(0.05,1.05)"/>
18+
<parameter name="useQuenchingFactor" value="true"/>
19+
</TRestWimpSensitivity>
20+
</wimp>

inc/TRestWimpNucleus.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,19 @@ class TRestWimpNucleus {
3838
Int_t fZnum;
3939
/// Abundance, in mass percentage
4040
Double_t fAbundance;
41+
/// Abundance, in mole (or volume)
42+
Double_t fAbundanceMol;
4143

4244
void PrintNucleus();
45+
int GetStechiometricFactorFromCompound(const std::string& compound);
4346

4447
// Constructor
4548
TRestWimpNucleus();
4649

4750
// Destructor
4851
virtual ~TRestWimpNucleus();
4952

50-
ClassDef(TRestWimpNucleus, 1);
53+
ClassDef(TRestWimpNucleus, 2);
5154
};
5255

5356
#endif

inc/TRestWimpUtils.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
*************************************************************************/
2222

2323
#include <iostream>
24+
#include <map>
2425

2526
#ifndef RestCore_TRestWimpUtils
2627
#define RestCore_TRestWimpUtils
@@ -54,7 +55,7 @@ const double GetRecoilRate(const double wimpMass, const double crossSection, con
5455
const double Anum, const double vLab, const double vRMS, const double vEscape,
5556
const double wimpDensity, const double abundance);
5657
const double GetQuenchingFactor(const double recoilEnergy, const double Anum, const double Znum);
57-
58+
std::map<std::string, int> ParseChemicalCompound(const std::string& compound);
5859
} // namespace TRestWimpUtils
5960

6061
#endif

src/TRestWimpNucleus.cxx

+31-1
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,17 @@
4242
#include "TRestWimpNucleus.h"
4343

4444
#include "TRestMetadata.h"
45+
#include "TRestWimpUtils.h"
4546

4647
ClassImp(TRestWimpNucleus);
4748

48-
TRestWimpNucleus::TRestWimpNucleus() {}
49+
TRestWimpNucleus::TRestWimpNucleus() {
50+
fNucleusName = "";
51+
fAnum = 0;
52+
fZnum = 0;
53+
fAbundance = 0;
54+
fAbundanceMol = 0;
55+
}
4956

5057
TRestWimpNucleus::~TRestWimpNucleus() {}
5158

@@ -55,5 +62,28 @@ void TRestWimpNucleus::PrintNucleus() {
5562
RESTMetadata << "Atomic number " << fAnum << RESTendl;
5663
RESTMetadata << "Number of protons " << fZnum << RESTendl;
5764
RESTMetadata << "Abundance " << fAbundance << RESTendl;
65+
RESTMetadata << "Abundance (mol) " << fAbundanceMol << RESTendl;
5866
RESTMetadata << "-----------------------------" << RESTendl;
5967
}
68+
69+
///////////////////////////////////////////////
70+
/// \brief Get the stechiometric factor of this nucleus in a given compound.
71+
///
72+
/// \param compound The compound to be parsed. See
73+
/// TRestWimpUtils::ParseChemicalCompound for the compound format.
74+
///
75+
int TRestWimpNucleus::GetStechiometricFactorFromCompound(const std::string& compound) {
76+
auto elementMap = TRestWimpUtils::ParseChemicalCompound(compound);
77+
78+
int stechiometricFactor = 0;
79+
for (auto& pair : elementMap) {
80+
if (pair.first == fNucleusName.Data()) {
81+
stechiometricFactor = pair.second;
82+
break;
83+
}
84+
}
85+
if (stechiometricFactor == 0)
86+
RESTWarning << "No nucleus " << fNucleusName.Data() << " founnd in compound " << compound << RESTendl;
87+
88+
return stechiometricFactor;
89+
}

src/TRestWimpSensitivity.cxx

+110-2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
/// - *anum* : Atomic number of the nucleus
3838
/// - *znum* : Number of protons in the nucleus
3939
/// - *abundance* : Mass percentage of the nucleus in the target material.
40+
/// - *abundanceInMol* : Mol (or volume) percentage of the nucleus in the
41+
/// target material.
4042
/// - *wimpDensity* : WIMP density in the DM halo in GeV/cm3
4143
/// - *labVelocity* : WIMP velocity in the lab (Earth) frame reference
4244
/// in km/s
@@ -68,6 +70,12 @@
6870
/// <parameter name="useQuenchingFactor" value="true"/>
6971
/// </TRestWimpSensitivity>
7072
/// \endcode
73+
/// Other ways to define the target material is through the use of
74+
/// compounds. Also, the abundances can be given in mol (or volume) using
75+
/// the parameter *abundanceInMol* instead of *abundance*. Examples for an
76+
/// Ar+Isobutane mixture at 99% in volume can be found inside the files
77+
/// 'REST_PATH/source/libraries/wimp/examples/WIMP_compound_1.rml' and
78+
/// 'REST_PATH/source/libraries/wimp/examples/WIMP_compound_2.rml'.
7179
///
7280
/// Besides target material elements, the other parameters are optional,
7381
/// and if not provided they will take their default values.
@@ -161,23 +169,123 @@ void TRestWimpSensitivity::InitFromConfigFile() {
161169
///
162170
void TRestWimpSensitivity::ReadNuclei() {
163171
fNuclei.clear();
172+
bool anyAbundanceGivenInMol = false;
164173

174+
// Read nuclei (standalone given) elements
165175
TiXmlElement* ElementDef = GetElement("addElement");
166176
while (ElementDef) {
167177
TRestWimpNucleus nucleus;
168178
nucleus.fNucleusName = GetFieldValue("nucleusName", ElementDef);
169179
nucleus.fAnum = StringToDouble(GetFieldValue("anum", ElementDef));
170180
nucleus.fZnum = StringToInteger(GetFieldValue("znum", ElementDef));
171-
nucleus.fAbundance = StringToDouble(GetFieldValue("abundance", ElementDef));
181+
182+
std::string el =
183+
!ElementDef->Attribute("abundance") ? "Not defined" : ElementDef->Attribute("abundance");
184+
if (!(el.empty() || el == "Not defined")) nucleus.fAbundance = StringToDouble(el);
185+
186+
el = !ElementDef->Attribute("abundanceInMol") ? "Not defined"
187+
: ElementDef->Attribute("abundanceInMol");
188+
if (!(el.empty() || el == "Not defined")) {
189+
nucleus.fAbundanceMol = StringToDouble(el);
190+
anyAbundanceGivenInMol = true;
191+
}
192+
193+
if (nucleus.fAbundance == 0 || nucleus.fAbundanceMol == 0) {
194+
if (nucleus.fAbundance == 0)
195+
nucleus.fAbundance = nucleus.fAbundanceMol * nucleus.fAnum;
196+
else if (nucleus.fAbundanceMol == 0)
197+
nucleus.fAbundanceMol = nucleus.fAbundance / nucleus.fAnum;
198+
else
199+
RESTError << "abundance or abundanceInMol not defined for nucleus " << nucleus.fNucleusName
200+
<< RESTendl;
201+
}
172202
fNuclei.emplace_back(nucleus);
173203
ElementDef = GetNextElement(ElementDef);
174204
}
205+
206+
// Read nuclei (compound form given) elements
207+
TiXmlElement* CompoundDef = GetElement("addCompound");
208+
while (CompoundDef) {
209+
bool compoundAbundanceGivenInMol = false;
210+
std::string compoundName = GetFieldValue("compoundName", CompoundDef);
211+
double compoundAbundance = 0, compoundAbundanceInMol = 0;
212+
double totalMolMass = 0;
213+
214+
std::string el =
215+
!CompoundDef->Attribute("abundance") ? "Not defined" : CompoundDef->Attribute("abundance");
216+
if (!(el.empty() || el == "Not defined")) compoundAbundance = StringToDouble(el);
217+
218+
el = !CompoundDef->Attribute("abundanceInMol") ? "Not defined"
219+
: CompoundDef->Attribute("abundanceInMol");
220+
if (!(el.empty() || el == "Not defined")) {
221+
compoundAbundanceInMol = StringToDouble(el);
222+
compoundAbundanceGivenInMol = true;
223+
anyAbundanceGivenInMol = true;
224+
}
225+
226+
if (compoundAbundance == 0 && compoundAbundanceInMol == 0) {
227+
RESTWarning << "abundance or abundanceInMol not defined for compound " << compoundName
228+
<< ". Setting its abundanceInMol to 1 " << RESTendl;
229+
compoundAbundanceInMol = 1;
230+
}
231+
232+
// Read nuclei (inside compound) elements
233+
TiXmlElement* ElementDef = GetElement("addElement", CompoundDef);
234+
int i = 0;
235+
while (ElementDef) {
236+
i++;
237+
TRestWimpNucleus nucleus;
238+
nucleus.fNucleusName = GetFieldValue("nucleusName", ElementDef);
239+
nucleus.fAnum = StringToDouble(GetFieldValue("anum", ElementDef));
240+
nucleus.fZnum = StringToInteger(GetFieldValue("znum", ElementDef));
241+
totalMolMass += nucleus.fAnum * nucleus.GetStechiometricFactorFromCompound(compoundName);
242+
243+
fNuclei.emplace_back(nucleus);
244+
ElementDef = GetNextElement(ElementDef);
245+
}
246+
if (compoundAbundanceGivenInMol)
247+
compoundAbundance = compoundAbundanceInMol * totalMolMass;
248+
else
249+
compoundAbundanceInMol = compoundAbundance / totalMolMass;
250+
// Set the compound abundance to all nuclei elements inside the compound
251+
for (auto it = fNuclei.end() - i; it != fNuclei.end(); it++) {
252+
auto& nucleus = *it;
253+
int stechiometricFactor = nucleus.GetStechiometricFactorFromCompound(compoundName);
254+
nucleus.fAbundanceMol = compoundAbundanceInMol * stechiometricFactor;
255+
nucleus.fAbundance = nucleus.fAbundanceMol * nucleus.fAnum;
256+
}
257+
258+
CompoundDef = GetNextElement(CompoundDef);
259+
}
260+
261+
// Merge the repeated nuclei (same name, anum and znum) by summing their abundances
262+
std::map<std::tuple<TString, Double_t, Int_t>, TRestWimpNucleus> uniqueNucleiMap;
263+
for (const auto& nucleus : fNuclei) {
264+
auto key = std::make_tuple(nucleus.fNucleusName, nucleus.fAnum, nucleus.fZnum);
265+
if (uniqueNucleiMap.find(key) != uniqueNucleiMap.end()) {
266+
uniqueNucleiMap[key].fAbundance += nucleus.fAbundance;
267+
uniqueNucleiMap[key].fAbundanceMol += nucleus.fAbundanceMol;
268+
} else
269+
uniqueNucleiMap[key] = nucleus;
270+
}
271+
fNuclei.clear();
272+
for (const auto& entry : uniqueNucleiMap) fNuclei.push_back(entry.second);
273+
274+
// normalize fAbundance (in mass only) if anyAbundanceGivenInMol
275+
if (anyAbundanceGivenInMol) {
276+
double sumMass = 0;
277+
for (auto& nucl : fNuclei) sumMass += nucl.fAbundance;
278+
for (auto& nucl : fNuclei) nucl.fAbundance /= sumMass;
279+
}
280+
281+
for (auto& nucl : fNuclei) nucl.PrintNucleus();
175282
}
176283

177284
///////////////////////////////////////////////
178285
/// \brief Get recoil spectra for a given WIMP
179286
/// mass and cross section
180-
/// Better performance version
287+
/// Better performance version (velocity integral
288+
/// is done only once for all recoil energies).
181289
///
182290
std::map<std::string, TH1D*> TRestWimpSensitivity::GetRecoilSpectra(const double wimpMass,
183291
const double crossSection) {

src/TRestWimpUtils.cxx

+84
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,87 @@ const double TRestWimpUtils::GetQuenchingFactor(const double recoilEnergy, const
207207

208208
return (1 + g) / g;
209209
}
210+
211+
//////////////////////////////////////////////////
212+
/// \brief Parse a chemical compound into a map
213+
/// of elements and coefficients. The compound
214+
/// can contain parentheses to indicate a
215+
/// subCompound. The subCompound can have a
216+
/// coefficient after the closing parenthesis.
217+
/// The function is recursive, so subCompounds
218+
/// can contain subCompounds. Examples:
219+
/// "H2O" -> {"H": 2, "O": 1}
220+
/// "Ne98(C4H10)2" -> {"Ne": 98, "C": 8, "H": 20}
221+
/// "C6H5CH(CH3)2" -> {"C": 9, "H": 12}
222+
///
223+
std::map<std::string, int> TRestWimpUtils::ParseChemicalCompound(const std::string& compound) {
224+
std::map<std::string, int> elementMap;
225+
std::string elementName;
226+
// Get the number of opnening and closing parentheses
227+
std::pair<int, int> parenthesisCount = {0, 0};
228+
for (size_t i = 0; i < compound.size();) {
229+
if (compound[i] == '(') parenthesisCount.first++;
230+
if (compound[i] == ')') parenthesisCount.second++;
231+
i++;
232+
}
233+
if (parenthesisCount.first != parenthesisCount.second) {
234+
std::cout << "Error: Parentheses in compound " << compound << " do not match." << std::endl;
235+
return elementMap; // empty map
236+
}
237+
238+
for (size_t i = 0; i < compound.size();) {
239+
int coefficient = 1;
240+
// Check for uppercase letter (start of an element)
241+
if (std::isupper(compound[i])) {
242+
elementName = compound[i];
243+
i++;
244+
245+
// Check for lowercase letters (additional characters in element name)
246+
while (i < compound.size() && std::islower(compound[i])) {
247+
elementName += compound[i];
248+
i++;
249+
}
250+
251+
// Check for a number (coefficient)
252+
if (i < compound.size() && std::isdigit(compound[i])) {
253+
coefficient = 0;
254+
while (i < compound.size() && std::isdigit(compound[i])) {
255+
coefficient = coefficient * 10 + (compound[i] - '0');
256+
i++;
257+
}
258+
}
259+
// Add the element and coefficient to the map
260+
elementMap[elementName] += coefficient;
261+
} else if (compound[i] == '(') { // Check for a subCompound inside parentheses
262+
i++;
263+
std::string subCompound;
264+
while (i < compound.size()) {
265+
if (compound[i] == ')') {
266+
if (parenthesisCount.second > 1)
267+
parenthesisCount.second--;
268+
else
269+
break;
270+
}
271+
subCompound += compound[i];
272+
i++;
273+
}
274+
i++; // Move past the closing parenthesis
275+
// Find the subscript after the closing parenthesis
276+
coefficient = 1;
277+
if (i < compound.size() && std::isdigit(compound[i])) {
278+
coefficient = 0;
279+
while (i < compound.size() && std::isdigit(compound[i])) {
280+
coefficient = coefficient * 10 + (compound[i] - '0');
281+
i++;
282+
}
283+
}
284+
// Recursively call the function to handle the subCompound
285+
std::map<std::string, int> subElementMap = ParseChemicalCompound(subCompound);
286+
for (auto& pair : subElementMap) {
287+
elementMap[pair.first] += pair.second * coefficient;
288+
}
289+
} else
290+
i++;
291+
}
292+
return elementMap;
293+
}

0 commit comments

Comments
 (0)