Skip to content

Commit 0995793

Browse files
feat: Dissolve2 XRaySQNode (#2311)
1 parent 1dfa44f commit 0995793

File tree

13 files changed

+658
-42
lines changed

13 files changed

+658
-42
lines changed

src/data/formFactors.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ EnumOptions<XRayFormFactors::XRayFormFactorData> xRayFormFactorData()
1717
"XRayFormFactors", {{XRayFormFactors::NoFormFactorData, "None"}, {XRayFormFactors::WaasmaierKirfel1995, "WK1995"}});
1818
}
1919

20+
EnumOptions<XRayFormFactorData> getEnumOptions(XRayFormFactors::XRayFormFactorData) { return xRayFormFactorData(); }
21+
2022
// Return form factor data from specified dataset for given element and formal charge (if it exists)
2123
OptionalReferenceWrapper<const FormFactorData> formFactorData(XRayFormFactorData dataSet, Elements::Element Z, int formalCharge)
2224
{

src/data/formFactors.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ enum XRayFormFactorData
2222

2323
// Return EnumOptions for XRayFormFactorData
2424
EnumOptions<XRayFormFactorData> xRayFormFactorData();
25+
EnumOptions<XRayFormFactorData> getEnumOptions(XRayFormFactorData);
2526

2627
// Return form factor data from specified dataset for given element and formal charge (if it exists)
2728
OptionalReferenceWrapper<const FormFactorData> formFactorData(XRayFormFactorData dataSet, Elements::Element Z,

src/nodes/registry.cpp

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "nodes/subtract.h"
2828
#include "nodes/vec3Assembly.h"
2929
#include "nodes/vec3Decomposition.h"
30+
#include "nodes/xRaySQ/xRaySQ.h"
3031
#include <memory>
3132
#include <ranges>
3233

@@ -46,33 +47,32 @@ void NodeRegistry::instantiateNodeProducers()
4647
if (!producers_.empty())
4748
return;
4849

49-
producers_ = {
50-
{"Add", makeDerivedNode<AddNode>()},
51-
{"AtomicMC", makeDerivedNode<AtomicMCNode>()},
52-
{"Configuration", makeDerivedNode<ConfigurationNode>()},
53-
{"Data1DImport", makeDerivedNode<Data1DImportNode>()},
54-
{"Derivative", makeDerivedNode<DerivativeNode>()},
55-
{"DotProduct", makeDerivedNode<DotProductNode>()},
56-
{"Energy", makeDerivedNode<EnergyNode>()},
57-
{"Forcefield", makeDerivedNode<ForcefieldNode>()},
58-
{"GR", makeDerivedNode<GRNode>()},
59-
{"Graph", makeDerivedNode<Graph>()},
60-
{"GR", makeDerivedNode<GRNode>()},
61-
{"ImportConfigurationCoordinates", makeDerivedNode<ImportConfigurationCoordinatesNode>()},
62-
{"ImportConfigurationTrajectory", makeDerivedNode<ImportConfigurationTrajectoryNode>()},
63-
{"Insert", makeDerivedNode<InsertNode>()},
64-
{"Integrator", makeDerivedNode<Integrator1DNode>()},
65-
{"Iterator", makeDerivedNode<IterableGraph>()},
66-
{"MD", makeDerivedNode<MDNode>()},
67-
{"Multiply", makeDerivedNode<MultiplyNode>()},
68-
{"NeutronSQ", makeDerivedNode<NeutronSQNode>()},
69-
{"Number", makeDerivedNode<NumberNode>()},
70-
{"SQ", makeDerivedNode<SQNode>()},
71-
{"Species", makeDerivedNode<SpeciesNode>()},
72-
{"Subtract", makeDerivedNode<SubtractNode>()},
73-
{"Vec3Assembly", makeDerivedNode<Vec3AssemblyNode>()},
74-
{"Vec3Decomposition", makeDerivedNode<Vec3DecompositionNode>()},
75-
};
50+
producers_ = {{"Add", makeDerivedNode<AddNode>()},
51+
{"AtomicMC", makeDerivedNode<AtomicMCNode>()},
52+
{"Configuration", makeDerivedNode<ConfigurationNode>()},
53+
{"Data1DImport", makeDerivedNode<Data1DImportNode>()},
54+
{"Derivative", makeDerivedNode<DerivativeNode>()},
55+
{"DotProduct", makeDerivedNode<DotProductNode>()},
56+
{"Energy", makeDerivedNode<EnergyNode>()},
57+
{"Forcefield", makeDerivedNode<ForcefieldNode>()},
58+
{"GR", makeDerivedNode<GRNode>()},
59+
{"Graph", makeDerivedNode<Graph>()},
60+
{"GR", makeDerivedNode<GRNode>()},
61+
{"ImportConfigurationCoordinates", makeDerivedNode<ImportConfigurationCoordinatesNode>()},
62+
{"ImportConfigurationTrajectory", makeDerivedNode<ImportConfigurationTrajectoryNode>()},
63+
{"Insert", makeDerivedNode<InsertNode>()},
64+
{"Integrator", makeDerivedNode<Integrator1DNode>()},
65+
{"Iterator", makeDerivedNode<IterableGraph>()},
66+
{"MD", makeDerivedNode<MDNode>()},
67+
{"Multiply", makeDerivedNode<MultiplyNode>()},
68+
{"NeutronSQ", makeDerivedNode<NeutronSQNode>()},
69+
{"Number", makeDerivedNode<NumberNode>()},
70+
{"SQ", makeDerivedNode<SQNode>()},
71+
{"Species", makeDerivedNode<SpeciesNode>()},
72+
{"Subtract", makeDerivedNode<SubtractNode>()},
73+
{"Vec3Assembly", makeDerivedNode<Vec3AssemblyNode>()},
74+
{"Vec3Decomposition", makeDerivedNode<Vec3DecompositionNode>()},
75+
{"XRaySQ", makeDerivedNode<XRaySQNode>()}};
7676
}
7777

7878
// Check whether the supplied node type is known

src/nodes/xRaySQ/helpers.cpp

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
// Copyright (c) 2026 Team Dissolve and contributors
3+
4+
#define _USE_MATH_DEFINES
5+
#include "classes/configuration.h"
6+
#include "classes/species.h"
7+
#include "classes/xRayWeights.h"
8+
#include "io/export/data1D.h"
9+
#include "math/filters.h"
10+
#include "math/ft.h"
11+
#include "nodes/xRaySQ/xRaySQ.h"
12+
13+
bool XRaySQNode::setReferenceData()
14+
{
15+
// Normalise reference data to be consistent with the calculated data
16+
if (referenceNormalisedTo_ != normaliseTo_)
17+
{
18+
auto bBarSquareOfAverage = weights_.boundCoherentSquareOfAverage(referenceFQ_->xAxis());
19+
auto bBarAverageOfSquares = weights_.boundCoherentAverageOfSquares(referenceFQ_->xAxis());
20+
std::vector<double> factors;
21+
22+
// Set up the multiplication factors
23+
switch (referenceNormalisedTo_)
24+
{
25+
case (StructureFactors::NoNormalisation):
26+
factors =
27+
normaliseTo_ == StructureFactors::SquareOfAverageNormalisation ? bBarSquareOfAverage : bBarAverageOfSquares;
28+
std::transform(factors.begin(), factors.end(), factors.begin(), [](const auto factor) { return 1.0 / factor; });
29+
break;
30+
case (StructureFactors::SquareOfAverageNormalisation):
31+
factors = bBarSquareOfAverage;
32+
if (normaliseTo_ == StructureFactors::AverageOfSquaresNormalisation)
33+
std::transform(factors.begin(), factors.end(), bBarAverageOfSquares.begin(), factors.begin(),
34+
std::divides<>());
35+
break;
36+
case (StructureFactors::AverageOfSquaresNormalisation):
37+
factors = bBarAverageOfSquares;
38+
if (normaliseTo_ == StructureFactors::SquareOfAverageNormalisation)
39+
std::transform(factors.begin(), factors.end(), bBarSquareOfAverage.begin(), factors.begin(),
40+
std::divides<>());
41+
break;
42+
default:
43+
Messenger::exception("Unhandled StructureFactor::NormalisationType ({}).\n",
44+
StructureFactors::normalisationTypes().keyword(referenceNormalisedTo_));
45+
}
46+
47+
// Apply normalisation factors to the data
48+
std::transform(referenceFQ_->values().begin(), referenceFQ_->values().end(), factors.begin(),
49+
referenceFQ_->values().begin(), std::multiplies<>());
50+
}
51+
52+
// Get Q-range and window function to use for transformation of F(Q) to G(r)
53+
auto ftQMin = referenceFTQMin_.value_or(0.0);
54+
auto ftQMax = referenceFTQMax_.value_or(referenceFQ_->xAxis().back() + 1.0);
55+
if (referenceWindowFunction_ == WindowFunction::Form::None)
56+
Node::message("[SETUP {}] No window function will be applied in Fourier transform of reference data to g(r).", name());
57+
else
58+
Node::message("[SETUP {}] Window function to be applied in Fourier transform of reference data is {}.", name(),
59+
WindowFunction::forms().keyword(referenceWindowFunction_));
60+
61+
referenceGR_ = *referenceFQ_;
62+
Filters::trim(referenceGR_, ftQMin, ftQMax);
63+
auto rho = unweightedGR_->effectiveDensity();
64+
if (rho)
65+
Node::message(
66+
"[SETUP {}] Effective atomic density used in Fourier transform of reference data is {} atoms/Angstrom3.\n", name(),
67+
rho);
68+
else
69+
Node::message("[SETUP {}] Effective atomic density used in Fourier transform of reference data not yet "
70+
"available, so a default of 0.1 atoms/Angstrom3 used.\n",
71+
name());
72+
Fourier::sineFT(referenceGR_, 1.0 / (2.0 * M_PI * M_PI * rho), referenceFTDeltaR_, referenceFTDeltaR_, 30.0,
73+
WindowFunction(referenceWindowFunction_));
74+
75+
// Save data?
76+
if (saveReference_)
77+
{
78+
Data1DExportFileFormat exportFormat(std::format("{}-ReferenceData.q", name()));
79+
if (!exportFormat.exportData(*referenceFQ_))
80+
return false;
81+
Data1DExportFileFormat exportFormatFT(std::format("{}-ReferenceData.r", name()));
82+
if (!exportFormatFT.exportData(referenceGR_))
83+
return false;
84+
}
85+
86+
return true;
87+
}
88+
89+
// Return xRay weights
90+
const XRayWeights &XRaySQNode::weights() const { return weights_; }
91+
92+
// Calculate weighted g(r) from supplied unweighted g(r) and Weights
93+
bool XRaySQNode::calculateWeightedGR(const PartialSet &unweightedgr, PartialSet &weightedgr, const XRayWeights &weights,
94+
StructureFactors::NormalisationType normalisation)
95+
{
96+
dissolve::for_each_pair(ParallelPolicies::seq, unweightedgr.atomTypeFractions(),
97+
[&](int indexI, const auto &popI, int indexJ, const auto &popJ)
98+
{
99+
auto key = DoubleKeyedMapKey{popI.first->name(), popJ.first->name()};
100+
101+
auto weight = weights.weight(popI.first, popJ.first, 0.0);
102+
103+
// Bound (intramolecular) partial (multiplied by the bound term weight)
104+
weightedgr.boundPartials().get(key).copyArrays(unweightedgr.boundPartials().get(key));
105+
weightedgr.boundPartials().get(key) *= weight;
106+
107+
// Unbound partial (multiplied by the full weight)
108+
weightedgr.unboundPartials().get(key).copyArrays(unweightedgr.unboundPartials().get(key));
109+
weightedgr.unboundPartials().get(key) -= 1.0;
110+
weightedgr.unboundPartials().get(key) *= weight;
111+
112+
// Full partial, summing bound and unbound terms
113+
weightedgr.partials().get(key).copyArrays(weightedgr.unboundPartials().get(key));
114+
weightedgr.partials().get(key) += weightedgr.boundPartials().get(key);
115+
});
116+
117+
// Form total G(r)
118+
weightedgr.formTotals(false);
119+
120+
// Normalise to Q=0.0 form factor if requested
121+
if (normalisation != StructureFactors::NoNormalisation)
122+
{
123+
auto norm = normalisation == StructureFactors::AverageOfSquaresNormalisation
124+
? weights.boundCoherentAverageOfSquares(0.0)
125+
: weights.boundCoherentSquareOfAverage(0.0);
126+
127+
weightedgr.total() /= norm;
128+
weightedgr.boundTotal() /= norm;
129+
weightedgr.unboundTotal() /= norm;
130+
}
131+
132+
return true;
133+
}
134+
135+
// Calculate weighted S(Q) from supplied unweighted S(Q) and Weights
136+
bool XRaySQNode::calculateWeightedSQ(const PartialSet &unweightedsq, PartialSet &weightedsq, const XRayWeights &weights,
137+
StructureFactors::NormalisationType normalisation)
138+
{
139+
dissolve::for_each_pair(ParallelPolicies::seq, unweightedsq.atomTypeFractions(),
140+
[&](int indexI, const auto &popI, int indexJ, const auto &popJ)
141+
{
142+
auto key = DoubleKeyedMapKey{popI.first->name(), popJ.first->name()};
143+
144+
// Weight bound and unbound S(Q) and sum into full partial
145+
auto qWeights =
146+
weights.weight(popI.first, popJ.first, unweightedsq.boundPartials().get(key).xAxis());
147+
148+
// Bound (intramolecular) and unbound partials
149+
weightedsq.boundPartials().get(key).copyArrays(unweightedsq.boundPartials().get(key));
150+
weightedsq.boundPartials().get(key) *= qWeights;
151+
weightedsq.unboundPartials().get(key).copyArrays(unweightedsq.unboundPartials().get(key));
152+
weightedsq.unboundPartials().get(key) *= qWeights;
153+
154+
// Full partial (sum of bound and unbound terms)
155+
weightedsq.partials().get(key).copyArrays(weightedsq.unboundPartials().get(key));
156+
weightedsq.partials().get(key) += weightedsq.boundPartials().get(key);
157+
});
158+
159+
// Form total structure factor
160+
weightedsq.formTotals(false);
161+
162+
// Apply normalisation to all totals
163+
if (normalisation != StructureFactors::NoNormalisation)
164+
{
165+
auto bbar = normalisation == StructureFactors::SquareOfAverageNormalisation
166+
? weights.boundCoherentSquareOfAverage(weightedsq.total().xAxis())
167+
: weights.boundCoherentAverageOfSquares(weightedsq.total().xAxis());
168+
169+
std::transform(weightedsq.total().values().begin(), weightedsq.total().values().end(), bbar.begin(),
170+
weightedsq.total().values().begin(), std::divides<>());
171+
std::transform(weightedsq.boundTotal().values().begin(), weightedsq.boundTotal().values().end(), bbar.begin(),
172+
weightedsq.boundTotal().values().begin(), std::divides<>());
173+
std::transform(weightedsq.unboundTotal().values().begin(), weightedsq.unboundTotal().values().end(), bbar.begin(),
174+
weightedsq.unboundTotal().values().begin(), std::divides<>());
175+
}
176+
177+
return true;
178+
}

0 commit comments

Comments
 (0)