<commit_before>/*
 * ParticleThermodyn.cpp
 *
 * Copyright (C) 2017 by MegaMol team
 * Alle Rechte vorbehalten.
 */
#include "stdafx.h"
#include "ParticleThermodyn.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <cfloat>
#include <cfenv>
#include <cstdint>
#include <limits>
#include <omp.h>
#include "mmcore/param/BoolParam.h"
#include "mmcore/param/EnumParam.h"
#include "mmcore/param/FloatParam.h"
#include "mmcore/param/IntParam.h"
#include "mmcore/utility/sys/ConsoleProgressBar.h"
#include "mmcore/utility/log/Log.h"

#include "MinSphereWrapper.h"

using namespace megamol;
using namespace megamol::stdplugin;

/*
 * datatools::ParticleThermodyn::ParticleThermodyn
 */
datatools::ParticleThermodyn::ParticleThermodyn(void)
    : cyclXSlot("cyclX", "Considers cyclic boundary conditions in X direction")
    , cyclYSlot("cyclY", "Considers cyclic boundary conditions in Y direction")
    , cyclZSlot("cyclZ", "Considers cyclic boundary conditions in Z direction")
    , radiusSlot("radius", "the radius in which to look for neighbors")
    , numNeighborSlot("numNeighbors", "how many neighbors to collect")
    , searchTypeSlot("searchType", "num of neighbors or radius")
    , minMetricSlot("minMetric", "the detected minimum of the selected metric")
    , maxMetricSlot("maxMetric", "the detected maximum of the selected metric")
    , massSlot("mass", "the mass of the particles")
    , freedomSlot("freedomFactor", "factor reducing T* based on degrees of freedom of the molecular model")
    , metricsSlot("metrics", "the metrics you want to computer from the neighborhood")
    , removeSelfSlot("remove self", "whether a particle itself is part of the neighborhood")
    , findExtremesSlot("find extremes", "whether to only color particles with values larger than extremevalue. Make "
                                        "sure you have a transfer function that has stops at 0.4 and 0.5. The 0.5 stop "
                                        "allows you to highlight the neighbors responsible for the extremes.")
    , extremeValueSlot("extreme value", "the extreme value that you find weird")
    , fluidDensitySlot("phase01::fluid density", "Density of the fluid")
    , tcSlot("phase02::Tc", "Critical temperature")
    , rhocSlot("phase02::RhoC", "Critical density")
    , datahash(0)
    , lastTime(-1)
    , newColors()
    , allParts()
    , maxDist(0.0f)
    , particleTree(nullptr)
    , myPts(nullptr)
    , outDataSlot("outData", "Provides intensities based on a local particle metric")
    , inDataSlot("inData", "Takes the directional particle data") {

    this->cyclXSlot.SetParameter(new core::param::BoolParam(true));
    this->MakeSlotAvailable(&this->cyclXSlot);

    this->cyclYSlot.SetParameter(new core::param::BoolParam(true));
    this->MakeSlotAvailable(&this->cyclYSlot);

    this->cyclZSlot.SetParameter(new core::param::BoolParam(true));
    this->MakeSlotAvailable(&this->cyclZSlot);

    this->radiusSlot.SetParameter(new core::param::FloatParam(2.0));
    this->MakeSlotAvailable(&this->radiusSlot);

    this->numNeighborSlot.SetParameter(new core::param::IntParam(10));
    this->MakeSlotAvailable(&this->numNeighborSlot);

    core::param::EnumParam* st = new core::param::EnumParam(searchTypeEnum::NUM_NEIGHBORS);
    st->SetTypePair(searchTypeEnum::RADIUS, "Radius");
    st->SetTypePair(searchTypeEnum::NUM_NEIGHBORS, "Num. Neighbors");
    this->searchTypeSlot << st;
    this->MakeSlotAvailable(&this->searchTypeSlot);

    core::param::EnumParam* mt = new core::param::EnumParam(metricsEnum::TEMPERATURE);
    mt->SetTypePair(metricsEnum::TEMPERATURE, "Temperature");
    mt->SetTypePair(metricsEnum::DENSITY, "Density");
    mt->SetTypePair(metricsEnum::FRACTIONAL_ANISOTROPY, "Fractional Anisotropy");
    mt->SetTypePair(metricsEnum::PRESSURE, "Pressure");
    mt->SetTypePair(metricsEnum::NEIGHBORS, "Num Neighbors");
    mt->SetTypePair(metricsEnum::NEAREST_DISTANCE, "Nearest Dist");
    mt->SetTypePair(metricsEnum::PHASE01, "Phase01");
    mt->SetTypePair(metricsEnum::PHASE02, "Phase02");
    this->metricsSlot << mt;
    this->MakeSlotAvailable(&this->metricsSlot);

    this->minMetricSlot.SetParameter(new core::param::FloatParam(0));
    this->MakeSlotAvailable(&this->minMetricSlot);

    this->maxMetricSlot.SetParameter(new core::param::FloatParam(0));
    this->MakeSlotAvailable(&this->maxMetricSlot);

    this->massSlot.SetParameter(new core::param::FloatParam(1.0f));
    this->MakeSlotAvailable(&this->massSlot);

    this->freedomSlot.SetParameter(
        new core::param::FloatParam(1.5f)); // works for single-center models. 3 degrees of freedom -> 3/2
    this->MakeSlotAvailable(&this->freedomSlot);

    this->removeSelfSlot.SetParameter(new core::param::BoolParam(false));
    this->MakeSlotAvailable(&this->removeSelfSlot);

    this->findExtremesSlot.SetParameter(new core::param::BoolParam(false));
    this->MakeSlotAvailable(&this->findExtremesSlot);

    this->extremeValueSlot.SetParameter(new core::param::FloatParam(50.0));
    this->MakeSlotAvailable(&this->extremeValueSlot);

    this->fluidDensitySlot.SetParameter(new core::param::FloatParam(1.0f));
    this->MakeSlotAvailable(&this->fluidDensitySlot);

    this->tcSlot.SetParameter(new core::param::FloatParam(1.0795f));
    this->MakeSlotAvailable(&this->tcSlot);
    this->rhocSlot.SetParameter(new core::param::FloatParam(0.3211f));
    this->MakeSlotAvailable(&this->rhocSlot);

    this->outDataSlot.SetCallback(
        megamol::core::moldyn::MultiParticleDataCall::ClassName(), "GetData", &ParticleThermodyn::getDataCallback);
    this->outDataSlot.SetCallback(
        megamol::core::moldyn::MultiParticleDataCall::ClassName(), "GetExtent", &ParticleThermodyn::getExtentCallback);
    this->MakeSlotAvailable(&this->outDataSlot);

    this->inDataSlot.SetCompatibleCall<megamol::core::moldyn::MultiParticleDataCallDescription>();
    this->MakeSlotAvailable(&this->inDataSlot);
}


