Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: L1 and L2 norms #2636

Merged
merged 31 commits into from
Jan 2, 2022
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
47c5bd8
working L1 norm and tests
lyndond Dec 29, 2021
7dbd6a9
fix typo
lyndond Dec 29, 2021
f89d5f0
norm2 and tests done
lyndond Dec 29, 2021
d27f237
revert back dot_self
lyndond Dec 29, 2021
71b3181
pass lint check
lyndond Dec 29, 2021
c6721c6
[Jenkins] auto-formatting by clang-format version 6.0.0-1ubuntu2~16.0…
stan-buildbot Dec 29, 2021
e54389c
make dot_self templated
lyndond Dec 30, 2021
a504f17
template and Eigen::Map norm1,norm2
lyndond Dec 30, 2021
94b5ff3
clean up tests
lyndond Dec 30, 2021
1b991ad
rebase
lyndond Dec 30, 2021
fc9d2ad
[Jenkins] auto-formatting by clang-format version 6.0.0-1ubuntu2~16.0…
stan-buildbot Dec 30, 2021
fe3be43
reduce redundancy by using apply_vector_unary
lyndond Dec 30, 2021
2d234bb
Merge branch 'l1_l2_norms' of https://github.com/lyndond/math into l1…
lyndond Dec 30, 2021
a7aaa48
cpplint
lyndond Dec 30, 2021
9b3c93d
change dot_self.hpp back bc no longer used in norms
lyndond Dec 30, 2021
dfc9a8a
[Jenkins] auto-formatting by clang-format version 6.0.0-1ubuntu2~16.0…
stan-buildbot Dec 30, 2021
8e70232
Revert "[Jenkins] auto-formatting by clang-format version 6.0.0-1ubun…
lyndond Dec 30, 2021
1b3ffc9
add require_not_st_var to prim funs to fix rev tests
lyndond Dec 30, 2021
665d254
Merge commit 'a43562ea29ef1bb892cb7942787d682f002dfc7c' into HEAD
yashikno Dec 30, 2021
ce03d8b
[Jenkins] auto-formatting by clang-format version 6.0.0-1ubuntu2~16.0…
stan-buildbot Dec 30, 2021
b745142
fix broken var in norm2
lyndond Dec 30, 2021
100eb4c
remove unnecessary includes
lyndond Dec 31, 2021
19e4736
commit suggestions by @andrjohns
lyndond Dec 31, 2021
fcbe661
[Jenkins] auto-formatting by clang-format version 6.0.0-1ubuntu2~16.0…
stan-buildbot Dec 31, 2021
7adbc75
added forward-mode specializations
lyndond Dec 31, 2021
18bee81
[Jenkins] auto-formatting by clang-format version 6.0.0-1ubuntu2~16.0…
stan-buildbot Dec 31, 2021
d731ef2
template argument fix
lyndond Dec 31, 2021
548bccf
Merge branch 'l1_l2_norms' of https://github.com/lyndond/math into l1…
lyndond Dec 31, 2021
61fc4ab
forward specializations
lyndond Jan 1, 2022
238420e
require_st_arithmetic for prim functions
lyndond Jan 2, 2022
535f0cb
replace omitted require_container_t
lyndond Jan 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions stan/math/fwd/fun.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@
#include <stan/math/fwd/fun/multiply_log.hpp>
#include <stan/math/fwd/fun/multiply_lower_tri_self_transpose.hpp>
#include <stan/math/fwd/fun/norm.hpp>
#include <stan/math/fwd/fun/norm1.hpp>
#include <stan/math/fwd/fun/norm2.hpp>
#include <stan/math/fwd/fun/owens_t.hpp>
#include <stan/math/fwd/fun/Phi.hpp>
#include <stan/math/fwd/fun/Phi_approx.hpp>
Expand Down
36 changes: 36 additions & 0 deletions stan/math/fwd/fun/norm1.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#ifndef STAN_MATH_FWD_FUN_NORM1_HPP
#define STAN_MATH_FWD_FUN_NORM1_HPP

#include <stan/math/fwd/meta.hpp>
#include <stan/math/fwd/core.hpp>
#include <stan/math/prim/meta.hpp>
#include <stan/math/prim/fun/Eigen.hpp>
#include <stan/math/prim/fun/constants.hpp>
#include <stan/math/prim/fun/norm1.hpp>
#include <stan/math/prim/fun/sign.hpp>
#include <stan/math/prim/fun/to_ref.hpp>

