|
| 1 | +/* Copyright 2025-2025 Tapish Narwal |
| 2 | + * |
| 3 | + * This file is part of PIConGPU. |
| 4 | + * |
| 5 | + * PIConGPU is free software: you can redistribute it and/or modify |
| 6 | + * it under the terms of the GNU General Public License as published by |
| 7 | + * the Free Software Foundation, either version 3 of the License, or |
| 8 | + * (at your option) any later version. |
| 9 | + * |
| 10 | + * PIConGPU is distributed in the hope that it will be useful, |
| 11 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | + * GNU General Public License for more details. |
| 14 | + * |
| 15 | + * You should have received a copy of the GNU General Public License |
| 16 | + * along with PIConGPU. |
| 17 | + * If not, see <http://www.gnu.org/licenses/>. |
| 18 | + */ |
| 19 | + |
| 20 | +#pragma once |
| 21 | + |
| 22 | +#include "picongpu/plugins/binning/UnitConversion.hpp" |
| 23 | +#include "picongpu/plugins/binning/axis/Axis.hpp" |
| 24 | + |
| 25 | +#include <pmacc/memory/buffers/HostDeviceBuffer.hpp> |
| 26 | + |
| 27 | +#include <array> |
| 28 | +#include <cmath> |
| 29 | +#include <cstdint> |
| 30 | +#include <stdexcept> |
| 31 | +#include <string> |
| 32 | +#include <type_traits> |
| 33 | +#include <vector> |
| 34 | + |
| 35 | +namespace picongpu::plugins::binning::axis |
| 36 | +{ |
| 37 | + /** |
| 38 | + * Symlog (symmetric log) axis with linear behavior near zero and logarithmic behavior for large values. |
| 39 | + * Similar to matplotlib's symlog scale. |
| 40 | + * The axis has a linear region within [-linearThreshold, linearThreshold] and logarithmic regions outside. |
| 41 | + * Bins are distributed to maintain smooth transitions at the threshold boundaries. |
| 42 | + * If overflow bins are enabled, allocates 2 extra bins for under and overflow. |
| 43 | + */ |
| 44 | + template<typename T_Attribute, typename T_AttrFunctor> |
| 45 | + class SymlogAxis |
| 46 | + { |
| 47 | + private: |
| 48 | + /** |
| 49 | + * Transform value to symlog space |
| 50 | + * For |val| <= threshold: returns val/threshold (normalized linear) |
| 51 | + * For |val| > threshold: returns sign(val) * (1 + log(|val|/threshold)) |
| 52 | + */ |
| 53 | + static double symlogTransform(double val, double threshold) |
| 54 | + { |
| 55 | + if(std::abs(val) <= threshold) |
| 56 | + { |
| 57 | + return val / threshold; |
| 58 | + } |
| 59 | + else |
| 60 | + { |
| 61 | + return std::copysign(1.0 + std::log(std::abs(val) / threshold), val); |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + /** |
| 66 | + * Inverse transform from symlog space to real space |
| 67 | + */ |
| 68 | + static double symlogInverseTransform(double y, double threshold) |
| 69 | + { |
| 70 | + if(std::abs(y) <= 1.0) |
| 71 | + { |
| 72 | + return y * threshold; |
| 73 | + } |
| 74 | + else |
| 75 | + { |
| 76 | + return std::copysign(threshold * std::exp(std::abs(y) - 1.0), y); |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + void initBinEdges() |
| 81 | + { |
| 82 | + binEdges.reserve(axisSplit.nBins + 1); |
| 83 | + |
| 84 | + // Transform min and max to symlog space |
| 85 | + double yMin = symlogTransform(axisSplit.m_range.min, linearThresholdSI); |
| 86 | + double yMax = symlogTransform(axisSplit.m_range.max, linearThresholdSI); |
| 87 | + double yRange = yMax - yMin; |
| 88 | + |
| 89 | + // Create evenly spaced bins in transformed space |
| 90 | + for(size_t i = 0; i <= axisSplit.nBins; i++) |
| 91 | + { |
| 92 | + double y = yMin + (i * yRange) / axisSplit.nBins; |
| 93 | + double edge = symlogInverseTransform(y, linearThresholdSI); |
| 94 | + binEdges.emplace_back(edge); |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + public: |
| 99 | + using Type = T_Attribute; |
| 100 | + using ScalingType = std::conditional_t< |
| 101 | + std::is_integral_v<T_Attribute>, |
| 102 | + std::conditional_t<sizeof(T_Attribute) <= 4, float_X, double>, |
| 103 | + T_Attribute>; |
| 104 | + |
| 105 | + AxisSplitting<T_Attribute> axisSplit; |
| 106 | + /** Axis name, written out to OpenPMD */ |
| 107 | + std::string label; |
| 108 | + /** Units(Dimensionality) of the axis */ |
| 109 | + std::array<double, numUnits> units; |
| 110 | + /** Linear threshold in SI units */ |
| 111 | + double linearThresholdSI; |
| 112 | + /** axisSplit.nBins + 1 bin edges in SI units */ |
| 113 | + std::vector<double> binEdges; |
| 114 | + |
| 115 | + struct SymlogAxisKernel |
| 116 | + { |
| 117 | + /** Function to place particle on axis */ |
| 118 | + T_AttrFunctor getAttributeValue; |
| 119 | + /** Linear threshold in PIC units */ |
| 120 | + ScalingType linearThresholdPIC; |
| 121 | + /** 1/linearThresholdPIC */ |
| 122 | + ScalingType invLinearThreshold; |
| 123 | + /** Range in PIC units in symlog space */ |
| 124 | + Range<ScalingType> symlogPicRange; |
| 125 | + // 1/yRange |
| 126 | + ScalingType invYRange; |
| 127 | + /** Enable or disable allocation of extra bins for out of range particles */ |
| 128 | + bool overflowEnabled; |
| 129 | + /** Number of bins excluding overflow */ |
| 130 | + uint32_t nBins; |
| 131 | + |
| 132 | + constexpr SymlogAxisKernel( |
| 133 | + T_AttrFunctor const& attrFunc, |
| 134 | + AxisSplitting<T_Attribute> const& axisSplit, |
| 135 | + double linearThreshold, |
| 136 | + std::array<double, numUnits> const& unitsArr) |
| 137 | + : getAttributeValue{attrFunc} |
| 138 | + , linearThresholdPIC{static_cast<ScalingType>(toPICUnits(linearThreshold, unitsArr))} |
| 139 | + , invLinearThreshold{ScalingType(1) / linearThresholdPIC} |
| 140 | + , symlogPicRange{symlogTransformKernel(toPICUnits(axisSplit.m_range.min, unitsArr)), symlogTransformKernel(toPICUnits(axisSplit.m_range.max, unitsArr))} |
| 141 | + , invYRange{ ScalingType(1) / (symlogPicRange.max - symlogPicRange.min)} |
| 142 | + , overflowEnabled{axisSplit.enableOverflowBins} |
| 143 | + , nBins{axisSplit.nBins} |
| 144 | + { |
| 145 | + } |
| 146 | + |
| 147 | + /** |
| 148 | + * Kernel-side symlog transform |
| 149 | + */ |
| 150 | + ALPAKA_FN_HOST_ACC ScalingType symlogTransformKernel(ScalingType val) const |
| 151 | + { |
| 152 | + ScalingType absVal = math::abs(val); |
| 153 | + |
| 154 | + // Compute both branches |
| 155 | + ScalingType linear = val * invLinearThreshold; |
| 156 | + ScalingType logarithmic = math::copysign(ScalingType(1) + math::log(absVal * invLinearThreshold), val); |
| 157 | + |
| 158 | + // Check if in linear region |
| 159 | + ScalingType isLinear = ScalingType(absVal <= linearThresholdPIC); |
| 160 | + |
| 161 | + return isLinear ? linear : logarithmic; |
| 162 | + } |
| 163 | + |
| 164 | + template<typename... Args> |
| 165 | + ALPAKA_FN_HOST_ACC std::pair<bool, uint32_t> getBinIdx(Args const&... args) const |
| 166 | + { |
| 167 | + auto val = getAttributeValue(args...); |
| 168 | + auto sVal = static_cast<ScalingType>(val); |
| 169 | + ScalingType y = symlogTransformKernel(sVal); |
| 170 | + |
| 171 | + uint32_t inRange = (y >= symlogPicRange.min) & (y < symlogPicRange.max); |
| 172 | + // uint32_t belowMin = (y < symlogPicRange.min); |
| 173 | + uint32_t aboveMax = (y >= symlogPicRange.max); |
| 174 | + |
| 175 | + ScalingType normalized = (y - symlogPicRange.min) * invYRange; |
| 176 | + |
| 177 | + uint32_t rangeIdx = math::min(static_cast<uint32_t>(normalized * nBins), nBins - 1); |
| 178 | + |
| 179 | + if(overflowEnabled) |
| 180 | + { |
| 181 | + // underflow*0 + inRange*(rangeIdx+1) + aboveMax*(nBins-1) |
| 182 | + // I dont compute underflow/belowMin since it is always zero |
| 183 | + uint32_t binIdx = inRange * (rangeIdx + 1) + aboveMax * (nBins + 1); |
| 184 | + return {true, binIdx}; |
| 185 | + } |
| 186 | + else |
| 187 | + { |
| 188 | + return {inRange, rangeIdx}; |
| 189 | + } |
| 190 | + } |
| 191 | + }; |
| 192 | + |
| 193 | + SymlogAxisKernel sAK; |
| 194 | + |
| 195 | + SymlogAxis( |
| 196 | + AxisSplitting<T_Attribute> const& axSplit, |
| 197 | + T_AttrFunctor const& attrFunctor, |
| 198 | + double linearThreshold, |
| 199 | + std::string const& label, |
| 200 | + std::array<double, numUnits> const& units) |
| 201 | + : axisSplit{axSplit} |
| 202 | + , label{label} |
| 203 | + , units{units} |
| 204 | + , linearThresholdSI{linearThreshold} |
| 205 | + , sAK{attrFunctor, axisSplit, linearThreshold, units} |
| 206 | + { |
| 207 | + if(linearThreshold <= 0) |
| 208 | + { |
| 209 | + throw std::domain_error("Linear threshold must be positive"); |
| 210 | + } |
| 211 | + initBinEdges(); |
| 212 | + } |
| 213 | + |
| 214 | + constexpr uint32_t getNBins() const |
| 215 | + { |
| 216 | + return sAK.nBins; |
| 217 | + } |
| 218 | + |
| 219 | + double getUnitConversion() const |
| 220 | + { |
| 221 | + return getConversionFactor(units); |
| 222 | + } |
| 223 | + |
| 224 | + SymlogAxisKernel getAxisKernel() const |
| 225 | + { |
| 226 | + return sAK; |
| 227 | + } |
| 228 | + |
| 229 | + /** |
| 230 | + * @return bin edges in SI units |
| 231 | + */ |
| 232 | + std::vector<double> getBinEdgesSI() const |
| 233 | + { |
| 234 | + return binEdges; |
| 235 | + } |
| 236 | + }; |
| 237 | + |
| 238 | + /** |
| 239 | + * @details Creates a symlog (symmetric log) axis |
| 240 | + * @tparam T_Attribute Type of the attribute |
| 241 | + * @param axisSplitting Range and number of bins |
| 242 | + * @param linearThreshold Threshold for linear region (in SI units) |
| 243 | + * @param functorDescription Functor and metadata |
| 244 | + */ |
| 245 | + template<typename T_Attribute, typename T_FunctorDescription> |
| 246 | + HINLINE auto createSymlog( |
| 247 | + AxisSplitting<T_Attribute> const& axSplit, |
| 248 | + double linearThreshold, |
| 249 | + T_FunctorDescription const& functorDesc) |
| 250 | + { |
| 251 | + static_assert( |
| 252 | + std::is_same_v<typename T_FunctorDescription::QuantityType, T_Attribute>, |
| 253 | + "Access functor return type and range type should be the same"); |
| 254 | + |
| 255 | + return SymlogAxis<T_Attribute, typename T_FunctorDescription::FunctorType>( |
| 256 | + axSplit, |
| 257 | + functorDesc.functor, |
| 258 | + linearThreshold, |
| 259 | + functorDesc.name, |
| 260 | + functorDesc.units); |
| 261 | + } |
| 262 | + |
| 263 | +} // namespace picongpu::plugins::binning::axis |
0 commit comments