/*
 * datatools::ParticleColorSignedDistance::~ParticleColorSignedDistance
 */
datatools::ParticleThermodyn::~ParticleThermodyn(void) { this->Release(); }

/*
 * datatools::ParticleThermodyn::create
 */
bool datatools::ParticleThermodyn::create(void) { return true; }


bool isListOK(megamol::core::moldyn::MultiParticleDataCall* in, const unsigned int i) {
    using megamol::core::moldyn::MultiParticleDataCall;
    auto& pl = in->AccessParticles(i);
    // TODO: double
    return ((pl.GetVertexDataType() == MultiParticleDataCall::Particles::VertexDataType::VERTDATA_FLOAT_XYZ) ||
               (pl.GetVertexDataType() == MultiParticleDataCall::Particles::VertexDataType::VERTDATA_FLOAT_XYZR));
}


bool hasDir(megamol::core::moldyn::MultiParticleDataCall* in, const unsigned int i) {
    using megamol::core::moldyn::MultiParticleDataCall;
    auto& pl = in->AccessParticles(i);
    return pl.GetDirDataType() == MultiParticleDataCall::Particles::DirDataType::DIRDATA_FLOAT_XYZ;
}


bool metricRequiresDir(megamol::stdplugin::datatools::ParticleThermodyn::metricsEnum metric) {
    return metric == megamol::stdplugin::datatools::ParticleThermodyn::metricsEnum::TEMPERATURE ||
           metric == megamol::stdplugin::datatools::ParticleThermodyn::metricsEnum::FRACTIONAL_ANISOTROPY;
}