namespace stan {
namespace math {

/**
* Compute the L1 norm of the specified vector of values.
*
* @tparam T Type of input vector.
* @param[in] x Vector of specified values.
* @return L1 norm of x.
*/
template <typename Container,
require_container_st<is_fvar, Container>* = nullptr>
inline auto norm1(const Container& x) {
return apply_vector_unary<ref_type_t<Container>>::reduce(
to_ref(x), [&](const auto& v) {
using T_fvar_inner = typename value_type_t<decltype(v)>::Scalar;
return fvar<T_fvar_inner>(norm1(v.val()),
v.d().cwiseProduct(sign(v.val())).sum());
});
}

} // namespace math
} // namespace stan
#endif
35 changes: 35 additions & 0 deletions stan/math/fwd/fun/norm2.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#ifndef STAN_MATH_FWD_FUN_NORM2_HPP
#define STAN_MATH_FWD_FUN_NORM2_HPP

#include <stan/math/fwd/meta.hpp>
#include <stan/math/fwd/core.hpp>
#include <stan/math/prim/meta.hpp>
#include <stan/math/prim/fun/Eigen.hpp>
#include <stan/math/prim/fun/norm2.hpp>
#include <stan/math/prim/fun/to_ref.hpp>

namespace stan {
namespace math {

/**
* Compute the L2 norm of the specified vector of values.
*
* @tparam T Type of input vector.
* @param[in] x Vector of specified values.
* @return L2 norm of x.
*/
template <typename Container,
require_container_st<is_fvar, Container>* = nullptr>
inline auto norm2(const Container& x) {
return apply_vector_unary<ref_type_t<Container>>::reduce(
to_ref(x), [&](const auto& v) {
using T_fvar_inner = typename value_type_t<decltype(v)>::Scalar;
T_fvar_inner res = norm2(v.val());
return fvar<T_fvar_inner>(res,
v.d().cwiseProduct((v.val() / res)).sum());
});
}

} // namespace math
} // namespace stan
#endif
2 changes: 2 additions & 0 deletions stan/math/prim/fun.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@
#include <stan/math/prim/fun/multiply_log.hpp>
#include <stan/math/prim/fun/multiply_lower_tri_self_transpose.hpp>
#include <stan/math/prim/fun/norm.hpp>
#include <stan/math/prim/fun/norm1.hpp>
#include <stan/math/prim/fun/norm2.hpp>
#include <stan/math/prim/fun/num_elements.hpp>
#include <stan/math/prim/fun/offset_multiplier_constrain.hpp>
#include <stan/math/prim/fun/offset_multiplier_free.hpp>
Expand Down
30 changes: 30 additions & 0 deletions stan/math/prim/fun/norm1.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef STAN_MATH_PRIM_FUN_NORM1_HPP
#define STAN_MATH_PRIM_FUN_NORM1_HPP

#include <stan/math/prim/meta.hpp>
#include <stan/math/prim/err.hpp>
#include <stan/math/prim/fun/Eigen.hpp>

namespace stan {
namespace math {

/**
* Returns L1 norm of a vector. For vectors that equals the
* sum of magnitudes of its individual elements.
*
* @tparam T type of the vector (must be derived from \c Eigen::MatrixBase)
* @param v Vector.
* @return L1 norm of v.
*/
template <typename Container, require_container_t<Container>* = nullptr,
require_not_st_var<Container>* = nullptr,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These templates can be a little stricter by specifying that they're only for arithmetic types, see the log_softmax header for an example

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, I changed the templates to include require_st_arithmetic<> to match the template of log_softmax.

Does require_st_arithmetic<> do the job of both require_not_st_var<> and require_not_st_fvar<>? These are what I used to resolve ambiguity between prim vs rev and prim vs fwd, respectively.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, it will restrict to only matching inputs that satisfy std::is_arithmetic

require_not_st_fvar<Container>* = nullptr>
inline auto norm1(const Container& x) {
return apply_vector_unary<ref_type_t<Container>>::reduce(
to_ref(x), [](const auto& v) { return v.template lpNorm<1>(); });
}

} // namespace math
} // namespace stan

#endif
30 changes: 30 additions & 0 deletions stan/math/prim/fun/norm2.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef STAN_MATH_PRIM_FUN_NORM2_HPP
#define STAN_MATH_PRIM_FUN_NORM2_HPP

#include <stan/math/prim/meta.hpp>
#include <stan/math/prim/err.hpp>
#include <stan/math/prim/fun/Eigen.hpp>

