Skip to content

Commit 23bcf21

Browse files
committed
add symlog axis for binning with tests
1 parent 3ff8a83 commit 23bcf21

File tree

2 files changed

+433
-0
lines changed

2 files changed

+433
-0
lines changed
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
/* Copyright 2024-2024 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+
double sign = (y > 0) ? 1.0 : -1.0;
77+
return sign * threshold * std::exp(std::abs(y) - 1.0);
78+
}
79+
}
80+
81+
void initBinEdges()
82+
{
83+
binEdges.reserve(axisSplit.nBins + 1);
84+
85+
// Transform min and max to symlog space
86+
double yMin = symlogTransform(axisSplit.m_range.min, linearThresholdSI);
87+
double yMax = symlogTransform(axisSplit.m_range.max, linearThresholdSI);
88+
double yRange = yMax - yMin;
89+
90+
// Create evenly spaced bins in transformed space
91+
for(size_t i = 0; i <= axisSplit.nBins; i++)
92+
{
93+
double y = yMin + (i * yRange) / axisSplit.nBins;
94+
double edge = symlogInverseTransform(y, linearThresholdSI);
95+
binEdges.emplace_back(edge);
96+
}
97+
}
98+
99+
public:
100+
using Type = T_Attribute;
101+
using ScalingType = std::conditional_t<
102+
std::is_integral_v<T_Attribute>,
103+
std::conditional_t<sizeof(T_Attribute) <= 4, float_X, double>,
104+
T_Attribute>;
105+
106+
AxisSplitting<T_Attribute> axisSplit;
107+
/** Axis name, written out to OpenPMD */
108+
std::string label;
109+
/** Units(Dimensionality) of the axis */
110+
std::array<double, numUnits> units;
111+
/** Linear threshold in SI units */
112+
double linearThresholdSI;
113+
/** axisSplit.nBins + 1 bin edges in SI units */
114+
std::vector<double> binEdges;
115+
116+
struct SymlogAxisKernel
117+
{
118+
/** Function to place particle on axis */
119+
T_AttrFunctor getAttributeValue;
120+
/** Linear threshold in PIC units */
121+
ScalingType linearThresholdPIC;
122+
/** 1/linearThresholdPIC */
123+
ScalingType invLinearThreshold;
124+
/** Range in PIC units in symlog space */
125+
Range<ScalingType> symlogPicRange;
126+
// 1/yRange
127+
ScalingType invYRange;
128+
/** Enable or disable allocation of extra bins for out of range particles */
129+
bool overflowEnabled;
130+
/** Number of bins excluding overflow */
131+
uint32_t nBins;
132+
133+
constexpr SymlogAxisKernel(
134+
T_AttrFunctor const& attrFunc,
135+
AxisSplitting<T_Attribute> const& axisSplit,
136+
double linearThreshold,
137+
std::array<double, numUnits> const& unitsArr)
138+
: getAttributeValue{attrFunc}
139+
, linearThresholdPIC{static_cast<ScalingType>(toPICUnits(linearThreshold, unitsArr))}
140+
, invLinearThreshold{ScalingType(1) / linearThresholdPIC}
141+
, symlogPicRange{symlogTransformKernel(toPICUnits(axisSplit.m_range.min, unitsArr)), symlogTransformKernel(toPICUnits(axisSplit.m_range.max, unitsArr))}
142+
, invYRange{ ScalingType(1) / (symlogPicRange.max - symlogPicRange.min)}
143+
, overflowEnabled{axisSplit.enableOverflowBins}
144+
, nBins{axisSplit.nBins}
145+
{
146+
}
147+
148+
/**
149+
* Kernel-side symlog transform
150+
*/
151+
ALPAKA_FN_HOST_ACC ScalingType symlogTransformKernel(ScalingType val) const
152+
{
153+
ScalingType absVal = math::abs(val);
154+
155+
// Compute both branches
156+
ScalingType linear = val * invLinearThreshold;
157+
ScalingType logarithmic = math::copysign(ScalingType(1) + math::log(absVal * invLinearThreshold), val);
158+
159+
// Check if in linear region
160+
ScalingType isLinear = ScalingType(absVal <= linearThresholdPIC);
161+
162+
return isLinear ? linear : logarithmic;
163+
}
164+
165+
template<typename... Args>
166+
ALPAKA_FN_HOST_ACC std::pair<bool, uint32_t> getBinIdx(Args const&... args) const
167+
{
168+
auto val = getAttributeValue(args...);
169+
auto sVal = static_cast<ScalingType>(val);
170+
ScalingType y = symlogTransformKernel(sVal);
171+
172+
uint32_t inRange = (y >= symlogPicRange.min) & (y < symlogPicRange.max);
173+
// uint32_t belowMin = (y < symlogPicRange.min);
174+
uint32_t aboveMax = (y >= symlogPicRange.max);
175+
176+
ScalingType normalized = (y - symlogPicRange.min) * invYRange;
177+
178+
uint32_t rangeIdx = math::min(static_cast<uint32_t>(normalized * nBins), nBins - 1);
179+
180+
if(overflowEnabled)
181+
{
182+
// underflow*0 + inRange*(rangeIdx+1) + aboveMax*(nBins-1)
183+
// I dont compute underflow/belowMin since it is always zero
184+
uint32_t binIdx = inRange * (rangeIdx + 1) + aboveMax * (nBins + 1);
185+
return {true, binIdx};
186+
}
187+
else
188+
{
189+
return {inRange, rangeIdx};
190+
}
191+
}
192+
};
193+
194+
SymlogAxisKernel sAK;
195+
196+
SymlogAxis(
197+
AxisSplitting<T_Attribute> const& axSplit,
198+
T_AttrFunctor const& attrFunctor,
199+
double linearThreshold,
200+
std::string const& label,
201+
std::array<double, numUnits> const& units)
202+
: axisSplit{axSplit}
203+
, label{label}
204+
, units{units}
205+
, linearThresholdSI{linearThreshold}
206+
, sAK{attrFunctor, axisSplit, linearThreshold, units}
207+
{
208+
if(linearThreshold <= 0)
209+
{
210+
throw std::domain_error("Linear threshold must be positive");
211+
}
212+
initBinEdges();
213+
}
214+
215+
constexpr uint32_t getNBins() const
216+
{
217+
return sAK.nBins;
218+
}
219+
220+
double getUnitConversion() const
221+
{
222+
return getConversionFactor(units);
223+
}
224+
225+
SymlogAxisKernel getAxisKernel() const
226+
{
227+
return sAK;
228+
}
229+
230+
/**
231+
* @return bin edges in SI units
232+
*/
233+
std::vector<double> getBinEdgesSI() const
234+
{
235+
return binEdges;
236+
}
237+
};
238+
239+
/**
240+
* @details Creates a symlog (symmetric log) axis
241+
* @tparam T_Attribute Type of the attribute
242+
* @param axisSplitting Range and number of bins
243+
* @param linearThreshold Threshold for linear region (in SI units)
244+
* @param functorDescription Functor and metadata
245+
*/
246+
template<typename T_Attribute, typename T_FunctorDescription>
247+
HINLINE auto createSymlog(
248+
AxisSplitting<T_Attribute> const& axSplit,
249+
double linearThreshold,
250+
T_FunctorDescription const& functorDesc)
251+
{
252+
static_assert(
253+
std::is_same_v<typename T_FunctorDescription::QuantityType, T_Attribute>,
254+
"Access functor return type and range type should be the same");
255+
256+
return SymlogAxis<T_Attribute, typename T_FunctorDescription::FunctorType>(
257+
axSplit,
258+
functorDesc.functor,
259+
linearThreshold,
260+
functorDesc.name,
261+
functorDesc.units);
262+
}
263+
264+
} // namespace picongpu::plugins::binning::axis

0 commit comments

Comments
 (0)