SedSat3 1.1.6
Sediment Source Apportionment Tool - Advanced statistical methods for environmental pollution research
Loading...
Searching...
No Matches
elemental_profile.cpp
Go to the documentation of this file.
1
7#include "elemental_profile.h"
8#include <iostream>
9#include "Utilities.h"
10#include <QStringList>
11#include <QFile>
12
13
14// =============================================================================
15// CONSTRUCTION AND ASSIGNMENT
16// =============================================================================
17
31 : map<string, double>(),
32 Interface(),
33 included_in_analysis_(true),
34 marked_elements_()
35{
36 // All initialization done in initializer list
37 // Empty body is fine - demonstrates proper C++ style
38}
39
40
58 : map<string, double>(other), // Copy base class (element concentrations)
59 Interface(other), // Copy Interface base class
60 included_in_analysis_(other.included_in_analysis_),
61 marked_elements_(other.marked_elements_)
62{
63 // All copying done in initializer list - efficient and exception-safe
64}
65
66
67/*/
68*@brief Copy assignment operator
69*
70* Assigns the contents of another profile to this one, replacing all
71* existing data.Handles self - assignment safely.
72*
73*@param other Profile to assign from
74* @return Reference to * this for chaining(e.g., a = b = c)
75*
76* @post* this contains copy of other's data
77* @post Previous contents of * this are replaced
78* @post Self - assignment(a = a) is safe
79*
80*@note Assignment order is important : Interface first, then members,
81* then base class to ensure proper cleanup if exceptions occur
82*/
84{
85 // Check for self-assignment (important for performance and correctness)
86 if (this == &other) {
87 return *this;
88 }
89
90 // Assign in proper order:
91 // 1. Interface base class
93
94 // 2. Member variables
97
98 // 3. map base class (element concentrations)
99 // Done last because it might throw, and we want other data copied first
100 map<string, double>::operator=(other);
101
102 return *this;
103}
104
146 const map<string, element_information>* element_info) const
147{
148 // If no metadata provided, return complete copy
149 if (element_info == nullptr) {
150 return *this;
151 }
152
153 // Create filtered profile
154 Elemental_Profile filtered_profile;
155
156 // Copy analysis inclusion flag
157 filtered_profile.included_in_analysis_ = this->included_in_analysis_;
158
159 // Iterate through all elements in current profile
160 for (const auto& element_entry : *this) {
161 const string& element_name = element_entry.first;
162 const double concentration = element_entry.second;
163
164 // Check if element exists in metadata
165 auto info_iter = element_info->find(element_name);
166 if (info_iter == element_info->end()) {
167 // Element not in metadata - skip it
168 continue;
169 }
170
171 const element_information& info = info_iter->second;
172
173 // Apply inclusion criteria
174 bool should_include = info.include_in_analysis &&
178
179 if (should_include) {
180 // Add element to filtered profile
181 filtered_profile[element_name] = concentration;
182
183 // Preserve marked status if element was marked
184 auto marked_iter = marked_elements_.find(element_name);
185 if (marked_iter != marked_elements_.end() && marked_iter->second) {
186 filtered_profile.marked_elements_[element_name] = true;
187 }
188 }
189 }
190
191 return filtered_profile;
192}
193
241 bool exclude_elements,
242 bool apply_corrections,
243 const vector<double>& reference_om_size,
244 const MultipleLinearRegressionSet* regression_models,
245 const map<string, element_information>* element_info) const
246{
247 // Start with current profile
248 Elemental_Profile result = *this;
249
250 // Step 1: Apply OM/size corrections if requested
251 if (apply_corrections) {
252 if (regression_models == nullptr) {
253 // Log warning or throw exception in production code
254 std::cerr << "Warning: apply_corrections=true but regression_models is null. "
255 << "Skipping corrections." << std::endl;
256 }
257 else {
259 reference_om_size,
260 regression_models,
261 element_info
262 );
263 }
264 }
265
266 // Step 2: Filter elements if requested
267 if (exclude_elements) {
268 if (element_info == nullptr) {
269 // Log warning or throw exception in production code
270 std::cerr << "Warning: exclude_elements=true but element_info is null. "
271 << "Skipping filtering." << std::endl;
272 }
273 else {
274 result = result.CreateAnalysisProfile(element_info);
275 }
276 }
277
278 return result;
279}
280
331 const map<string, element_information>* element_info,
332 bool include_isotopes) const
333{
334 // Validate input - element_info is required
335 if (element_info == nullptr) {
336 // For now, log error and return empty profile
337 // Future: throw std::invalid_argument("element_info cannot be null")
338 std::cerr << "Error: ExtractChemicalElements called with null element_info. "
339 << "Returning empty profile." << std::endl;
340 return Elemental_Profile();
341 }
342
343 Elemental_Profile extracted_profile;
344
345 // Iterate through all elements in current profile
346 for (const auto& [element_name, concentration] : *this) {
347
348 // Check if element exists in metadata
349 auto info_iter = element_info->find(element_name);
350 if (info_iter == element_info->end()) {
351 // Element not in metadata - skip it
352 // Could log warning: std::cerr << "Warning: Element '" << element_name
353 // << "' not found in metadata. Skipping.\n";
354 continue;
355 }
356
357 const element_information& info = info_iter->second;
358
359 // Check if element should be included based on role
360 bool should_include = false;
361
363 // Always include chemical elements
364 should_include = true;
365 }
366 else if (info.Role == element_information::role::isotope) {
367 // Include isotopes only if requested
368 should_include = include_isotopes;
369 }
370 // All other roles (organic_carbon, particle_size, do_not_include) are excluded
371
372 if (should_include) {
373 extracted_profile[element_name] = concentration;
374 }
375 }
376
377 return extracted_profile;
378}
379
422 const vector<string>& element_names) const
423{
424 Elemental_Profile extracted_profile;
425
426 // Reserve space if we expect most elements to exist (optimization)
427 // Note: Can't reserve in map, but this shows intent
428
429 // Iterate through requested elements
430 for (const string& element_name : element_names) {
431 // Check if element exists in current profile
432 auto it = find(element_name);
433 if (it != end()) {
434 // Element exists - add to extracted profile
435 extracted_profile[element_name] = it->second;
436 }
437 // Element doesn't exist - silently skip
438 }
439
440 return extracted_profile;
441}
442
450double Elemental_Profile::GetValue(const string& element_name) const
451{
452 auto it = find(element_name);
453 if (it == end()) {
454 throw std::out_of_range(
455 "Element '" + element_name + "' does not exist in profile"
456 );
457 }
458 return it->second;
459}
460
461
484 bool Elemental_Profile::SetValue(const string& element_name, double value)
485{
486 auto it = find(element_name);
487 if (it == end()) {
488 return false;
489 }
490
491 it->second = value;
492 return true;
493}
494
507 bool Elemental_Profile::SetMarked(const string& element_name, bool marked)
508 {
509 if (find(element_name) == end()) {
510 return false;
511 }
512
513 marked_elements_[element_name] = marked;
514 return true;
515 }
516
525 bool Elemental_Profile::IsMarked(const string& element_name) const
526 {
527 if (find(element_name) == end()) {
528 return false;
529 }
530
531 auto it = marked_elements_.find(element_name);
532 return (it != marked_elements_.end() && it->second);
533 }
534
535
552 bool Elemental_Profile::AppendElement(const string& element_name, double value)
553 {
554 if (find(element_name) != end()) {
555 return false;
556 }
557
558 (*this)[element_name] = value;
559 marked_elements_[element_name] = false;
560 return true;
561 }
562
575 vector<double> Elemental_Profile::GetAllValues() const
576 {
577 vector<double> values;
578 values.reserve(size());
579
580 for (const auto& [element_name, concentration] : *this) {
581 values.push_back(concentration);
582 }
583
584 return values;
585 }
586
600 {
601 string output;
602
603 for (const auto& [element_name, concentration] : *this) {
604 output += element_name + ":" + aquiutils::numbertostring(concentration) + "\n";
605 }
606
607 return output;
608 }
609
633 {
634 QTableWidget* table = new QTableWidget();
635 table->setEditTriggers(QAbstractItemView::NoEditTriggers);
636 table->setColumnCount(1);
637 table->setRowCount(size());
638
639 QStringList headers;
640 QStringList element_names;
641
642 int row = 0;
643 for (const auto& [element_name, concentration] : *this) {
644 element_names << QString::fromStdString(element_name);
645 table->setItem(row, 0, new QTableWidgetItem(QString::number(concentration)));
646
647 // Highlight values outside limits if enabled
649 (concentration > highlimit || concentration < lowlimit)) {
650 table->item(row, 0)->setForeground(QColor(Qt::red));
651 }
652
653 row++;
654 }
655
656 headers << YAxisLabel();
657 table->setHorizontalHeaderLabels(headers);
658 table->setVerticalHeaderLabels(element_names);
659
660 return table;
661 }
662
675 {
676 QJsonObject json_object;
677 QJsonObject element_contents;
678
679 json_object["Include"] = IsIncludedInAnalysis();
680
681 for (const auto& [element_name, concentration] : *this) {
682 element_contents[QString::fromStdString(element_name)] = concentration;
683 }
684
685 json_object["Element Contents"] = element_contents;
686 json_object["XAxisLabel"] = XAxisLabel();
687 json_object["YAxisLabel"] = YAxisLabel();
688
689 return json_object;
690 }
691
707 bool Elemental_Profile::ReadFromJsonObject(const QJsonObject& json_object)
708 {
709 clear();
710
711 if (json_object.contains("Include")) {
712 // Full format with metadata
713 SetIncludedInAnalysis(json_object["Include"].toBool());
714
715 QJsonObject contents = json_object["Element Contents"].toObject();
716 for (const QString& key : contents.keys()) {
717 (*this)[key.toStdString()] = contents[key].toDouble();
718 }
719 }
720 else {
721 // Legacy format - elements directly in root
722 for (const QString& key : json_object.keys()) {
723 (*this)[key.toStdString()] = json_object[key].toDouble();
724 }
725 }
726
727 if (json_object.contains("XAxisLabel")) {
728 SetXAxisLabel(json_object["XAxisLabel"].toString());
729 }
730
731 if (json_object.contains("YAxisLabel")) {
732 SetYAxisLabel(json_object["YAxisLabel"].toString()); // Fixed: was SetXAxisLabel
733 }
734
735 return true;
736 }
737
738
753 bool Elemental_Profile::Read(const QStringList& string_list)
754 {
755 clear();
756
757 for (const QString& line : string_list) {
758 QStringList parts = line.split(":");
759 if (parts.size() > 1) {
760 AppendElement(parts[0].toStdString(), parts[1].toDouble());
761 }
762 }
763
764 return true;
765 }
766
779 {
780 file->write(QString::fromStdString(ToString()).toUtf8());
781 return true;
782 }
783
794 {
795 if (empty()) {
796 return -1e12;
797 }
798
799 double max_value = -1e12;
800 for (const auto& [element_name, concentration] : *this) {
801 if (concentration > max_value) {
802 max_value = concentration;
803 }
804 }
805
806 return max_value;
807 }
808
819 {
820 if (empty()) {
821 return 1e12;
822 }
823
824 double min_value = 1e12;
825 for (const auto& [element_name, concentration] : *this) {
826 if (concentration < min_value) {
827 min_value = concentration;
828 }
829 }
830
831 return min_value;
832 }
833
840 {
841 vector<string> names;
842 names.reserve(size());
843
844 for (const auto& [element_name, concentration] : *this) {
845 names.push_back(element_name);
846 }
847
848 return names;
849 }
850
857 bool Elemental_Profile::Contains(const string& element_name) const
858 {
859 return (find(element_name) != end());
860 }
861
862
879 const vector<double>& reference_om_size,
880 const MultipleLinearRegressionSet* regression_models,
881 const map<string, element_information>* element_info) const
882 {
883 Elemental_Profile corrected_profile;
884
885 for (const auto& [element_name, concentration] : *this) {
886 const element_information& info = element_info->at(element_name);
887
888 // Skip organic carbon and particle size indicators
891 continue;
892 }
893
894 // Start with original concentration
895 corrected_profile[element_name] = concentration;
896
897 // Apply corrections if regression model exists for this element
898 if (regression_models->count(element_name) == 0) {
899 continue;
900 }
901
902 const MultipleLinearRegression* mlr = &regression_models->at(element_name);
903 const auto& var_names = mlr->GetIndependentVariableNames();
904 const auto& coefficients = mlr->CoefficientsIntercept();
905 bool is_linear = (mlr->Equation() == regression_form::linear);
906
907 // Apply first correction (typically organic matter)
908 if (mlr->Effective(0) && var_names[0] != element_name) {
909 double reference = reference_om_size[0];
910 double measured = this->at(var_names[0]);
911
912 if (is_linear) {
913 corrected_profile[element_name] += (reference - measured) * coefficients[1];
914 }
915 else {
916 corrected_profile[element_name] *= pow(reference / measured, coefficients[1]);
917 }
918 }
919
920 // Apply second correction (typically particle size) if available
921 if (var_names.size() > 1 && mlr->Effective(1) && var_names[1] != element_name) {
922 double reference = reference_om_size[1];
923 double measured = this->at(var_names[1]);
924
925 if (is_linear) {
926 corrected_profile[element_name] += (reference - measured) * coefficients[2];
927 }
928 else {
929 corrected_profile[element_name] *= pow(reference / measured, coefficients[2]);
930 }
931 }
932
933 // Warn about negative corrected values (except for isotopes)
934 if (corrected_profile[element_name] < 0 &&
936 std::cerr << "Warning: Corrected value for " << element_name
937 << " is negative: " << corrected_profile[element_name] << std::endl;
938 }
939 }
940
941 return corrected_profile;
942 }
943
944
960 {
961 if (size() != weights.getsize()) {
962 return -999;
963 }
964
965 double sum = 0.0;
966 int index = 0;
967
968 for (const auto& [element_name, concentration] : *this) {
969 sum += concentration * weights[index];
970 index++;
971 }
972
973 return sum;
974 }
975
989 {
990 CMBVector sorted_result;
991 vector<string> processed_elements;
992
993 for (size_t i = 0; i < size(); i++) {
994 double target_value = ascending ? 1e6 : -1e6;
995 string target_element;
996
997 for (const auto& [element_name, concentration] : *this) {
998 // Skip already processed elements
999 if (lookup(processed_elements, element_name) != -1) {
1000 continue;
1001 }
1002
1003 // Find min (ascending) or max (descending)
1004 bool is_better = ascending ? (concentration < target_value)
1005 : (concentration > target_value);
1006
1007 if (is_better) {
1008 target_element = element_name;
1009 target_value = concentration;
1010 }
1011 }
1012
1013 processed_elements.push_back(target_element);
1014 sorted_result.append(target_element, target_value);
1015 }
1016
1017 return sorted_result;
1018 }
1019
1032 vector<string> Elemental_Profile::SelectTopElements(int n, bool ascending) const
1033 {
1034 CMBVector sorted = SortByConcentration(ascending);
1035 vector<string> top_elements;
1036
1037 for (int i = 0; i < n && i < sorted.num; i++) {
1038 top_elements.push_back(sorted.Label(i));
1039 }
1040
1041 return top_elements;
1042 }
Vector class with string labels for Chemical Mass Balance analysis.
Definition cmbvector.h:17
void append(const string &label, const double &val)
Appends a labeled element to the vector.
string Label(int i) const
Gets label at specified index.
Definition cmbvector.h:118
Container for elemental concentration data of a single sediment sample.
Elemental_Profile & operator=(const Elemental_Profile &other)
Copy assignment operator.
Elemental_Profile CreateAnalysisProfile(const map< string, element_information > *elementinfo=nullptr) const
Create profile with only analyzed elements.
Elemental_Profile ExtractChemicalElements(const map< string, element_information > *elementinfo, bool include_isotopes=false) const
Extract chemical elements only (exclude isotopes, metadata)
Elemental_Profile ExtractElements(const vector< string > &element_names) const
Extract subset of specified elements.
Elemental_Profile ApplyOrganicMatterAndSizeCorrections(const vector< double > &reference_om_size, const MultipleLinearRegressionSet *regression_models, const map< string, element_information > *elementinfo=nullptr) const
Apply organic matter and particle size corrections.
bool IsMarked(const string &name) const
Check if element is marked.
map< string, bool > marked_elements_
Map tracking marked elements for quality control.
double GetMaximum() const
Get maximum concentration value.
vector< string > GetElementNames() const
Get list of all element names.
double CalculateDotProduct(const CMBVector &weights) const
Calculate dot product with weight vector.
CMBVector SortByConcentration(bool ascending=true) const
Sort elements by concentration value.
bool included_in_analysis_
Whether profile should be included in analysis.
double GetMinimum() const
Get minimum concentration value.
vector< double > GetAllValues() const
Get all concentration values as vector.
bool SetMarked(const string &name, bool marked)
Set marked status for an element.
QTableWidget * ToTable() override
Create Qt table widget representation.
void SetIncludedInAnalysis(bool included)
Set analysis inclusion status.
Elemental_Profile CreateCorrectedProfile(bool exclude_elements, bool apply_corrections, const vector< double > &reference_om_size, const MultipleLinearRegressionSet *regression_models=nullptr, const map< string, element_information > *elementinfo=nullptr) const
Create corrected profile with filtering and corrections.
bool AppendElement(const string &name, double val=0.0)
Add new element to profile.
double GetValue(const string &name) const
Get element concentration by name.
vector< string > SelectTopElements(int n, bool ascending=true) const
Select top N elements by concentration.
Elemental_Profile()
Default constructor - creates empty profile.
string ToString() const override
Convert profile to string representation.
bool writetofile(QFile *file) override
Write profile to file.
bool ReadFromJsonObject(const QJsonObject &json) override
Deserialize from JSON object.
QJsonObject toJsonObject() const override
Serialize to JSON object.
bool IsIncludedInAnalysis() const
Check if profile is included in analysis.
bool SetValue(const string &name, double val)
Set concentration value for existing element.
bool Read(const QStringList &string_list) override
Read profile from string list.
bool Contains(const string &name) const
Check if element exists in profile.
Abstract base class providing common serialization and visualization interface.
Definition interface.h:65
double lowlimit
Lower threshold for value highlighting (when enabled)
Definition interface.h:211
Interface & operator=(const Interface &intf)
Assignment operator.
Definition interface.cpp:14
QString XAxisLabel() const
Get the X-axis label.
Definition interface.h:287
bool SetXAxisLabel(const QString &label)
Set the X-axis label.
Definition interface.h:307
QString YAxisLabel() const
Get the Y-axis label.
Definition interface.h:298
bool highlightoutsideoflimit
Flag indicating whether to highlight values outside defined limits.
Definition interface.h:221
bool SetYAxisLabel(const QString &label)
Set the Y-axis label.
Definition interface.h:317
double highlimit
Upper threshold for value highlighting (when enabled)
Definition interface.h:212
Elemental concentration profile for sediment source fingerprinting.
Metadata describing an element's role and properties in analysis.
enum element_information::role Role
@ element
Standard chemical element concentration.
@ isotope
Isotopic ratio (e.g., 206Pb/207Pb)
@ organic_carbon
Organic carbon content.
@ particle_size
Particle size distribution parameter.
@ do_not_include
Exclude from all analyses.
bool include_in_analysis
Whether to include in statistical analyses.