From 4c0f69416a40957529e85a22db4948e56248eabc Mon Sep 17 00:00:00 2001 From: takenori-y Date: Mon, 25 May 2026 12:28:05 +0900 Subject: [PATCH 1/2] add lpnorm --- CMakeLists.txt | 2 + doc/main/lpnorm.rst | 13 ++ include/SPTK/math/lp_norm_calculation.h | 78 +++++++++ src/main/lpnorm.cc | 202 ++++++++++++++++++++++++ src/main/sopr.cc | 2 + src/math/lp_norm_calculation.cc | 75 +++++++++ src/utils/sptk_utils.cc | 75 +++++---- test/test_lpnorm.bats | 59 +++++++ 8 files changed, 474 insertions(+), 32 deletions(-) create mode 100644 doc/main/lpnorm.rst create mode 100644 include/SPTK/math/lp_norm_calculation.h create mode 100644 src/main/lpnorm.cc create mode 100644 src/math/lp_norm_calculation.cc create mode 100755 test/test_lpnorm.bats diff --git a/CMakeLists.txt b/CMakeLists.txt index 434bc59..c584036 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -203,6 +203,7 @@ set(CC_SOURCES ${SOURCE_DIR}/math/inverse_fast_fourier_transform.cc ${SOURCE_DIR}/math/inverse_fourier_transform.cc ${SOURCE_DIR}/math/levinson_durbin_recursion.cc + ${SOURCE_DIR}/math/lp_norm_calculation.cc ${SOURCE_DIR}/math/matrix.cc ${SOURCE_DIR}/math/matrix2d.cc ${SOURCE_DIR}/math/minmax_accumulation.cc @@ -354,6 +355,7 @@ set(MAIN_SOURCES ${SOURCE_DIR}/main/lpc2lsp.cc ${SOURCE_DIR}/main/lpc2par.cc ${SOURCE_DIR}/main/lpccheck.cc + ${SOURCE_DIR}/main/lpnorm.cc ${SOURCE_DIR}/main/lsp2lpc.cc ${SOURCE_DIR}/main/lspcheck.cc ${SOURCE_DIR}/main/lspdf.cc diff --git a/doc/main/lpnorm.rst b/doc/main/lpnorm.rst new file mode 100644 index 0000000..98e0241 --- /dev/null +++ b/doc/main/lpnorm.rst @@ -0,0 +1,13 @@ +.. _lpnorm: + +lpnorm +====== + +.. doxygenfile:: lpnorm.cc + +.. seealso:: + + :ref:`vopr` :ref:`vsum` + +.. doxygenclass:: sptk::LpNormCalculation + :members: diff --git a/include/SPTK/math/lp_norm_calculation.h b/include/SPTK/math/lp_norm_calculation.h new file mode 100644 index 0000000..bb85ea1 --- /dev/null +++ b/include/SPTK/math/lp_norm_calculation.h @@ -0,0 +1,78 @@ +// ------------------------------------------------------------------------ // +// Copyright 2021 SPTK Working Group // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ------------------------------------------------------------------------ // + +#ifndef SPTK_MATH_LP_NORM_CALCULATION_H_ +#define SPTK_MATH_LP_NORM_CALCULATION_H_ + +#include // std::vector + +#include "SPTK/utils/sptk_utils.h" + +namespace sptk { + +/** + * Calculate Lp-norm. + * + * The input is the @f$M@f$-th order vector: + * @f[ + * \begin{array}{cccc} + * x(0), & x(1), & \ldots, & x(M), + * \end{array} + * @f] + * The output is the Lp-norm represented as + * @f[ + * \begin{array}{cccc} + * \|x\|_p = \left(\displaystyle\sum_{m=0}^M |x(m)|^p\right)^{\frac{1}{p}}. + * \end{array} + * @f] + */ +class LpNormCalculation { + public: + /** + * @param[in] num_order Order of input, @f$M@f$. + * @param[in] p Power, @f$p@f$. + */ + LpNormCalculation(int num_order, double p); + + virtual ~LpNormCalculation() { + } + + /** + * @return True if this object is valid. + */ + bool IsValid() const { + return is_valid_; + } + + /** + * @param[in] data @f$M@f$-th order vector. + * @param[out] norm Lp-norm. + * @return True on success, false on failure. + */ + bool Run(const std::vector& data, double* norm) const; + + private: + const int num_order_; + const double p_; + + bool is_valid_; + + DISALLOW_COPY_AND_ASSIGN(LpNormCalculation); +}; + +} // namespace sptk + +#endif // SPTK_MATH_LP_NORM_CALCULATION_H_ diff --git a/src/main/lpnorm.cc b/src/main/lpnorm.cc new file mode 100644 index 0000000..c4e29cd --- /dev/null +++ b/src/main/lpnorm.cc @@ -0,0 +1,202 @@ +// ------------------------------------------------------------------------ // +// Copyright 2021 SPTK Working Group // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ------------------------------------------------------------------------ // + +#include // std::transform +#include // std::ifstream +#include // std::setw +#include // std::cerr, std::cin, std::cout, std::endl, etc. +#include // std::ostringstream +#include // std::vector + +#include "GETOPT/ya_getopt.h" +#include "SPTK/math/lp_norm_calculation.h" +#include "SPTK/utils/sptk_utils.h" + +namespace { + +const int kDefaultNumOrder(25); +const double kDefaultNormOrder(2.0); + +void PrintUsage(std::ostream* stream) { + // clang-format off + *stream << std::endl; + *stream << " lpnorm - normalize by Lp-norm" << std::endl; + *stream << std::endl; + *stream << " usage:" << std::endl; + *stream << " lpnorm [ options ] [ infile ] > stdout" << std::endl; + *stream << " options:" << std::endl; + *stream << " -l l : length of vector ( int)[" << std::setw(5) << std::right << kDefaultNumOrder + 1 << "][ 1 <= l <= ]" << std::endl; // NOLINT + *stream << " -m m : order of vector ( int)[" << std::setw(5) << std::right << "l-1" << "][ 0 <= m <= ]" << std::endl; // NOLINT + *stream << " -p p : order of norm (double)[" << std::setw(5) << std::right << kDefaultNormOrder << "][ 1.0 <= p <= ]" << std::endl; // NOLINT + *stream << " -h : print this message" << std::endl; + *stream << " infile:" << std::endl; + *stream << " vector (double)[stdin]" << std::endl; + *stream << " stdout:" << std::endl; + *stream << " normalized vector (double)" << std::endl; + *stream << " notice:" << std::endl; + *stream << " for L-infinity norm, specify -p inf" << std::endl; + *stream << std::endl; + *stream << " SPTK: version " << sptk::kVersion << std::endl; + *stream << std::endl; + // clang-format on +} + +} // namespace + +/** + * @a lpnorm [ @e option ] [ @e infile ] + * + * - @b -l @e int + * - length of vector + * - @b -m @e int + * - order of vector + * - @b -p @e double or @e str + * - order of norm + * - @b infile @e str + * - double-type vector sequence + * - @b stdout + * - double-type normalized vector sequence + * + * The below example calculates l2-normalized vector sequence: + * + * @code{.sh} + * echo 3 4 | x2x +ad | lpnorm -l 2 -p 2 | x2x +da + * # 0.6 0.8 + * @endcode + * + * @param[in] argc Number of arguments. + * @param[in] argv Argument vector. + * @return 0 on success, 1 on failure. + */ +int main(int argc, char* argv[]) { + int num_order(kDefaultNumOrder); + double norm_order(kDefaultNormOrder); + + for (;;) { + const int option_char(getopt_long(argc, argv, "l:m:p:h", NULL, NULL)); + if (-1 == option_char) break; + + switch (option_char) { + case 'l': { + if (!sptk::ConvertStringToInteger(optarg, &num_order) || + num_order <= 0) { + std::ostringstream error_message; + error_message + << "The argument for the -l option must be a positive integer"; + sptk::PrintErrorMessage("lpnorm", error_message); + return 1; + } + --num_order; + break; + } + case 'm': { + if (!sptk::ConvertStringToInteger(optarg, &num_order) || + num_order < 0) { + std::ostringstream error_message; + error_message << "The argument for the -m option must be a " + << "non-negative integer"; + sptk::PrintErrorMessage("lpnorm", error_message); + return 1; + } + break; + } + case 'p': { + if ((!sptk::ConvertSpecialStringToDouble(optarg, &norm_order) && + !sptk::ConvertStringToDouble(optarg, &norm_order)) || + norm_order < 1.0) { + std::ostringstream error_message; + error_message << "The argument for the -p option must be equal to or " + << "greater than 1.0"; + sptk::PrintErrorMessage("lpnorm", error_message); + return 1; + } + break; + } + case 'h': { + PrintUsage(&std::cout); + return 0; + } + default: { + PrintUsage(&std::cerr); + return 1; + } + } + } + + const int num_input_files(argc - optind); + if (1 < num_input_files) { + std::ostringstream error_message; + error_message << "Too many input files"; + sptk::PrintErrorMessage("lpnorm", error_message); + return 1; + } + const char* input_file(0 == num_input_files ? NULL : argv[optind]); + + if (!sptk::SetBinaryMode()) { + std::ostringstream error_message; + error_message << "Cannot set translation mode"; + sptk::PrintErrorMessage("lpnorm", error_message); + return 1; + } + + std::ifstream ifs; + if (NULL != input_file) { + ifs.open(input_file, std::ios::in | std::ios::binary); + if (ifs.fail()) { + std::ostringstream error_message; + error_message << "Cannot open file " << input_file; + sptk::PrintErrorMessage("lpnorm", error_message); + return 1; + } + } + std::istream& input_stream(ifs.is_open() ? ifs : std::cin); + + sptk::LpNormCalculation lp_norm_calculation(num_order, norm_order); + if (!lp_norm_calculation.IsValid()) { + std::ostringstream error_message; + error_message << "Failed to initialize LpNormCalculation"; + sptk::PrintErrorMessage("lpnorm", error_message); + return 1; + } + + const int vector_length(num_order + 1); + std::vector vector(vector_length); + std::vector normalized_vector(vector_length); + + while (sptk::ReadStream(false, 0, 0, vector_length, &vector, &input_stream, + NULL)) { + double norm; + if (!lp_norm_calculation.Run(vector, &norm)) { + std::ostringstream error_message; + error_message << "Failed to calculate Lp-norm"; + sptk::PrintErrorMessage("lpnorm", error_message); + return 1; + } + + std::transform(vector.begin(), vector.end(), normalized_vector.begin(), + [norm](double x) { return x / norm; }); + + if (!sptk::WriteStream(0, vector_length, normalized_vector, &std::cout, + NULL)) { + std::ostringstream error_message; + error_message << "Failed to write normalized vector"; + sptk::PrintErrorMessage("lpnorm", error_message); + return 1; + } + } + + return 0; +} diff --git a/src/main/sopr.cc b/src/main/sopr.cc index 62b6910..9b554c3 100644 --- a/src/main/sopr.cc +++ b/src/main/sopr.cc @@ -116,6 +116,8 @@ void PrintUsage(std::ostream* stream) { *stream << " sqrtX : sqrt(X) [ 0.0 <= X <= ]" << std::endl; // NOLINT *stream << " lnX : ln(X) [ 0.0 < X <= ]" << std::endl; // NOLINT *stream << " expX : exp(X) [ <= X <= ]" << std::endl; // NOLINT + *stream << " inf : infinity" << std::endl; + *stream << " nan : not a number" << std::endl; *stream << "" << std::endl; *stream << " they are case-insensitive" << std::endl; *stream << "" << std::endl; diff --git a/src/math/lp_norm_calculation.cc b/src/math/lp_norm_calculation.cc new file mode 100644 index 0000000..ddb621d --- /dev/null +++ b/src/math/lp_norm_calculation.cc @@ -0,0 +1,75 @@ +// ------------------------------------------------------------------------ // +// Copyright 2021 SPTK Working Group // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// ------------------------------------------------------------------------ // + +#include "SPTK/math/lp_norm_calculation.h" + +#include // std::max_element +#include // std::abs, std::pow +#include // std::size_t +#include // std::numeric_limits +#include // std::inner_product +#include // std::vector + +namespace sptk { + +LpNormCalculation::LpNormCalculation(int num_order, double p) + : num_order_(num_order), p_(p), is_valid_(true) { + if (num_order_ < 0 || p_ < 1.0) { + is_valid_ = false; + return; + } +} + +bool LpNormCalculation::Run(const std::vector& data, + double* norm) const { + if (!is_valid_ || data.size() != static_cast(num_order_ + 1) || + NULL == norm) { + return false; + } + + // Specialize for p = 2 as it is the most common case and can be computed + // faster than the general case. + if (p_ == 2.0) { + *norm = std::sqrt( + std::inner_product(data.begin(), data.end(), data.begin(), 0.0)); + return true; + } + + const double max_value(std::abs(*std::max_element( + data.begin(), data.end(), + [](double a, double b) { return std::abs(a) < std::abs(b); }))); + + if (0.0 == max_value) { + *norm = 0.0; + return true; + } + + if (p_ == std::numeric_limits::infinity()) { + *norm = max_value; + return true; + } + + const double* input(&(data[0])); + double sum(0.0); + for (int i(0); i <= num_order_; ++i) { + sum += std::pow(std::abs(input[i]) / max_value, p_); + } + *norm = max_value * std::pow(sum, 1.0 / p_); + + return true; +} + +} // namespace sptk diff --git a/src/utils/sptk_utils.cc b/src/utils/sptk_utils.cc index 2266a31..e8004a2 100644 --- a/src/utils/sptk_utils.cc +++ b/src/utils/sptk_utils.cc @@ -31,6 +31,7 @@ #include // std::setw #include // std::ios_base #include // std::cerr, std::cin, std::endl, std::left +#include // std::numeric_limits #include // std::string #include "SPTK/utils/int24_t.h" @@ -337,47 +338,57 @@ bool ConvertSpecialStringToDouble(const std::string& input, double* output) { return false; } - std::string lowercase_input(input); - std::transform(input.begin(), input.end(), lowercase_input.begin(), - [](unsigned char c) { + bool is_negative; + std::string no_sign_input; + if ('+' == input[0]) { + is_negative = false; + no_sign_input = input.substr(1); + } else if ('-' == input[0]) { + is_negative = true; + no_sign_input = input.substr(1); + } else { + is_negative = false; + no_sign_input = input; + } + + std::string lowercase_input(no_sign_input); + std::transform(no_sign_input.begin(), no_sign_input.end(), + lowercase_input.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); + + double value; if ("pi" == lowercase_input) { - *output = sptk::kPi; - return true; + value = sptk::kPi; } else if ("db" == lowercase_input) { - *output = sptk::kNeper; - return true; + value = sptk::kNeper; } else if ("cent" == lowercase_input) { - *output = 1200.0 * sptk::kOctave; - return true; + value = 1200.0 * sptk::kOctave; } else if ("semitone" == lowercase_input) { - *output = 12.0 * sptk::kOctave; - return true; + value = 12.0 * sptk::kOctave; } else if ("octave" == lowercase_input) { - *output = sptk::kOctave; - return true; - } else if (lowercase_input.find("sqrt") == 0) { - double tmp; - if (ConvertStringToDouble(lowercase_input.substr(4), &tmp) && 0.0 <= tmp) { - *output = std::sqrt(tmp); - return true; - } - } else if (lowercase_input.find("ln") == 0) { - double tmp; - if (ConvertStringToDouble(lowercase_input.substr(2), &tmp) && 0.0 < tmp) { - *output = std::log(tmp); - return true; - } - } else if (lowercase_input.find("exp") == 0) { - double tmp; - if (ConvertStringToDouble(lowercase_input.substr(3), &tmp)) { - *output = std::exp(tmp); - return true; - } + value = sptk::kOctave; + } else if (lowercase_input.find("sqrt") == 0 && + ConvertStringToDouble(lowercase_input.substr(4), &value) && + 0.0 <= value) { + value = std::sqrt(value); + } else if (lowercase_input.find("ln") == 0 && + ConvertStringToDouble(lowercase_input.substr(2), &value) && + 0.0 < value) { + value = std::log(value); + } else if (lowercase_input.find("exp") == 0 && + ConvertStringToDouble(lowercase_input.substr(3), &value)) { + value = std::exp(value); + } else if ("inf" == lowercase_input) { + value = std::numeric_limits::infinity(); + } else if ("nan" == lowercase_input) { + value = std::numeric_limits::quiet_NaN(); + } else { + return false; } - return false; + *output = is_negative ? -value : value; + return true; } bool IsEven(int num) { diff --git a/test/test_lpnorm.bats b/test/test_lpnorm.bats new file mode 100755 index 0000000..8857cfd --- /dev/null +++ b/test/test_lpnorm.bats @@ -0,0 +1,59 @@ +#!/usr/bin/env bats +# ------------------------------------------------------------------------ # +# Copyright 2021 SPTK Working Group # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# ------------------------------------------------------------------------ # + +sptk3=tools/sptk/bin +sptk4=bin +tmp=test_lpnorm + +setup() { + mkdir -p $tmp +} + +teardown() { + rm -rf $tmp +} + +@test "lpnorm: compatibility" { + for p in 1 2 inf; do + case $p in + inf) ord="float('inf')" ;; + *) ord="$p" ;; + esac + cmd="from scipy.linalg import norm; " + cmd+="x = [0.1, 0.2, 0.3, 0.4]; " + cmd+="n = norm(x, ord=$ord); " + cmd+="y = [i / n for i in x]; " + cmd+="print(*y)" + tools/venv/bin/python -c "${cmd}" | $sptk3/x2x +ad > $tmp/1 + $sptk3/ramp -s 0.1 -t 0.1 -l 4 | $sptk4/lpnorm -l 4 -p $p > $tmp/2 + run $sptk4/aeq $tmp/1 $tmp/2 + [ "$status" -eq 0 ] + done +} + +@test "lpnorm: large p value" { + $sptk3/step -l 1 -v 1 > $tmp/1 + $sptk3/nrand -l 1 | $sptk4/lpnorm -l 1 -p 1000 > $tmp/2 + run $sptk4/aeq $tmp/1 $tmp/2 + [ "$status" -eq 0 ] +} + +@test "lpnorm: valgrind" { + $sptk3/ramp -l 10 > $tmp/1 + run valgrind $sptk4/lpnorm -l 10 -f $tmp/1 + [ "$(echo "${lines[-1]}" | sed -r 's/.*SUMMARY: ([0-9]*) .*/\1/')" -eq 0 ] +} From a2a985e799bf2b20b54bcb458b0647894416dc25 Mon Sep 17 00:00:00 2001 From: takenori-y Date: Tue, 26 May 2026 10:08:28 +0900 Subject: [PATCH 2/2] minor fix [skip ci] --- src/main/lpnorm.cc | 2 +- src/math/lp_norm_calculation.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/lpnorm.cc b/src/main/lpnorm.cc index c4e29cd..32d711b 100644 --- a/src/main/lpnorm.cc +++ b/src/main/lpnorm.cc @@ -70,7 +70,7 @@ void PrintUsage(std::ostream* stream) { * - @b stdout * - double-type normalized vector sequence * - * The below example calculates l2-normalized vector sequence: + * The below example calculates L2-normalized vector sequence: * * @code{.sh} * echo 3 4 | x2x +ad | lpnorm -l 2 -p 2 | x2x +da diff --git a/src/math/lp_norm_calculation.cc b/src/math/lp_norm_calculation.cc index ddb621d..303a8cf 100644 --- a/src/math/lp_norm_calculation.cc +++ b/src/math/lp_norm_calculation.cc @@ -17,7 +17,7 @@ #include "SPTK/math/lp_norm_calculation.h" #include // std::max_element -#include // std::abs, std::pow +#include // std::abs, std::pow, std::sqrt #include // std::size_t #include // std::numeric_limits #include // std::inner_product