Line data Source code
1 : /* Copyright (C) 2025 Robert Schiwon
2 : *
3 : * This file is part of libjdx.
4 : *
5 : * libjdx is free software: you can redistribute it and/or modify it under the
6 : * terms of the GNU Lesser General Public License as published by the Free
7 : * Software Foundation, either version 3 of the License, or (at your option) any
8 : * later version.
9 : *
10 : * libjdx is distributed in the hope that it will be useful, but WITHOUT ANY
11 : * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 : * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
13 : * details.
14 : *
15 : * You should have received a copy of the GNU Lesser General Public License
16 : * along with libjdx. If not, see <https://www.gnu.org/licenses/>.
17 : */
18 : #include "jdx/DataTable.hpp"
19 : #include "jdx/ParseException.hpp"
20 : #include "util/DataParser.hpp"
21 : #include "util/LdrUtils.hpp"
22 : #include "util/PeakTableParser.hpp"
23 : #include "util/StringUtils.hpp"
24 :
25 : #include <limits>
26 : #include <tuple>
27 :
28 67 : libjdx::jdx::DataTable::DataTable(std::string label, std::string variableList,
29 : std::optional<std::string> plotDescriptor,
30 : const std::vector<StringLdr>& blockLdrs,
31 : const std::vector<NTuplesAttributes>& nTuplesAttributes,
32 : const std::vector<StringLdr>& pageLdrs, io::TextReader& reader,
33 67 : std::optional<std::string>& nextLine)
34 67 : : Data2D(std::move(label), std::move(variableList), reader)
35 137 : , m_plotDescriptor{std::move(plotDescriptor)}
36 : {
37 : // extract permitted variable lists from mapping keys
38 134 : std::vector<std::string> permittedVarLists;
39 67 : permittedVarLists.reserve(s_varListMapping.size());
40 670 : for (const auto& keyValuePair : s_varListMapping)
41 : {
42 603 : permittedVarLists.emplace_back(keyValuePair.first);
43 : }
44 : // validate label and variable list
45 69 : validateInput(getLabel(), getVariableList(), s_label, permittedVarLists);
46 : // validate plot descriptor if present
47 66 : if (m_plotDescriptor)
48 : {
49 64 : determinePlotDescriptor(m_plotDescriptor.value());
50 : }
51 : // parse
52 66 : parse(blockLdrs, nTuplesAttributes, pageLdrs, nextLine);
53 66 : }
54 :
55 : const std::optional<std::string>&
56 12 : libjdx::jdx::DataTable::getPlotDescriptor() const
57 : {
58 12 : return m_plotDescriptor;
59 : }
60 :
61 : const libjdx::jdx::DataTable::Attributes&
62 18 : libjdx::jdx::DataTable::getAttributes() const
63 : {
64 18 : return m_mergedAttributes;
65 : }
66 :
67 11 : std::vector<std::pair<double, double>> libjdx::jdx::DataTable::getData() const
68 : {
69 11 : auto variableList = determineVariableList(getVariableList());
70 22 : auto dataTableParams = m_mergedAttributes;
71 :
72 11 : if (variableList == VariableList::XYXY || variableList == VariableList::XRXR
73 7 : || variableList == VariableList::XIXI)
74 : {
75 4 : auto xFactor = dataTableParams.xAttributes.factor.value_or(1.0);
76 4 : auto yFactor = dataTableParams.yAttributes.factor.value_or(1.0);
77 4 : auto nPoints = dataTableParams.yAttributes.varDim;
78 : return Data2D::parseXyXyData(
79 4 : getLabel(), getReader(), xFactor, yFactor, nPoints);
80 : }
81 :
82 7 : auto firstX = dataTableParams.xAttributes.first.value();
83 7 : auto lastX = dataTableParams.xAttributes.last.value();
84 7 : auto nPoints = dataTableParams.yAttributes.varDim.value();
85 7 : auto yFactor = dataTableParams.yAttributes.factor.value_or(1.0);
86 : return Data2D::parseXppYYData(
87 7 : getLabel(), getReader(), firstX, lastX, yFactor, nPoints);
88 : }
89 :
90 66 : void libjdx::jdx::DataTable::parse(const std::vector<StringLdr>& blockLdrs,
91 : const std::vector<NTuplesAttributes>& nTuplesVars,
92 : const std::vector<StringLdr>& pageLdrs,
93 : std::optional<std::string>& nextLine)
94 : {
95 66 : auto variableList = parseDataTableVars().first;
96 :
97 584 : auto findNTuplesIndex = [&nTuplesVars](const std::string& symbol) {
98 226 : for (size_t i = 0; i < nTuplesVars.size(); ++i)
99 : {
100 226 : if (nTuplesVars.at(i).symbol == symbol)
101 : {
102 132 : return i;
103 : }
104 : }
105 : throw ParseException(
106 0 : "Could not find NTUPLES parameters for SYMBOL: " + symbol);
107 66 : };
108 :
109 66 : auto xColumnIndex = std::numeric_limits<size_t>::max();
110 66 : auto yColumnIndex = std::numeric_limits<size_t>::max();
111 66 : if (variableList == VariableList::XppYY
112 64 : || variableList == VariableList::XYXY)
113 : {
114 21 : xColumnIndex = findNTuplesIndex("X");
115 21 : yColumnIndex = findNTuplesIndex("Y");
116 : }
117 45 : else if (variableList == VariableList::XppRR)
118 : {
119 21 : xColumnIndex = findNTuplesIndex("X");
120 21 : yColumnIndex = findNTuplesIndex("R");
121 : }
122 24 : else if (variableList == VariableList::XppII)
123 : {
124 21 : xColumnIndex = findNTuplesIndex("X");
125 21 : yColumnIndex = findNTuplesIndex("I");
126 : }
127 3 : else if (variableList == VariableList::T2ppRR)
128 : {
129 1 : xColumnIndex = findNTuplesIndex("T2");
130 1 : yColumnIndex = findNTuplesIndex("R");
131 : }
132 2 : else if (variableList == VariableList::T2ppII)
133 : {
134 1 : xColumnIndex = findNTuplesIndex("T2");
135 1 : yColumnIndex = findNTuplesIndex("I");
136 : }
137 1 : else if (variableList == VariableList::F2ppYY)
138 : {
139 1 : xColumnIndex = findNTuplesIndex("F2");
140 1 : yColumnIndex = findNTuplesIndex("Y");
141 : }
142 : else
143 : {
144 : // should never happen
145 : throw ParseException(
146 0 : "Unsupported variabe list in DATA TABLE: " + getVariableList());
147 : }
148 :
149 132 : auto xNTuplesAttrs = nTuplesVars.at(xColumnIndex);
150 132 : auto yNTuplesAttrs = nTuplesVars.at(yColumnIndex);
151 :
152 132 : auto mergedXVars = mergeVars(blockLdrs, xNTuplesAttrs, pageLdrs);
153 132 : auto mergedYVars = mergeVars(blockLdrs, yNTuplesAttrs, pageLdrs);
154 :
155 : // special treatment for "FIRST" page LDR if present
156 : // this is described in the README for the JCAMP-DX nD-NMR test file round
157 : // robin
158 66 : mergePageFirstLdr(mergedXVars, pageLdrs, xColumnIndex);
159 66 : mergePageFirstLdr(mergedYVars, pageLdrs, yColumnIndex);
160 :
161 66 : m_mergedAttributes = {mergedXVars, mergedYVars};
162 :
163 66 : auto& reader = getReader();
164 66 : util::skipToNextLdr(reader, nextLine, true);
165 66 : }
166 :
167 : std::pair<libjdx::jdx::Data2D::VariableList,
168 : std::optional<libjdx::jdx::DataTable::PlotDescriptor>>
169 66 : libjdx::jdx::DataTable::parseDataTableVars()
170 : {
171 66 : auto varType = determineVariableList(getVariableList());
172 66 : if (!m_plotDescriptor.has_value() || m_plotDescriptor.value().empty())
173 : {
174 2 : return {varType, std::nullopt};
175 : }
176 64 : auto plotDesc = determinePlotDescriptor(m_plotDescriptor.value());
177 64 : return {varType, plotDesc};
178 : }
179 :
180 77 : libjdx::jdx::Data2D::VariableList libjdx::jdx::DataTable::determineVariableList(
181 : const std::string& varList)
182 : {
183 77 : return findValue(s_varListMapping, varList, "variable list");
184 : }
185 :
186 : libjdx::jdx::DataTable::PlotDescriptor
187 128 : libjdx::jdx::DataTable::determinePlotDescriptor(
188 : const std::string& plotDescriptor)
189 : {
190 256 : return findValue(
191 256 : s_plotDescriptorMapping, plotDescriptor, "plot descriptor");
192 : }
193 :
194 132 : libjdx::jdx::NTuplesAttributes libjdx::jdx::DataTable::mergeVars(
195 : const std::vector<StringLdr>& blockLdrs,
196 : const NTuplesAttributes& nTuplesVars,
197 : const std::vector<StringLdr>& pageLdrs)
198 : {
199 132 : auto outputVars = nTuplesVars;
200 132 : outputVars.applicationAttributes.clear();
201 :
202 132 : if (std::any_of(s_xSymbols.begin(), s_xSymbols.end(),
203 268 : [&nTuplesVars](
204 268 : const std::string& s) { return s == nTuplesVars.symbol; }))
205 : {
206 : // use values from block relevant for abscissa
207 : std::map<std::string, std::optional<std::string>&> stringMapping{
208 66 : {"XUNITS", outputVars.units},
209 264 : };
210 : std::map<std::string, std::optional<double>&> doubleMapping{
211 66 : {"FIRSTX", outputVars.first},
212 66 : {"LASTX", outputVars.last},
213 66 : {"MINX", outputVars.min},
214 66 : {"MAXX", outputVars.max},
215 66 : {"XFACTOR", outputVars.factor},
216 528 : };
217 : std::map<std::string, std::optional<uint64_t>&> uint64Mapping{
218 66 : {"NPOINTS", outputVars.varDim},
219 198 : };
220 :
221 : // fill in block params for missing NTUPLE attributes
222 66 : mergeLdrs(
223 : blockLdrs, stringMapping, doubleMapping, uint64Mapping, false);
224 :
225 : // replace with page LDR values if available
226 66 : mergeLdrs(pageLdrs, stringMapping, doubleMapping, uint64Mapping, true);
227 : }
228 66 : else if (std::any_of(s_ySymbols.begin(), s_ySymbols.end(),
229 132 : [&nTuplesVars](
230 132 : const std::string& s) { return s == nTuplesVars.symbol; }))
231 : {
232 : // use values from block relevant for ordinate
233 : std::map<std::string, std::optional<std::string>&> stringMapping{
234 66 : {"YUNITS", outputVars.units},
235 264 : };
236 : std::map<std::string, std::optional<double>&> doubleMapping{
237 66 : {"FIRSTY", outputVars.first},
238 66 : {"LASTY", outputVars.last},
239 66 : {"MINY", outputVars.min},
240 66 : {"MAXY", outputVars.max},
241 66 : {"YFACTOR", outputVars.factor},
242 528 : };
243 : std::map<std::string, std::optional<uint64_t>&> uint64Mapping{
244 66 : {"NPOINTS", outputVars.varDim},
245 198 : };
246 : // Also check for other symbols but Y? Does not seem relevant for NMR
247 : // and MS.
248 :
249 : // fill in block params for missing NTUPLE attributes
250 66 : mergeLdrs(
251 : blockLdrs, stringMapping, doubleMapping, uint64Mapping, false);
252 :
253 : // replace with page LDR values if available
254 66 : mergeLdrs(pageLdrs, stringMapping, doubleMapping, uint64Mapping, true);
255 : }
256 : else
257 : {
258 : throw ParseException("Unexpected symbol found during parsing of PAGE: "
259 0 : + nTuplesVars.symbol);
260 : }
261 :
262 132 : return outputVars;
263 : }
264 :
265 264 : void libjdx::jdx::DataTable::mergeLdrs(const std::vector<StringLdr>& ldrs,
266 : std::map<std::string, std::optional<std::string>&> stringMapping,
267 : std::map<std::string, std::optional<double>&> doubleMapping,
268 : std::map<std::string, std::optional<uint64_t>&> uint64Mapping, bool replace)
269 : {
270 778 : for (const auto& ldr : ldrs)
271 : {
272 514 : if (stringMapping.count(ldr.getLabel()) > 0)
273 : {
274 4 : auto& field = stringMapping.at(ldr.getLabel());
275 4 : if (replace || !field || field.value().empty())
276 : {
277 4 : field = ldr.getValue();
278 : }
279 : }
280 510 : else if (doubleMapping.count(ldr.getLabel()) > 0)
281 : {
282 20 : auto& field = doubleMapping.at(ldr.getLabel());
283 20 : if (replace || !field)
284 : {
285 20 : field = std::stod(ldr.getValue());
286 : }
287 : }
288 490 : else if (uint64Mapping.count(ldr.getLabel()) > 0)
289 : {
290 32 : auto& field = uint64Mapping.at(ldr.getLabel());
291 32 : if (replace || !field)
292 : {
293 32 : field = std::stol(ldr.getValue());
294 : }
295 : }
296 : }
297 264 : }
298 :
299 132 : void libjdx::jdx::DataTable::mergePageFirstLdr(
300 : libjdx::jdx::NTuplesAttributes& mergedVars,
301 : const std::vector<StringLdr>& pageLdrs, size_t columnIndex)
302 : {
303 184 : for (const auto& ldr : pageLdrs)
304 : {
305 58 : if ("FIRST" == ldr.getLabel())
306 : {
307 18 : auto segments = util::split(ldr.getValue(), ",", true);
308 6 : if (segments.size() > columnIndex)
309 : {
310 6 : auto value = std::stod(segments.at(columnIndex));
311 6 : mergedVars.first = value;
312 : }
313 6 : break;
314 : }
315 : }
316 132 : }
|