fourdst::libcomposition v1.5.2
Robust atomic species information library
Loading...
Searching...
No Matches
composition.cpp
Go to the documentation of this file.
1/* ***********************************************************************
2//
3// Copyright (C) 2025 -- The 4D-STAR Collaboration
4// File Author: Emily Boudreaux
5// Last Modified: March 26, 2025
6//
7// 4DSSE is free software; you can use it and/or modify
8// it under the terms and restrictions the GNU General Library Public
9// License version 3 (GPLv3) as published by the Free Software Foundation.
10//
11// 4DSSE is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14// See the GNU Library General Public License for more details.
15//
16// You should have received a copy of the GNU Library General Public License
17// along with this software; if not, write to the Free Software
18// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19//
20// *********************************************************************** */
21#include "quill/LogMacros.h"
22
23#include <stdexcept>
24#include <unordered_map>
25#include <vector>
26#include <array>
27#include <ranges>
28
29#include <utility>
30
35
36namespace fourdst::composition {
37
39 m_symbol("H-1"),
40 m_isotope(fourdst::atomic::species.at("H-1")),
41 m_initialized(false) {} // Note: Default entry is uninitialized, must be explicitly set.
42
43 CompositionEntry::CompositionEntry(const std::string& symbol, const bool massFracMode) : m_symbol(symbol), m_isotope(fourdst::atomic::species.at(symbol)), m_massFracMode(massFracMode) {
45 }
46
55
56 void CompositionEntry::setSpecies(const std::string& symbol) {
57 if (m_initialized) {
58 throw exceptions::EntryAlreadyInitializedError("Composition entry is already initialized.");
59 }
60 if (!fourdst::atomic::species.contains(symbol)) {
61 throw exceptions::InvalidSpeciesSymbolError("Invalid symbol.");
62 }
65 m_initialized = true;
66 }
67
68 std::string CompositionEntry::symbol() const {
69 return m_symbol;
70 }
71
73 if (!m_massFracMode) {
74 throw exceptions::CompositionModeError("Composition entry is in number fraction mode.");
75 }
76 return m_massFraction;
77 }
78
79 double CompositionEntry::mass_fraction(const double meanMolarMass) const {
80 if (m_massFracMode) {
81 return m_massFraction;
82 }
83 // Convert from number fraction to mass fraction using: X_i = n_i * A_i / <A>
84 // where m_relAbundance is n_i * A_i and meanMolarMass is <A>.
85 return m_relAbundance / meanMolarMass;
86 }
87
88
90 if (m_massFracMode) {
91 throw exceptions::CompositionModeError("Composition entry is in mass fraction mode.");
92 }
93 return m_numberFraction;
94 }
95
96 double CompositionEntry::number_fraction(const double totalMoles) const {
97 if (m_massFracMode) {
98 // Convert from mass fraction to number fraction using: n_i = (X_i / A_i) / sum(X_j / A_j)
99 // where m_relAbundance is X_i / A_i and totalMoles is sum(X_j / A_j).
100 return m_relAbundance / totalMoles;
101 }
102 return m_numberFraction;
103 }
104
106 return m_relAbundance;
107 }
108
112
114 if (!m_massFracMode) {
115 throw exceptions::CompositionModeError("Composition entry is in number fraction mode.");
116 }
119 }
120
122 if (m_massFracMode) {
123 throw exceptions::CompositionModeError("Composition entry is in mass fraction mode.");
124 }
127 }
128
129 bool CompositionEntry::setMassFracMode(const double meanParticleMass) {
130 if (m_massFracMode) {
131 return false;
132 }
133 m_massFracMode = true;
134 m_massFraction = m_relAbundance / meanParticleMass;
135 return true;
136 }
137
138 bool CompositionEntry::setNumberFracMode(const double specificNumberDensity) {
139 if (!m_massFracMode) {
140 return false;
141 }
142 m_massFracMode = false;
143 m_numberFraction = m_relAbundance / specificNumberDensity;
144 return true;
145 }
146
148 return m_massFracMode;
149 }
150
151 Composition::Composition(const std::vector<std::string>& symbols) {
152 for (const auto& symbol : symbols) {
153 registerSymbol(symbol);
154 }
155 }
156
157 Composition::Composition(const std::set<std::string>& symbols) {
158 for (const auto& symbol : symbols) {
159 registerSymbol(symbol);
160 }
161 }
162
163 Composition::Composition(const std::vector<std::string>& symbols, const std::vector<double>& fractions, const bool massFracMode) : m_massFracMode(massFracMode) {
164 if (symbols.size() != fractions.size()) {
165 LOG_CRITICAL(m_logger, "The number of symbols and fractions must be equal (got {} symbols and {} fractions).", symbols.size(), fractions.size());
166 throw exceptions::InvalidCompositionError("The number of symbols and fractions must be equal. Got " + std::to_string(symbols.size()) + " symbols and " + std::to_string(fractions.size()) + " fractions.");
167 }
168
169 validateComposition(fractions);
170
171 for (const auto &symbol : symbols) {
172 registerSymbol(symbol);
173 }
174
175 for (size_t i = 0; i < symbols.size(); ++i) {
176 if (m_massFracMode) {
177 setMassFraction(symbols[i], fractions[i]);
178 } else {
179 setNumberFraction(symbols[i], fractions[i]);
180 }
181 }
182 finalize();
183 }
184
186 m_finalized = composition.m_finalized;
187 m_specificNumberDensity = composition.m_specificNumberDensity;
188 m_meanParticleMass = composition.m_meanParticleMass;
189 m_massFracMode = composition.m_massFracMode;
190 m_registeredSymbols = composition.m_registeredSymbols;
191 m_compositions = composition.m_compositions;
192 }
193
195 if (this != &other) {
196 m_finalized = other.m_finalized;
202 // note: m_config remains bound to the same singleton, so we skip it
203 }
204 return *this;
205
206 }
207
208 void Composition::registerSymbol(const std::string& symbol, bool massFracMode) {
209 if (!isValidSymbol(symbol)) {
210 LOG_ERROR(m_logger, "Invalid symbol: {}", symbol);
211 throw exceptions::InvalidSymbolError("Invalid symbol: " + symbol);
212 }
213
214 // If no symbols have been registered allow mode to be set
215 if (m_registeredSymbols.empty()) {
216 m_massFracMode = massFracMode;
217 } else {
218 if (m_massFracMode != massFracMode) {
219 LOG_ERROR(m_logger, "Composition is in mass fraction mode. Cannot register symbol ({}) in number fraction mode.", symbol);
220 throw exceptions::CompositionModeError("Composition is in mass fraction mode. Cannot register symbol (" + symbol + ") in number fraction mode.");
221 }
222 }
223
224 if (m_registeredSymbols.contains(symbol)) {
225 LOG_WARNING(m_logger, "Symbol {} is already registered.", symbol);
226 return;
227 }
228
229 m_registeredSymbols.insert(symbol);
230 const CompositionEntry entry(symbol, m_massFracMode);
231 m_compositions[symbol] = entry;
232 LOG_TRACE_L3(m_logger, "Registered symbol: {}", symbol);
233 }
234
235 void Composition::registerSymbol(const std::vector<std::string>& symbols, bool massFracMode) {
236 for (const auto& symbol : symbols) {
237 registerSymbol(symbol, massFracMode);
238 }
239 }
240
241 void Composition::registerSpecies(const fourdst::atomic::Species &species, bool massFracMode) {
242 registerSymbol(std::string(species.name()));
243 }
244
245 void Composition::registerSpecies(const std::vector<fourdst::atomic::Species> &species, bool massFracMode) {
246 for (const auto& s : species) {
247 registerSpecies(s, massFracMode);
248 }
249 }
250
251 std::set<std::string> Composition::getRegisteredSymbols() const {
252 return m_registeredSymbols;
253 }
254
255 std::set<fourdst::atomic::Species> Composition::getRegisteredSpecies() const {
256 std::set<fourdst::atomic::Species> result;
257 for (const auto& entry : m_compositions | std::views::values) {
258 result.insert(entry.isotope());
259 }
260 return result;
261 }
262
263 void Composition::validateComposition(const std::vector<double>& fractions) const {
264 if (!isValidComposition(fractions)) {
265 LOG_ERROR(m_logger, "Invalid composition.");
266 throw exceptions::InvalidCompositionError("Invalid composition.");
267 }
268 }
269
270 bool Composition::isValidComposition(const std::vector<double>& fractions) const {
271 double sum = 0.0;
272 for (const auto& fraction : fractions) {
273 sum += fraction;
274 }
275 if (sum < 0.999999 || sum > 1.000001) {
276 LOG_ERROR(m_logger, "The sum of fractions must be equal to 1 (expected 1, got {}).", sum);
277 return false;
278 }
279
280 return true;
281 }
282
283 bool Composition::isValidSymbol(const std::string& symbol) {
284 return fourdst::atomic::species.contains(symbol);
285 }
286
287 double Composition::setMassFraction(const std::string& symbol, const double& mass_fraction) {
288 if (!m_registeredSymbols.contains(symbol)) {
289 LOG_ERROR(m_logger, "Symbol {} is not registered.", symbol);
290 throw exceptions::UnregisteredSymbolError("Symbol (" + symbol + ") is not registered.");
291 }
292
293 if (!m_massFracMode) {
294 LOG_ERROR(m_logger, "Composition is in number fraction mode.");
295 throw exceptions::CompositionModeError("Composition is in number fraction mode.");
296 }
297
298 if (mass_fraction < 0.0 || mass_fraction > 1.0) {
299 LOG_ERROR(m_logger, "Mass fraction must be between 0 and 1 for symbol {}. Currently it is {}.", symbol, mass_fraction);
300 throw exceptions::InvalidCompositionError("Mass fraction must be between 0 and 1 for symbol " + symbol + ". Currently it is " + std::to_string(mass_fraction) + ".");
301 }
302
303 m_finalized = false;
304 const double old_mass_fraction = m_compositions.at(symbol).mass_fraction();
305 m_compositions.at(symbol).setMassFraction(mass_fraction);
306
307 return old_mass_fraction;
308 }
309
310 std::vector<double> Composition::setMassFraction(const std::vector<std::string>& symbols, const std::vector<double>& mass_fractions) {
311 if (symbols.size() != mass_fractions.size()) {
312 LOG_ERROR(m_logger, "The number of symbols and mass fractions must be equal (currently {} symbols and {} mass fractions).", symbols.size(), mass_fractions.size());
313 throw exceptions::InvalidCompositionError("The number of symbols and mass fractions must be equal (currently " + std::to_string(symbols.size()) + " symbols and " + std::to_string(mass_fractions.size()) + " mass fractions).");
314 }
315
316 std::vector<double> old_mass_fractions;
317 old_mass_fractions.reserve(symbols.size());
318 for (size_t i = 0; i < symbols.size(); ++i) {
319 old_mass_fractions.push_back(setMassFraction(symbols[i], mass_fractions[i]));
320 }
321 return old_mass_fractions;
322 }
323
324 double Composition::setMassFraction(const fourdst::atomic::Species &species, const double &mass_fraction) {
325 return setMassFraction(std::string(species.name()), mass_fraction);
326 }
327
328 std::vector<double> Composition::setMassFraction(const std::vector<fourdst::atomic::Species> &species,
329 const std::vector<double> &mass_fractions) {
330 std::vector<double> old_mass_fractions;
331 old_mass_fractions.reserve(species.size());
332 for (const auto& spec : species) {
333 old_mass_fractions.push_back(setMassFraction(spec, mass_fractions[&spec - &species[0]]));
334 }
335 return old_mass_fractions;
336 }
337
338 double Composition::setNumberFraction(const std::string& symbol, const double& number_fraction) {
339 if (!m_registeredSymbols.contains(symbol)) {
340 LOG_ERROR(m_logger, "Symbol {} is not registered.", symbol);
341 throw exceptions::UnregisteredSymbolError("Symbol (" + symbol + ") is not registered.");
342 }
343
344 if (m_massFracMode) {
345 LOG_ERROR(m_logger, "Composition is in mass fraction mode, should be in number fraction mode to call setNumberFraction. Hint: The mode can be switched by first finalizing and then calling setCompositionMode(false).");
346 throw exceptions::CompositionModeError("Composition is in mass fraction mode, should be in number fraction mode to call setNumberFraction. Hint: The mode can be switched by first finalizing and then calling setCompositionMode(false).");
347 }
348
349 if (number_fraction < 0.0 || number_fraction > 1.0) {
350 LOG_ERROR(m_logger, "Number fraction must be between 0 and 1 for symbol {}. Currently it is {}.", symbol, number_fraction);
351 throw exceptions::InvalidCompositionError("Number fraction must be between 0 and 1 for symbol " + symbol + ". Currently it is " + std::to_string(number_fraction) + ".");
352 }
353
354 m_finalized = false;
355 const double old_number_fraction = m_compositions.at(symbol).number_fraction();
356 m_compositions.at(symbol).setNumberFraction(number_fraction);
357
358 return old_number_fraction;
359 }
360
361 std::vector<double> Composition::setNumberFraction(const std::vector<std::string>& symbols, const std::vector<double>& number_fractions) {
362 if (symbols.size() != number_fractions.size()) {
363 LOG_ERROR(m_logger, "The number of symbols and number fractions must be equal. (Currently {} symbols and {} number fractions).", symbols.size(), number_fractions.size());
364 throw exceptions::InvalidCompositionError("The number of symbols and number fractions must be equal. (Currently " + std::to_string(symbols.size()) + " symbols and " + std::to_string(number_fractions.size()) + " number fractions).");
365 }
366
367 std::vector<double> old_number_fractions;
368 old_number_fractions.reserve(symbols.size());
369 for (size_t i = 0; i < symbols.size(); ++i) {
370 old_number_fractions.push_back(setNumberFraction(symbols[i], number_fractions[i]));
371 }
372 return old_number_fractions;
373 }
374
375 double Composition::setNumberFraction(const fourdst::atomic::Species &species, const double &number_fraction) {
376 return setNumberFraction(std::string(species.name()), number_fraction);
377 }
378
379 std::vector<double> Composition::setNumberFraction(const std::vector<fourdst::atomic::Species> &species,
380 const std::vector<double> &number_fractions) {
381 std::vector<double> old_number_fractions;
382 old_number_fractions.reserve(species.size());
383 for (const auto& spec : species) {
384 old_number_fractions.push_back(setNumberFraction(spec, number_fractions[&spec - &species[0]]));
385 }
386 return old_number_fractions;
387 }
388
389 bool Composition::finalize(const bool norm) {
390 bool finalized = false;
391 if (m_massFracMode) {
392 finalized = finalizeMassFracMode(norm);
393 } else {
394 finalized = finalizeNumberFracMode(norm);
395 }
396 if (finalized) {
397 m_finalized = true;
398 }
399 return finalized;
400 }
401
403 std::vector<double> mass_fractions;
404 mass_fractions.reserve(m_compositions.size());
405 for (const auto &entry: m_compositions | std::views::values) {
406 mass_fractions.push_back(entry.mass_fraction());
407 }
408 if (norm) {
409 double sum = 0.0;
410 for (const auto& mass_fraction : mass_fractions) {
411 sum += mass_fraction;
412 }
413 for (double & mass_fraction : mass_fractions) {
414 mass_fraction /= sum;
415 }
416 for (auto& [symbol, entry] : m_compositions) {
417 setMassFraction(symbol, entry.mass_fraction() / sum);
418 }
419 }
420 try {
421 validateComposition(mass_fractions);
422 } catch ([[maybe_unused]] const exceptions::InvalidCompositionError& e) {
423 double massSum = 0.0;
424 for (const auto &entry: m_compositions | std::views::values) {
425 massSum += entry.mass_fraction();
426 }
427 LOG_ERROR(m_logger, "Composition is invalid (Total mass {}).", massSum);
428 m_finalized = false;
429 return false;
430 }
431 // After validation, calculate the specific number density (total moles per unit mass).
432 for (const auto &entry: m_compositions | std::views::values) {
433 m_specificNumberDensity += entry.rel_abundance();
434 }
436 return true;
437 }
438
440 std::vector<double> number_fractions;
441 number_fractions.reserve(m_compositions.size());
442 for (const auto &entry: m_compositions | std::views::values) {
443 number_fractions.push_back(entry.number_fraction());
444 }
445 if (norm) {
446 double sum = 0.0;
447 for (const auto& number_fraction : number_fractions) {
448 sum += number_fraction;
449 }
450 for (auto& [symbol, entry] : m_compositions) {
451 setNumberFraction(symbol, entry.number_fraction() / sum);
452 }
453 }
454 try {
455 validateComposition(number_fractions);
456 } catch ([[maybe_unused]] const std::runtime_error& e) {
457 double numberSum = 0.0;
458 for (const auto &entry: m_compositions | std::views::values) {
459 numberSum += entry.number_fraction();
460 }
461 LOG_ERROR(m_logger, "Composition is invalid (Total number {}).", numberSum);
462 m_finalized = false;
463 return false;
464 }
465 // After validation, calculate the mean particle mass.
466 for (const auto &entry: m_compositions | std::views::values) {
467 m_meanParticleMass += entry.rel_abundance();
468 }
470 return true;
471 }
472
473 Composition Composition::mix(const Composition& other, const double fraction) const {
474 if (!m_finalized || !other.m_finalized) {
475 LOG_ERROR(m_logger, "Compositions have not both been finalized. Hint: Consider running .finalize() on both compositions before mixing.");
476 throw exceptions::CompositionNotFinalizedError("Compositions have not been finalized (Hint: Consider running .finalize() on both compositions before mixing).");
477 }
478
479 if (fraction < 0.0 || fraction > 1.0) {
480 LOG_ERROR(m_logger, "Mixing fraction must be between 0 and 1. Currently it is {}.", fraction);
481 throw exceptions::InvalidCompositionError("Mixing fraction must be between 0 and 1. Currently it is " + std::to_string(fraction) + ".");
482 }
483
484 std::set<std::string> mixedSymbols = other.getRegisteredSymbols();
485 // Get the union of the two sets of symbols to ensure all species are included in the new composition.
486 mixedSymbols.insert(m_registeredSymbols.begin(), m_registeredSymbols.end());
487
488 Composition mixedComposition(mixedSymbols);
489 for (const auto& symbol : mixedSymbols) {
490 double otherMassFrac = 0.0;
491
492 const double thisMassFrac = hasSymbol(symbol) ? getMassFraction(symbol) : 0.0;
493 otherMassFrac = other.hasSymbol(symbol) ? other.getMassFraction(symbol) : 0.0;
494
495 // The mixing formula is a linear interpolation of mass fractions.
496 double massFraction = fraction * thisMassFrac + otherMassFrac * (1-fraction);
497 mixedComposition.setMassFraction(symbol, massFraction);
498 }
499 mixedComposition.finalize();
500 return mixedComposition;
501 }
502
503 double Composition::getMassFraction(const std::string& symbol) const {
504 if (!m_finalized) {
505 LOG_ERROR(m_logger, "Composition has not been finalized. Hint: Consider running .finalize().");
506 throw exceptions::CompositionNotFinalizedError("Composition has not been finalized. Hint: Consider running .finalize().");
507 }
508 if (!m_compositions.contains(symbol)) {
509 LOG_ERROR(m_logger, "Symbol {} is not in the composition.", symbol);
510 std::string currentSymbols;
511 size_t count = 0;
512 for (const auto& sym : m_compositions | std::views::keys) {
513 currentSymbols += sym;
514 if (count < m_compositions.size() - 2) {
515 currentSymbols += ", ";
516 } else if (count == m_compositions.size() - 2) {
517 currentSymbols += ", and ";
518 }
519 count++;
520 }
521 throw exceptions::UnregisteredSymbolError("Symbol(" + symbol + ") is not in the current composition. Current composition has symbols: " + currentSymbols + ".");
522 }
523 if (m_massFracMode) {
524 return m_compositions.at(symbol).mass_fraction();
525 } else {
526 return m_compositions.at(symbol).mass_fraction(m_meanParticleMass);
527 }
528 }
529
531 return getMassFraction(std::string(species.name()));
532 }
533
534 std::unordered_map<std::string, double> Composition::getMassFraction() const {
535 std::unordered_map<std::string, double> mass_fractions;
536 for (const auto &symbol: m_compositions | std::views::keys) {
537 mass_fractions[symbol] = getMassFraction(symbol);
538 }
539 return mass_fractions;
540 }
541
542
543 double Composition::getNumberFraction(const std::string& symbol) const {
544 if (!m_finalized) {
545 LOG_ERROR(m_logger, "Composition has not been finalized. Hint: Consider running .finalize().");
546 throw exceptions::CompositionNotFinalizedError("Composition has not been finalized. Hint: Consider running .finalize().");
547 }
548 if (!m_compositions.contains(symbol)) {
549 LOG_ERROR(m_logger, "Symbol {} is not in the composition.", symbol);
550 throw exceptions::CompositionNotFinalizedError("Symbol " + symbol + " is not in the composition.");
551 }
552 if (!m_massFracMode) {
553 return m_compositions.at(symbol).number_fraction();
554 } else {
555 return m_compositions.at(symbol).number_fraction(m_specificNumberDensity);
556 }
557 }
558
560 return getNumberFraction(std::string(species.name()));
561 }
562
563 std::unordered_map<std::string, double> Composition::getNumberFraction() const {
564 std::unordered_map<std::string, double> number_fractions;
565 for (const auto &symbol: m_compositions | std::views::keys) {
566 number_fractions[symbol] = getNumberFraction(symbol);
567 }
568 return number_fractions;
569 }
570
571 double Composition::getMolarAbundance(const std::string &symbol) const {
572 if (!m_finalized) {
573 LOG_ERROR(m_logger, "Composition has not been finalized. Hint: Consider running .finalize().");
574 throw exceptions::CompositionNotFinalizedError("Composition has not been finalized. Hint: Consider running .finalize().");
575 }
576 if (!m_compositions.contains(symbol)) {
577 LOG_ERROR(m_logger, "Symbol {} is not in the composition.", symbol);
578 throw exceptions::UnregisteredSymbolError("Symbol " + symbol + " is not in the composition.");
579 }
580 return getMassFraction(symbol) / m_compositions.at(symbol).isotope().mass();
581
582 }
583
585 return getMolarAbundance(std::string(species.name()));
586 }
587
588 std::pair<CompositionEntry, GlobalComposition> Composition::getComposition(const std::string& symbol) const {
589 if (!m_finalized) {
590 LOG_ERROR(m_logger, "Composition has not been finalized. Hint: Consider running .finalize().");
591 throw exceptions::CompositionNotFinalizedError("Composition has not been finalized. Hint: Consider running .finalize().");
592 }
593 if (!m_compositions.contains(symbol)) {
594 LOG_ERROR(m_logger, "Symbol {} is not in the composition.", symbol);
595 throw exceptions::UnregisteredSymbolError("Symbol " + symbol + " is not in the composition.");
596 }
598 }
599
600 std::pair<CompositionEntry, GlobalComposition> Composition::getComposition(
601 const fourdst::atomic::Species &species) const {
602 return getComposition(std::string(species.name()));
603 }
604
605 std::pair<std::unordered_map<std::string, CompositionEntry>, GlobalComposition> Composition::getComposition() const {
606 if (!m_finalized) {
607 LOG_ERROR(m_logger, "Composition has not been finalized. Hint: Consider running .finalize().");
608 throw exceptions::CompositionNotFinalizedError("Composition has not been finalized. Hint: Consider running .finalize().");
609 }
611 }
612
614 if (!m_finalized) {
615 LOG_ERROR(m_logger, "Composition has not been finalized. Hint: Consider running .finalize().");
616 throw exceptions::CompositionNotFinalizedError("Composition has not been finalized. Hint: Consider running .finalize().");
617 }
618 return m_meanParticleMass;
619 }
620
622 if (!m_finalized) {
623 LOG_ERROR(m_logger, "Composition must be finalized before getting the mean atomic mass number. Hint: Consider running .finalize().");
624 throw exceptions::CompositionNotFinalizedError("Composition not finalized. Cannot retrieve mean atomic mass number. Hint: Consider running .finalize().");
625 }
626
627 double zSum = 0.0;
628
629 // Loop through all registered species in the composition.
630 for (const auto &val: m_compositions | std::views::values) {
631 // Sum of (X_i * Z_i / A_i)
632 zSum += (val.mass_fraction() * val.m_isotope.z())/val.m_isotope.a();
633 }
634
635 // Calculate mean atomic number <Z> = <A> * sum(X_i * Z_i / A_i)
636 const double mean_A = m_meanParticleMass * zSum;
637 return mean_A;
638 }
639
640 Composition Composition::subset(const std::vector<std::string>& symbols, const std::string& method) const {
641 const std::array<std::string, 2> methods = {"norm", "none"};
642
643 if (std::ranges::find(methods, method) == methods.end()) {
644 const std::string errorMessage = "Invalid method: " + method + ". Valid methods are 'norm' and 'none'.";
645 LOG_ERROR(m_logger, "Invalid method: {}. Valid methods are norm and none.", method);
646 throw exceptions::InvalidMixingMode(errorMessage);
647 }
648
649 Composition subsetComposition;
650 for (const auto& symbol : symbols) {
651 if (!m_compositions.contains(symbol)) {
652 LOG_ERROR(m_logger, "Symbol {} is not in the composition.", symbol);
653 throw exceptions::UnregisteredSymbolError("Symbol " + symbol + " is not in the composition.");
654 } else {
655 subsetComposition.registerSymbol(symbol);
656 }
657 subsetComposition.setMassFraction(symbol, m_compositions.at(symbol).mass_fraction());
658 }
659 if (method == "norm") {
660 const bool isNorm = subsetComposition.finalize(true);
661 if (!isNorm) {
662 LOG_ERROR(m_logger, "Subset composition is invalid. (Unable to finalize with normalization).");
663 throw exceptions::FailedToFinalizeCompositionError("Subset composition is invalid. (Unable to finalize with normalization).");
664 }
665 }
666 return subsetComposition;
667 }
668
669 void Composition::setCompositionMode(const bool massFracMode) {
670 if (!m_finalized) {
671 LOG_ERROR(m_logger, "Composition has not been finalized. Mode cannot be set unless composition is finalized. Hint: Consider running .finalize().");
672 throw exceptions::CompositionNotFinalizedError("Composition has not been finalized. Mode cannot be set unless composition is finalized. Hint: Consider running .finalize().");
673 }
674
675 bool okay = true;
676 for (auto &entry: m_compositions | std::views::values) {
677 if (massFracMode) {
678 okay = entry.setMassFracMode(m_meanParticleMass);
679 } else {
680 okay = entry.setNumberFracMode(m_specificNumberDensity);
681 }
682 if (!okay) {
683 LOG_ERROR(m_logger, "Composition mode could not be set due to some unknown error.");
684 throw std::runtime_error("Composition mode could not be set due to an unknown error.");
685 }
686 }
687 m_massFracMode = massFracMode;
688 }
689
691 if (!m_finalized) {
692 LOG_ERROR(m_logger, "Composition has not been finalized. Hint: Consider running .finalize().");
693 throw exceptions::CompositionNotFinalizedError("Composition has not been finalized. Hint: Consider running .finalize().");
694 }
695 CanonicalComposition canonicalComposition;
696 const std::array<std::string, 7> canonicalH = {
697 "H-1", "H-2", "H-3", "H-4", "H-5", "H-6", "H-7"
698 };
699 const std::array<std::string, 8> canonicalHe = {
700 "He-3", "He-4", "He-5", "He-6", "He-7", "He-8", "He-9", "He-10"
701 };
702 for (const auto& symbol : canonicalH) {
703 if (hasSymbol(symbol)) {
704 canonicalComposition.X += getMassFraction(symbol);
705 }
706 }
707 for (const auto& symbol : canonicalHe) {
708 if (hasSymbol(symbol)) {
709 canonicalComposition.Y += getMassFraction(symbol);
710 }
711 }
712
713 for (const auto& symbol : getRegisteredSymbols()) {
714 const bool isHSymbol = std::ranges::find(canonicalH, symbol) != std::end(canonicalH);
715 const bool isHeSymbol = std::ranges::find(canonicalHe, symbol) != std::end(canonicalHe);
716
717 if (isHSymbol || isHeSymbol) {
718 continue; // Skip canonical H and He symbols
719 }
720
721 canonicalComposition.Z += getMassFraction(symbol);
722 }
723
724 const double Z = 1.0 - (canonicalComposition.X + canonicalComposition.Y);
725 if (std::abs(Z - canonicalComposition.Z) > 1e-6) {
726 if (!harsh) {
727 LOG_WARNING(m_logger, "Validation composition Z (X-Y = {}) is different than canonical composition Z ({}) (∑a_i where a_i != H/He).", Z, canonicalComposition.Z);
728 }
729 else {
730 LOG_ERROR(m_logger, "Validation composition Z (X-Y = {}) is different than canonical composition Z ({}) (∑a_i where a_i != H/He).", Z, canonicalComposition.Z);
731 throw std::runtime_error("Validation composition Z (X-Y = " + std::to_string(Z) + ") is different than canonical composition Z (" + std::to_string(canonicalComposition.Z) + ") (∑a_i where a_i != H/He).");
732 }
733 }
734 return canonicalComposition;
735 }
736
737 bool Composition::hasSymbol(const std::string& symbol) const {
738 return m_compositions.contains(symbol);
739 }
740
742 // Check if the isotope's symbol is in the composition
743 if (!m_finalized) {
744 LOG_ERROR(m_logger, "Composition has not been finalized. Hint: Consider running .finalize().");
745 throw exceptions::CompositionNotFinalizedError("Composition has not been finalized. Hint: Consider running .finalize().");
746 }
747 const auto symbol = static_cast<std::string>(isotope.name());
748 if (m_compositions.contains(symbol)) {
749 return true;
750 }
751 return false;
752 }
753
755
757 return mix(other, 0.5);
758 }
759
760 std::ostream& operator<<(std::ostream& os, const GlobalComposition& comp) {
761 os << "Global Composition: \n";
762 os << "\tSpecific Number Density: " << comp.specificNumberDensity << "\n";
763 os << "\tMean Particle Mass: " << comp.meanParticleMass << "\n";
764 return os;
765 }
766
767 std::ostream& operator<<(std::ostream& os, const CompositionEntry& entry) {
768 os << "<" << entry.m_symbol << " : m_frac = " << entry.mass_fraction() << ">";
769 return os;
770 }
771
772 std::ostream& operator<<(std::ostream& os, const Composition& composition) {
773 os << "Composition(finalized: " << (composition.m_finalized ? "true" : "false") << ", " ;
774 size_t count = 0;
775 for (const auto &entry: composition.m_compositions | std::views::values) {
776 os << entry;
777 if (count < composition.m_compositions.size() - 1) {
778 os << ", ";
779 }
780 }
781 os << ")";
782 return os;
783 }
784
785} // namespace fourdst::composition
void setCompositionMode(bool massFracMode)
Sets the composition mode (mass fraction vs. number fraction).
std::pair< std::unordered_map< std::string, CompositionEntry >, GlobalComposition > getComposition() const
Gets all composition entries and the global composition data.
Composition subset(const std::vector< std::string > &symbols, const std::string &method="norm") const
Creates a new Composition object containing a subset of species from this one.
void registerSymbol(const std::string &symbol, bool massFracMode=true)
Registers a new symbol for inclusion in the composition.
Composition()=default
Default constructor.
Composition operator+(const Composition &other) const
Overloads the + operator to mix two compositions with a 50/50 fraction.
std::set< std::string > m_registeredSymbols
The registered symbols.
Composition mix(const Composition &other, double fraction) const
Mixes this composition with another to produce a new composition.
std::set< fourdst::atomic::Species > getRegisteredSpecies() const
Get a set of all species that are registered in the composition.
bool finalizeNumberFracMode(bool norm)
Finalizes the composition in number fraction mode.
double setMassFraction(const std::string &symbol, const double &mass_fraction)
Sets the mass fraction for a given symbol.
double m_meanParticleMass
The mean particle mass of the composition (\sum_{i} \frac{n_i}{m_i}. where n_i is the number fraction...
void registerSpecies(const fourdst::atomic::Species &species, bool massFracMode=true)
Registers a new species by extracting its symbol.
Composition & operator=(Composition const &other)
Assignment operator.
double getMeanParticleMass() const
Compute the mean particle mass of the composition.
bool m_massFracMode
True if mass fraction mode, false if number fraction mode.
double getMolarAbundance(const std::string &symbol) const
Gets the molar abundance (X_i / A_i) for a given symbol.
bool hasSymbol(const std::string &symbol) const
Checks if a symbol is registered in the composition.
bool finalize(bool norm=false)
Finalizes the composition, making it ready for querying.
std::unordered_map< std::string, double > getNumberFraction() const
Gets the number fractions of all species in the composition.
double setNumberFraction(const std::string &symbol, const double &number_fraction)
Sets the number fraction for a given symbol.
std::set< std::string > getRegisteredSymbols() const
Gets the registered symbols.
void validateComposition(const std::vector< double > &fractions) const
Validates the given fractions, throwing an exception on failure.
bool finalizeMassFracMode(bool norm)
Finalizes the composition in mass fraction mode.
static bool isValidSymbol(const std::string &symbol)
Checks if the given symbol is valid by checking against the global species database.
double getMeanAtomicNumber() const
Compute the mean atomic number of the composition.
bool m_finalized
True if the composition is finalized.
std::unordered_map< std::string, CompositionEntry > m_compositions
The compositions.
CanonicalComposition getCanonicalComposition(bool harsh=false) const
Gets the current canonical composition (X, Y, Z).
bool contains(const fourdst::atomic::Species &isotope) const
Checks if a given isotope is present in the composition.
std::unordered_map< std::string, double > getMassFraction() const
Gets the mass fractions of all species in the composition.
double m_specificNumberDensity
The specific number density of the composition (\sum_{i} X_i m_i. Where X_i is the number fraction of...
bool isValidComposition(const std::vector< double > &fractions) const
Checks if the given fractions are valid (sum to ~1.0).
Exception thrown due to a conflict in composition modes at the entry level.
Exception thrown when an operation is attempted on a composition that has not been finalized.
Exception thrown when attempting to initialize a composition entry that has already been initialized.
Exception thrown when the finalization process of a composition fails.
Exception thrown when a composition is in an invalid or inconsistent state.
Exception thrown for an invalid or unsupported mixing mode.
Exception thrown for an invalid chemical species symbol in a composition entry.
Exception thrown when a symbol used in a composition is invalid.
Exception thrown when a symbol is used that has not been registered.
Contains classes and functions related to atomic data, such as properties of atomic species.
static const std::unordered_map< std::string, const Species & > species
Definition species.h:3580
std::ostream & operator<<(std::ostream &os, const GlobalComposition &comp)
Represents an atomic species (isotope) with its fundamental physical properties.
std::string_view name() const
Gets the name of the species.
Represents the canonical (X, Y, Z) composition of stellar material.
Definition composition.h:43
double Y
Mass fraction of Helium.
Definition composition.h:45
double X
Mass fraction of Hydrogen.
Definition composition.h:44
double Z
Mass fraction of Metals.
Definition composition.h:46
Represents a single entry (an isotope) within a composition.
Definition composition.h:83
double m_relAbundance
The relative abundance, used internally for conversions. For mass fraction mode, this is X_i / A_i; f...
Definition composition.h:90
bool getMassFracMode() const
Gets the mode of the composition entry.
CompositionEntry()
Default constructor. Initializes a default entry (H-1), but in an uninitialized state.
bool m_massFracMode
The mode of the composition entry. True if mass fraction, false if number fraction.
Definition composition.h:86
double m_numberFraction
The number fraction (mole fraction) of the species. Valid only if m_massFracMode is false.
Definition composition.h:89
double number_fraction() const
Gets the number fraction of the species.
bool m_initialized
True if the composition entry has been initialized with a valid species.
Definition composition.h:92
bool setMassFracMode(double meanMolarMass)
Switches the mode to mass fraction mode.
void setMassFraction(double mass_fraction)
Sets the mass fraction of the species.
std::string symbol() const
Gets the chemical symbol of the species.
void setSpecies(const std::string &symbol)
Sets the species for the composition entry. This can only be done once.
double mass_fraction() const
Gets the mass fraction of the species.
bool setNumberFracMode(double totalMoles)
Switches the mode to number fraction mode.
atomic::Species m_isotope
The atomic::Species object containing detailed isotope data.
Definition composition.h:85
void setNumberFraction(double number_fraction)
Sets the number fraction of the species.
double rel_abundance() const
Gets the relative abundance of the species.
std::string m_symbol
The chemical symbol of the species (e.g., "H-1", "Fe-56").
Definition composition.h:84
double m_massFraction
The mass fraction of the species. Valid only if m_massFracMode is true.
Definition composition.h:88
atomic::Species isotope() const
Gets the isotope data for the species.
Represents global properties of a finalized composition.
Definition composition.h:69
double specificNumberDensity
The specific number density (moles per unit mass, sum of X_i/M_i), where X_i is mass fraction and M_i...
Definition composition.h:70
double meanParticleMass
The mean mass per particle (inverse of specific number density). Units: g/mol.
Definition composition.h:71