bool isDirOK(megamol::stdplugin::datatools::ParticleThermodyn::metricsEnum metric,
    megamol::core::moldyn::MultiParticleDataCall* in, const unsigned int i) {
    if (metricRequiresDir(metric) && !hasDir(in, i))
        return false;
    return true;
}


/*
 * datatools::ParticleThermodyn::release
 */
void datatools::ParticleThermodyn::release(void) {}


bool datatools::ParticleThermodyn::assertData(core::moldyn::MultiParticleDataCall* in,
    core::moldyn::MultiParticleDataCall* outMPDC) {

    using megamol::core::moldyn::MultiParticleDataCall;

    megamol::core::AbstractGetData3DCall* out;
    if (outMPDC != nullptr) out = outMPDC;

    const unsigned int time = out->FrameID();
    unsigned int plc = in->GetParticleListCount();
    float theRadius = this->radiusSlot.Param<core::param::FloatParam>()->Value();
    float theSquaredRadius = theRadius * theRadius;
    const float theMass = this->massSlot.Param<core::param::FloatParam>()->Value();
    const float theFreedom = this->freedomSlot.Param<core::param::FloatParam>()->Value();
    const int theNumber = this->numNeighborSlot.Param<core::param::IntParam>()->Value();
    const auto theSearchType = this->searchTypeSlot.Param<core::param::EnumParam>()->Value();
    const auto theMetrics = this->metricsSlot.Param<core::param::EnumParam>()->Value();
    const auto theFluidDensity = this->fluidDensitySlot.Param<core::param::FloatParam>()->Value();
    size_t allpartcnt = 0;

    if (this->lastTime != time || this->datahash != in->DataHash()) {
        in->SetFrameID(time, true);

        if (!(*in)(0)) {
            megamol::core::utility::log::Log::DefaultLog.WriteError("ParticleThermodyn: could not get frame (%u)", time);
            return false;
        }

        size_t totalParts = 0;
        plc = in->GetParticleListCount();

        for (unsigned int i = 0; i < plc; i++) {
            if (isListOK(in, i) && isDirOK(static_cast<metricsEnum>(theMetrics), in, i))
                totalParts += in->AccessParticles(i).GetCount();
        }

        if (theSearchType == searchTypeEnum::RADIUS) {
            this->newColors.resize(totalParts, theRadius);
        } else {
            this->newColors.resize(totalParts);
        }

        allParts.clear();
        allParts.reserve(totalParts);

        // we could now filter particles according to something. but currently we need not.
        allpartcnt = 0;
        for (unsigned int pli = 0; pli < plc; pli++) {
            auto& pl = in->AccessParticles(pli);
            if (!isListOK(in, pli) || !isDirOK(static_cast<metricsEnum>(theMetrics), in, pli)) {
                megamol::core::utility::log::Log::DefaultLog.WriteWarn(
                    "ParticleThermodyn: ignoring list %d because it either has no proper positions or no velocity",
                    pli);
                continue;
            }

            // unsigned int vert_stride = 0;
            // if (pl.GetVertexDataType() == DirectionalParticleDataCall::Particles::VertexDataType::VERTDATA_FLOAT_XYZ)
            // vert_stride = 12; else if (pl.GetVertexDataType() ==
            // DirectionalParticleDataCall::Particles::VertexDataType::VERTDATA_FLOAT_XYZR) vert_stride = 16; else
            // continue; vert_stride = std::max<unsigned int>(vert_stride, pl.GetVertexDataStride()); const unsigned char
            // *vert = static_cast<const unsigned char*>(pl.GetVertexData());

            UINT64 part_cnt = pl.GetCount();

            for (int part_i = 0; part_i < part_cnt; ++part_i) {
                allParts.push_back(allpartcnt + part_i);
            }
            allpartcnt += pl.GetCount();
        }

        // allocate nanoflann data structures for border
        assert(allpartcnt == totalParts);
        this->myPts = std::make_shared<simplePointcloud>(in, allParts);

        megamol::core::utility::log::Log::DefaultLog.WriteInfo("ParticleThermodyn: building acceleration structure...");
        particleTree = std::make_shared<my_kd_tree_t>(
            3 /* dim */, *myPts, nanoflann::KDTreeSingleIndexAdaptorParams(10 /* max leaf */));
        particleTree->buildIndex();
        megamol::core::utility::log::Log::DefaultLog.WriteInfo("ParticleThermodyn: done.");

        this->datahash = in->DataHash();
        this->lastTime = time;
        this->radiusSlot.ForceSetDirty();
    }

    if (this->radiusSlot.IsDirty() || this->cyclXSlot.IsDirty() || this->cyclYSlot.IsDirty() ||
        this->cyclZSlot.IsDirty() || this->numNeighborSlot.IsDirty() || this->searchTypeSlot.IsDirty() ||
        this->metricsSlot.IsDirty() || this->removeSelfSlot.IsDirty() || this->findExtremesSlot.IsDirty() ||
        this->extremeValueSlot.IsDirty() || this->fluidDensitySlot.IsDirty()) {
        allpartcnt = 0;
        ++myHash;

        // final computation
        bool cycl_x = this->cyclXSlot.Param<megamol::core::param::BoolParam>()->Value();
        bool cycl_y = this->cyclYSlot.Param<megamol::core::param::BoolParam>()->Value();
        bool cycl_z = this->cyclZSlot.Param<megamol::core::param::BoolParam>()->Value();
        auto bbox = in->AccessBoundingBoxes().ObjectSpaceBBox();
        // bbox.EnforcePositiveSize(); // paranoia
        auto bbox_cntr = bbox.CalcCenter();

        vislib::sys::ConsoleProgressBar cpb;
        const int progressDivider = 100;
        cpb.Start("measuring thermodynamics",
            static_cast<vislib::sys::ConsoleProgressBar::Size>(newColors.size() / progressDivider));

        float theMinTemp = FLT_MAX;
        float theMaxTemp = 0.0f;

        /*auto const search_volume = 4.0f / 3.0f * 3.14f * theRadius * theRadius * theRadius;
        auto const inv_search_volume = 1.0f / search_volume;*/
        auto const phase_krit = 0.5f * theFluidDensity;

        const bool remove_self = this->removeSelfSlot.Param<megamol::core::param::BoolParam>()->Value();

        auto const T_c = tcSlot.Param<core::param::FloatParam>()->Value();
        auto const rho_c = rhocSlot.Param<core::param::FloatParam>()->Value();

        allpartcnt = 0;
        for (unsigned int pli = 0; pli < plc; pli++) {
            auto& pl = in->AccessParticles(pli);
            if (!isListOK(in, pli) || !isDirOK(static_cast<metricsEnum>(theMetrics), in, pli)) {
                continue;
            }

            int num_thr = omp_get_max_threads();
            INT64 counter = 0;

            std::vector<float> metricMin(num_thr, FLT_MAX);<commit_msg>Thermodyn progress fix and forceframe paranoia<commit_after>/*
 * ParticleThermodyn.cpp
 *
 * Copyright (C) 2017 by MegaMol team
 * Alle Rechte vorbehalten.
 */
#include "stdafx.h"
#include "ParticleThermodyn.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <cfloat>
#include <cfenv>
#include <cstdint>
#include <limits>
#include <omp.h>
#include "mmcore/param/BoolParam.h"
#include "mmcore/param/EnumParam.h"
#include "mmcore/param/FloatParam.h"
#include "mmcore/param/IntParam.h"
#include "mmcore/utility/sys/ConsoleProgressBar.h"
#include "mmcore/utility/log/Log.h"

#include "MinSphereWrapper.h"

using namespace megamol;
using namespace megamol::stdplugin;

/*
 * datatools::ParticleThermodyn::ParticleThermodyn
 */
datatools::ParticleThermodyn::ParticleThermodyn(void)
    : cyclXSlot("cyclX", "Considers cyclic boundary conditions in X direction")
    , cyclYSlot("cyclY", "Considers cyclic boundary conditions in Y direction")
    , cyclZSlot("cyclZ", "Considers cyclic boundary conditions in Z direction")
    , radiusSlot("radius", "the radius in which to look for neighbors")
    , numNeighborSlot("numNeighbors", "how many neighbors to collect")
    , searchTypeSlot("searchType", "num of neighbors or radius")
    , minMetricSlot("minMetric", "the detected minimum of the selected metric")
    , maxMetricSlot("maxMetric", "the detected maximum of the selected metric")
    , massSlot("mass", "the mass of the particles")
    , freedomSlot("freedomFactor", "factor reducing T* based on degrees of freedom of the molecular model")
    , metricsSlot("metrics", "the metrics you want to computer from the neighborhood")
    , removeSelfSlot("remove self", "whether a particle itself is part of the neighborhood")
    , findExtremesSlot("find extremes", "whether to only color particles with values larger than extremevalue. Make "
                                        "sure you have a transfer function that has stops at 0.4 and 0.5. The 0.5 stop "
                                        "allows you to highlight the neighbors responsible for the extremes.")
    , extremeValueSlot("extreme value", "the extreme value that you find weird")
    , fluidDensitySlot("phase01::fluid density", "Density of the fluid")
    , tcSlot("phase02::Tc", "Critical temperature")
    , rhocSlot("phase02::RhoC", "Critical density")
    , datahash(0)
    , lastTime(-1)
    , newColors()
    , allParts()
    , maxDist(0.0f)
    , particleTree(nullptr)
    , myPts(nullptr)
    , outDataSlot("outData", "Provides intensities based on a local particle metric")
    , inDataSlot("inData", "Takes the directional particle data") {

    this->cyclXSlot.SetParameter(new core::param::BoolParam(true));
    this->MakeSlotAvailable(&this->cyclXSlot);

    this->cyclYSlot.SetParameter(new core::param::BoolParam(true));
    this->MakeSlotAvailable(&this->cyclYSlot);

    this->cyclZSlot.SetParameter(new core::param::BoolParam(true));
    this->MakeSlotAvailable(&this->cyclZSlot);

    this->radiusSlot.SetParameter(new core::param::FloatParam(2.0));
    this->MakeSlotAvailable(&this->radiusSlot);

    this->numNeighborSlot.SetParameter(new core::param::IntParam(10));
    this->MakeSlotAvailable(&this->numNeighborSlot);

    core::param::EnumParam* st = new core::param::EnumParam(searchTypeEnum::NUM_NEIGHBORS);
    st->SetTypePair(searchTypeEnum::RADIUS, "Radius");
    st->SetTypePair(searchTypeEnum::NUM_NEIGHBORS, "Num. Neighbors");
    this->searchTypeSlot << st;
    this->MakeSlotAvailable(&this->searchTypeSlot);

    core::param::EnumParam* mt = new core::param::EnumParam(metricsEnum::TEMPERATURE);
    mt->SetTypePair(metricsEnum::TEMPERATURE, "Temperature");
    mt->SetTypePair(metricsEnum::DENSITY, "Density");
    mt->SetTypePair(metricsEnum::FRACTIONAL_ANISOTROPY, "Fractional Anisotropy");
    mt->SetTypePair(metricsEnum::PRESSURE, "Pressure");
    mt->SetTypePair(metricsEnum::NEIGHBORS, "Num Neighbors");
    mt->SetTypePair(metricsEnum::NEAREST_DISTANCE, "Nearest Dist");
    mt->SetTypePair(metricsEnum::PHASE01, "Phase01");
    mt->SetTypePair(metricsEnum::PHASE02, "Phase02");
    this->metricsSlot << mt;
    this->MakeSlotAvailable(&this->metricsSlot);

    this->minMetricSlot.SetParameter(new core::param::FloatParam(0));
    this->MakeSlotAvailable(&this->minMetricSlot);

    this->maxMetricSlot.SetParameter(new core::param::FloatParam(0));
    this->MakeSlotAvailable(&this->maxMetricSlot);

    this->massSlot.SetParameter(new core::param::FloatParam(1.0f));
    this->MakeSlotAvailable(&this->massSlot);

    this->freedomSlot.SetParameter(
        new core::param::FloatParam(1.5f)); // works for single-center models. 3 degrees of freedom -> 3/2
    this->MakeSlotAvailable(&this->freedomSlot);

    this->removeSelfSlot.SetParameter(new core::param::BoolParam(false));
    this->MakeSlotAvailable(&this->removeSelfSlot);

    this->findExtremesSlot.SetParameter(new core::param::BoolParam(false));
    this->MakeSlotAvailable(&this->findExtremesSlot);

    this->extremeValueSlot.SetParameter(new core::param::FloatParam(50.0));
    this->MakeSlotAvailable(&this->extremeValueSlot);

    this->fluidDensitySlot.SetParameter(new core::param::FloatParam(1.0f));
    this->MakeSlotAvailable(&this->fluidDensitySlot);

    this->tcSlot.SetParameter(new core::param::FloatParam(1.0795f));
    this->MakeSlotAvailable(&this->tcSlot);
    this->rhocSlot.SetParameter(new core::param::FloatParam(0.3211f));
    this->MakeSlotAvailable(&this->rhocSlot);

    this->outDataSlot.SetCallback(
        megamol::core::moldyn::MultiParticleDataCall::ClassName(), "GetData", &ParticleThermodyn::getDataCallback);
    this->outDataSlot.SetCallback(
        megamol::core::moldyn::MultiParticleDataCall::ClassName(), "GetExtent", &ParticleThermodyn::getExtentCallback);
    this->MakeSlotAvailable(&this->outDataSlot);

    this->inDataSlot.SetCompatibleCall<megamol::core::moldyn::MultiParticleDataCallDescription>();
    this->MakeSlotAvailable(&this->inDataSlot);
}


/*
 * datatools::ParticleColorSignedDistance::~ParticleColorSignedDistance
 */
datatools::ParticleThermodyn::~ParticleThermodyn(void) { this->Release(); }

/*
 * datatools::ParticleThermodyn::create
 */
bool datatools::ParticleThermodyn::create(void) { return true; }


bool isListOK(megamol::core::moldyn::MultiParticleDataCall* in, const unsigned int i) {
    using megamol::core::moldyn::MultiParticleDataCall;
    auto& pl = in->AccessParticles(i);
    // TODO: double
    return ((pl.GetVertexDataType() == MultiParticleDataCall::Particles::VertexDataType::VERTDATA_FLOAT_XYZ) ||
               (pl.GetVertexDataType() == MultiParticleDataCall::Particles::VertexDataType::VERTDATA_FLOAT_XYZR));
}


bool hasDir(megamol::core::moldyn::MultiParticleDataCall* in, const unsigned int i) {
    using megamol::core::moldyn::MultiParticleDataCall;
    auto& pl = in->AccessParticles(i);
    return pl.GetDirDataType() == MultiParticleDataCall::Particles::DirDataType::DIRDATA_FLOAT_XYZ;
}


bool metricRequiresDir(megamol::stdplugin::datatools::ParticleThermodyn::metricsEnum metric) {
    return metric == megamol::stdplugin::datatools::ParticleThermodyn::metricsEnum::TEMPERATURE ||
           metric == megamol::stdplugin::datatools::ParticleThermodyn::metricsEnum::FRACTIONAL_ANISOTROPY;
}


bool isDirOK(megamol::stdplugin::datatools::ParticleThermodyn::metricsEnum metric,
    megamol::core::moldyn::MultiParticleDataCall* in, const unsigned int i) {
    if (metricRequiresDir(metric) && !hasDir(in, i))
        return false;
    return true;
}


/*
 * datatools::ParticleThermodyn::release
 */
void datatools::ParticleThermodyn::release(void) {}


bool datatools::ParticleThermodyn::assertData(core::moldyn::MultiParticleDataCall* in,
    core::moldyn::MultiParticleDataCall* outMPDC) {

    using megamol::core::moldyn::MultiParticleDataCall;

    megamol::core::AbstractGetData3DCall* out;
    if (outMPDC != nullptr) out = outMPDC;

    const unsigned int time = out->FrameID();
    unsigned int plc = in->GetParticleListCount();
    float theRadius = this->radiusSlot.Param<core::param::FloatParam>()->Value();
    float theSquaredRadius = theRadius * theRadius;
    const float theMass = this->massSlot.Param<core::param::FloatParam>()->Value();
    const float theFreedom = this->freedomSlot.Param<core::param::FloatParam>()->Value();
    const int theNumber = this->numNeighborSlot.Param<core::param::IntParam>()->Value();
    const auto theSearchType = this->searchTypeSlot.Param<core::param::EnumParam>()->Value();
    const auto theMetrics = this->metricsSlot.Param<core::param::EnumParam>()->Value();
    const auto theFluidDensity = this->fluidDensitySlot.Param<core::param::FloatParam>()->Value();
    size_t allpartcnt = 0;

    if (this->lastTime != time || this->datahash != in->DataHash()) {
        in->SetFrameID(time, true);
        do {
            if (!(*in)(1)) return false;
            if (!(*in)(0)) return false;
        } while(in->FrameID() != time);

        if (!(*in)(0)) {
            megamol::core::utility::log::Log::DefaultLog.WriteError("ParticleThermodyn: could not get frame (%u)", time);
            return false;
        }

        size_t totalParts = 0;
        plc = in->GetParticleListCount();

        for (unsigned int i = 0; i < plc; i++) {
            if (isListOK(in, i) && isDirOK(static_cast<metricsEnum>(theMetrics), in, i))
                totalParts += in->AccessParticles(i).GetCount();
        }

        if (theSearchType == searchTypeEnum::RADIUS) {
            this->newColors.resize(totalParts, theRadius);
        } else {
            this->newColors.resize(totalParts);
        }

        allParts.clear();
        allParts.reserve(totalParts);

        // we could now filter particles according to something. but currently we need not.
        allpartcnt = 0;
        for (unsigned int pli = 0; pli < plc; pli++) {
            auto& pl = in->AccessParticles(pli);
            if (!isListOK(in, pli) || !isDirOK(static_cast<metricsEnum>(theMetrics), in, pli)) {
                megamol::core::utility::log::Log::DefaultLog.WriteWarn(
                    "ParticleThermodyn: ignoring list %d because it either has no proper positions or no velocity",
                    pli);
                continue;
            }

            // unsigned int vert_stride = 0;
            // if (pl.GetVertexDataType() == DirectionalParticleDataCall::Particles::VertexDataType::VERTDATA_FLOAT_XYZ)
            // vert_stride = 12; else if (pl.GetVertexDataType() ==
            // DirectionalParticleDataCall::Particles::VertexDataType::VERTDATA_FLOAT_XYZR) vert_stride = 16; else
            // continue; vert_stride = std::max<unsigned int>(vert_stride, pl.GetVertexDataStride()); const unsigned char
            // *vert = static_cast<const unsigned char*>(pl.GetVertexData());

            UINT64 part_cnt = pl.GetCount();

            for (int part_i = 0; part_i < part_cnt; ++part_i) {
                allParts.push_back(allpartcnt + part_i);
            }
            allpartcnt += pl.GetCount();
        }

        // allocate nanoflann data structures for border
        assert(allpartcnt == totalParts);
        this->myPts = std::make_shared<simplePointcloud>(in, allParts);

        megamol::core::utility::log::Log::DefaultLog.WriteInfo("ParticleThermodyn: building acceleration structure for frame %u...", out->FrameID());
        particleTree = std::make_shared<my_kd_tree_t>(
            3 /* dim */, *myPts, nanoflann::KDTreeSingleIndexAdaptorParams(10 /* max leaf */));
        particleTree->buildIndex();
        megamol::core::utility::log::Log::DefaultLog.WriteInfo("ParticleThermodyn: done.");

        this->datahash = in->DataHash();
        this->lastTime = time;
        this->radiusSlot.ForceSetDirty();
    }

    if (this->radiusSlot.IsDirty() || this->cyclXSlot.IsDirty() || this->cyclYSlot.IsDirty() ||
        this->cyclZSlot.IsDirty() || this->numNeighborSlot.IsDirty() || this->searchTypeSlot.IsDirty() ||
        this->metricsSlot.IsDirty() || this->removeSelfSlot.IsDirty() || this->findExtremesSlot.IsDirty() ||
        this->extremeValueSlot.IsDirty() || this->fluidDensitySlot.IsDirty()) {
        allpartcnt = 0;
        ++myHash;

        // final computation
        bool cycl_x = this->cyclXSlot.Param<megamol::core::param::BoolParam>()->Value();
        bool cycl_y = this->cyclYSlot.Param<megamol::core::param::BoolParam>()->Value();
        bool cycl_z = this->cyclZSlot.Param<megamol::core::param::BoolParam>()->Value();
        auto bbox = in->AccessBoundingBoxes().ObjectSpaceBBox();
        // bbox.EnforcePositiveSize(); // paranoia
        auto bbox_cntr = bbox.CalcCenter();

        megamol::core::utility::log::Log::DefaultLog.WriteInfo("ParticleThermodyn: calculating thermodynamics for frame %u...", out->FrameID());
        vislib::sys::ConsoleProgressBar cpb;
        const int progressDivider = 100;
        cpb.Start("computing",
            static_cast<vislib::sys::ConsoleProgressBar::Size>(newColors.size() / progressDivider));

        float theMinTemp = FLT_MAX;
        float theMaxTemp = 0.0f;

        /*auto const search_volume = 4.0f / 3.0f * 3.14f * theRadius * theRadius * theRadius;
        auto const inv_search_volume = 1.0f / search_volume;*/
        auto const phase_krit = 0.5f * theFluidDensity;

        const bool remove_self = this->removeSelfSlot.Param<megamol::core::param::BoolParam>()->Value();

        auto const T_c = tcSlot.Param<core::param::FloatParam>()->Value();
        auto const rho_c = rhocSlot.Param<core::param::FloatParam>()->Value();

        allpartcnt = 0;
        INT64 counter = 0;
        for (unsigned int pli = 0; pli < plc; pli++) {
            auto& pl = in->AccessParticles(pli);
            if (!isListOK(in, pli) || !isDirOK(static_cast<metricsEnum>(theMetrics), in, pli)) {
                continue;
            }

            int num_thr = omp_get_max_threads();

            std::vector<float> metricMin(num_thr, FLT_MAX);
{'fraction_non_alphanumeric': 0.143827450413671, 'fraction_numerical': 0.007008923732793815, 'mean_word_length': 4.019038984587489, 'pattern_counts': {'":': 0, '<': 81, '<?xml version=': 0, '>': 320, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3131964', 'n_tokens_mistral': 8724, 'n_tokens_neox': 8465, 'n_words': 1519}