namespace stan {
namespace math {

/**
* Returns L2 norm of a vector. For vectors that equals the square-root of the
* sum of squares of the elements.
*
* @tparam T type of the vector (must be derived from \c Eigen::MatrixBase)
* @param v Vector.
* @return L2 norm of v.
*/
template <typename Container, require_container_t<Container>* = nullptr,
require_not_st_var<Container>* = nullptr,
require_not_st_fvar<Container>* = nullptr>
inline auto norm2(const Container& x) {
return apply_vector_unary<ref_type_t<Container>>::reduce(
to_ref(x), [](const auto& v) { return v.template lpNorm<2>(); });
}

} // namespace math
} // namespace stan

#endif
2 changes: 2 additions & 0 deletions stan/math/rev/fun.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@
#include <stan/math/rev/fun/multiply_log.hpp>
#include <stan/math/rev/fun/multiply_lower_tri_self_transpose.hpp>
#include <stan/math/rev/fun/norm.hpp>
#include <stan/math/rev/fun/norm1.hpp>
#include <stan/math/rev/fun/norm2.hpp>
#include <stan/math/rev/fun/ordered_constrain.hpp>
#include <stan/math/rev/fun/owens_t.hpp>
#include <stan/math/rev/fun/polar.hpp>
Expand Down
49 changes: 49 additions & 0 deletions stan/math/rev/fun/norm1.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#ifndef STAN_MATH_REV_FUN_NORM1_HPP
#define STAN_MATH_REV_FUN_NORM1_HPP

#include <stan/math/rev/meta.hpp>
#include <stan/math/rev/core.hpp>
#include <stan/math/rev/core/typedefs.hpp>
#include <stan/math/prim/err.hpp>
#include <stan/math/prim/fun/Eigen.hpp>
#include <stan/math/prim/fun/sign.hpp>

namespace stan {
namespace math {

/**
* Returns the L1 norm of a vector of var.
*
* @tparam T type of the vector (must have one compile-time dimension equal to
* 1)
* @param[in] v Vector.
* @return L1 norm of v.
*/
template <typename T, require_eigen_vector_vt<is_var, T>* = nullptr>
inline var norm1(const T& v) {
arena_t<T> arena_v = v;
var res = norm1(arena_v.val());
reverse_pass_callback([res, arena_v]() mutable {
arena_v.adj().array() += res.adj() * sign(arena_v.val().array());
});
return res;
}

/**
* Returns the L1 norm of a `var_value<Vector>`.
*
* @tparam A `var_value<>` whose inner type has one compile-time row or column.
* @param[in] v Vector.
* @return L1 norm of v.
*/
//
template <typename T, require_var_matrix_t<T>* = nullptr>
inline var norm1(const T& v) {
return make_callback_vari(norm1(v.val()), [v](const auto& res) mutable {
v.adj().array() += res.adj() * sign(v.val().array());
});
}

} // namespace math
} // namespace stan
#endif
47 changes: 47 additions & 0 deletions stan/math/rev/fun/norm2.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#ifndef STAN_MATH_REV_FUN_NORM2_HPP
#define STAN_MATH_REV_FUN_NORM2_HPP

#include <stan/math/rev/meta.hpp>
#include <stan/math/rev/core.hpp>
#include <stan/math/rev/core/typedefs.hpp>
#include <stan/math/prim/err.hpp>
#include <stan/math/prim/fun/Eigen.hpp>

namespace stan {
namespace math {

/**
* Returns the L2 norm of a vector of var.
*
* @tparam T type of the vector (must have one compile-time dimension equal to
* 1)
* @param[in] v Vector.
* @return L2 norm of v.
*/
template <typename T, require_eigen_vector_vt<is_var, T>* = nullptr>
inline var norm2(const T& v) {
arena_t<T> arena_v = v;
var res = norm2(arena_v.val());
reverse_pass_callback([res, arena_v]() mutable {
arena_v.adj().array() += res.adj() * (arena_v.val().array() / res.val());
});
return res;
}

/**
* Returns the L2 norm of a `var_value<Vector>`.
*
* @tparam A `var_value<>` whose inner type has one compile-time row or column.
* @param[in] v Vector.
* @return L2 norm of v.
*/
template <typename T, require_var_matrix_t<T>* = nullptr>
inline var norm2(const T& v) {
return make_callback_vari(norm2(v.val()), [v](const auto& res) mutable {
v.adj().array() += res.adj() * (v.val().array() / res.val());
});
}

} // namespace math
} // namespace stan
#endif
22 changes: 22 additions & 0 deletions test/unit/math/mix/fun/norm1_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include <test/unit/math/test_ad.hpp>
#include <vector>

