Skip to content

Commit 1a2bbfd

Browse files
edfink234ferdymercury
edfink234
authored andcommitted
[RVecOp] Adding linSpace, logSpace, and arange prototypes
Fixes #17855
1 parent 8ec5398 commit 1a2bbfd

File tree

2 files changed

+303
-0
lines changed

2 files changed

+303
-0
lines changed

math/vecops/inc/ROOT/RVec.hxx

+238
Original file line numberDiff line numberDiff line change
@@ -3242,6 +3242,242 @@ RVec<typename RVec<T>::size_type> Enumerate(const RVec<T> &v)
32423242
return ret;
32433243
}
32443244

3245+
/**
3246+
* \brief Produce RVec with N evenly-spaced entries from start to end.
3247+
*
3248+
* This function generates a vector of evenly spaced values, starting at \p start and (depending on the
3249+
* \p endpoint parameter) either including or excluding \p end. If \p endpoint is true (default),
3250+
* the vector contains \p n values with \p end as the final element, and the spacing is computed as
3251+
* \f$\text{step} = \frac{\text{end} - \text{start}}{n-1}\f$. If \p endpoint is false,
3252+
* the sequence consists of n values computed as if there were n+1 evenly spaced samples, with the final
3253+
* value (\p end) omitted; in this case, \f$\text{step} = \frac{\text{end} - \text{start}}{n}\f$.
3254+
*
3255+
* The function is templated to allow for different arithmetic types. The deduced type \c Common_t is
3256+
* determined as follows: if the common type of \p T1 and \p T2 is a floating point type, that type is used;
3257+
* otherwise, the arithmetic is performed using \c double.
3258+
*
3259+
* \tparam T1 Type of the start value. Default is double.
3260+
* \tparam T2 Type of the end value. Default is double.
3261+
* \tparam Common_t Deduced type used for arithmetic, which is std::common_type_t<T1, T2> if that is a
3262+
* floating point type, or double otherwise.
3263+
*
3264+
* \param start The first value in the sequence.
3265+
* \param end The last value in the sequence if \p endpoint is true; otherwise, \p end is excluded.
3266+
* \param n The number of evenly spaced entries to produce.
3267+
* \param endpoint If true (default), \p end is included as the final element; if false, \p end is excluded.
3268+
*
3269+
* \return A vector (RVec<Common_t>) containing \p n evenly spaced values.
3270+
*
3271+
* \note If \p n is 1, the resulting vector will contain only the value \p start.
3272+
* \note The check `if (!n || (n > std::numeric_limits<long long>::max()))` is used to ensure that:
3273+
* - n is nonzero, and
3274+
* - n does not exceed std::numeric_limits<long long>::max(), which would indicate that a negative range (or other arithmetic issue)
3275+
* has resulted in an extremely large unsigned value, thereby preventing an attempt to reserve an absurd
3276+
* amount of memory.
3277+
* \note If the template parameter \c Common_t is explicitly overridden with an integral type, the arithmetic may cause rounding issues. Consequently, the resulting sequence may not end exactly at \p end. To ensure that the sequence ends exactly at \p end, consider casting the result as follows: `RVec<integral_type>(Linspace(...))`. This behavior is different than NumPy in Python.
3278+
*
3279+
* \par C++23 Enumerate Support:
3280+
* With C++23, you can use the range-based enumerate view to iterate over the resulting vector with both the index
3281+
* and the value, similar to Python's `enumerate`. For example:
3282+
* ~~~{.cpp}
3283+
* for (auto const [index, val] : std::views::enumerate(ROOT::VecOps::Linspace(6, 10, 16))) {
3284+
* // Process index and val.
3285+
* }
3286+
* ~~~
3287+
*
3288+
* \par Example code, at the ROOT prompt:
3289+
* ~~~{.cpp}
3290+
* using namespace ROOT::VecOps;
3291+
* cout << Linspace(-1, 5, 5) << "\n";
3292+
* // { -1, 0.5, 2, 3.5, 5 }
3293+
* cout << Linspace(3, 12, 5) << "\n";
3294+
* // { 3, 5.25, 7.5, 9.75, 12 }
3295+
* cout << Linspace(3, 12, 5, false) << "\n";
3296+
* // { 3, 4.8, 6.6, 8.4, 10.2 }
3297+
* Linspace<int, int, int>(1, 10, 3) << "\n";
3298+
* // { 1, 5, 9 }
3299+
* ~~~
3300+
*/
3301+
template <typename T1 = double, typename T2 = double, typename Common_t = std::conditional_t<std::is_floating_point_v<std::common_type_t<T1, T2>>, std::common_type_t<T1, T2>, double>>
3302+
inline RVec<Common_t> Linspace(T1 start, T2 end, unsigned long long n = 128, const bool endpoint = true)
3303+
{
3304+
RVec<Common_t> temp;
3305+
3306+
if (!n || (n > std::numeric_limits<long long>::max())) // Check for invalid or absurd n.
3307+
{
3308+
return temp;
3309+
}
3310+
3311+
Common_t step = (static_cast<Common_t>(end) - static_cast<Common_t>(start)) / static_cast<Common_t>(n - endpoint);
3312+
temp.reserve(n);
3313+
temp.push_back(static_cast<Common_t>(start));
3314+
for (unsigned long long i = 1; i < n; i++)
3315+
{
3316+
temp.push_back(static_cast<Common_t>(start) + static_cast<Common_t>(i) * step);
3317+
}
3318+
return temp;
3319+
}
3320+
3321+
/**
3322+
* \brief Produce RVec with n log-spaced entries from base^{start} to base^{end}.
3323+
*
3324+
* This function generates a vector of values where the exponents are evenly spaced, and then returns the
3325+
* corresponding values of base raised to these exponents. If \p endpoint is true (default), the vector
3326+
* contains \p n values with the last element equal to \f$base^{end}\f$. If \p endpoint is false, the
3327+
* sequence is computed as if there were n+1 evenly spaced samples over the interval in the exponent space,
3328+
* and the final value (\f$base^{end}\f$) is excluded, resulting in a sequence of n values.
3329+
*
3330+
* The function is templated to allow for different arithmetic types. The deduced type \c Common_t is determined as follows:
3331+
* if the common type of \p T1, \p T2, and \p T3 is a floating point type, that type is used; otherwise, the arithmetic is performed using \c double.
3332+
*
3333+
* \tparam T1 Type of the start exponent. Default is double.
3334+
* \tparam T2 Type of the end exponent. Default is double.
3335+
* \tparam T3 Type of the base (and auxiliary type for deducing common type). Default is double.
3336+
* \tparam Common_t Deduced type used for arithmetic, which is std::common_type_t<T1, T2, T3> if that is a floating point type, or double otherwise.
3337+
*
3338+
* \param start The exponent corresponding to the first element (i.e., the first element is \f$base^{start}\f$).
3339+
* \param end The exponent corresponding to the final element if \p endpoint is true; otherwise, \p end is excluded.
3340+
* \param n The number of log-spaced entries to produce.
3341+
* \param base The base to be used in the exponentiation (default is 10.0).
3342+
* \param endpoint If true (default), \f$base^{end}\f$ is included as the final element; if false, \f$base^{end}\f$ is excluded.
3343+
*
3344+
* \return A vector (RVec<Common_t>) containing n log-spaced values.
3345+
*
3346+
* \note If \p n is 1, the resulting vector will contain only the value \f$base^{start}\f$.
3347+
* \note The check `if (!n || (n > std::numeric_limits<long long>::max()))` is used to ensure that:
3348+
* - n is nonzero, and
3349+
* - n does not exceed std::numeric_limits<long long>::max(), which would indicate that a negative range (or other arithmetic issue)
3350+
* has resulted in an extremely large unsigned value, thereby preventing an attempt to reserve an absurd
3351+
* amount of memory.
3352+
* \note If the template parameter \c Common_t is explicitly overridden with an integral type, the arithmetic may introduce rounding errors, and as a consequence, the sequence may not end exactly at \f$base^{end}\f>. To ensure that the final element is exactly \f$base^{end}\f>, consider casting the result as follows: `RVec<integral_type>(Logspace(...))`. This behavior is different than NumPy in Python.
3353+
*
3354+
* \par C++23 Enumerate Support:
3355+
* With C++23, you can use the range-based enumerate view to iterate over the resulting vector with both the index
3356+
* and the value, similar to Python's `enumerate`. For example:
3357+
* ~~~{.cpp}
3358+
* for (auto const [index, val] : std::views::enumerate(ROOT::VecOps::Logspace(4, 10, 12))) {
3359+
* // Process index and val.
3360+
* }
3361+
* ~~~
3362+
*
3363+
* \par Example code, at the ROOT prompt:
3364+
* ~~~{.cpp}
3365+
* using namespace ROOT::VecOps;
3366+
* cout << Logspace(4, 10, 12) << '\n';
3367+
* // { 10000, 35111.9, 123285, 432876, 1.51991e+06, 5.3367e+06, 1.87382e+07, 6.57933e+07, 2.31013e+08, 8.11131e+08, 2.84804e+09, 1e+10 }
3368+
* cout << Logspace(0, 0, 50) << '\n';
3369+
* // { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
3370+
* cout << Logspace(0, 0, 0) << '\n';
3371+
* // { }
3372+
* cout << Logspace(4, 10, 12, 10.0, false) << '\n';
3373+
* // { 10000, 31622.8, 100000, 316228, 1e+06, 3.16228e+06, 1e+07, 3.16228e+07, 1e+08, 3.16228e+08, 1e+09, 3.16228e+09 }
3374+
* cout << Logspace<int, int, int, int>(1, 5, 3) << '\n';
3375+
* // { 10, 1000, 100000 }
3376+
* ~~~
3377+
*/
3378+
template <typename T1 = double, typename T2 = double, typename T3 = double, typename Common_t = std::conditional_t<std::is_floating_point_v<std::common_type_t<T1, T2, T3>>, std::common_type_t<T1, T2, T3>, double>>
3379+
inline RVec<Common_t> Logspace(T1 start, T2 end, unsigned long long n = 128, T3 base = 10.0, const bool endpoint = true)
3380+
{
3381+
RVec<Common_t> temp;
3382+
3383+
if (!n || (n > std::numeric_limits<long long>::max())) // Check for invalid or absurd n.
3384+
{
3385+
return temp;
3386+
}
3387+
3388+
Common_t start_c = static_cast<Common_t>(start);
3389+
Common_t end_c = static_cast<Common_t>(end);
3390+
Common_t base_c = static_cast<Common_t>(base);
3391+
Common_t step = static_cast<Common_t>(end_c - start_c)/static_cast<Common_t>(n - endpoint);
3392+
temp.reserve(n);
3393+
temp.push_back(static_cast<Common_t>(std::pow(base_c, start_c)));
3394+
for (unsigned long long i = 1; i < n; i++)
3395+
{
3396+
Common_t exponent = start_c + i * step;
3397+
temp.push_back(static_cast<Common_t>(std::pow(base_c, exponent)));
3398+
}
3399+
return temp;
3400+
}
3401+
3402+
/**
3403+
* \brief Produce RVec with entries in the range [start, end) in increments of step.
3404+
*
3405+
* This function generates a vector of values starting at \p start and incremented by \p step,
3406+
* continuing until the values reach or exceed \p end (the interval is half-open: [start, end)).
3407+
* The number of elements is computed as:
3408+
* \f[
3409+
* n = \lceil \frac{\text{end} - \text{start}}{\text{step}} \rceil
3410+
* \f]
3411+
* ensuring that the arithmetic is performed in a floating-point context when needed.
3412+
*
3413+
* The function is templated to allow for different arithmetic types. The deduced type \c Common_t is
3414+
* determined as follows: if the common type of \p T1, \p T2, and \p T3 is a floating point type, that type is used;
3415+
* otherwise, the arithmetic is performed using \c double.
3416+
*
3417+
* \tparam T1 Type of the start value. Default is double.
3418+
* \tparam T2 Type of the end value. Default is double.
3419+
* \tparam T3 Type of the step value. Default is double.
3420+
* \tparam Common_t Deduced type used for arithmetic, which is
3421+
* std::common_type_t<T1, T2, T3> if that is a floating point type, or double otherwise.
3422+
*
3423+
* \param start The first value in the range.
3424+
* \param end The end of the range (exclusive).
3425+
* \param step The increment between consecutive values.
3426+
*
3427+
* \return A vector (RVec<Common_t>) containing values starting at \p start, each incremented by \p step,
3428+
* up to but not including any value equal to or greater than \p end.
3429+
*
3430+
* \note The check `if (!n || (n > std::numeric_limits<long long>::max()))` is used to ensure that:
3431+
* - n is nonzero, and
3432+
* - n does not exceed std::numeric_limits<long long>::max(), which would indicate that a negative range (or other arithmetic issue)
3433+
* has resulted in an extremely large unsigned value, thereby preventing an attempt to reserve an absurd
3434+
* amount of memory.
3435+
* \note If the template parameter \c Common_t is explicitly overridden with an integral type, the arithmetic may introduce rounding errors, and as a consequence, the produced sequence may not strictly adhere to the intended progression. If an exact sequence is desired, consider casting the result as follows: `RVec<integral_type>(Arange(...))`. This behavior is different than NumPy in Python.
3436+
*
3437+
* \par C++23 Enumerate Support:
3438+
* With C++23, you can use the range-based enumerate view to iterate over the resulting vector with both the index
3439+
* and the value, similar to Python's `enumerate`. For example:
3440+
* ~~~{.cpp}
3441+
* for (auto const [index, val] : std::views::enumerate(ROOT::VecOps::Arange(1, 13, 5))) {
3442+
* // Process index and val.
3443+
* }
3444+
* ~~~
3445+
*
3446+
* \par Example code, at the ROOT prompt:
3447+
* ~~~{.cpp}
3448+
* using namespace ROOT::VecOps;
3449+
* cout << Arange(0, 0, 5) << '\n';
3450+
* // { }
3451+
* cout << Arange(-7, 20, 4) << '\n';
3452+
* // { -7, -3, 1, 5, 9, 13, 17 }
3453+
* cout << Arange(1, 13, 5) << '\n';
3454+
* // { 1, 6, 11 }
3455+
* cout << Arange<unsigned int, unsigned int, unsigned int, unsigned int>(5, 9, 1) << '\n';
3456+
* // { 5, 6, 7, 8 }
3457+
* ~~~
3458+
*/
3459+
template <typename T1 = double, typename T2 = double, typename T3 = double, typename Common_t = std::conditional_t<std::is_floating_point_v<std::common_type_t<T1, T2, T3>>, std::common_type_t<T1, T2, T3>, double>>
3460+
inline RVec<Common_t> Arange(T1 start, T2 end, T3 step)
3461+
{
3462+
RVec<Common_t> temp;
3463+
unsigned long long n = std::ceil(static_cast<Common_t>(end-start)/static_cast<Common_t>(step)); // Ensure floating-point division.
3464+
3465+
if (!n || (n > std::numeric_limits<long long>::max())) // Check for invalid or absurd n.
3466+
{
3467+
return temp;
3468+
}
3469+
3470+
Common_t start_c = static_cast<Common_t>(start);
3471+
Common_t step_c = static_cast<Common_t>(step);
3472+
temp.reserve(n);
3473+
temp.push_back(start_c);
3474+
for (unsigned long long i = 1; i < n; i++)
3475+
{
3476+
temp.push_back(start_c + i * step_c);
3477+
}
3478+
return temp;
3479+
}
3480+
32453481
/// Produce RVec with entries starting from 0, and incrementing by 1 until a user-specified N is reached.
32463482
/// Example code, at the ROOT prompt:
32473483
/// ~~~{.cpp}
@@ -3309,6 +3545,8 @@ inline RVec<long long int> Range(long long int begin, long long int end, long lo
33093545
return ret;
33103546
}
33113547

3548+
3549+
33123550
////////////////////////////////////////////////////////////////////////////////
33133551
/// Print a RVec at the prompt:
33143552
template <class T>

0 commit comments

Comments
 (0)