From 15120ce9ab48db2860ea2704a504d799a95420b2 Mon Sep 17 00:00:00 2001 From: Gerry Chen Date: Wed, 4 Aug 2021 15:30:35 -0400 Subject: [PATCH 1/7] python unit test for FitBasis --- python/gtsam/tests/test_basis.py | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 python/gtsam/tests/test_basis.py diff --git a/python/gtsam/tests/test_basis.py b/python/gtsam/tests/test_basis.py new file mode 100644 index 000000000..944bdd9f3 --- /dev/null +++ b/python/gtsam/tests/test_basis.py @@ -0,0 +1,38 @@ +""" +GTSAM Copyright 2010-2019, Georgia Tech Research Corporation, +Atlanta, Georgia 30332-0415 +All Rights Reserved + +See LICENSE for the license information + +Basis unit tests. +Author: Frank Dellaert & Gerry Chen (Python) +""" +import unittest + +import numpy as np + +import gtsam +from gtsam.utils.test_case import GtsamTestCase +from gtsam.symbol_shorthand import B + +class TestBasis(GtsamTestCase): + def test_fit_basis(self): + f = lambda x: x # line y = x + N = 2 + datax = [0., 0.5, 0.75] + interpx = np.linspace(0., 1., 10) + noise = gtsam.noiseModel.Unit.Create(1) + def testBasis(fitter, basis, f=f): + data = {x: f(x) for x in datax} + fit = fitter(N, data, noise) + coeff = fit.parameters() + interpy = basis.WeightMatrix(N, interpx) @ coeff + np.testing.assert_almost_equal(interpy, np.array([f(x) for x in interpx]), decimal=7) + testBasis(gtsam.FitBasisFourierBasis, gtsam.FourierBasis, f=lambda x: 0.7*np.cos(x)) + testBasis(gtsam.FitBasisChebyshev1Basis, gtsam.Chebyshev1Basis) + testBasis(gtsam.FitBasisChebyshev2Basis, gtsam.Chebyshev2Basis) + testBasis(gtsam.FitBasisChebyshev2, gtsam.Chebyshev2) + +if __name__ == "__main__": + unittest.main() From b99bf4e92912f4ad0020f79d6b222a3d6593514f Mon Sep 17 00:00:00 2001 From: Gerry Chen Date: Fri, 27 Aug 2021 11:23:38 -0400 Subject: [PATCH 2/7] add and fix constructor argument order --- python/gtsam/preamble/basis.h | 2 ++ python/gtsam/tests/test_basis.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/python/gtsam/preamble/basis.h b/python/gtsam/preamble/basis.h index d07a75f6f..56a07cfdd 100644 --- a/python/gtsam/preamble/basis.h +++ b/python/gtsam/preamble/basis.h @@ -10,3 +10,5 @@ * Without this they will be automatically converted to a Python object, and all * mutations on Python side will not be reflected on C++. */ + +#include diff --git a/python/gtsam/tests/test_basis.py b/python/gtsam/tests/test_basis.py index 944bdd9f3..0df9a80b0 100644 --- a/python/gtsam/tests/test_basis.py +++ b/python/gtsam/tests/test_basis.py @@ -25,7 +25,7 @@ class TestBasis(GtsamTestCase): noise = gtsam.noiseModel.Unit.Create(1) def testBasis(fitter, basis, f=f): data = {x: f(x) for x in datax} - fit = fitter(N, data, noise) + fit = fitter(data, noise, N) coeff = fit.parameters() interpy = basis.WeightMatrix(N, interpx) @ coeff np.testing.assert_almost_equal(interpy, np.array([f(x) for x in interpx]), decimal=7) From 2f6b8d6314ede652df5cb2125cd9d41490da98a9 Mon Sep 17 00:00:00 2001 From: Gerry Chen Date: Fri, 27 Aug 2021 12:01:06 -0400 Subject: [PATCH 3/7] docstrings and formatting --- python/gtsam/tests/test_basis.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/python/gtsam/tests/test_basis.py b/python/gtsam/tests/test_basis.py index 0df9a80b0..8d4039249 100644 --- a/python/gtsam/tests/test_basis.py +++ b/python/gtsam/tests/test_basis.py @@ -16,23 +16,41 @@ import gtsam from gtsam.utils.test_case import GtsamTestCase from gtsam.symbol_shorthand import B + class TestBasis(GtsamTestCase): + """Tests Basis module python bindings + """ def test_fit_basis(self): + """Tests FitBasis python binding for FourierBasis, Chebyshev1Basis, Chebyshev2Basis, and + Chebyshev2. + It tests FitBasis by fitting to a ground-truth function that can be represented exactly in + the basis, then checking that the regression (fit result) matches the function. For the + Chebyshev bases, the line y=x is used to generate the data while for Fourier, 0.7*cos(x) is + used. + """ f = lambda x: x # line y = x N = 2 datax = [0., 0.5, 0.75] interpx = np.linspace(0., 1., 10) noise = gtsam.noiseModel.Unit.Create(1) + + def evaluate(basis, fitparams, x): + # until wrapper for Basis functors are ready, this is how to evaluate a basis function. + return basis.WeightMatrix(N, x) @ fitparams + def testBasis(fitter, basis, f=f): + # test a basis by checking that the fit result matches the function at x-values interpx. data = {x: f(x) for x in datax} fit = fitter(data, noise, N) coeff = fit.parameters() - interpy = basis.WeightMatrix(N, interpx) @ coeff + interpy = evaluate(basis, coeff, interpx) np.testing.assert_almost_equal(interpy, np.array([f(x) for x in interpx]), decimal=7) - testBasis(gtsam.FitBasisFourierBasis, gtsam.FourierBasis, f=lambda x: 0.7*np.cos(x)) + + testBasis(gtsam.FitBasisFourierBasis, gtsam.FourierBasis, f=lambda x: 0.7 * np.cos(x)) testBasis(gtsam.FitBasisChebyshev1Basis, gtsam.Chebyshev1Basis) testBasis(gtsam.FitBasisChebyshev2Basis, gtsam.Chebyshev2Basis) testBasis(gtsam.FitBasisChebyshev2, gtsam.Chebyshev2) + if __name__ == "__main__": unittest.main() From 286b2fa4b0acd8611be292e4e7a9e8b29a144a5f Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sat, 28 Aug 2021 15:37:06 -0400 Subject: [PATCH 4/7] fix python tests and make verbose so they are easy to debug --- python/CMakeLists.txt | 5 +++-- python/gtsam/tests/test_basis.py | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index a5fdc80a6..4254a21c6 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -166,5 +166,6 @@ add_custom_target( COMMAND ${CMAKE_COMMAND} -E env # add package to python path so no need to install "PYTHONPATH=${GTSAM_PYTHON_BUILD_DIRECTORY}/$ENV{PYTHONPATH}" - ${PYTHON_EXECUTABLE} -m unittest discover -s "${GTSAM_PYTHON_BUILD_DIRECTORY}/gtsam/tests" - DEPENDS ${GTSAM_PYTHON_DEPENDENCIES}) + ${PYTHON_EXECUTABLE} -m unittest discover -v -s . + DEPENDS ${GTSAM_PYTHON_DEPENDENCIES} + WORKING_DIRECTORY "${GTSAM_PYTHON_BUILD_DIRECTORY}/gtsam/tests") diff --git a/python/gtsam/tests/test_basis.py b/python/gtsam/tests/test_basis.py index 8d4039249..3af0a87f1 100644 --- a/python/gtsam/tests/test_basis.py +++ b/python/gtsam/tests/test_basis.py @@ -18,10 +18,12 @@ from gtsam.symbol_shorthand import B class TestBasis(GtsamTestCase): - """Tests Basis module python bindings + """ + Tests Basis module python bindings. """ def test_fit_basis(self): - """Tests FitBasis python binding for FourierBasis, Chebyshev1Basis, Chebyshev2Basis, and + """ + Tests FitBasis python binding for FourierBasis, Chebyshev1Basis, Chebyshev2Basis, and Chebyshev2. It tests FitBasis by fitting to a ground-truth function that can be represented exactly in the basis, then checking that the regression (fit result) matches the function. For the From 066bd0f05bb91a0f7aedfbc7a382bfef2acf0869 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sat, 28 Aug 2021 20:03:03 -0400 Subject: [PATCH 5/7] verbose python testing --- .github/scripts/python.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/python.sh b/.github/scripts/python.sh index 22098ec08..3f5701281 100644 --- a/.github/scripts/python.sh +++ b/.github/scripts/python.sh @@ -85,4 +85,4 @@ make -j2 install cd $GITHUB_WORKSPACE/build/python $PYTHON setup.py install --user --prefix= cd $GITHUB_WORKSPACE/python/gtsam/tests -$PYTHON -m unittest discover +$PYTHON -m unittest discover -v From 65837c103010beb48ddc152bc684983c1fd8671b Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sun, 29 Aug 2021 04:21:57 -0400 Subject: [PATCH 6/7] Fix bug in FourierBasis --- gtsam/basis/Fourier.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gtsam/basis/Fourier.h b/gtsam/basis/Fourier.h index 6943150d7..d264e182d 100644 --- a/gtsam/basis/Fourier.h +++ b/gtsam/basis/Fourier.h @@ -40,9 +40,13 @@ class GTSAM_EXPORT FourierBasis : public Basis { static Weights CalculateWeights(size_t N, double x) { Weights b(N); b[0] = 1; - for (size_t i = 1, n = 1; i < N; i += 2, n++) { - b[i] = cos(n * x); - b[i + 1] = sin(n * x); + for (size_t i = 1, n = 1; i < N; i++) { + if (i % 2 == 1) { + b[i] = cos(n * x); + } else { + b[i] = sin(n * x); + n++; + } } return b; } From 289cb8f35b1b68f280b220f3eb755a5d44148eae Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sun, 29 Aug 2021 04:36:57 -0400 Subject: [PATCH 7/7] break down tests to make reporting clearer --- python/gtsam/tests/test_basis.py | 92 ++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 27 deletions(-) diff --git a/python/gtsam/tests/test_basis.py b/python/gtsam/tests/test_basis.py index 3af0a87f1..5d3c5ace3 100644 --- a/python/gtsam/tests/test_basis.py +++ b/python/gtsam/tests/test_basis.py @@ -19,39 +19,77 @@ from gtsam.symbol_shorthand import B class TestBasis(GtsamTestCase): """ - Tests Basis module python bindings. + Tests FitBasis python binding for FourierBasis, Chebyshev1Basis, Chebyshev2Basis, and Chebyshev2. + + It tests FitBasis by fitting to a ground-truth function that can be represented exactly in + the basis, then checking that the regression (fit result) matches the function. For the + Chebyshev bases, the line y=x is used to generate the data while for Fourier, 0.7*cos(x) is + used. """ - def test_fit_basis(self): + def setUp(self): + self.N = 2 + self.x = [0., 0.5, 0.75] + self.interpx = np.linspace(0., 1., 10) + self.noise = gtsam.noiseModel.Unit.Create(1) + + def evaluate(self, basis, fitparams, x): """ - Tests FitBasis python binding for FourierBasis, Chebyshev1Basis, Chebyshev2Basis, and - Chebyshev2. - It tests FitBasis by fitting to a ground-truth function that can be represented exactly in - the basis, then checking that the regression (fit result) matches the function. For the - Chebyshev bases, the line y=x is used to generate the data while for Fourier, 0.7*cos(x) is - used. + Until wrapper for Basis functors are ready, + this is how to evaluate a basis function. """ - f = lambda x: x # line y = x - N = 2 - datax = [0., 0.5, 0.75] - interpx = np.linspace(0., 1., 10) - noise = gtsam.noiseModel.Unit.Create(1) + return basis.WeightMatrix(self.N, x) @ fitparams - def evaluate(basis, fitparams, x): - # until wrapper for Basis functors are ready, this is how to evaluate a basis function. - return basis.WeightMatrix(N, x) @ fitparams + def fit_basis_helper(self, fitter, basis, f=lambda x: x): + """Helper method to fit data to a specified fitter using a specified basis.""" + data = {x: f(x) for x in self.x} + fit = fitter(data, self.noise, self.N) + coeff = fit.parameters() + interpy = self.evaluate(basis, coeff, self.interpx) + return interpy - def testBasis(fitter, basis, f=f): - # test a basis by checking that the fit result matches the function at x-values interpx. - data = {x: f(x) for x in datax} - fit = fitter(data, noise, N) - coeff = fit.parameters() - interpy = evaluate(basis, coeff, interpx) - np.testing.assert_almost_equal(interpy, np.array([f(x) for x in interpx]), decimal=7) + def test_fit_basis_fourier(self): + """Fit a Fourier basis.""" - testBasis(gtsam.FitBasisFourierBasis, gtsam.FourierBasis, f=lambda x: 0.7 * np.cos(x)) - testBasis(gtsam.FitBasisChebyshev1Basis, gtsam.Chebyshev1Basis) - testBasis(gtsam.FitBasisChebyshev2Basis, gtsam.Chebyshev2Basis) - testBasis(gtsam.FitBasisChebyshev2, gtsam.Chebyshev2) + f = lambda x: 0.7 * np.cos(x) + interpy = self.fit_basis_helper(gtsam.FitBasisFourierBasis, + gtsam.FourierBasis, f) + # test a basis by checking that the fit result matches the function at x-values interpx. + np.testing.assert_almost_equal(interpy, + np.array([f(x) for x in self.interpx]), + decimal=7) + + def test_fit_basis_chebyshev1basis(self): + """Fit a Chebyshev1 basis.""" + + f = lambda x: x + interpy = self.fit_basis_helper(gtsam.FitBasisChebyshev1Basis, + gtsam.Chebyshev1Basis, f) + # test a basis by checking that the fit result matches the function at x-values interpx. + np.testing.assert_almost_equal(interpy, + np.array([f(x) for x in self.interpx]), + decimal=7) + + def test_fit_basis_chebyshev2basis(self): + """Fit a Chebyshev2 basis.""" + + f = lambda x: x + interpy = self.fit_basis_helper(gtsam.FitBasisChebyshev2Basis, + gtsam.Chebyshev2Basis) + # test a basis by checking that the fit result matches the function at x-values interpx. + np.testing.assert_almost_equal(interpy, + np.array([f(x) for x in self.interpx]), + decimal=7) + + def test_fit_basis_chebyshev2(self): + """Fit a Chebyshev2 pseudospectral basis.""" + + f = lambda x: x + interpy = self.fit_basis_helper(gtsam.FitBasisChebyshev2, + gtsam.Chebyshev2) + # test a basis by checking that the fit result matches the function at x-values interpx. + np.testing.assert_almost_equal(interpy, + np.array([f(x) for x in self.interpx]), + decimal=7) if __name__ == "__main__":