Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/classes/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ add_library(
distributor.cpp
empiricalFormula.cpp
fullPairIterator.cpp
histogramSet.cpp
isotopeData.cpp
isotopologue.cpp
isotopologues.cpp
Expand Down Expand Up @@ -96,6 +97,7 @@ add_library(
dataSource.h
distributor.h
empiricalFormula.h
histogramSet.h
interactionPotential.h
isotopeData.h
isotopologue.h
Expand Down
128 changes: 128 additions & 0 deletions src/classes/histogramSet.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2025 Team Dissolve and contributors

#include "classes/histogramSet.h"
#include "base/lineParser.h"
#include "classes/atomType.h"
#include "items/deserialisers.h"
#include "math/mathFunc.h"
#include "templates/algorithms.h"

HistogramSet::~HistogramSet()
{
fullHistograms_.clear();
boundHistograms_.clear();
unboundHistograms_.clear();
}

/*
* Data
*/

// Initialise histograms
void HistogramSet::initialise(const AtomTypeMix &atomTypeMix, double rdfRange, double binWidth)
{
atomTypeMix_ = atomTypeMix;

auto nTypes = atomTypeMix_.nItems();

fullHistograms_.initialise(nTypes, nTypes, half_);
boundHistograms_.initialise(nTypes, nTypes, half_);
unboundHistograms_.initialise(nTypes, nTypes, half_);

dissolve::for_each_pair(
ParallelPolicies::par, nTypes,
[&](int i, int j)
{
fullHistograms_[{i, j}].initialise(0.0, rdfRange, binWidth);
boundHistograms_[{i, j}].initialise(0.0, rdfRange, binWidth);
unboundHistograms_[{i, j}].initialise(0.0, rdfRange, binWidth);
},
half_);
}

// Clear all histogram data
void HistogramSet::clear()
{
fullHistograms_.clear();
boundHistograms_.clear();
unboundHistograms_.clear();
}

// Zero histogram bins
void HistogramSet::zeroBins()
{
for (auto &histo : fullHistograms_.linearArray())
histo.zeroBins();
for (auto &histo : boundHistograms_.linearArray())
histo.zeroBins();
for (auto &histo : unboundHistograms_.linearArray())
histo.zeroBins();
Comment on lines +55 to +60

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of a static method in Array2D, flatten, that takes a couple of Array2D<T>'s (like the above full/bound/unboundHistograms_) and flattens their linearArray()s into a single vector?

Suggested change
for (auto &histo : fullHistograms_.linearArray())
histo.zeroBins();
for (auto &histo : boundHistograms_.linearArray())
histo.zeroBins();
for (auto &histo : unboundHistograms_.linearArray())
histo.zeroBins();
for (auto &histo : Array2D::flatten({fullHistograms_, boundHistograms_, unboundHistograms_}))
histo.zeroBins();

I believe the helper method could be implemented like:

static std::vector<A> flatten(const std::vector<Array2D<A>> arrays)
{
    std::vector<A> flat;
    for (const auto &a : arrays)
          flat.insert(flat.end(), a.begin(), a.end());
    return flat;
} 

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean - in fact Adam's zip() operator would allow us to do this in one loop, albeit with three variables. Your suggestion makes a copy of the data, so unfortunately wouldn't have the intended result. I think I will leave this as-is for now, but your suggestion makes me think that some kind of function which takes a lambda and operates on every element in the Array2D would be useful to implement in the future (e.g. fullHistograms_.operate([](auto &histo) { histo.zeroBins(); }))

}

// Return atom types list
const AtomTypeMix &HistogramSet::atomTypeMix() const { return atomTypeMix_; }

// Set new fingerprint
void HistogramSet::setFingerprint(std::string_view fingerprint) { fingerprint_ = fingerprint; }

// Return fingerprint of partials
std::string_view HistogramSet::fingerprint() const { return fingerprint_; }

// Return full histogram specified
Histogram1D &HistogramSet::fullHistogram(int i, int j) { return fullHistograms_[{i, j}]; }

// Return bound histogram specified
Histogram1D &HistogramSet::boundHistogram(int i, int j) { return boundHistograms_[{i, j}]; }

// Return unbound histogram specified
Histogram1D &HistogramSet::unboundHistogram(int i, int j) { return unboundHistograms_[{i, j}]; }

/*
* Manipulation
*/

// Create partials from stored Histogram data
void HistogramSet::formPartials(PartialSet &partials, double boxVolume)
{
dissolve::for_each_pair(
ParallelPolicies::seq, atomTypeMix_,
[&](int n, const AtomTypeData &at1, int m, const AtomTypeData &at2)
{
// Calculate RDFs from histogram data
calculateRDF(partials.partial(n, m), fullHistograms_[{n, m}], boxVolume, at1.population(), at2.population(),
&at1 == &at2 ? 2.0 : 1.0);
calculateRDF(partials.boundPartial(n, m), boundHistograms_[{n, m}], boxVolume, at1.population(), at2.population(),
&at1 == &at2 ? 2.0 : 1.0);
calculateRDF(partials.unboundPartial(n, m), unboundHistograms_[{n, m}], boxVolume, at1.population(),
at2.population(), &at1 == &at2 ? 2.0 : 1.0);

// Set flags for bound partials specifying if they are empty (i.e. there are no
// contributions of that type)
partials.emptyBoundPartial(n, m) = boundHistograms_[{n, m}].nBinned() == 0;
},
half_);
}

// Calculate RDF from supplied Histogram and normalisation data
void HistogramSet::calculateRDF(Data1D &destination, const Histogram1D &histogram, double boxVolume, int nCentres,
int nSurrounding, double multiplier)
{
auto nBins = histogram.nBins();
double delta = histogram.binWidth();
const auto &bins = histogram.bins();

destination.clear();

double shellVolume, factor, r = 0.5 * delta, lowerShellLimit = 0.0, numberDensity = nSurrounding / boxVolume;
for (auto n = 0; n < nBins; ++n)
{
shellVolume = (4.0 / 3.0) * M_PI * (pow(lowerShellLimit + delta, 3.0) - pow(lowerShellLimit, 3.0));
factor = nCentres * (shellVolume * numberDensity);

destination.addPoint(r, bins[n] * (multiplier / factor));

r += delta;
lowerShellLimit += delta;
}
}
64 changes: 64 additions & 0 deletions src/classes/histogramSet.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2025 Team Dissolve and contributors

#pragma once

#include "classes/atomTypeMix.h"
#include "classes/partialSet.h"
#include "math/histogram1D.h"
#include "templates/array2D.h"

// Set of Histograms
class HistogramSet
{
public:
HistogramSet() = default;
~HistogramSet();

/*
* Data
*/
private:
// AtomTypeMix used to generate matrices
AtomTypeMix atomTypeMix_;
// Fingerprint for these partials (e.g. reflecting Configuration indices at which they were calculated)
std::string fingerprint_;
// Histograms used for calculating full atom-atom partials in r
Array2D<Histogram1D> fullHistograms_;
// Histograms used for calculating bound atom-atom partials in r
Array2D<Histogram1D> boundHistograms_;
// Histograms used for deriving unbound atom-atom partials in r
Array2D<Histogram1D> unboundHistograms_;
// Check for full or half matrix
bool half_{true};

public:
// Set up histograms
void initialise(const AtomTypeMix &atomTypeMix, double rdfRange, double binWidth);
// Clear all histogram data
void clear();
// Zero histogram bins
void zeroBins();
// Return atom types mis
const AtomTypeMix &atomTypeMix() const;
// Set new fingerprint
void setFingerprint(std::string_view fingerprint);
Comment thread
trisyoungs marked this conversation as resolved.
// Return fingerprint of partials
std::string_view fingerprint() const;
// Return full histogram specified
Histogram1D &fullHistogram(int i, int j);
// Return bound histogram specified
Histogram1D &boundHistogram(int i, int j);
// Return unbound histogram specified
Histogram1D &unboundHistogram(int i, int j);

/*
* Manipulation
*/
public:
// Form partials from stored Histogram data
void formPartials(PartialSet &partials, double boxVolume);
// Calculate RDF from supplied Histogram and normalisation data
static void calculateRDF(Data1D &destination, const Histogram1D &histogram, double boxVolume, int nCentres,
int nSurrounding, double multiplier);
};
110 changes: 6 additions & 104 deletions src/classes/partialSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ PartialSet::PartialSet(const std::map<const Species *, double> &realSpeciesPopul

PartialSet::~PartialSet()
{
fullHistograms_.clear();
boundHistograms_.clear();
unboundHistograms_.clear();

partials_.clear();
boundPartials_.clear();
emptyBoundPartials_.clear();
Expand All @@ -33,21 +29,8 @@ PartialSet::~PartialSet()
* Set of Partials
*/

// Set up PartialSet
bool PartialSet::setUp(const AtomTypeMix &atomTypeMix, double rdfRange, double binWidth)
{
// Set up partial arrays
if (!setUpPartials(atomTypeMix, half_))
return false;

// Initialise histograms for g(r) calculation
setUpHistograms(rdfRange, binWidth);

return true;
}

// Set up PartialSet without initialising histogram arrays
bool PartialSet::setUpPartials(const AtomTypeMix &atomTypeMix, bool half)
// Initialise
void PartialSet::initialise(const AtomTypeMix &atomTypeMix, bool half)
{
// Copy type array
atomTypeMix_ = atomTypeMix;
Expand Down Expand Up @@ -78,42 +61,11 @@ bool PartialSet::setUpPartials(const AtomTypeMix &atomTypeMix, bool half)
total_.clear();
boundTotal_.clear();
unboundTotal_.clear();

return true;
}

// Set up histogram arrays for g(r) calculation
void PartialSet::setUpHistograms(double rdfRange, double binWidth)
{
auto nTypes = atomTypeMix_.nItems();

fullHistograms_.initialise(nTypes, nTypes, half_);
boundHistograms_.initialise(nTypes, nTypes, half_);
unboundHistograms_.initialise(nTypes, nTypes, half_);

dissolve::for_each_pair(
ParallelPolicies::par, nTypes,
[&](int i, int j)
{
fullHistograms_[{i, j}].initialise(0.0, rdfRange, binWidth);
boundHistograms_[{i, j}].initialise(0.0, rdfRange, binWidth);
unboundHistograms_[{i, j}].initialise(0.0, rdfRange, binWidth);
},
half_);
}

// Reset partial arrays
void PartialSet::reset()
{
// Zero histogram bins if present
for (auto n = 0; n < fullHistograms_.nRows(); ++n)
for (auto m = n; m < fullHistograms_.nColumns(); ++m)
{
fullHistograms_[{n, m}].zeroBins();
boundHistograms_[{n, m}].zeroBins();
unboundHistograms_[{n, m}].zeroBins();
}

// Zero partials
dissolve::for_each_pair(
ParallelPolicies::par, atomTypeMix_.nItems(),
Expand Down Expand Up @@ -144,15 +96,6 @@ void PartialSet::setFingerprint(std::string_view fingerprint) { fingerprint_ = f
// Return fingerprint of partials
std::string_view PartialSet::fingerprint() const { return fingerprint_; }

// Return full histogram specified
Histogram1D &PartialSet::fullHistogram(int i, int j) { return fullHistograms_[{i, j}]; }

// Return bound histogram specified
Histogram1D &PartialSet::boundHistogram(int i, int j) { return boundHistograms_[{i, j}]; }

// Return unbound histogram specified
Histogram1D &PartialSet::unboundHistogram(int i, int j) { return unboundHistograms_[{i, j}]; }

// Return full atom-atom partial specified
Data1D &PartialSet::partial(int i, int j) { return partials_[{i, j}]; }
const Data1D &PartialSet::partial(int i, int j) const { return partials_[{i, j}]; }
Expand All @@ -165,6 +108,10 @@ const Data1D &PartialSet::unboundPartial(int i, int j) const { return unboundPar
Data1D &PartialSet::boundPartial(int i, int j) { return boundPartials_[{i, j}]; }
const Data1D &PartialSet::boundPartial(int i, int j) const { return boundPartials_[{i, j}]; }

// Return emptyBound flag
char &PartialSet::emptyBoundPartial(int i, int j) { return emptyBoundPartials_[{i, j}]; }
const char &PartialSet::emptyBoundPartial(int i, int j) const { return emptyBoundPartials_[{i, j}]; }

// Return whether specified bound partial is empty
bool PartialSet::isBoundPartialEmpty(int i, int j) const { return emptyBoundPartials_[{i, j}]; }

Expand Down Expand Up @@ -344,28 +291,6 @@ void PartialSet::adjust(double delta)
unboundTotal_ += delta;
}

// Form partials from stored Histogram data
void PartialSet::formPartials(double boxVolume)
{
dissolve::for_each_pair(
ParallelPolicies::seq, atomTypeMix_,
[&](int n, const AtomTypeData &at1, int m, const AtomTypeData &at2)
{
// Calculate RDFs from histogram data
calculateRDF(partials_[{n, m}], fullHistograms_[{n, m}], boxVolume, at1.population(), at2.population(),
&at1 == &at2 ? 2.0 : 1.0);
calculateRDF(boundPartials_[{n, m}], boundHistograms_[{n, m}], boxVolume, at1.population(), at2.population(),
&at1 == &at2 ? 2.0 : 1.0);
calculateRDF(unboundPartials_[{n, m}], unboundHistograms_[{n, m}], boxVolume, at1.population(), at2.population(),
&at1 == &at2 ? 2.0 : 1.0);

// Set flags for bound partials specifying if they are empty (i.e. there are no
// contributions of that type)
emptyBoundPartials_[{n, m}] = boundHistograms_[{n, m}].nBinned() == 0;
},
half_);
}

// Add in partials from source PartialSet to our own
bool PartialSet::addPartials(PartialSet &source, double weighting)
{
Expand Down Expand Up @@ -410,29 +335,6 @@ bool PartialSet::addPartials(PartialSet &source, double weighting)
return true;
}

// Calculate RDF from supplied Histogram and normalisation data
void PartialSet::calculateRDF(Data1D &destination, const Histogram1D &histogram, double boxVolume, int nCentres,
int nSurrounding, double multiplier)
{
auto nBins = histogram.nBins();
double delta = histogram.binWidth();
const auto &bins = histogram.bins();

destination.clear();

double shellVolume, factor, r = 0.5 * delta, lowerShellLimit = 0.0, numberDensity = nSurrounding / boxVolume;
for (auto n = 0; n < nBins; ++n)
{
shellVolume = (4.0 / 3.0) * M_PI * (pow(lowerShellLimit + delta, 3.0) - pow(lowerShellLimit, 3.0));
factor = nCentres * (shellVolume * numberDensity);

destination.addPoint(r, bins[n] * (multiplier / factor));

r += delta;
lowerShellLimit += delta;
}
}

/*
* Operators
*/
Expand Down
Loading
Loading