TEST(MathMixMatFun, norm1) {
auto f = [](const auto& y) { return stan::math::norm1(y); };

Eigen::VectorXd x0(0);

Eigen::VectorXd x1(1);
x1 << 2;

Eigen::VectorXd x2(2);
x2 << 2, 3;

Eigen::VectorXd x3(3);
x3 << 2, 3, 4;

for (const auto& a : std::vector<Eigen::VectorXd>{x0, x1, x2, x3}) {
stan::test::expect_ad(f, a);
stan::test::expect_ad_matvar(f, a);
}
}
22 changes: 22 additions & 0 deletions test/unit/math/mix/fun/norm2_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include <test/unit/math/test_ad.hpp>
#include <vector>

TEST(MathMixMatFun, norm2) {
auto f = [](const auto& y) { return stan::math::norm2(y); };

Eigen::VectorXd x0(0);

Eigen::VectorXd x1(1);
x1 << 2;

Eigen::VectorXd x2(2);
x2 << 2, 3;

Eigen::VectorXd x3(3);
x3 << 2, 3, 4;

for (const auto& a : std::vector<Eigen::VectorXd>{x0, x1, x2, x3}) {
stan::test::expect_ad(f, a);
stan::test::expect_ad_matvar(f, a);
}
}
76 changes: 76 additions & 0 deletions test/unit/math/prim/fun/norm1_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include <stan/math/prim.hpp>
#include <gtest/gtest.h>
#include <cmath>
#include <limits>
#include <vector>

TEST(MathFunctions, norm1) {
std::vector<double> x(3), y(3);
x[0] = 2.33;
x[1] = 8.88;
x[2] = 9.81;
y[0] = 2.46;
y[1] = 4.45;
y[2] = 1.03;

EXPECT_FLOAT_EQ(21.02, stan::math::norm1(x));
EXPECT_FLOAT_EQ(7.94, stan::math::norm1(y));
}

TEST(MathFunctions, norm1_nan) {
std::vector<double> x(3);
x[0] = 2.33;
x[1] = 8.88;
x[2] = 9.81;

double nan = std::numeric_limits<double>::quiet_NaN();
x[2] = nan;

EXPECT_TRUE(std::isnan(stan::math::norm1(x)));

x[0] = nan;
x[1] = nan;
x[2] = nan;
EXPECT_TRUE(std::isnan(stan::math::norm1(x)));
}

TEST(MathMatrixPrimMat, norm1) {
using stan::math::norm1;

Eigen::Matrix<double, Eigen::Dynamic, 1> v1(1);
v1 << 2.0;
EXPECT_NEAR(2.0, norm1(v1), 1E-12);
Eigen::Matrix<double, Eigen::Dynamic, 1> v2(2);
v2 << 2.0, 3.0;
EXPECT_NEAR(5.0, norm1(v2), 1E-12);
Eigen::Matrix<double, Eigen::Dynamic, 1> v3(3);
v3 << 2.0, 3.0, 4.0;
EXPECT_NEAR(9.0, norm1(v3), 1E-12);

Eigen::Matrix<double, 1, Eigen::Dynamic> rv1(1);
rv1 << 2.0;
EXPECT_NEAR(2.0, norm1(rv1), 1E-12);
Eigen::Matrix<double, 1, Eigen::Dynamic> rv2(2);
rv2 << 2.0, 3.0;
EXPECT_NEAR(5.0, norm1(rv2), 1E-12);
Eigen::Matrix<double, 1, Eigen::Dynamic> rv3(3);
rv3 << 2.0, 3.0, 4.0;
EXPECT_NEAR(9.0, norm1(rv3), 1E-12);

Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> m1(1, 1);
m1 << 2.0;
EXPECT_NEAR(2.0, norm1(m1), 1E-12);
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> m2(2, 1);
m2 << 2.0, 3.0;
EXPECT_NEAR(5.0, norm1(m2), 1E-12);
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> m3(3, 1);
m3 << 2.0, 3.0, 4.0;
EXPECT_NEAR(9.0, norm1(m3), 1E-12);

Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> mm2(1, 2);
mm2 << 2.0, 3.0;
EXPECT_NEAR(5.0, norm1(mm2), 1E-12);
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> mm3(1, 3);
mm3 << 2.0, 3.0, 4.0;
EXPECT_NEAR(9.0, norm1(mm3), 1E-12);
}
Loading