consume PX4/Matrix repository and preserve history

This commit is contained in:
Daniel Agar
2021-11-16 12:24:19 -05:00
59 changed files with 7837 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
Checks: 'clang-diagnostic-*,clang-analyzer-*,*,
,-cppcoreguidelines-pro-type-vararg
,-cppcoreguidelines-pro-bounds-array-to-pointer-decay
,-cppcoreguidelines-pro-bounds-constant-array-index
,-cppcoreguidelines-pro-bounds-pointer-arithmetic
'
WarningsAsErrors: '*'
HeaderFilterRegex: '*.h, *.hpp, *.hh, *.hxx'
+53
View File
@@ -0,0 +1,53 @@
name: Tests
on:
push:
branches:
- 'master'
pull_request:
branches:
- '*'
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false # don't cancel if a job from the matrix fails
matrix:
compiler:
- { compiler: GNU, CC: gcc, CXX: g++, FORMAT: ON }
- { compiler: LLVM, CC: clang, CXX: clang++, FORMAT: OFF }
#flavor: [ Release, Coverage ] # TODO
flavor: [ Release ]
exclude:
- compiler: { compiler: LLVM, CC: clang, CXX: clang++ }
flavor: Coverage
steps:
- uses: actions/checkout@v2
with:
submodules: 'recursive'
- name: Install Dependencies
run: |
sudo apt install -y clang cmake g++ gcc lcov
- name: Fetch coveralls
if: matrix.flavor == 'Coverage'
run: |
pip install --user cpp-coveralls
- name: Build & Run
run: |
cmake -DCMAKE_BUILD_TYPE=${{ matrix.flavor }} -DSUPPORT_STDIOSTREAM=ON -DTESTING=ON -DFORMAT=${{ matrix.compiler.FORMAT }} .
make check
# TODO: enable
# - name: Coveralls build
# if: matrix.flavor == 'Coverage'
# run: |
# cpp-coveralls -i matrix
# - name: Coveralls
# if: matrix.flavor == 'Coverage'
# uses: coverallsapp/github-action@master
# with:
# github-token: ${{ secrets.GITHUB_TOKEN }}
+39
View File
@@ -0,0 +1,39 @@
*.orig
*.swp
*~
astyle/
build*/
cmake_install.cmake
CMakeCache.txt
CMakeFiles/
compile_commands.json
CPackConfig.cmake
CPackSourceConfig.cmake
CTestTestfile.cmake
Makefile
test/attitude
test/cmake_install.cmake
test/CMakeFiles/
test/copyto
test/coverage.info
test/CTestTestfile.cmake
test/filter
test/hatvee
test/helper
test/integration
test/inverse
test/Makefile
test/matrixAssignment
test/matrixMult
test/matrixScalarMult
test/out/
test/pseudoinverse
test/setIdentity
test/slice
test/squareMatrix
test/transpose
test/vector
test/vector2
test/vector3
test/vectorAssignment
Testing/
View File
+180
View File
@@ -0,0 +1,180 @@
cmake_minimum_required(VERSION 2.8)
set(VERSION_MAJOR "1")
set(VERSION_MINOR "0")
set(VERSION_PATCH "2")
project(matrix CXX)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if (NOT CMAKE_BUILD_TYPE)
if(TESTING)
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type" FORCE)
else()
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Build type" FORCE)
endif()
message(STATUS "set build type to ${CMAKE_BUILD_TYPE}")
endif()
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release;RelWithDebInfo;MinSizeRel;Coverage")
option(SUPPORT_STDIOSTREAM "If enabled provides support for << operator (as used with std::cout)" OFF)
option(TESTING "Enable testing" OFF)
option(FORMAT "Enable formatting" OFF)
option(COV_HTML "Display html for coverage" OFF)
option(ASAN "Enable address sanitizer" OFF)
option(UBSAN "Enable undefined behaviour sanitizer" OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include_directories(${CMAKE_SOURCE_DIR})
if(SUPPORT_STDIOSTREAM)
add_definitions(-DSUPPORT_STDIOSTREAM)
endif()
if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "AppleClang"))
set(CMAKE_CXX_FLAGS_COVERAGE
"--coverage -ftest-coverage -fdiagnostics-absolute-paths -O0 -fprofile-arcs -fno-inline-functions -fno-elide-constructors"
CACHE STRING "Flags used by the C++ compiler during coverage builds" FORCE)
set(CMAKE_EXE_LINKER_FLAGS_COVERAGE
"-ftest-coverage -fdiagnostics-absolute-paths"
CACHE STRING "Flags used for linking binaries during coverage builds" FORCE)
else()
set(CMAKE_CXX_FLAGS_COVERAGE
# Add when GCC 9 or later is available as part of the default toolchain: -fprofile-abs-path
"--coverage -fprofile-arcs -ftest-coverage -O0 -fno-default-inline -fno-inline -fno-inline-small-functions -fno-elide-constructors"
CACHE STRING "Flags used by the C++ compiler during coverage builds" FORCE)
set(CMAKE_EXE_LINKER_FLAGS_COVERAGE
"--coverage -ftest-coverage -lgcov"
CACHE STRING "Flags used for linking binaries during coverage builds" FORCE)
endif()
mark_as_advanced(CMAKE_CXX_FLAGS_COVERAGE CMAKE_C_FLAGS_COVERAGE CMAKE_EXE_LINKER_FLAGS_COVERAGE)
add_compile_options(
-pedantic
-Wall
-Warray-bounds
-Wcast-align
-Wcast-qual
-Wconversion
-Wctor-dtor-privacy
-Wdisabled-optimization
-Werror
-Wextra
-Wfloat-equal
-Wformat-security
-Wformat=2
-Winit-self
-Wlogical-op
-Wmissing-declarations
-Wmissing-include-dirs
-Wno-sign-compare
-Wno-unused
-Wno-unused-parameter
-Wnoexcept
-Wold-style-cast
-Woverloaded-virtual
-Wpointer-arith
-Wredundant-decls
-Wreorder
-Wshadow
-Wsign-conversion
-Wsign-promo
-Wstrict-null-sentinel
-Wswitch-default
-Wundef
-Wuninitialized
-Wunused-variable
)
# clang tolerate unknown gcc options
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
add_compile_options(
-Wno-error=unused-command-line-argument-hard-error-in-future
-Wno-unknown-warning-option
)
else()
add_compile_options(
-Wstrict-overflow=5
)
endif()
# santiziers (ASAN, UBSAN)
if(ASAN)
message(STATUS "address sanitizer enabled")
# environment variables
# ASAN_OPTIONS=detect_stack_use_after_return=1
# ASAN_OPTIONS=check_initialization_order=1
add_compile_options(
-fsanitize=address
-g3
-O1
-fno-omit-frame-pointer
)
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address)
elseif(UBSAN)
message(STATUS "undefined behaviour sanitizer enabled")
add_compile_options(
-fsanitize=undefined
-fsanitize=integer
-g3
-O1
-fno-omit-frame-pointer
)
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined)
endif()
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure)
if(TESTING)
enable_testing()
add_subdirectory(test)
add_dependencies(check test_build)
add_custom_target(clang-tidy COMMAND clang-tidy -p . ${CMAKE_SOURCE_DIR}/test/*.cpp)
add_dependencies(clang-tidy test_build)
endif()
if(FORMAT)
set(astyle_exe ${CMAKE_BINARY_DIR}/astyle/src/bin/astyle)
add_custom_command(OUTPUT ${astyle_exe}
COMMAND wget http://sourceforge.net/projects/astyle/files/astyle/astyle%202.06/astyle_2.06_linux.tar.gz -O /tmp/astyle.tar.gz
COMMAND tar -xvf /tmp/astyle.tar.gz
COMMAND cd astyle/src && make -f ../build/gcc/Makefile
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_target(check_format
COMMAND scripts/format.sh ${astyle_exe} 0
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
DEPENDS ${astyle_exe}
VERBATIM
)
add_custom_target(format
COMMAND scripts/format.sh ${astyle_exe} 1
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
VERBATIM
DEPENDS ${astyle_exe}
)
add_dependencies(check check_format)
endif()
set(CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${VERSION_PATCH})
set(CPACK_PACKAGE_CONTACT "james.goppert@gmail.com")
include(CPack)
# vim: set et fenc=utf-8 ft=cmake ff=unix sts=0 sw=4 ts=4 :
+29
View File
@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2015, PX4 Pro Drone Autopilot
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+146
View File
@@ -0,0 +1,146 @@
# matrix [![Build Status](https://travis-ci.org/PX4/Matrix.svg?branch=master)](https://travis-ci.org/PX4/Matrix) [![Coverage Status](https://coveralls.io/repos/PX4/Matrix/badge.svg?branch=master&service=github)](https://coveralls.io/github/PX4/Matrix?branch=master)
A simple and efficient template based matrix library.
## License
* (BSD) The Matrix library is licensed under a permissive 3-clause BSD license. Contributions must be made under the same license.
## Features
* Compile time size checking.
* Euler angle (321), DCM, Quaternion conversion through constructors.
* High testing coverage.
## Limitations
* No dynamically sized matrices.
## Library Overview
* matrix/math.hpp : Provides matrix math routines.
* Matrix (MxN)
* Square Matrix (MxM, has inverse)
* Vector (Mx1)
* Scalar (1x1)
* Quaternion
* Dcm
* Euler (Body 321)
* Axis Angle
* matrix/filter.hpp : Provides filtering routines.
* kalman_correct
* matrix/integrate.hpp : Provides integration routines.
* integrate_rk4 (Runge-Kutta 4th order)
## Testing
The tests can be executed as following:
```
mkdir build
cd build
cmake -DTESTING=ON ..
make check
```
## Formatting
The format can be checked as following:
```
mkdir build
cd build
cmake -DFORMAT=ON ..
make check_format
```
## Example
See the test directory for detailed examples. Some simple examples are included below:
```c++
// define an euler angle (Body 3(yaw)-2(pitch)-1(roll) rotation)
float roll = 0.1f;
float pitch = 0.2f;
float yaw = 0.3f;
Eulerf euler(roll, pitch, yaw);
// convert to quaternion from euler
Quatf q_nb(euler);
// convert to DCM from quaternion
Dcmf dcm(q_nb);
// you can assign a rotation instance that already exist to another rotation instance, e.g.
dcm = euler;
// you can also directly create a DCM instance from euler angles like this
dcm = Eulerf(roll, pitch, yaw);
// create an axis angle representation from euler angles
AxisAngle<float> axis_angle = euler;
// use axis angle to initialize a DCM
Dcmf dcm2(AxisAngle(1, 2, 3));
// use axis angle with axis/angle separated to init DCM
Dcmf dcm3(AxisAngle(Vector3f(1, 0, 0), 0.2));
// do some kalman filtering
const size_t n_x = 5;
const size_t n_y = 3;
// define matrix sizes
SquareMatrix<float, n_x> P;
Vector<float, n_x> x;
Vector<float, n_y> y;
Matrix<float, n_y, n_x> C;
SquareMatrix<float, n_y> R;
SquareMatrix<float, n_y> S;
Matrix<float, n_x, n_y> K;
// define measurement matrix
C = zero<float, n_y, n_x>(); // or C.setZero()
C(0,0) = 1;
C(1,1) = 2;
C(2,2) = 3;
// set x to zero
x = zero<float, n_x, 1>(); // or x.setZero()
// set P to identity * 0.01
P = eye<float, n_x>()*0.01;
// set R to identity * 0.1
R = eye<float, n_y>()*0.1;
// measurement
y(0) = 1;
y(1) = 2;
y(2) = 3;
// innovation
r = y - C*x;
// innovations variance
S = C*P*C.T() + R;
// Kalman gain matrix
K = P*C.T()*S.I();
// S.I() is the inverse, defined for SquareMatrix
// correction
x += K*r;
// slicing
float data[9] = {0, 2, 3,
4, 5, 6,
7, 8, 10
};
SquareMatrix<float, 3> A(data);
// Slice a 3,3 matrix starting at row 1, col 0,
// with size 2 x 3, warning, no size checking
Matrix<float, 2, 3> B(A.slice<2, 3>(1, 0));
// this results in:
// 4, 5, 6
// 7, 8, 10
```
<!-- vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : -->
+1
View File
@@ -0,0 +1 @@
.ipynb_checkpoints/
File diff suppressed because one or more lines are too long
Binary file not shown.
+160
View File
@@ -0,0 +1,160 @@
/**
* @file AxisAngle.hpp
*
* @author James Goppert <james.goppert@gmail.com>
*/
#pragma once
#include "math.hpp"
namespace matrix
{
template <typename Type>
class Dcm;
template <typename Type>
class Euler;
template <typename Type>
class AxisAngle;
/**
* AxisAngle class
*
* The rotation between two coordinate frames is
* described by this class.
*/
template<typename Type>
class AxisAngle : public Vector<Type, 3>
{
public:
using Matrix31 = Matrix<Type, 3, 1>;
/**
* Constructor from array
*
* @param data_ array
*/
explicit AxisAngle(const Type data_[3]) :
Vector<Type, 3>(data_)
{
}
/**
* Standard constructor
*/
AxisAngle() = default;
/**
* Constructor from Matrix31
*
* @param other Matrix31 to copy
*/
AxisAngle(const Matrix31 &other) :
Vector<Type, 3>(other)
{
}
/**
* Constructor from quaternion
*
* This sets the instance from a quaternion representing coordinate transformation from
* frame 2 to frame 1 where the rotation from frame 1 to frame 2 is described
* by a 3-2-1 intrinsic Tait-Bryan rotation sequence.
*
* @param q quaternion
*/
AxisAngle(const Quaternion<Type> &q)
{
AxisAngle &v = *this;
Type mag = q.imag().norm();
if (fabs(mag) >= Type(1e-10)) {
v = q.imag() * Type(Type(2) * atan2(mag, q(0)) / mag);
} else {
v = q.imag() * Type(Type(2) * Type(sign(q(0))));
}
}
/**
* Constructor from dcm
*
* Instance is initialized from a dcm representing coordinate transformation
* from frame 2 to frame 1.
*
* @param dcm dcm to set quaternion to
*/
AxisAngle(const Dcm<Type> &dcm)
{
AxisAngle &v = *this;
v = AxisAngle<Type>(Quaternion<Type>(dcm));
}
/**
* Constructor from euler angles
*
* This sets the instance to a quaternion representing coordinate transformation from
* frame 2 to frame 1 where the rotation from frame 1 to frame 2 is described
* by a 3-2-1 intrinsic Tait-Bryan rotation sequence.
*
* @param euler euler angle instance
*/
AxisAngle(const Euler<Type> &euler)
{
AxisAngle &v = *this;
v = AxisAngle<Type>(Quaternion<Type>(euler));
}
/**
* Constructor from 3 axis angle values (unit vector * angle)
*
* @param x r_x*angle
* @param y r_y*angle
* @param z r_z*angle
*/
AxisAngle(Type x, Type y, Type z)
{
AxisAngle &v = *this;
v(0) = x;
v(1) = y;
v(2) = z;
}
/**
* Constructor from axis and angle
*
* @param axis An axis of rotation, normalized if not unit length
* @param angle The amount to rotate
*/
AxisAngle(const Matrix31 & axis_, Type angle_)
{
AxisAngle &v = *this;
// make sure axis is a unit vector
Vector<Type, 3> a = axis_;
a = a.unit();
v(0) = a(0)*angle_;
v(1) = a(1)*angle_;
v(2) = a(2)*angle_;
}
Vector<Type, 3> axis() {
if (Vector<Type, 3>::norm() > 0) {
return Vector<Type, 3>::unit();
} else {
return Vector3<Type>(1, 0, 0);
}
}
Type angle() {
return Vector<Type, 3>::norm();
}
};
using AxisAnglef = AxisAngle<float>;
using AxisAngled = AxisAngle<double>;
} // namespace matrix
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+187
View File
@@ -0,0 +1,187 @@
/**
* @file Dcm.hpp
*
* A direction cosine matrix class.
* All rotations and axis systems follow the right-hand rule.
*
* This library uses the convention that premultiplying a three dimensional
* vector represented in coordinate system 1 will apply a rotation from coordinate system
* 1 to coordinate system 2 to the vector.
* Likewise, a matrix instance of this class also represents a coordinate transformation
* from frame 2 to frame 1.
*
* @author James Goppert <james.goppert@gmail.com>
*/
#pragma once
#include "math.hpp"
namespace matrix
{
template<typename Type>
class Quaternion;
template<typename Type>
class Euler;
template<typename Type>
class AxisAngle;
/**
* Direction cosine matrix class
*
* The rotation between two coordinate frames is
* described by this class.
*/
template<typename Type>
class Dcm : public SquareMatrix<Type, 3>
{
public:
using Vector3 = Matrix<Type, 3, 1>;
/**
* Standard constructor
*
* Initializes to identity
*/
Dcm() : SquareMatrix<Type, 3>(eye<Type, 3>()) {}
/**
* Constructor from array
*
* @param _data pointer to array
*/
explicit Dcm(const Type data_[3][3]) : SquareMatrix<Type, 3>(data_)
{
}
/**
* Constructor from array
*
* @param _data pointer to array
*/
explicit Dcm(const Type data_[9]) : SquareMatrix<Type, 3>(data_)
{
}
/**
* Copy constructor
*
* @param other Matrix33 to set dcm to
*/
Dcm(const Matrix<Type, 3, 3> &other) : SquareMatrix<Type, 3>(other)
{
}
/**
* Constructor from quaternion
*
* Instance is initialized from quaternion representing
* coordinate transformation from frame 2 to frame 1.
*
* @param q quaternion to set dcm to
*/
Dcm(const Quaternion<Type> &q)
{
Dcm &dcm = *this;
const Type a = q(0);
const Type b = q(1);
const Type c = q(2);
const Type d = q(3);
const Type aa = a * a;
const Type ab = a * b;
const Type ac = a * c;
const Type ad = a * d;
const Type bb = b * b;
const Type bc = b * c;
const Type bd = b * d;
const Type cc = c * c;
const Type cd = c * d;
const Type dd = d * d;
dcm(0, 0) = aa + bb - cc - dd;
dcm(0, 1) = Type(2) * (bc - ad);
dcm(0, 2) = Type(2) * (ac + bd);
dcm(1, 0) = Type(2) * (bc + ad);
dcm(1, 1) = aa - bb + cc - dd;
dcm(1, 2) = Type(2) * (cd - ab);
dcm(2, 0) = Type(2) * (bd - ac);
dcm(2, 1) = Type(2) * (ab + cd);
dcm(2, 2) = aa - bb - cc + dd;
}
/**
* Constructor from euler angles
*
* This sets the transformation matrix from frame 2 to frame 1 where the rotation
* from frame 1 to frame 2 is described by a 3-2-1 intrinsic Tait-Bryan rotation sequence.
*
*
* @param euler euler angle instance
*/
Dcm(const Euler<Type> &euler)
{
Dcm &dcm = *this;
Type cosPhi = Type(cos(euler.phi()));
Type sinPhi = Type(sin(euler.phi()));
Type cosThe = Type(cos(euler.theta()));
Type sinThe = Type(sin(euler.theta()));
Type cosPsi = Type(cos(euler.psi()));
Type sinPsi = Type(sin(euler.psi()));
dcm(0, 0) = cosThe * cosPsi;
dcm(0, 1) = -cosPhi * sinPsi + sinPhi * sinThe * cosPsi;
dcm(0, 2) = sinPhi * sinPsi + cosPhi * sinThe * cosPsi;
dcm(1, 0) = cosThe * sinPsi;
dcm(1, 1) = cosPhi * cosPsi + sinPhi * sinThe * sinPsi;
dcm(1, 2) = -sinPhi * cosPsi + cosPhi * sinThe * sinPsi;
dcm(2, 0) = -sinThe;
dcm(2, 1) = sinPhi * cosThe;
dcm(2, 2) = cosPhi * cosThe;
}
/**
* Constructor from axis angle
*
* This sets the transformation matrix from frame 2 to frame 1 where the rotation
* from frame 1 to frame 2 is described by a 3-2-1 intrinsic Tait-Bryan rotation sequence.
*
*
* @param euler euler angle instance
*/
Dcm(const AxisAngle<Type> &aa)
{
Dcm &dcm = *this;
dcm = Quaternion<Type>(aa);
}
Vector<Type, 3> vee() const // inverse to Vector.hat() operation
{
const Dcm &A(*this);
Vector<Type, 3> v;
v(0) = -A(1, 2);
v(1) = A(0, 2);
v(2) = -A(0, 1);
return v;
}
void renormalize()
{
/* renormalize rows */
for (size_t r = 0; r < 3; r++) {
matrix::Vector3<Type> rvec(Matrix<Type,1,3>(this->Matrix<Type,3,3>::row(r)).transpose());
this->Matrix<Type,3,3>::row(r) = rvec.normalized();
}
}
};
using Dcmf = Dcm<float>;
using Dcmd = Dcm<double>;
} // namespace matrix
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+373
View File
@@ -0,0 +1,373 @@
/**
* @file Dual.hpp
*
* This is a dual number type for calculating automatic derivatives.
*
* Based roughly on the methods described in:
* Automatic Differentiation, C++ Templates and Photogrammetry, by Dan Piponi
* and
* Ceres Solver's excellent Jet.h
*
* @author Julian Kent <julian@auterion.com>
*/
#pragma once
#include "math.hpp"
namespace matrix
{
template <typename Type, size_t M>
class Vector;
template <typename Scalar, size_t N>
struct Dual
{
static constexpr size_t WIDTH = N;
Scalar value {};
Vector<Scalar, N> derivative;
Dual() = default;
explicit Dual(Scalar v, size_t inputDimension = 65535)
{
value = v;
if (inputDimension < N) {
derivative(inputDimension) = Scalar(1);
}
}
explicit Dual(Scalar v, const Vector<Scalar, N>& d) :
value(v), derivative(d)
{}
Dual<Scalar, N>& operator=(const Scalar& a)
{
derivative.setZero();
value = a;
return *this;
}
Dual<Scalar, N>& operator +=(const Dual<Scalar, N>& a)
{
return (*this = *this + a);
}
Dual<Scalar, N>& operator *=(const Dual<Scalar, N>& a)
{
return (*this = *this * a);
}
Dual<Scalar, N>& operator -=(const Dual<Scalar, N>& a)
{
return (*this = *this - a);
}
Dual<Scalar, N>& operator /=(const Dual<Scalar, N>& a)
{
return (*this = *this / a);
}
Dual<Scalar, N>& operator +=(Scalar a)
{
return (*this = *this + a);
}
Dual<Scalar, N>& operator -=(Scalar a)
{
return (*this = *this - a);
}
Dual<Scalar, N>& operator *=(Scalar a)
{
return (*this = *this * a);
}
Dual<Scalar, N>& operator /=(Scalar a)
{
return (*this = *this / a);
}
};
// operators
template <typename Scalar, size_t N>
Dual<Scalar, N> operator+(const Dual<Scalar, N>& a)
{
return a;
}
template <typename Scalar, size_t N>
Dual<Scalar, N> operator-(const Dual<Scalar, N>& a)
{
return Dual<Scalar, N>(-a.value, -a.derivative);
}
template <typename Scalar, size_t N>
Dual<Scalar, N> operator+(const Dual<Scalar, N>& a, const Dual<Scalar, N>& b)
{
return Dual<Scalar, N>(a.value + b.value, a.derivative + b.derivative);
}
template <typename Scalar, size_t N>
Dual<Scalar, N> operator-(const Dual<Scalar, N>& a, const Dual<Scalar, N>& b)
{
return a + (-b);
}
template <typename Scalar, size_t N>
Dual<Scalar, N> operator+(const Dual<Scalar, N>& a, Scalar b)
{
return Dual<Scalar, N>(a.value + b, a.derivative);
}
template <typename Scalar, size_t N>
Dual<Scalar, N> operator-(const Dual<Scalar, N>& a, Scalar b)
{
return a + (-b);
}
template <typename Scalar, size_t N>
Dual<Scalar, N> operator+(Scalar a, const Dual<Scalar, N>& b)
{
return Dual<Scalar, N>(a + b.value, b.derivative);
}
template <typename Scalar, size_t N>
Dual<Scalar, N> operator-(Scalar a, const Dual<Scalar, N>& b)
{
return a + (-b);
}
template <typename Scalar, size_t N>
Dual<Scalar, N> operator*(const Dual<Scalar, N>& a, const Dual<Scalar, N>& b)
{
return Dual<Scalar, N>(a.value * b.value, a.value * b.derivative + b.value * a.derivative);
}
template <typename Scalar, size_t N>
Dual<Scalar, N> operator*(const Dual<Scalar, N>& a, Scalar b)
{
return Dual<Scalar, N>(a.value * b, a.derivative * b);
}
template <typename Scalar, size_t N>
Dual<Scalar, N> operator*(Scalar a, const Dual<Scalar, N>& b)
{
return b * a;
}
template <typename Scalar, size_t N>
Dual<Scalar, N> operator/(const Dual<Scalar, N>& a, const Dual<Scalar, N>& b)
{
const Scalar inv_b_real = Scalar(1) / b.value;
return Dual<Scalar, N>(a.value * inv_b_real, a.derivative * inv_b_real -
a.value * b.derivative * inv_b_real * inv_b_real);
}
template <typename Scalar, size_t N>
Dual<Scalar, N> operator/(const Dual<Scalar, N>& a, Scalar b)
{
return a * (Scalar(1) / b);
}
template <typename Scalar, size_t N>
Dual<Scalar, N> operator/(Scalar a, const Dual<Scalar, N>& b)
{
const Scalar inv_b_real = Scalar(1) / b.value;
return Dual<Scalar, N>(a * inv_b_real, (-inv_b_real * a * inv_b_real) * b.derivative);
}
// basic math
// sqrt
template <typename Scalar, size_t N>
Dual<Scalar, N> sqrt(const Dual<Scalar, N>& a)
{
Scalar real = sqrt(a.value);
return Dual<Scalar, N>(real, a.derivative * (Scalar(1) / (Scalar(2) * real)));
}
// abs
template <typename Scalar, size_t N>
Dual<Scalar, N> abs(const Dual<Scalar, N>& a)
{
return a.value >= Scalar(0) ? a : -a;
}
// ceil
template <typename Scalar, size_t N>
Dual<Scalar, N> ceil(const Dual<Scalar, N>& a)
{
return Dual<Scalar, N>(ceil(a.value));
}
// floor
template <typename Scalar, size_t N>
Dual<Scalar, N> floor(const Dual<Scalar, N>& a)
{
return Dual<Scalar, N>(floor(a.value));
}
// fmod
template <typename Scalar, size_t N>
Dual<Scalar, N> fmod(const Dual<Scalar, N>& a, Scalar mod)
{
return Dual<Scalar, N>(a.value - Scalar(size_t(a.value / mod)) * mod, a.derivative);
}
// max
template <typename Scalar, size_t N>
Dual<Scalar, N> max(const Dual<Scalar, N>& a, const Dual<Scalar, N>& b)
{
return a.value >= b.value ? a : b;
}
// min
template <typename Scalar, size_t N>
Dual<Scalar, N> min(const Dual<Scalar, N>& a, const Dual<Scalar, N>& b)
{
return a.value < b.value ? a : b;
}
// isnan
template <typename Scalar>
bool IsNan(Scalar a)
{
return isnan(a);
}
template <typename Scalar, size_t N>
bool IsNan(const Dual<Scalar, N>& a)
{
return IsNan(a.value);
}
// isfinite
template <typename Scalar>
bool IsFinite(Scalar a)
{
return isfinite(a);
}
template <typename Scalar, size_t N>
bool IsFinite(const Dual<Scalar, N>& a)
{
return IsFinite(a.value);
}
// isinf
template <typename Scalar>
bool IsInf(Scalar a)
{
return isinf(a);
}
template <typename Scalar, size_t N>
bool IsInf(const Dual<Scalar, N>& a)
{
return IsInf(a.value);
}
// trig
// sin
template <typename Scalar, size_t N>
Dual<Scalar, N> sin(const Dual<Scalar, N>& a)
{
return Dual<Scalar, N>(sin(a.value), cos(a.value) * a.derivative);
}
// cos
template <typename Scalar, size_t N>
Dual<Scalar, N> cos(const Dual<Scalar, N>& a)
{
return Dual<Scalar, N>(cos(a.value), -sin(a.value) * a.derivative);
}
// tan
template <typename Scalar, size_t N>
Dual<Scalar, N> tan(const Dual<Scalar, N>& a)
{
Scalar real = tan(a.value);
return Dual<Scalar, N>(real, (Scalar(1) + real * real) * a.derivative);
}
// asin
template <typename Scalar, size_t N>
Dual<Scalar, N> asin(const Dual<Scalar, N>& a)
{
Scalar asin_d = Scalar(1) / sqrt(Scalar(1) - a.value * a.value);
return Dual<Scalar, N>(asin(a.value), asin_d * a.derivative);
}
// acos
template <typename Scalar, size_t N>
Dual<Scalar, N> acos(const Dual<Scalar, N>& a)
{
Scalar acos_d = -Scalar(1) / sqrt(Scalar(1) - a.value * a.value);
return Dual<Scalar, N>(acos(a.value), acos_d * a.derivative);
}
// atan
template <typename Scalar, size_t N>
Dual<Scalar, N> atan(const Dual<Scalar, N>& a)
{
Scalar atan_d = Scalar(1) / (Scalar(1) + a.value * a.value);
return Dual<Scalar, N>(atan(a.value), atan_d * a.derivative);
}
// atan2
template <typename Scalar, size_t N>
Dual<Scalar, N> atan2(const Dual<Scalar, N>& a, const Dual<Scalar, N>& b)
{
// derivative is equal to that of atan(a/b), so substitute a/b into atan and simplify
Scalar atan_d = Scalar(1) / (a.value * a.value + b.value * b.value);
return Dual<Scalar, N>(atan2(a.value, b.value), (a.derivative * b.value - a.value * b.derivative) * atan_d);
}
// retrieve the derivative elements of a vector of Duals into a matrix
template <typename Scalar, size_t M, size_t N>
Matrix<Scalar, M, N> collectDerivatives(const Matrix<Dual<Scalar, N>, M, 1>& input)
{
Matrix<Scalar, M, N> jac;
for (size_t i = 0; i < M; i++) {
jac.row(i) = input(i, 0).derivative;
}
return jac;
}
// retrieve the real (non-derivative) elements of a matrix of Duals into an equally sized matrix
template <typename Scalar, size_t M, size_t N, size_t D>
Matrix<Scalar, M, N> collectReals(const Matrix<Dual<Scalar, D>, M, N>& input)
{
Matrix<Scalar, M, N> r;
for (size_t i = 0; i < M; i++) {
for (size_t j = 0; j < N; j++) {
r(i,j) = input(i,j).value;
}
}
return r;
}
#if defined(SUPPORT_STDIOSTREAM)
template<typename Type, size_t N>
std::ostream& operator<<(std::ostream& os,
const matrix::Dual<Type, N>& dual)
{
os << "[";
os << std::setw(10) << dual.value << ";";
for (size_t j = 0; j < N; ++j) {
os << "\t";
os << std::setw(10) << static_cast<double>(dual.derivative(j));
}
os << "]";
return os;
}
#endif // defined(SUPPORT_STDIOSTREAM)
}
+157
View File
@@ -0,0 +1,157 @@
/**
* @file Euler.hpp
*
* All rotations and axis systems follow the right-hand rule
*
* An instance of this class defines a rotation from coordinate frame 1 to coordinate frame 2.
* It follows the convention of a 3-2-1 intrinsic Tait-Bryan rotation sequence.
* In order to go from frame 1 to frame 2 we apply the following rotations consecutively.
* 1) We rotate about our initial Z axis by an angle of _psi.
* 2) We rotate about the newly created Y' axis by an angle of _theta.
* 3) We rotate about the newly created X'' axis by an angle of _phi.
*
* @author James Goppert <james.goppert@gmail.com>
*/
#pragma once
#include "math.hpp"
namespace matrix
{
template <typename Type>
class Dcm;
template <typename Type>
class Quaternion;
/**
* Euler angles class
*
* This class describes the rotation from frame 1
* to frame 2 via 3-2-1 intrinsic Tait-Bryan rotation sequence.
*/
template<typename Type>
class Euler : public Vector<Type, 3>
{
public:
/**
* Standard constructor
*/
Euler() = default;
/**
* Copy constructor
*
* @param other vector to copy
*/
Euler(const Vector<Type, 3> &other) :
Vector<Type, 3>(other)
{
}
/**
* Constructor from Matrix31
*
* @param other Matrix31 to copy
*/
Euler(const Matrix<Type, 3, 1> &other) :
Vector<Type, 3>(other)
{
}
/**
* Constructor from euler angles
*
* Instance is initialized from an 3-2-1 intrinsic Tait-Bryan
* rotation sequence representing transformation from frame 1
* to frame 2.
*
* @param phi_ rotation angle about X axis
* @param theta_ rotation angle about Y axis
* @param psi_ rotation angle about Z axis
*/
Euler(Type phi_, Type theta_, Type psi_) : Vector<Type, 3>()
{
phi() = phi_;
theta() = theta_;
psi() = psi_;
}
/**
* Constructor from DCM matrix
*
* Instance is set from Dcm representing transformation from
* frame 2 to frame 1.
* This instance will hold the angles defining the 3-2-1 intrinsic
* Tait-Bryan rotation sequence from frame 1 to frame 2.
*
* @param dcm Direction cosine matrix
*/
Euler(const Dcm<Type> &dcm)
{
theta() = asin(-dcm(2, 0));
if ((fabs(theta() - Type(M_PI / 2))) < Type(1.0e-3)) {
phi() = 0;
psi() = atan2(dcm(1, 2), dcm(0, 2));
} else if ((fabs(theta() + Type(M_PI / 2))) < Type(1.0e-3)) {
phi() = 0;
psi() = atan2(-dcm(1, 2), -dcm(0, 2));
} else {
phi() = atan2(dcm(2, 1), dcm(2, 2));
psi() = atan2(dcm(1, 0), dcm(0, 0));
}
}
/**
* Constructor from quaternion instance.
*
* Instance is set from a quaternion representing transformation
* from frame 2 to frame 1.
* This instance will hold the angles defining the 3-2-1 intrinsic
* Tait-Bryan rotation sequence from frame 1 to frame 2.
*
* @param q quaternion
*/
Euler(const Quaternion<Type> &q) : Vector<Type, 3>(Euler(Dcm<Type>(q)))
{
}
inline Type phi() const
{
return (*this)(0);
}
inline Type theta() const
{
return (*this)(1);
}
inline Type psi() const
{
return (*this)(2);
}
inline Type &phi()
{
return (*this)(0);
}
inline Type &theta()
{
return (*this)(1);
}
inline Type &psi()
{
return (*this)(2);
}
};
using Eulerf = Euler<float>;
using Eulerd = Euler<double>;
} // namespace matrix
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
@@ -0,0 +1,146 @@
/**
* @file LeastSquaresSolver.hpp
*
* Least Squares Solver using QR householder decomposition.
* It calculates x for Ax = b.
* A = Q*R
* where R is an upper triangular matrix.
*
* R*x = Q^T*b
* This is efficiently solved for x because of the upper triangular property of R.
*
* @author Bart Slinger <bartslinger@gmail.com>
*/
#pragma once
#include "math.hpp"
namespace matrix {
template<typename Type, size_t M, size_t N>
class LeastSquaresSolver
{
public:
/**
* @brief Class calculates QR decomposition which can be used for linear
* least squares
* @param A Matrix of size MxN
*
* Initialize the class with a MxN matrix. The constructor starts the
* QR decomposition. This class does not check the rank of the matrix.
* The user needs to make sure that rank(A) = N and M >= N.
*/
LeastSquaresSolver(const Matrix<Type, M, N> &A)
{
static_assert(M >= N, "Matrix dimension should be M >= N");
// Copy contentents of matrix A
_A = A;
for (size_t j = 0; j < N; j++) {
Type normx = Type(0);
for (size_t i = j; i < M; i++) {
normx += _A(i,j) * _A(i,j);
}
normx = sqrt(normx);
Type s = _A(j,j) > 0 ? Type(-1) : Type(1);
Type u1 = _A(j,j) - s*normx;
// prevent divide by zero
// also covers u1. normx is never negative
if (normx < Type(1e-8)) {
break;
}
Type w[M] = {};
w[0] = Type(1);
for (size_t i = j+1; i < M; i++) {
w[i-j] = _A(i,j) / u1;
_A(i,j) = w[i-j];
}
_A(j,j) = s*normx;
_tau(j) = -s*u1/normx;
for (size_t k = j+1; k < N; k++) {
Type tmp = Type(0);
for (size_t i = j; i < M; i++) {
tmp += w[i-j] * _A(i,k);
}
for (size_t i = j; i < M; i++) {
_A(i,k) -= _tau(j) * w[i-j] * tmp;
}
}
}
}
/**
* @brief qtb Calculate Q^T * b
* @param b
* @return Q^T*b
*
* This function calculates Q^T * b. This is useful for the solver
* because R*x = Q^T*b.
*/
Vector<Type, M> qtb(const Vector<Type, M> &b) {
Vector<Type, M> qtbv = b;
for (size_t j = 0; j < N; j++) {
Type w[M];
w[0] = Type(1);
// fill vector w
for (size_t i = j+1; i < M; i++) {
w[i-j] = _A(i,j);
}
Type tmp = Type(0);
for (size_t i = j; i < M; i++) {
tmp += w[i-j] * qtbv(i);
}
for (size_t i = j; i < M; i++) {
qtbv(i) -= _tau(j) * w[i-j] * tmp;
}
}
return qtbv;
}
/**
* @brief Solve Ax=b for x
* @param b
* @return Vector x
*
* Find x in the equation Ax = b.
* A is provided in the initializer of the class.
*/
Vector<Type, N> solve(const Vector<Type, M> &b) {
Vector<Type, M> qtbv = qtb(b);
Vector<Type, N> x;
// size_t is unsigned and wraps i = 0 - 1 to i > N
for (size_t i = N - 1; i < N; i--) {
printf("i %d\n", static_cast<int>(i));
x(i) = qtbv(i);
for (size_t r = i+1; r < N; r++) {
x(i) -= _A(i,r) * x(r);
}
// divide by zero, return vector of zeros
if (isEqualF(_A(i,i), Type(0), Type(1e-8))) {
for (size_t z = 0; z < N; z++) {
x(z) = Type(0);
}
break;
}
x(i) /= _A(i,i);
}
return x;
}
private:
Matrix<Type, M, N> _A;
Vector<Type, N> _tau;
};
} // namespace matrix
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
File diff suppressed because it is too large Load Diff
+119
View File
@@ -0,0 +1,119 @@
/**
* @file PseudoInverse.hpp
*
* Implementation of matrix pseudo inverse
*
* @author Julien Lecoeur <julien.lecoeur@gmail.com>
* @author Julian Kent <julian@auterion.com>
*/
#pragma once
#include "math.hpp"
namespace matrix
{
/**
* Geninv
* Fast pseudoinverse based on full rank cholesky factorisation
*
* Courrieu, P. (2008). Fast Computation of Moore-Penrose Inverse Matrices, 8(2), 2529. http://arxiv.org/abs/0804.4809
*/
template<typename Type, size_t M, size_t N>
bool geninv(const Matrix<Type, M, N> & G, Matrix<Type, N, M>& res)
{
size_t rank;
if (M <= N) {
SquareMatrix<Type, M> A = G * G.transpose();
SquareMatrix<Type, M> L = fullRankCholesky(A, rank);
A = L.transpose() * L;
SquareMatrix<Type, M> X;
if (!inv(A, X, rank)) {
res = Matrix<Type, N, M>();
return false; // LCOV_EXCL_LINE -- this can only be hit from numerical issues
}
// doing an intermediate assignment reduces stack usage
A = X * X * L.transpose();
res = G.transpose() * (L * A);
} else {
SquareMatrix<Type, N> A = G.transpose() * G;
SquareMatrix<Type, N> L = fullRankCholesky(A, rank);
A = L.transpose() * L;
SquareMatrix<Type, N> X;
if(!inv(A, X, rank)) {
res = Matrix<Type, N, M>();
return false; // LCOV_EXCL_LINE -- this can only be hit from numerical issues
}
// doing an intermediate assignment reduces stack usage
A = X * X * L.transpose();
res = (L * A) * G.transpose();
}
return true;
}
template<typename Type>
Type typeEpsilon();
template<> inline
float typeEpsilon<float>()
{
return FLT_EPSILON;
}
/**
* Full rank Cholesky factorization of A
*/
template<typename Type, size_t N>
SquareMatrix<Type, N> fullRankCholesky(const SquareMatrix<Type, N> & A,
size_t& rank)
{
// Loses one ulp accuracy per row of diag, relative to largest magnitude
const Type tol = N * typeEpsilon<Type>() * A.diag().max();
Matrix<Type, N, N> L;
size_t r = 0;
for (size_t k = 0; k < N; k++) {
if (r == 0) {
for (size_t i = k; i < N; i++) {
L(i, r) = A(i, k);
}
} else {
for (size_t i = k; i < N; i++) {
// Compute LL = L[k:n, :r] * L[k, :r].T
Type LL = Type();
for (size_t j = 0; j < r; j++) {
LL += L(i, j) * L(k, j);
}
L(i, r) = A(i, k) - LL;
}
}
if (L(k, r) > tol) {
L(k, r) = sqrt(L(k, r));
if (k < N - 1) {
for (size_t i = k + 1; i < N; i++) {
L(i, r) = L(i, r) / L(k, r);
}
}
r = r + 1;
}
}
// Return rank
rank = r;
return L;
}
} // namespace matrix
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
File diff suppressed because it is too large Load Diff
+58
View File
@@ -0,0 +1,58 @@
/**
* @file Scalar.hpp
*
* Defines conversion of matrix to scalar.
*
* @author James Goppert <james.goppert@gmail.com>
*/
#pragma once
#include "math.hpp"
namespace matrix
{
template<typename Type>
class Scalar
{
public:
Scalar() = delete;
Scalar(const Matrix<Type, 1, 1> & other) :
_value{other(0,0)}
{
}
Scalar(Type other) : _value(other)
{
}
operator const Type &()
{
return _value;
}
operator Matrix<Type, 1, 1>() const {
Matrix<Type, 1, 1> m;
m(0, 0) = _value;
return m;
}
operator Vector<Type, 1>() const {
Vector<Type, 1> m;
m(0) = _value;
return m;
}
private:
const Type _value;
};
using Scalarf = Scalar<float>;
using Scalard = Scalar<double>;
} // namespace matrix
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+304
View File
@@ -0,0 +1,304 @@
/**
* @file Slice.hpp
*
* A simple matrix template library.
*
* @author Julian Kent < julian@auterion.com >
*/
#pragma once
#include "math.hpp"
namespace matrix {
template<typename Type, size_t M, size_t N>
class Matrix;
template<typename Type, size_t M>
class Vector;
template <typename Type, size_t P, size_t Q, size_t M, size_t N>
class Slice {
public:
Slice(size_t x0, size_t y0, const Matrix<Type, M, N>* data) :
_x0(x0),
_y0(y0),
_data(const_cast<Matrix<Type, M, N>*>(data)) {
static_assert(P <= M, "Slice rows bigger than backing matrix");
static_assert(Q <= N, "Slice cols bigger than backing matrix");
assert(x0 + P <= M);
assert(y0 + Q <= N);
}
const Type &operator()(size_t i, size_t j) const
{
assert(i < P);
assert(j < Q);
return (*_data)(_x0 + i, _y0 + j);
}
Type &operator()(size_t i, size_t j)
{
assert(i < P);
assert(j < Q);
return (*_data)(_x0 + i, _y0 + j);
}
template<size_t MM, size_t NN>
Slice<Type, P, Q, M, N>& operator=(const Slice<Type, P, Q, MM, NN>& other)
{
Slice<Type, P, Q, M, N>& self = *this;
for (size_t i = 0; i < P; i++) {
for (size_t j = 0; j < Q; j++) {
self(i, j) = other(i, j);
}
}
return self;
}
Slice<Type, P, Q, M, N>& operator=(const Matrix<Type, P, Q>& other)
{
Slice<Type, P, Q, M, N>& self = *this;
for (size_t i = 0; i < P; i++) {
for (size_t j = 0; j < Q; j++) {
self(i, j) = other(i, j);
}
}
return self;
}
Slice<Type, P, Q, M, N>& operator=(const Type& other)
{
Slice<Type, P, Q, M, N>& self = *this;
for (size_t i = 0; i < P; i++) {
for (size_t j = 0; j < Q; j++) {
self(i, j) = other;
}
}
return self;
}
// allow assigning vectors to a slice that are in the axis
template <size_t DUMMY = 1> // make this a template function since it only exists for some instantiations
Slice<Type, 1, Q, M, N>& operator=(const Vector<Type, Q>& other)
{
Slice<Type, 1, Q, M, N>& self = *this;
for (size_t j = 0; j < Q; j++) {
self(0, j) = other(j);
}
return self;
}
template<size_t MM, size_t NN>
Slice<Type, P, Q, M, N>& operator+=(const Slice<Type, P, Q, MM, NN>& other)
{
Slice<Type, P, Q, M, N>& self = *this;
for (size_t i = 0; i < P; i++) {
for (size_t j = 0; j < Q; j++) {
self(i, j) += other(i, j);
}
}
return self;
}
Slice<Type, P, Q, M, N>& operator+=(const Matrix<Type, P, Q>& other)
{
Slice<Type, P, Q, M, N>& self = *this;
for (size_t i = 0; i < P; i++) {
for (size_t j = 0; j < Q; j++) {
self(i, j) += other(i, j);
}
}
return self;
}
Slice<Type, P, Q, M, N>& operator+=(const Type& other)
{
Slice<Type, P, Q, M, N>& self = *this;
for (size_t i = 0; i < P; i++) {
for (size_t j = 0; j < Q; j++) {
self(i, j) += other;
}
}
return self;
}
template<size_t MM, size_t NN>
Slice<Type, P, Q, M, N>& operator-=(const Slice<Type, P, Q, MM, NN>& other)
{
Slice<Type, P, Q, M, N>& self = *this;
for (size_t i = 0; i < P; i++) {
for (size_t j = 0; j < Q; j++) {
self(i, j) -= other(i, j);
}
}
return self;
}
Slice<Type, P, Q, M, N>& operator-=(const Matrix<Type, P, Q>& other)
{
Slice<Type, P, Q, M, N>& self = *this;
for (size_t i = 0; i < P; i++) {
for (size_t j = 0; j < Q; j++) {
self(i, j) -= other(i, j);
}
}
return self;
}
Slice<Type, P, Q, M, N>& operator-=(const Type& other)
{
Slice<Type, P, Q, M, N>& self = *this;
for (size_t i = 0; i < P; i++) {
for (size_t j = 0; j < Q; j++) {
self(i, j) -= other;
}
}
return self;
}
Slice<Type, P, Q, M, N>& operator*=(const Type& other)
{
Slice<Type, P, Q, M, N>& self = *this;
for (size_t i = 0; i < P; i++) {
for (size_t j = 0; j < Q; j++) {
self(i, j) *= other;
}
}
return self;
}
Slice<Type, P, Q, M, N>& operator/=(const Type& other)
{
return operator*=(Type(1) / other);
}
Matrix<Type, P, Q> operator*(const Type& other) const
{
const Slice<Type, P, Q, M, N>& self = *this;
Matrix<Type, P, Q> res;
for (size_t i = 0; i < P; i++) {
for (size_t j = 0; j < Q; j++) {
res(i, j) = self(i, j) * other;
}
}
return res;
}
Matrix<Type, P, Q> operator/(const Type& other) const
{
const Slice<Type, P, Q, M, N>& self = *this;
return self * (Type(1) / other);
}
template<size_t R, size_t S>
const Slice<Type, R, S, M, N> slice(size_t x0, size_t y0) const
{
return Slice<Type, R, S, M, N>(x0 + _x0, y0 + _y0, _data);
}
template<size_t R, size_t S>
Slice<Type, R, S, M, N> slice(size_t x0, size_t y0)
{
return Slice<Type, R, S, M, N>(x0 + _x0, y0 + _y0, _data);
}
void copyTo(Type dst[P*Q]) const
{
const Slice<Type, P, Q, M, N> &self = *this;
for (size_t i = 0; i < P; i++) {
for (size_t j = 0; j < Q; j++) {
dst[i*N+j] = self(i, j);
}
}
}
void copyToColumnMajor(Type dst[P*Q]) const
{
const Slice<Type, P, Q, M, N> &self = *this;
for (size_t i = 0; i < P; i++) {
for (size_t j = 0; j < Q; j++) {
dst[i+(j*M)] = self(i, j);
}
}
}
Vector<Type, P<Q?P:Q> diag() const
{
const Slice<Type, P, Q, M, N>& self = *this;
Vector<Type,P<Q?P:Q> res;
for (size_t j = 0; j < (P<Q?P:Q); j++) {
res(j) = self(j,j);
}
return res;
}
Type norm_squared() const
{
const Slice<Type, P, Q, M, N>& self = *this;
Type accum(0);
for (size_t i = 0; i < P; i++) {
for (size_t j = 0; j < Q; j++) {
accum += self(i, j)*self(i, j);
}
}
return accum;
}
Type norm() const
{
return matrix::sqrt(norm_squared());
}
bool longerThan(Type testVal) const
{
return norm_squared() > testVal*testVal;
}
Type max() const
{
Type max_val = (*this)(0,0);
for (size_t i = 0; i < P; i++) {
for (size_t j = 0; j < Q; j++) {
Type val = (*this)(i,j);
if (val > max_val) {
max_val = val;
}
}
}
return max_val;
}
Type min() const
{
Type min_val = (*this)(0,0);
for (size_t i = 0; i < P; i++) {
for (size_t j = 0; j < Q; j++) {
Type val = (*this)(i,j);
if (val < min_val) {
min_val = val;
}
}
}
return min_val;
}
private:
size_t _x0, _y0;
Matrix<Type,M,N>* _data;
};
}
+189
View File
@@ -0,0 +1,189 @@
/**
* @file SparseVector.hpp
*
* SparseVector class.
*
* @author Kamil Ritz <kritz@ethz.ch>
* @author Julian Kent <julian@auterion.com>
*
*/
#pragma once
#include "math.hpp"
namespace matrix {
template<int N> struct force_constexpr_eval {
static const int value = N;
};
// Vector that only store nonzero elements,
// which indices are specified as parameter pack
template<typename Type, size_t M, size_t... Idxs>
class SparseVector {
private:
static constexpr size_t N = sizeof...(Idxs);
static constexpr size_t _indices[N] {Idxs...};
static constexpr bool duplicateIndices() {
for (size_t i = 0; i < N; i++) {
for (size_t j = 0; j < i; j++) {
if (_indices[i] == _indices[j]) {
return true;
}
}
}
return false;
}
static constexpr size_t findMaxIndex() {
size_t maxIndex = 0;
for (size_t i = 0; i < N; i++) {
if (maxIndex < _indices[i]) {
maxIndex = _indices[i];
}
}
return maxIndex;
}
static_assert(!duplicateIndices(), "Duplicate indices");
static_assert(N < M, "More entries than elements, use a dense vector");
static_assert(N > 0, "A sparse vector needs at least one element");
static_assert(findMaxIndex() < M, "Largest entry doesn't fit in sparse vector");
Type _data[N] {};
static constexpr int findCompressedIndex(size_t index) {
int compressedIndex = -1;
for (size_t i = 0; i < N; i++) {
if (index == _indices[i]) {
compressedIndex = static_cast<int>(i);
}
}
return compressedIndex;
}
public:
constexpr size_t non_zeros() const {
return N;
}
constexpr size_t index(size_t i) const {
return SparseVector::_indices[i];
}
SparseVector() = default;
SparseVector(const matrix::Vector<Type, M>& data) {
for (size_t i = 0; i < N; i++) {
_data[i] = data(_indices[i]);
}
}
explicit SparseVector(const Type data[N]) {
memcpy(_data, data, sizeof(_data));
}
template <size_t i>
inline Type at() const {
static constexpr int compressed_index = force_constexpr_eval<findCompressedIndex(i)>::value;
static_assert(compressed_index >= 0, "cannot access unpopulated indices");
return _data[compressed_index];
}
template <size_t i>
inline Type& at() {
static constexpr int compressed_index = force_constexpr_eval<findCompressedIndex(i)>::value;
static_assert(compressed_index >= 0, "cannot access unpopulated indices");
return _data[compressed_index];
}
inline Type atCompressedIndex(size_t i) const {
assert(i < N);
return _data[i];
}
inline Type& atCompressedIndex(size_t i) {
assert(i < N);
return _data[i];
}
void setZero() {
for (size_t i = 0; i < N; i++) {
_data[i] = Type(0);
}
}
Type dot(const matrix::Vector<Type, M>& other) const {
Type accum (0);
for (size_t i = 0; i < N; i++) {
accum += _data[i] * other(_indices[i]);
}
return accum;
}
matrix::Vector<Type, M> operator+(const matrix::Vector<Type, M>& other) const {
matrix::Vector<Type, M> vec = other;
for (size_t i = 0; i < N; i++) {
vec(_indices[i]) += _data[i];
}
return vec;
}
SparseVector& operator+=(Type t) {
for (size_t i = 0; i < N; i++) {
_data[i] += t;
}
return *this;
}
Type norm_squared() const
{
Type accum(0);
for (size_t i = 0; i < N; i++) {
accum += _data[i] * _data[i];
}
return accum;
}
Type norm() const
{
return matrix::sqrt(norm_squared());
}
bool longerThan(Type testVal) const
{
return norm_squared() > testVal*testVal;
}
};
template<typename Type, size_t Q, size_t M, size_t ... Idxs>
matrix::Vector<Type, Q> operator*(const matrix::Matrix<Type, Q, M>& mat, const matrix::SparseVector<Type, M, Idxs...>& vec) {
matrix::Vector<Type, Q> res;
for (size_t i = 0; i < Q; i++) {
const Vector<Type, M> row = mat.row(i);
res(i) = vec.dot(row);
}
return res;
}
// returns x.T * A * x
template<typename Type, size_t M, size_t ... Idxs>
Type quadraticForm(const matrix::SquareMatrix<Type, M>& A, const matrix::SparseVector<Type, M, Idxs...>& x) {
Type res = Type(0);
for (size_t i = 0; i < x.non_zeros(); i++) {
Type tmp = Type(0);
for (size_t j = 0; j < x.non_zeros(); j++) {
tmp += A(x.index(i), x.index(j)) * x.atCompressedIndex(j);
}
res += x.atCompressedIndex(i) * tmp;
}
return res;
}
template<typename Type,size_t M, size_t... Idxs>
constexpr size_t SparseVector<Type, M, Idxs...>::_indices[SparseVector<Type, M, Idxs...>::N];
template<size_t M, size_t ... Idxs>
using SparseVectorf = SparseVector<float, M, Idxs...>;
}
File diff suppressed because it is too large Load Diff
+136
View File
@@ -0,0 +1,136 @@
/**
* @file Vector.hpp
*
* Vector class.
*
* @author James Goppert <james.goppert@gmail.com>
*/
#pragma once
#include "math.hpp"
namespace matrix
{
template <typename Type, size_t M, size_t N>
class Matrix;
template<typename Type, size_t M>
class Vector : public Matrix<Type, M, 1>
{
public:
using MatrixM1 = Matrix<Type, M, 1>;
Vector() = default;
Vector(const MatrixM1 & other) :
MatrixM1(other)
{
}
explicit Vector(const Type data_[M]) :
MatrixM1(data_)
{
}
template<size_t P, size_t Q>
Vector(const Slice<Type, M, 1, P, Q>& slice_in) :
Matrix<Type, M, 1>(slice_in)
{
}
template<size_t P, size_t Q, size_t DUMMY = 1>
Vector(const Slice<Type, 1, M, P, Q>& slice_in)
{
Vector &self(*this);
for (size_t i = 0; i<M; i++) {
self(i) = slice_in(0, i);
}
}
inline const Type &operator()(size_t i) const
{
assert(i < M);
const MatrixM1 &v = *this;
return v(i, 0);
}
inline Type &operator()(size_t i)
{
assert(i < M);
MatrixM1 &v = *this;
return v(i, 0);
}
Type dot(const MatrixM1 & b) const {
const Vector &a(*this);
Type r(0);
for (size_t i = 0; i<M; i++) {
r += a(i)*b(i,0);
}
return r;
}
inline Type operator*(const MatrixM1 & b) const {
const Vector &a(*this);
return a.dot(b);
}
inline Vector operator*(Type b) const {
return Vector(MatrixM1::operator*(b));
}
Type norm() const {
const Vector &a(*this);
return Type(matrix::sqrt(a.dot(a)));
}
Type norm_squared() const {
const Vector &a(*this);
return a.dot(a);
}
inline Type length() const {
return norm();
}
inline void normalize() {
(*this) /= norm();
}
Vector unit() const {
return (*this) / norm();
}
Vector unit_or_zero(const Type eps = Type(1e-5)) const {
const Type n = norm();
if (n > eps) {
return (*this) / n;
}
return Vector();
}
inline Vector normalized() const {
return unit();
}
bool longerThan(Type testVal) const {
return norm_squared() > testVal*testVal;
}
Vector sqrt() const {
const Vector &a(*this);
Vector r;
for (size_t i = 0; i<M; i++) {
r(i) = Type(matrix::sqrt(a(i)));
}
return r;
}
};
} // namespace matrix
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+80
View File
@@ -0,0 +1,80 @@
/**
* @file Vector2.hpp
*
* 2D vector class.
*
* @author James Goppert <james.goppert@gmail.com>
*/
#pragma once
#include "math.hpp"
namespace matrix
{
template <typename Type, size_t M>
class Vector;
template<typename Type>
class Vector2 : public Vector<Type, 2>
{
public:
using Matrix21 = Matrix<Type, 2, 1>;
using Vector3 = Vector<Type, 3>;
Vector2() = default;
Vector2(const Matrix21 & other) :
Vector<Type, 2>(other)
{
}
explicit Vector2(const Type data_[2]) :
Vector<Type, 2>(data_)
{
}
Vector2(Type x, Type y)
{
Vector2 &v(*this);
v(0) = x;
v(1) = y;
}
template<size_t P, size_t Q>
Vector2(const Slice<Type, 2, 1, P, Q>& slice_in) : Vector<Type, 2>(slice_in)
{
}
template<size_t P, size_t Q>
Vector2(const Slice<Type, 1, 2, P, Q>& slice_in) : Vector<Type, 2>(slice_in)
{
}
explicit Vector2(const Vector3 & other)
{
Vector2 &v(*this);
v(0) = other(0);
v(1) = other(1);
}
Type cross(const Matrix21 & b) const {
const Vector2 &a(*this);
return a(0)*b(1, 0) - a(1)*b(0, 0);
}
Type operator%(const Matrix21 & b) const {
return (*this).cross(b);
}
};
using Vector2f = Vector2<float>;
using Vector2d = Vector2<double>;
} // namespace matrix
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+156
View File
@@ -0,0 +1,156 @@
/**
* @file Vector3.hpp
*
* 3D vector class.
*
* @author James Goppert <james.goppert@gmail.com>
*/
#pragma once
#include "math.hpp"
namespace matrix
{
template <typename Type, size_t M, size_t N>
class Matrix;
template <typename Type, size_t M>
class Vector;
template <typename Type>
class Dcm;
template <typename Type>
class Vector2;
template<typename Type>
class Vector3 : public Vector<Type, 3>
{
public:
using Matrix31 = Matrix<Type, 3, 1>;
Vector3() = default;
Vector3(const Matrix31 & other) :
Vector<Type, 3>(other)
{
}
explicit Vector3(const Type data_[3]) :
Vector<Type, 3>(data_)
{
}
Vector3(Type x, Type y, Type z) {
Vector3 &v(*this);
v(0) = x;
v(1) = y;
v(2) = z;
}
template<size_t P, size_t Q>
Vector3(const Slice<Type, 3, 1, P, Q>& slice_in) : Vector<Type, 3>(slice_in)
{
}
template<size_t P, size_t Q>
Vector3(const Slice<Type, 1, 3, P, Q>& slice_in) : Vector<Type, 3>(slice_in)
{
}
Vector3 cross(const Matrix31 & b) const {
const Vector3 &a(*this);
return {a(1)*b(2,0) - a(2)*b(1,0), -a(0)*b(2,0) + a(2)*b(0,0), a(0)*b(1,0) - a(1)*b(0,0)};
}
/**
* Override matrix ops so Vector3 type is returned
*/
inline Vector3 operator+(Vector3 other) const
{
return Matrix31::operator+(other);
}
inline Vector3 operator+(Type scalar) const
{
return Matrix31::operator+(scalar);
}
inline Vector3 operator-(Vector3 other) const
{
return Matrix31::operator-(other);
}
inline Vector3 operator-(Type scalar) const
{
return Matrix31::operator-(scalar);
}
inline Vector3 operator-() const
{
return Matrix31::operator-();
}
inline Vector3 operator*(Type scalar) const
{
return Matrix31::operator*(scalar);
}
inline Type operator*(Vector3 b) const
{
return Vector<Type, 3>::operator*(b);
}
inline Vector3 operator%(const Matrix31 & b) const {
return (*this).cross(b);
}
/**
* Override vector ops so Vector3 type is returned
*/
inline Vector3 unit() const {
return Vector3(Vector<Type, 3>::unit());
}
inline Vector3 normalized() const {
return unit();
}
const Slice<Type, 2, 1, 3, 1> xy() const
{
return Slice<Type, 2, 1, 3, 1>(0, 0, this);
}
Slice<Type, 2, 1, 3, 1> xy()
{
return Slice<Type, 2, 1, 3, 1>(0, 0, this);
}
Dcm<Type> hat() const { // inverse to Dcm.vee() operation
const Vector3 &v(*this);
Dcm<Type> A;
A(0,0) = 0;
A(0,1) = -v(2);
A(0,2) = v(1);
A(1,0) = v(2);
A(1,1) = 0;
A(1,2) = -v(0);
A(2,0) = -v(1);
A(2,1) = v(0);
A(2,2) = 0;
return A;
}
};
using Vector3f = Vector3<float>;
using Vector3d = Vector3<double>;
} // namespace matrix
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include "math.hpp"
namespace matrix {
template<typename Type, size_t M, size_t N>
int kalman_correct(
const Matrix<Type, M, M> & P,
const Matrix<Type, N, M> & C,
const Matrix<Type, N, N> & R,
const Matrix<Type, N, 1> &r,
Matrix<Type, M, 1> & dx,
Matrix<Type, M, M> & dP,
Type & beta
)
{
SquareMatrix<Type, N> S_I = SquareMatrix<Type, N>(C*P*C.T() + R).I();
Matrix<Type, M, N> K = P*C.T()*S_I;
dx = K*r;
beta = Scalar<Type>(r.T()*S_I*r);
dP = K*C*P*(-1);
return 0;
}
} // namespace matrix
+127
View File
@@ -0,0 +1,127 @@
#pragma once
#include "math.hpp"
#if defined (__PX4_NUTTX) || defined (__PX4_QURT)
#include <px4_defines.h>
#endif
namespace matrix
{
template<typename Type>
bool is_finite(Type x) {
#if defined (__PX4_NUTTX)
return PX4_ISFINITE(x);
#elif defined (__PX4_QURT)
return __builtin_isfinite(x);
#else
return std::isfinite(x);
#endif
}
/**
* Compare if two floating point numbers are equal
*
* NAN is considered equal to NAN and -NAN
* INFINITY is considered equal INFINITY but not -INFINITY
*
* @param x right side of equality check
* @param y left side of equality check
* @param eps numerical tolerance of the check
* @return true if the two values are considered equal, false otherwise
*/
template<typename Type>
bool isEqualF(const Type x, const Type y, const Type eps = Type(1e-4f))
{
return (matrix::fabs(x - y) <= eps)
|| (isnan(x) && isnan(y))
|| (isinf(x) && isinf(y) && isnan(x - y));
}
namespace detail
{
template<typename Floating>
Floating wrap_floating(Floating x, Floating low, Floating high) {
// already in range
if (low <= x && x < high) {
return x;
}
const auto range = high - low;
const auto inv_range = Floating(1) / range; // should evaluate at compile time, multiplies below at runtime
const auto num_wraps = floor((x - low) * inv_range);
return x - range * num_wraps;
}
} // namespace detail
/**
* Wrap single precision floating point value to stay in range [low, high)
*
* @param x input possibly outside of the range
* @param low lower limit of the allowed range
* @param high upper limit of the allowed range
* @return wrapped value inside the range
*/
inline float wrap(float x, float low, float high) {
return matrix::detail::wrap_floating(x, low, high);
}
/**
* Wrap double precision floating point value to stay in range [low, high)
*
* @param x input possibly outside of the range
* @param low lower limit of the allowed range
* @param high upper limit of the allowed range
* @return wrapped value inside the range
*/
inline double wrap(double x, double low, double high) {
return matrix::detail::wrap_floating(x, low, high);
}
/**
* Wrap integer value to stay in range [low, high)
*
* @param x input possibly outside of the range
* @param low lower limit of the allowed range
* @param high upper limit of the allowed range
* @return wrapped value inside the range
*/
template<typename Integer>
Integer wrap(Integer x, Integer low, Integer high) {
const auto range = high - low;
if (x < low) {
x += range * ((low - x) / range + 1);
}
return low + (x - low) % range;
}
/**
* Wrap value in range [-π, π)
*/
template<typename Type>
Type wrap_pi(Type x)
{
return wrap(x, Type(-M_PI), Type(M_PI));
}
/**
* Wrap value in range [0, 2π)
*/
template<typename Type>
Type wrap_2pi(Type x)
{
return wrap(x, Type(0), Type(M_TWOPI));
}
template<typename T>
int sign(T val)
{
return (T(FLT_EPSILON) < val) - (val < T(FLT_EPSILON));
}
} // namespace matrix
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include "math.hpp"
namespace matrix {
template<typename Type, size_t M, size_t N>
int integrate_rk4(
Vector<Type, M> (*f)(Type, const Matrix<Type, M, 1> &x, const Matrix<Type, N, 1> & u),
const Matrix<Type, M, 1> & y0,
const Matrix<Type, N, 1> & u,
Type t0,
Type tf,
Type h0,
Matrix<Type, M, 1> & y1
)
{
// https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods
Type t1 = t0;
y1 = y0;
Type h = h0;
Vector<Type, M> k1, k2, k3, k4;
if (tf < t0) return -1; // make sure t1 > t0
while (t1 < tf) {
if (t1 + h0 < tf) {
h = h0;
} else {
h = tf - t1;
}
k1 = f(t1, y1, u);
k2 = f(t1 + h/2, y1 + k1*h/2, u);
k3 = f(t1 + h/2, y1 + k2*h/2, u);
k4 = f(t1 + h, y1 + k3*h, u);
y1 += (k1 + k2*2 + k3*2 + k4)*(h/6);
t1 += h;
}
return 0;
}
} // namespace matrix
// vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 :
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <assert.h>
#include "stdlib_imports.hpp"
#ifdef __PX4_QURT
#include "dspal_math.h"
#endif
#include "helper_functions.hpp"
#include "Matrix.hpp"
#include "SquareMatrix.hpp"
#include "Slice.hpp"
#include "Vector.hpp"
#include "Vector2.hpp"
#include "Vector3.hpp"
#include "Euler.hpp"
#include "Dcm.hpp"
#include "Scalar.hpp"
#include "Quaternion.hpp"
#include "AxisAngle.hpp"
#include "LeastSquaresSolver.hpp"
#include "Dual.hpp"
#include "PseudoInverse.hpp"
#include "SparseVector.hpp"
+139
View File
@@ -0,0 +1,139 @@
/**
* @file stdlib_imports.hpp
*
* This file is needed to shadow the C standard library math functions with ones provided by the C++ standard library.
* This way we can guarantee that unwanted functions from the C library will never creep back in unexpectedly.
*
* @author Pavel Kirienko <pavel.kirienko@zubax.com>
*/
#pragma once
#include <cmath>
#include <cstdlib>
#include <inttypes.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#ifndef M_TWOPI
#define M_TWOPI (M_PI * 2.0)
#endif
namespace matrix {
#if !defined(FLT_EPSILON)
#define FLT_EPSILON __FLT_EPSILON__
#endif
#if defined(__PX4_NUTTX)
/*
* NuttX has no usable C++ math library, so we need to provide the needed definitions here manually.
*/
#define MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(name) \
inline float name(float x) { return ::name##f(x); } \
inline double name(double x) { return ::name(x); } \
inline long double name(long double x) { return ::name##l(x); }
#define MATRIX_NUTTX_WRAP_MATH_FUN_BINARY(name) \
inline float name(float x, float y) { return ::name##f(x, y); } \
inline double name(double x, double y) { return ::name(x, y); } \
inline long double name(long double x, long double y) { return ::name##l(x, y); }
MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(fabs)
MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(log)
MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(log10)
MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(exp)
MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(sqrt)
MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(sin)
MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(cos)
MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(tan)
MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(asin)
MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(acos)
MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(atan)
MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(sinh)
MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(cosh)
MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(tanh)
MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(ceil)
MATRIX_NUTTX_WRAP_MATH_FUN_UNARY(floor)
MATRIX_NUTTX_WRAP_MATH_FUN_BINARY(pow)
MATRIX_NUTTX_WRAP_MATH_FUN_BINARY(atan2)
#else // Not NuttX, using the C++ standard library
using std::abs;
using std::div;
using std::fabs;
using std::fmod;
using std::exp;
using std::log;
using std::log10;
using std::pow;
using std::sqrt;
using std::sin;
using std::cos;
using std::tan;
using std::asin;
using std::acos;
using std::atan;
using std::atan2;
using std::sinh;
using std::cosh;
using std::tanh;
using std::ceil;
using std::floor;
using std::frexp;
using std::ldexp;
using std::modf;
# if (__cplusplus >= 201103L)
using std::remainder;
using std::remquo;
using std::fma;
using std::fmax;
using std::fmin;
using std::fdim;
using std::nan;
using std::nanf;
using std::nanl;
using std::exp2;
using std::expm1;
using std::log2;
using std::log1p;
using std::cbrt;
using std::hypot;
using std::asinh;
using std::acosh;
using std::atanh;
using std::erf;
using std::erfc;
using std::tgamma;
using std::lgamma;
using std::trunc;
using std::round;
using std::nearbyint;
using std::rint;
using std::scalbn;
using std::ilogb;
using std::logb;
using std::nextafter;
using std::copysign;
using std::fpclassify;
using std::isfinite;
using std::isinf;
using std::isnan;
using std::isnormal;
using std::signbit;
using std::isgreater;
using std::isgreaterequal;
using std::isless;
using std::islessequal;
using std::islessgreater;
using std::isunordered;
# endif
#endif
}
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
echo pwd:$PWD
astyle=$1
format=$2
format_wildcards="""
./matrix/*.*pp
./test/*.*pp
"""
#echo astyle: $astyle
#echo format: $format
if [[ $format -eq 1 ]]
then
echo formatting
$astyle ${format_wildcards}
else
echo checking format...
$astyle --dry-run ${format_wildcards} | grep Formatted &>/dev/null
if [[ $? -eq 0 ]]
then
echo Error: need to format
echo "From cmake build directory run: 'make format'"
exit 1
fi
echo no formatting needed
exit 0
fi
# vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 :
+67
View File
@@ -0,0 +1,67 @@
include(gtest.cmake)
set(tests
setIdentity
inverse
slice
matrixMult
vectorAssignment
matrixAssignment
matrixScalarMult
transpose
vector
vector2
vector3
attitude
filter
integration
sparseVector
squareMatrix
helper
hatvee
copyto
least_squares
upperRightTriangle
dual
pseudoInverse
)
add_custom_target(test_build)
foreach(test_name ${tests})
add_executable(${test_name}
${test_name}.cpp)
add_test(test_${test_name} ${test_name})
add_dependencies(test_build ${test_name})
endforeach()
target_link_libraries(sparseVector gtest_main)
if (${CMAKE_BUILD_TYPE} STREQUAL "Coverage")
add_custom_target(coverage_build
COMMAND ${CMAKE_CTEST_COMMAND}
COMMAND lcov --capture --directory . --output-file coverage.info --rc lcov_branch_coverage=1
COMMAND lcov --rc lcov_branch_coverage=1 --summary coverage.info
WORKING_DIRECTORY ${CMAKE_BUILD_DIR}
DEPENDS test_build
)
add_custom_target(coverage_html
COMMAND genhtml coverage.info --output-directory out --branch-coverage
COMMAND x-www-browser out/index.html
WORKING_DIRECTORY ${CMAKE_BUILD_DIR}
DEPENDS coverage_build
)
set(coverage_deps
coverage_build)
if (COV_HTML)
list(APPEND coverage_deps coverage_html)
endif()
add_custom_target(coverage
DEPENDS ${coverage_deps}
)
endif()
# vim: set et fenc=utf-8 ft=cmake ff=unix sts=0 sw=4 ts=4 :
+18
View File
@@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 2.8.4)
project(googletest-download NONE)
include(ExternalProject)
ExternalProject_Add(googletest
URL https://github.com/google/googletest/archive/e2239ee6043f73722e7aa812a459f54a28552929.zip
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src"
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
# Wrap download, configure and build steps in a script to log output
LOG_DOWNLOAD ON
LOG_CONFIGURE ON
LOG_BUILD ON
)
+490
View File
@@ -0,0 +1,490 @@
#include "test_macros.hpp"
#include <matrix/math.hpp>
#include <iostream>
using namespace matrix;
// manually instantiated all files we intend to test
// so that coverage works correctly
// doesn't matter what test this is in
namespace matrix {
template class Matrix<float, 3, 3>;
template class Vector3<float>;
template class Vector2<float>;
template class Vector<float, 4>;
template class Quaternion<float>;
template class AxisAngle<float>;
template class Scalar<float>;
template class SquareMatrix<float, 4>;
}
int main()
{
// check data
Eulerf euler_check(0.1f, 0.2f, 0.3f);
Quatf q_check(0.98334744f, 0.0342708f, 0.10602051f, .14357218f);
float dcm_data[] = {
0.93629336f, -0.27509585f, 0.21835066f,
0.28962948f, 0.95642509f, -0.03695701f,
-0.19866933f, 0.0978434f, 0.97517033f
};
Dcmf dcm_check(dcm_data);
// euler ctor
TEST(isEqual(euler_check, Vector3f(0.1f, 0.2f, 0.3f)));
// euler default ctor
Eulerf e;
Eulerf e_zero = zeros<float, 3, 1>();
TEST(isEqual(e, e_zero));
TEST(isEqual(e, e));
// euler vector ctor
Vector3f v(0.1f, 0.2f, 0.3f);
Eulerf euler_copy(v);
TEST(isEqual(euler_copy, euler_check));
// quaternion ctor
Quatf q0(1, 2, 3, 4);
Quatf q(q0);
double eps = 1e-6;
TEST(fabs(q(0) - 1) < eps);
TEST(fabs(q(1) - 2) < eps);
TEST(fabs(q(2) - 3) < eps);
TEST(fabs(q(3) - 4) < eps);
// quaternion ctor: vector to vector
// identity test
Quatf quat_v(v,v);
TEST(isEqual(quat_v.conjugate(v), v));
// random test (vector norm can not be preserved with a pure rotation)
Vector3f v1(-80.1f, 1.5f, -6.89f);
quat_v = Quatf(v1, v);
TEST(isEqual(quat_v.conjugate(v1).normalized() * v.norm(), v));
// special 180 degree case 1
v1 = Vector3f(0.f, 1.f, 1.f);
quat_v = Quatf(v1, -v1);
TEST(isEqual(quat_v.conjugate(v1), -v1));
// special 180 degree case 2
v1 = Vector3f(1.f, 2.f, 0.f);
quat_v = Quatf(v1, -v1);
TEST(isEqual(quat_v.conjugate(v1), -v1));
// special 180 degree case 3
v1 = Vector3f(0.f, 0.f, 1.f);
quat_v = Quatf(v1, -v1);
TEST(isEqual(quat_v.conjugate(v1), -v1));
// special 180 degree case 4
v1 = Vector3f(1.f, 1.f, 1.f);
quat_v = Quatf(v1, -v1);
TEST(isEqual(quat_v.conjugate(v1), -v1));
// quat normalization
q.normalize();
TEST(isEqual(q, Quatf(0.18257419f, 0.36514837f,
0.54772256f, 0.73029674f)));
TEST(isEqual(q0.unit(), q));
TEST(isEqual(q0.unit(), q0.normalized()));
// quat default ctor
q = Quatf();
TEST(isEqual(q, Quatf(1, 0, 0, 0)));
// quaternion exponential with v=0
v = Vector3f();
q = Quatf(1.0f, 0.0f, 0.0f, 0.0f);
Dcmf M = Dcmf()*0.5f;
TEST(isEqual(q, Quatf::expq(v)));
TEST(isEqual(M, Quatf::inv_r_jacobian(v)));
// quaternion exponential with small v
v = Vector3f(0.001f,0.002f,-0.003f);
q = Quatf(0.999993000008167f, 0.000999997666668f,
0.001999995333337f, -0.002999993000005f);
{
float M_data[] = {
0.499997833331311f, 0.001500333333644f, 0.000999499999533f,
-0.001499666666356f, 0.499998333331778f, -0.000501000000933f,
-0.001000500000467f, 0.000498999999067f, 0.499999166665889f
};
M = Dcmf(M_data);
}
TEST(isEqual(q, Quatf::expq(v)));
TEST(isEqual(M, Quatf::inv_r_jacobian(v)));
// quaternion exponential with v
v = Vector3f(1.0f, -2.0f, 3.0f);
q = Quatf(-0.825299062075259f, -0.150921327219964f,
0.301842654439929f, -0.452763981659893f);
{
float M_data[] = {
2.574616981530584f, -1.180828156687602f, -1.478757764968596f,
1.819171843312398f, 2.095859216561988f, 0.457515529937193f,
0.521242235031404f, 1.457515529937193f, 1.297929608280994f
};
M = Dcmf(M_data);
}
TEST(isEqual(q, Quatf::expq(v)));
TEST(isEqual(M, Quatf::inv_r_jacobian(v)));
// quaternion kinematic update
q = Quatf();
float h=0.001f; // sampling time [s]
Vector3f w_B=Vector3f(0.1f,0.2f,0.3f); // body rate in body frame
Quatf qa=q+0.5f*h*q.derivative1(w_B);
qa.normalize();
Quatf qb=q*Quatf::expq(0.5f*h*w_B);
TEST(isEqual(qa, qb));
// euler to quaternion
q = Quatf(euler_check);
TEST(isEqual(q, q_check));
// euler to dcm
Dcmf dcm(euler_check);
TEST(isEqual(dcm, dcm_check));
// quaternion to euler
Eulerf e1(q_check);
TEST(isEqual(e1, euler_check));
// quaternion to dcm
Dcmf dcm1(q_check);
TEST(isEqual(dcm1, dcm_check));
// quaternion z-axis unit base vector
Vector3f q_z = q_check.dcm_z();
Vector3f R_z(dcm_check(0, 2), dcm_check(1, 2), dcm_check(2, 2));
TEST(isEqual(q_z, R_z));
// dcm default ctor
Dcmf dcm2;
SquareMatrix<float, 3> I = eye<float, 3>();
TEST(isEqual(dcm2, I));
// dcm to euler
Eulerf e2(dcm_check);
TEST(isEqual(e2, euler_check));
// dcm to quaterion
Quatf q2(dcm_check);
TEST(isEqual(q2, q_check));
// dcm renormalize
Dcmf A = eye<float, 3>();
Dcmf R(euler_check);
for (size_t i = 0; i < 1000; i++) {
A = R * A;
}
A.renormalize();
float err = 0.0f;
for (size_t r = 0; r < 3; r++) {
Vector3f rvec(matrix::Matrix<float,1,3>(A.row(r)).transpose());
err += fabs(1.0f - rvec.length());
}
TEST(err < eps);
// constants
double deg2rad = M_PI / 180.0;
double rad2deg = 180.0 / M_PI;
// euler dcm round trip check
for (double roll = -90; roll <= 90; roll += 90) {
for (double pitch = -90; pitch <= 90; pitch += 90) {
for (double yaw = -179; yaw <= 180; yaw += 90) {
// note if theta = pi/2, then roll is set to zero
double roll_expected = roll;
double yaw_expected = yaw;
if (fabs(pitch -90) < eps) {
roll_expected = 0;
yaw_expected = yaw - roll;
} else if (fabs(pitch + 90) < eps) {
roll_expected = 0;
yaw_expected = yaw + roll;
}
if (yaw_expected < -180) {
yaw_expected += 360;
}
if (yaw_expected > 180) {
yaw_expected -= 360;
}
//printf("roll:%d pitch:%d yaw:%d\n", roll, pitch, yaw);
Euler<double> euler_expected(
deg2rad * roll_expected,
deg2rad * pitch,
deg2rad * yaw_expected);
Euler<double> euler(
deg2rad * roll,
deg2rad * pitch,
deg2rad * yaw);
Dcm<double> dcm_from_euler(euler);
//dcm_from_euler.print();
Euler<double> euler_out(dcm_from_euler);
TEST(isEqual(rad2deg * euler_expected, rad2deg * euler_out));
Eulerf eulerf_expected(
float(deg2rad)*float(roll_expected),
float(deg2rad)*float(pitch),
float(deg2rad)*float(yaw_expected));
Eulerf eulerf(float(deg2rad)*float(roll),
float(deg2rad)*float(pitch),
float(deg2rad)*float(yaw));
Dcm<float> dcm_from_eulerf;
dcm_from_eulerf = eulerf;
Euler<float> euler_outf(dcm_from_eulerf);
TEST(isEqual(float(rad2deg)*eulerf_expected,
float(rad2deg)*euler_outf));
}
}
}
// quaterion copy ctors
float data_v4[] = {1, 2, 3, 4};
Vector<float, 4> v4(data_v4);
Quatf q_from_v(v4);
TEST(isEqual(q_from_v, v4));
Matrix<float, 4, 1> m4(data_v4);
Quatf q_from_m(m4);
TEST(isEqual(q_from_m, m4));
// quaternion derivative in frame 1
Quatf q1(0, 1, 0, 0);
Vector<float, 4> q1_dot1 = q1.derivative1(Vector3f(1, 2, 3));
float data_q_dot1_check[] = { -0.5f, 0.0f, -1.5f, 1.0f};
Vector<float, 4> q1_dot1_check(data_q_dot1_check);
TEST(isEqual(q1_dot1, q1_dot1_check));
// quaternion derivative in frame 2
Vector<float, 4> q1_dot2 = q1.derivative2(Vector3f(1, 2, 3));
float data_q_dot2_check[] = { -0.5f, 0.0f, 1.5f, -1.0f};
Vector<float, 4> q1_dot2_check(data_q_dot2_check);
TEST(isEqual(q1_dot2, q1_dot2_check));
// quaternion product
Quatf q_prod_check(
0.93394439f, 0.0674002f, 0.20851f, 0.28236266f);
TEST(isEqual(q_prod_check, q_check * q_check));
q_check *= q_check;
TEST(isEqual(q_prod_check, q_check));
// Quaternion scalar multiplication
float scalar = 0.5;
Quatf q_scalar_mul(1.0f, 2.0f, 3.0f, 4.0f);
Quatf q_scalar_mul_check(1.0f * scalar, 2.0f * scalar,
3.0f * scalar, 4.0f * scalar);
Quatf q_scalar_mul_res = scalar * q_scalar_mul;
TEST(isEqual(q_scalar_mul_check, q_scalar_mul_res));
Quatf q_scalar_mul_res2 = q_scalar_mul * scalar;
TEST(isEqual(q_scalar_mul_check, q_scalar_mul_res2));
Quatf q_scalar_mul_res3(q_scalar_mul);
q_scalar_mul_res3 *= scalar;
TEST(isEqual(q_scalar_mul_check, q_scalar_mul_res3));
// quaternion inverse
q = q_check.inversed();
TEST(fabs(q_check(0) - q(0)) < eps);
TEST(fabs(q_check(1) + q(1)) < eps);
TEST(fabs(q_check(2) + q(2)) < eps);
TEST(fabs(q_check(3) + q(3)) < eps);
q = q_check;
q.invert();
TEST(fabs(q_check(0) - q(0)) < eps);
TEST(fabs(q_check(1) + q(1)) < eps);
TEST(fabs(q_check(2) + q(2)) < eps);
TEST(fabs(q_check(3) + q(3)) < eps);
// quaternion canonical
Quatf q_non_canonical_1(-0.7f,0.4f, 0.3f, -0.3f);
Quatf q_canonical_1(0.7f,-0.4f, -0.3f, 0.3f);
Quatf q_canonical_ref_1(0.7f,-0.4f, -0.3f, 0.3f);
TEST(isEqual(q_non_canonical_1.canonical(),q_canonical_ref_1));
TEST(isEqual(q_canonical_1.canonical(),q_canonical_ref_1));
q_non_canonical_1.canonicalize();
q_canonical_1.canonicalize();
TEST(isEqual(q_non_canonical_1,q_canonical_ref_1));
TEST(isEqual(q_canonical_1,q_canonical_ref_1));
Quatf q_non_canonical_2(0.0f, -1.0f, 0.0f, 0.0f);
Quatf q_canonical_2(0.0f, 1.0f, 0.0f, 0.0f);
Quatf q_canonical_ref_2(0.0f, 1.0f, 0.0f, 0.0f);
TEST(isEqual(q_non_canonical_2.canonical(),q_canonical_ref_2));
TEST(isEqual(q_canonical_2.canonical(),q_canonical_ref_2));
q_non_canonical_2.canonicalize();
q_canonical_2.canonicalize();
TEST(isEqual(q_non_canonical_2,q_canonical_ref_2));
TEST(isEqual(q_canonical_2,q_canonical_ref_2));
Quatf q_non_canonical_3(0.0f, 0.0f, -1.0f, 0.0f);
Quatf q_canonical_3(0.0f, 0.0f, 1.0f, 0.0f);
Quatf q_canonical_ref_3(0.0f, 0.0f, 1.0f, 0.0f);
TEST(isEqual(q_non_canonical_3.canonical(),q_canonical_ref_3));
TEST(isEqual(q_canonical_3.canonical(),q_canonical_ref_3));
q_non_canonical_3.canonicalize();
q_canonical_3.canonicalize();
TEST(isEqual(q_non_canonical_3,q_canonical_ref_3));
TEST(isEqual(q_canonical_3,q_canonical_ref_3));
Quatf q_non_canonical_4(0.0f, 0.0f, 0.0f, -1.0f);
Quatf q_canonical_4(0.0f, 0.0f, 0.0f, 1.0f);
Quatf q_canonical_ref_4(0.0f, 0.0f, 0.0f, 1.0f);
TEST(isEqual(q_non_canonical_4.canonical(),q_canonical_ref_4));
TEST(isEqual(q_canonical_4.canonical(),q_canonical_ref_4));
q_non_canonical_4.canonicalize();
q_canonical_4.canonicalize();
TEST(isEqual(q_non_canonical_4,q_canonical_ref_4));
TEST(isEqual(q_canonical_4,q_canonical_ref_4));
Quatf q_non_canonical_5(0.0f, 0.0f, 0.0f, 0.0f);
Quatf q_canonical_5(0.0f, 0.0f, 0.0f, 0.0f);
Quatf q_canonical_ref_5(0.0f, 0.0f, 0.0f, 0.0f);
TEST(isEqual(q_non_canonical_5.canonical(),q_canonical_ref_5));
TEST(isEqual(q_canonical_5.canonical(),q_canonical_ref_5));
q_non_canonical_5.canonicalize();
q_canonical_5.canonicalize();
TEST(isEqual(q_non_canonical_5,q_canonical_ref_5));
TEST(isEqual(q_canonical_5,q_canonical_ref_5));
// quaternion setIdentity
Quatf q_nonIdentity(-0.7f, 0.4f, 0.5f, -0.3f);
q_nonIdentity.setIdentity();
TEST(isEqual(q_nonIdentity, Quatf()));
// non-unit quaternion invese
Quatf q_nonunit(0.1f, 0.2f, 0.3f, 0.4f);
TEST(isEqual(q_nonunit*q_nonunit.inversed(), Quatf()));
// rotate quaternion (nonzero rotation)
Vector3f rot(1.f, 0.f, 0.f);
Quatf q_test;
q_test.rotate(rot);
Quatf q_true(cos(1.0f / 2), sin(1.0f / 2), 0.0f, 0.0f);
TEST(isEqual(q_test, q_true));
// rotate quaternion (zero rotation)
rot(0) = rot(1) = rot(2) = 0.0f;
q_test = Quatf();
q_test.rotate(rot);
q_true = Quatf(cos(0.0f), sin(0.0f), 0.0f, 0.0f);
TEST(isEqual(q_test, q_true));
// rotate quaternion (random non-commutating rotation)
q = Quatf(AxisAnglef(5.1f, 3.2f, 8.4f));
rot = Vector3f(1.1f, 2.5f, 3.8f);
q.rotate(rot);
q_true = Quatf(0.3019f, 0.2645f, 0.2268f, 0.8874f);
TEST(isEqual(q, q_true));
// get rotation axis from quaternion (nonzero rotation)
q = Quatf(cos(1.0f / 2), 0.0f, sin(1.0f / 2), 0.0f);
rot = AxisAnglef(q);
TEST(fabs(rot(0)) < eps);
TEST(fabs(rot(1) - 1.0f) < eps);
TEST(fabs(rot(2)) < eps);
// get rotation axis from quaternion (zero rotation)
q = Quatf(1.0f, 0.0f, 0.0f, 0.0f);
rot = AxisAnglef(q);
TEST(fabs(rot(0)) < eps);
TEST(fabs(rot(1)) < eps);
TEST(fabs(rot(2)) < eps);
// from axis angle (zero rotation)
rot(0) = rot(1) = rot(2) = 0.0f;
q = Quatf(AxisAnglef(rot));
q_true = Quatf(1.0f, 0.0f, 0.0f, 0.0f);
TEST(isEqual(q, q_true));
// from axis angle, with length of vector the rotation
float n = float(sqrt(4*M_PI*M_PI/3));
q = AxisAnglef(n, n, n);
TEST(isEqual(q, Quatf(-1, 0, 0, 0)));
q = AxisAnglef(0, 0, 0);
TEST(isEqual(q, Quatf(1, 0, 0, 0)));
// Quaternion initialisation per array
float q_array[] = {0.9833f, -0.0343f, -0.1060f, -0.1436f};
Quaternion<float>q_from_array(q_array);
for (size_t i = 0; i < 4; i++) {
TEST(fabs(q_from_array(i) - q_array[i]) < eps);
}
// axis angle
AxisAnglef aa_true(Vector3f(1.0f, 2.0f, 3.0f));
TEST(isEqual(aa_true, Vector3f(1.0f, 2.0f, 3.0f)));
AxisAnglef aa_empty;
TEST(isEqual(aa_empty, AxisAnglef(0.0f, 0.0f, 0.0f)));
float aa_data[] = {4.0f, 5.0f, 6.0f};
AxisAnglef aa_data_init(aa_data);
TEST(isEqual(aa_data_init, AxisAnglef(4.0f, 5.0f, 6.0f)));
AxisAnglef aa_norm_check(Vector3f(0.0f, 0.0f, 0.0f));
TEST(isEqual(aa_norm_check.axis(), Vector3f(1, 0, 0)));
TEST(isEqualF(aa_norm_check.angle(), 0.0f));
q = Quatf(-0.29555112749297824f, 0.25532186f, 0.51064372f, 0.76596558f);
float r_array[9] = {-0.6949206f, 0.713521f, 0.089292854f, -0.19200698f, -0.30378509f, 0.93319237f, 0.69297814f, 0.63134968f, 0.34810752f};
R = Dcmf(r_array);
TEST(isEqual(q.imag(), Vector3f(0.25532186f, 0.51064372f, 0.76596558f)));
// from dcm
TEST(isEqual(Quatf(R), q));
TEST(isEqual(Quatf(Dcmf(q)), q));
// to dcm
TEST(isEqual(Dcmf(q), R));
TEST(isEqual(Dcmf(Quatf(R)), R));
// conjugate
v = Vector3f(1.5f, 2.2f, 3.2f);
TEST(isEqual(q.conjugate_inversed(v1), Dcmf(q).T()*v1));
TEST(isEqual(q.conjugate(v1), Dcmf(q)*v1));
AxisAnglef aa_q_init(q);
TEST(isEqual(aa_q_init, AxisAnglef(1.0f, 2.0f, 3.0f)));
AxisAnglef aa_euler_init(Eulerf(0.0f, 0.0f, 0.0f));
TEST(isEqual(aa_euler_init, Vector3f(0.0f, 0.0f, 0.0f)));
Dcmf dcm_aa_check = AxisAnglef(dcm_check);
TEST(isEqual(dcm_aa_check, dcm_check));
AxisAnglef aa_axis_angle_init(Vector3f(1.0f, 2.0f, 3.0f), 3.0f);
TEST(isEqual(aa_axis_angle_init, Vector3f(0.80178373f, 1.60356745f, 2.40535118f)));
TEST(isEqual(aa_axis_angle_init.axis(), Vector3f(0.26726124f, 0.53452248f, 0.80178373f)));
TEST(isEqualF(aa_axis_angle_init.angle(), 3.0f));
TEST(isEqual(Quatf((AxisAnglef(Vector3f(0.0f, 0.0f, 1.0f), 0.0f))),
Quatf(1.0f, 0.0f, 0.0f, 0.0f)));
// check consistentcy of quaternion and dcm product
Dcmf dcm3(Eulerf(1, 2, 3));
Dcmf dcm4(Eulerf(4, 5, 6));
Dcmf dcm34 = dcm3 * dcm4;
TEST(isEqual(Eulerf(Quatf(dcm3)*Quatf(dcm4)), Eulerf(dcm34)));
// check corner cases of matrix to quaternion conversion
q = Quatf(0,1,0,0); // 180 degree rotation around the x axis
R = Dcmf(q);
TEST(isEqual(q, Quatf(R)));
q = Quatf(0,0,1,0); // 180 degree rotation around the y axis
R = Dcmf(q);
TEST(isEqual(q, Quatf(R)));
q = Quatf(0,0,0,1); // 180 degree rotation around the z axis
R = Dcmf(q);
TEST(isEqual(q, Quatf(R)));
#if defined(SUPPORT_STDIOSTREAM)
std::cout << "q:" << q;
#endif
return 0;
}
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+72
View File
@@ -0,0 +1,72 @@
#include "test_macros.hpp"
#include <matrix/math.hpp>
using namespace matrix;
namespace {
void doTheCopy(const Matrix<float, 2, 3>& A, float array_A[6])
{
A.copyTo(array_A);
}
}
int main()
{
float eps = 1e-6f;
// Vector3 copyTo
const Vector3f v(1, 2, 3);
float dst3[3] = {};
v.copyTo(dst3);
for (size_t i = 0; i < 3; i++) {
TEST(fabs(v(i) - dst3[i]) < eps);
}
// Quaternion copyTo
Quatf q(1, 2, 3, 4);
float dst4[4] = {};
q.copyTo(dst4);
for (size_t i = 0; i < 4; i++) {
TEST(fabs(q(i) - dst4[i]) < eps);
}
// Matrix copyTo
Matrix<float, 2, 3> A;
A(0,0) = 1;
A(0,1) = 2;
A(0,2) = 3;
A(1,0) = 4;
A(1,1) = 5;
A(1,2) = 6;
float array_A[6] = {};
doTheCopy(A, array_A);
float array_row[6] = {1, 2, 3, 4, 5, 6};
for (size_t i = 0; i < 6; i++) {
TEST(fabs(array_A[i] - array_row[i]) < eps);
}
// Matrix copyToColumnMajor
A.copyToColumnMajor(array_A);
float array_column[6] = {1, 4, 2, 5, 3, 6};
for (size_t i = 0; i < 6; i++) {
TEST(fabs(array_A[i] - array_column[i]) < eps);
}
// Slice copyTo
float dst5[2] = {};
v.slice<2,1>(0,0).copyTo(dst5);
for (size_t i = 0; i < 2; i++) {
TEST(fabs(v(i) - dst5[i]) < eps);
}
float subarray_A[4] = {};
A.slice<2,2>(0,0).copyToColumnMajor(subarray_A);
float subarray_column[4] = {1,4,2,5};
for (size_t i = 0; i < 4; i++) {
TEST(fabs(subarray_A[i] - subarray_column[i]) < eps);
}
return 0;
}
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+310
View File
@@ -0,0 +1,310 @@
#include "test_macros.hpp"
#include <matrix/math.hpp>
#include <iostream>
using namespace matrix;
template <typename Scalar, size_t N>
bool isEqualAll(Dual<Scalar, N> a, Dual<Scalar, N> b)
{
return isEqualF(a.value, b.value) && a.derivative == b.derivative;
}
template <typename T>
T testFunction(const Vector<T, 3>& point) {
// function is f(x,y,z) = x^2 + 2xy + 3y^2 + z
return point(0)*point(0)
+ 2.f * point(0) * point(1)
+ 3.f * point(1) * point(1)
+ point(2);
}
template <typename Scalar>
Vector<Scalar, 3> positionError(const Vector<Scalar, 3>& positionState,
const Vector<Scalar, 3>& velocityStateBody,
const Quaternion<Scalar>& bodyOrientation,
const Vector<Scalar, 3>& positionMeasurement,
Scalar dt
)
{
return positionMeasurement - (positionState + bodyOrientation.conjugate(velocityStateBody) * dt);
}
int main()
{
const Dual<float, 1> a(3,0);
const Dual<float, 1> b(6,0);
{
TEST(isEqualF(a.value, 3.f));
TEST(isEqualF(a.derivative(0), 1.f));
}
{
// addition
Dual<float, 1> c = a + b;
TEST(isEqualF(c.value, 9.f));
TEST(isEqualF(c.derivative(0), 2.f));
Dual<float, 1> d = +a;
TEST(isEqualAll(d, a));
d += b;
TEST(isEqualAll(d, c));
Dual<float, 1> e = a;
e += b.value;
TEST(isEqualF(e.value, c.value));
TEST(isEqual(e.derivative, a.derivative));
Dual<float, 1> f = b.value + a;
TEST(isEqualAll(f, e));
}
{
// subtraction
Dual<float, 1> c = b - a;
TEST(isEqualF(c.value, 3.f));
TEST(isEqualF(c.derivative(0), 0.f));
Dual<float, 1> d = b;
TEST(isEqualAll(d, b));
d -= a;
TEST(isEqualAll(d, c));
Dual<float, 1> e = b;
e -= a.value;
TEST(isEqualF(e.value, c.value));
TEST(isEqual(e.derivative, b.derivative));
Dual<float, 1> f = a.value - b;
TEST(isEqualAll(f, -e));
}
{
// multiplication
Dual<float, 1> c = a*b;
TEST(isEqualF(c.value, 18.f));
TEST(isEqualF(c.derivative(0), 9.f));
Dual<float, 1> d = a;
TEST(isEqualAll(d, a));
d *= b;
TEST(isEqualAll(d, c));
Dual<float, 1> e = a;
e *= b.value;
TEST(isEqualF(e.value, c.value));
TEST(isEqual(e.derivative, a.derivative * b.value));
Dual<float, 1> f = b.value * a;
TEST(isEqualAll(f, e));
}
{
// division
Dual<float, 1> c = b/a;
TEST(isEqualF(c.value, 2.f));
TEST(isEqualF(c.derivative(0), -1.f/3.f));
Dual<float, 1> d = b;
TEST(isEqualAll(d, b));
d /= a;
TEST(isEqualAll(d, c));
Dual<float, 1> e = b;
e /= a.value;
TEST(isEqualF(e.value, c.value));
TEST(isEqual(e.derivative, b.derivative / a.value));
Dual<float, 1> f = a.value / b;
TEST(isEqualAll(f, 1.f/e));
}
{
Dual<float, 1> blank;
TEST(isEqualF(blank.value, 0.f));
TEST(isEqualF(blank.derivative(0), 0.f));
}
{
// sqrt
TEST(isEqualF(sqrt(a).value, sqrt(a.value)));
TEST(isEqualF(sqrt(a).derivative(0), 1.f/sqrt(12.f)));
}
{
// abs
TEST(isEqualAll(a, abs(-a)));
TEST(!isEqualAll(-a, abs(a)));
TEST(isEqualAll(-a, -abs(a)));
}
{
// ceil
Dual<float, 1> c(1.5,0);
TEST(isEqualF(ceil(c).value, ceil(c.value)));
TEST(isEqualF(ceil(c).derivative(0), 0.f));
}
{
// floor
Dual<float, 1> c(1.5,0);
TEST(isEqualF(floor(c).value, floor(c.value)));
TEST(isEqualF(floor(c).derivative(0), 0.f));
}
{
// fmod
TEST(isEqualF(fmod(a, 0.8f).value, fmod(a.value, 0.8f)));
TEST(isEqual(fmod(a, 0.8f).derivative, a.derivative));
}
{
// max/min
TEST(isEqualAll(b, max(a, b)));
TEST(isEqualAll(a, min(a, b)));
}
{
// isnan
TEST(!IsNan(a));
Dual<float, 1> c(sqrt(-1.f),0);
TEST(IsNan(c));
}
{
// isfinite/isinf
TEST(IsFinite(a));
TEST(!IsInf(a));
Dual<float, 1> c(sqrt(-1.f),0);
TEST(!IsFinite(c));
TEST(!IsInf(c));
Dual<float, 1> d(INFINITY,0);
TEST(!IsFinite(d));
TEST(IsInf(d));
}
{
// sin/cos/tan
TEST(isEqualF(sin(a).value, sin(a.value)));
TEST(isEqualF(sin(a).derivative(0), cos(a.value))); // sin'(x) = cos(x)
TEST(isEqualF(cos(a).value, cos(a.value)));
TEST(isEqualF(cos(a).derivative(0), -sin(a.value))); // cos'(x) = -sin(x)
TEST(isEqualF(tan(a).value, tan(a.value)));
TEST(isEqualF(tan(a).derivative(0), 1.f + tan(a.value)*tan(a.value))); // tan'(x) = 1 + tan^2(x)
}
{
// asin/acos/atan
Dual<float, 1> c(0.3f, 0);
TEST(isEqualF(asin(c).value, asin(c.value)));
TEST(isEqualF(asin(c).derivative(0), 1.f/sqrt(1.f - 0.3f*0.3f))); // asin'(x) = 1/sqrt(1-x^2)
TEST(isEqualF(acos(c).value, acos(c.value)));
TEST(isEqualF(acos(c).derivative(0), -1.f/sqrt(1.f - 0.3f*0.3f))); // acos'(x) = -1/sqrt(1-x^2)
TEST(isEqualF(atan(c).value, atan(c.value)));
TEST(isEqualF(atan(c).derivative(0), 1.f/(1.f + 0.3f*0.3f))); // tan'(x) = 1 + x^2
}
{
// atan2
TEST(isEqualF(atan2(a, b).value, atan2(a.value, b.value)));
TEST(isEqualAll(atan2(a, Dual<float,1>(b.value)), atan(a/b.value))); // atan2'(y, x) = atan'(y/x)
}
{
// partial derivatives
// function is f(x,y,z) = x^2 + 2xy + 3y^2 + z, we need with respect to d/dx and d/dy at the point (0.5, -0.8, 2)
using D = Dual<float, 2>;
// set our starting point, requesting partial derivatives of x and y in column 0 and 1
Vector3<D> dualPoint(D(0.5f, 0), D(-0.8f, 1), D(2.f));
Dual<float, 2> dualResult = testFunction(dualPoint);
// compare to a numerical derivative:
Vector<float, 3> floatPoint = collectReals(dualPoint);
float floatResult = testFunction(floatPoint);
float h = 0.0001f;
Vector<float, 3> floatPoint_plusDX = floatPoint;
floatPoint_plusDX(0) += h;
float floatResult_plusDX = testFunction(floatPoint_plusDX);
Vector<float, 3> floatPoint_plusDY = floatPoint;
floatPoint_plusDY(1) += h;
float floatResult_plusDY = testFunction(floatPoint_plusDY);
Vector2f numerical_derivative((floatResult_plusDX - floatResult)/h,
(floatResult_plusDY - floatResult)/h);
TEST(isEqualF(dualResult.value, floatResult, 0.0f));
TEST(isEqual(dualResult.derivative, numerical_derivative, 1e-2f));
}
{
// jacobian
// get residual of x/y/z with partial derivatives of rotation
Vector3f direct_error;
Matrix<float, 3, 4> numerical_jacobian;
{
Vector3f positionState(5,6,7);
Vector3f velocityState(-1,0,1);
Quaternionf velocityOrientation(0.2f,-0.1f,0,1);
Vector3f positionMeasurement(4.5f, 6.2f, 7.9f);
float dt = 0.1f;
direct_error = positionError(positionState,
velocityState,
velocityOrientation,
positionMeasurement,
dt);
float h = 0.001f;
for (size_t i = 0; i < 4; i++)
{
Quaternion<float> h4 = velocityOrientation;
h4(i) += h;
numerical_jacobian.col(i) = (positionError(positionState,
velocityState,
h4,
positionMeasurement,
dt)
- direct_error)/h;
}
}
Vector3f auto_error;
Matrix<float, 3, 4> auto_jacobian;
{
using D4 = Dual<float, 4>;
using Vector3d4 = Vector3<D4>;
Vector3d4 positionState(D4(5), D4(6), D4(7));
Vector3d4 velocityState(D4(-1), D4(0), D4(1));
// request partial derivatives of velocity orientation
// by setting these variables' derivatives in corresponding columns [0...3]
Quaternion<D4> velocityOrientation(D4(0.2f, 0),D4(-0.1f, 1),D4(0, 2),D4(1, 3));
Vector3d4 positionMeasurement(D4(4.5f), D4(6.2f), D4(7.9f));
D4 dt(0.1f);
Vector3d4 error = positionError(positionState,
velocityState,
velocityOrientation,
positionMeasurement,
dt);
auto_error = collectReals(error);
auto_jacobian = collectDerivatives(error);
}
TEST(isEqual(direct_error, auto_error, 0.0f));
TEST(isEqual(numerical_jacobian, auto_jacobian, 1e-3f));
}
return 0;
}
+29
View File
@@ -0,0 +1,29 @@
#include "test_macros.hpp"
#include <matrix/filter.hpp>
using namespace matrix;
int main()
{
const size_t n_x = 6;
const size_t n_y = 5;
SquareMatrix<float, n_x> P = eye<float, n_x>();
SquareMatrix<float, n_y> R = eye<float, n_y>();
Matrix<float, n_y, n_x> C;
C.setIdentity();
float data[] = {1,2,3,4,5};
Vector<float, n_y> r(data);
Vector<float, n_x> dx;
SquareMatrix<float, n_x> dP;
float beta = 0;
kalman_correct<float, 6, 5>(P, C, R, r, dx, dP, beta);
float data_check[] = {0.5,1,1.5,2,2.5,0};
Vector<float, n_x> dx_check(data_check);
TEST(isEqual(dx, dx_check));
return 0;
}
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+12
View File
@@ -0,0 +1,12 @@
set_directory_properties(PROPERTIES COMPILE_OPTIONS "")
# Download and unpack googletest at configure time
configure_file(${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt.in googletest-download/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . RESULT_VARIABLE result1 WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download)
execute_process(COMMAND ${CMAKE_COMMAND} --build . RESULT_VARIABLE result2 WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download)
if(result1 OR result2)
message(FATAL_ERROR "Preparing googletest failed: ${result1} ${result2}")
endif()
# Add googletest, defines gtest and gtest_main targets
add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src ${CMAKE_CURRENT_BINARY_DIR}/googletest-build EXCLUDE_FROM_ALL)
+19
View File
@@ -0,0 +1,19 @@
#include "test_macros.hpp"
#include <matrix/math.hpp>
using namespace matrix;
int main()
{
Euler<float> euler(0.1f, 0.2f, 0.3f);
Dcm<float> R(euler);
Dcm<float> skew = R - R.T();
Vector3<float> w = skew.vee();
Vector3<float> w_check(0.1348f, 0.4170f, 0.5647f);
TEST(isEqual(w, w_check));
TEST(isEqual(skew, w.hat()));
return 0;
}
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+78
View File
@@ -0,0 +1,78 @@
#include "test_macros.hpp"
#include <matrix/math.hpp>
using namespace matrix;
int main()
{
// general wraps
TEST(fabs(wrap(4., 0., 10.) - 4.) < FLT_EPSILON);
TEST(fabs(wrap(4., 0., 1.)) < FLT_EPSILON);
TEST(fabs(wrap(-4., 0., 10.) - 6.) < FLT_EPSILON);
TEST(fabs(wrap(-18., 0., 10.) - 2.) < FLT_EPSILON);
TEST(fabs(wrap(-1.5, 3., 5.) - 4.5) < FLT_EPSILON);
TEST(fabs(wrap(15.5, 3., 5.) - 3.5) < FLT_EPSILON);
TEST(fabs(wrap(-1., 30., 40.) - 39.) < FLT_EPSILON);
TEST(fabs(wrap(-8000., -555., 1.) - (-216.)) < FLT_EPSILON);
TEST(fabs(wrap(0., 0., 360.)) < FLT_EPSILON);
TEST(fabs(wrap(0. - FLT_EPSILON, 0., 360.) - (360. - FLT_EPSILON)) < FLT_EPSILON);
TEST(fabs(wrap(0. + FLT_EPSILON, 0., 360.) - FLT_EPSILON) < FLT_EPSILON);
TEST(fabs(wrap(360., 0., 360.)) < FLT_EPSILON);
TEST(fabs(wrap(360. - FLT_EPSILON, 0., 360.) - (360. - FLT_EPSILON)) < FLT_EPSILON);
TEST(fabs(wrap(360. + FLT_EPSILON, 0., 360.) - FLT_EPSILON) < FLT_EPSILON);
// integer wraps
TEST(wrap(-10, 0, 10) == 0);
TEST(wrap(-4, 0, 10) == 6);
TEST(wrap(0, 0, 10) == 0)
TEST(wrap(4, 0, 10) == 4);
TEST(wrap(10, 0, 10) == 0);
// wrap pi
TEST(fabs(wrap_pi(0.)) < FLT_EPSILON);
TEST(fabs(wrap_pi(4.) - (4. - M_TWOPI)) < FLT_EPSILON);
TEST(fabs(wrap_pi(-4.) - (-4. + M_TWOPI)) < FLT_EPSILON);
TEST(fabs(wrap_pi(3.) - (3.)) < FLT_EPSILON);
TEST(fabs(wrap_pi(100.) - (100. - 32. * M_PI)) < FLT_EPSILON);
TEST(fabs(wrap_pi(-100.) - (-100. + 32. * M_PI)) < FLT_EPSILON);
TEST(fabs(wrap_pi(-101.) - (-101. + 32. * M_PI)) < FLT_EPSILON);
TEST(!is_finite(wrap_pi(NAN)));
// wrap 2pi
TEST(fabs(wrap_2pi(0.)) < FLT_EPSILON);
TEST(fabs(wrap_2pi(-4.) - (-4. + 2. * M_PI)) < FLT_EPSILON);
TEST(fabs(wrap_2pi(3.) - (3.)) < FLT_EPSILON);
TEST(fabs(wrap_2pi(200.) - (200. - 31. * M_TWOPI)) < FLT_EPSILON);
TEST(fabs(wrap_2pi(-201.) - (-201. + 32. * M_TWOPI)) < FLT_EPSILON);
TEST(!is_finite(wrap_2pi(NAN)));
// Equality checks
TEST(isEqualF(1., 1.));
TEST(!isEqualF(1., 2.));
TEST(!isEqualF(NAN, 1.f));
TEST(!isEqualF(1.f, NAN));
TEST(!isEqualF(INFINITY, 1.f));
TEST(!isEqualF(1.f, INFINITY));
TEST(isEqualF(NAN, NAN));
TEST(isEqualF(NAN, -NAN));
TEST(isEqualF(-NAN, NAN));
TEST(isEqualF(INFINITY, INFINITY));
TEST(!isEqualF(INFINITY, -INFINITY));
TEST(!isEqualF(-INFINITY, INFINITY));
TEST(isEqualF(-INFINITY, -INFINITY));
Vector3f a(1, 2, 3);
Vector3f b(4, 5, 6);
TEST(!isEqual(a, b));
TEST(isEqual(a, a));
Vector3f c(1, 2, 3);
Vector3f d(1, 2, NAN);
TEST(!isEqual(c, d));
TEST(isEqual(c, c));
TEST(isEqual(d, d));
return 0;
}
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+26
View File
@@ -0,0 +1,26 @@
#include "test_macros.hpp"
#include <matrix/integration.hpp>
using namespace matrix;
Vector<float, 6> f(float t, const Matrix<float, 6, 1> & /*y*/, const Matrix<float, 3, 1> & /*u*/);
Vector<float, 6> f(float t, const Matrix<float, 6, 1> & /*y*/, const Matrix<float, 3, 1> & /*u*/) {
float v = -sin(t);
return v*ones<float, 6, 1>();
}
int main()
{
Vector<float, 6> y = ones<float, 6, 1>();
Vector<float, 3> u = ones<float, 3, 1>();
float t0 = 0;
float tf = 2;
float h = 0.001f;
integrate_rk4(f, y, u, t0, tf, h, y);
float v = 1 + cos(tf) - cos(t0);
TEST(isEqual(y, (ones<float, 6, 1>()*v)));
return 0;
}
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+164
View File
@@ -0,0 +1,164 @@
#include "test_macros.hpp"
#include <matrix/math.hpp>
using namespace matrix;
static const size_t n_large = 50;
int main()
{
float data[9] = {0, 2, 3,
4, 5, 6,
7, 8, 10
};
float data_check[9] = {
-0.4f, -0.8f, 0.6f,
-0.4f, 4.2f, -2.4f,
0.6f, -2.8f, 1.6f
};
SquareMatrix<float, 3> A(data);
SquareMatrix<float, 3> A_I = inv(A);
SquareMatrix<float, 3> A_I_check(data_check);
TEST((A_I - A_I_check).abs().max() < 1e-6f);
float data_2x2[4] = {12, 2,
-7, 5
};
float data_2x2_check[4] = {
0.0675675675f, -0.02702702f,
0.0945945945f, 0.162162162f
};
SquareMatrix<float, 2> A2x2(data_2x2);
SquareMatrix<float, 2> A2x2_I = inv(A2x2);
SquareMatrix<float, 2> A2x2_I_check(data_2x2_check);
TEST(isEqual(A2x2_I, A2x2_I_check));
SquareMatrix<float, 2> A2x2_sing = ones<float, 2, 2>();
SquareMatrix<float, 2> A2x2_sing_I;
TEST(inv(A2x2_sing, A2x2_sing_I) == false);
SquareMatrix<float, 3> A3x3_sing = ones<float, 3, 3>();
SquareMatrix<float, 3> A3x3_sing_I;
TEST(inv(A3x3_sing, A3x3_sing_I) == false)
// stess test
SquareMatrix<float, n_large> A_large;
A_large.setIdentity();
SquareMatrix<float, n_large> A_large_I;
A_large_I.setZero();
for (size_t i = 0; i < n_large; i++) {
A_large_I = inv(A_large);
TEST(isEqual(A_large, A_large_I));
}
SquareMatrix<float, 3> zero_test = zeros<float, 3, 3>();
TEST(isEqual(inv(zero_test), zeros<float, 3, 3>()));
// test pivotting
float data2[81] = {
-2, 1, 1, -1, -5, 1, 2, -1, 0,
-3, 2, -1, 0, 2, 2, -1, -5, 3,
0, 0, 0, 1, 4, -3, 3, 0, -2,
2, 2, -1, -2, -1, 0, 3, 0, 1,
-1, 2, -1, -1, -3, 3, 0, -2, 3,
0, 1, 1, -3, 3, -2, 0, -4, 0,
1, 0, 0, 0, 0, 0, -2, 4, -3,
1, -1, 0, -1, -1, 1, -1, -3, 4,
0, 3, -1, -2, 2, 1, -2, 0, -1
};
float data2_check[81] = {
6, -4, 3, -3, -9, -8, -10, 8, 14,
-2, -7, -5, -3, -2, -2, -16, -5, 8,
-2, 0, -23, 7, -24, -5, -28, -14, 9,
3, -7, 2, -5, -4, -6, -13, 4, 13,
-1, 4, -8, 5, -8, 0, -3, -5, -2,
6, 7, -7, 7, -21, -7, -5, 3, 6,
1, 4, -4, 4, -7, -1, 0, -1, -1,
-7, 3, -11, 5, 1, 6, -1, -13, -10,
-8, 0, -11, 3, 3, 6, -5, -14, -8
};
SquareMatrix<float, 9> A2(data2);
SquareMatrix<float, 9> A2_I = inv(A2);
SquareMatrix<float, 9> A2_I_check(data2_check);
TEST((A2_I - A2_I_check).abs().max() < 1e-3f);
float data3[9] = {
0, 1, 2,
3, 4, 5,
6, 7, 9
};
float data3_check[9] = {
-0.3333333f, -1.6666666f, 1,
-1, 4, -2,
1, -2, 1
};
SquareMatrix<float, 3> A3(data3);
SquareMatrix<float, 3> A3_I = inv(A3);
SquareMatrix<float, 3> A3_I_check(data3_check);
TEST(isEqual(inv(A3), A3_I_check));
TEST(isEqual(A3_I, A3_I_check));
TEST(A3.I(A3_I));
TEST(isEqual(A3_I, A3_I_check));
// cover singular matrices
A3(0, 0) = 0;
A3(0, 1) = 0;
A3(0, 2) = 0;
A3_I = inv(A3);
SquareMatrix<float, 3> Z3 = zeros<float, 3, 3>();
TEST(!A3.I(A3_I));
TEST(!Z3.I(A3_I));
TEST(isEqual(A3_I, Z3));
TEST(isEqual(A3.I(), Z3));
for(size_t i = 0; i < 9; i++) {
A2(0, i) = 0;
}
A2_I = inv(A2);
SquareMatrix<float, 9> Z9 = zeros<float, 9, 9>();
TEST(!A2.I(A2_I));
TEST(!Z9.I(A2_I));
TEST(isEqual(A2_I, Z9));
TEST(isEqual(A2.I(), Z9));
// cover NaN
A3(0, 0) = NAN;
A3(0, 1) = 0;
A3(0, 2) = 0;
A3_I = inv(A3);
TEST(isEqual(A3_I, Z3));
TEST(isEqual(A3.I(), Z3));
A2(0, 0) = NAN;
A2_I = inv(A2);
TEST(isEqual(A2_I, Z9));
TEST(isEqual(A2.I(), Z9));
float data4[9] = {
1.33471626f, 0.74946721f, -0.0531679f,
0.74946721f, 1.07519593f, 0.08036323f,
-0.0531679f, 0.08036323f, 1.01618474f
};
SquareMatrix<float, 3> A4(data4);
float data4_cholesky[9] = {
1.15529921f, 0.f, 0.f,
0.6487213f, 0.80892311f, 0.f,
-0.04602089f, 0.13625271f, 0.99774847f
};
SquareMatrix<float, 3> A4_cholesky_check(data4_cholesky);
SquareMatrix<float, 3> A4_cholesky = cholesky(A4);
TEST(isEqual(A4_cholesky_check, A4_cholesky));
SquareMatrix<float, 3> I3;
I3.setIdentity();
TEST(isEqual(choleskyInv(A4)*A4, I3));
TEST(isEqual(cholesky(Z3), Z3));
return 0;
}
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+100
View File
@@ -0,0 +1,100 @@
#include "test_macros.hpp"
#include <matrix/math.hpp>
using namespace matrix;
int test_4x3(void);
template<typename Type> int test_4x4(void);
int test_4x4_type_double(void);
int test_div_zero(void);
int main()
{
int ret;
ret = test_4x4<float>();
if (ret != 0) return ret;
ret = test_4x4<double>();
if (ret != 0) return ret;
ret = test_4x3();
if (ret != 0) return ret;
ret = test_div_zero();
if (ret != 0) return ret;
return 0;
}
int test_4x3() {
// Start with an (m x n) A matrix
float data[12] = {20.f, -10.f, -13.f,
17.f, 16.f, -18.f,
0.7f, -0.8f, 0.9f,
-1.f, -1.1f, -1.2f
};
Matrix<float, 4, 3> A(data);
float b_data[4] = {2.0f, 3.0f, 4.0f, 5.0f};
Vector<float, 4> b(b_data);
float x_check_data[3] = {-0.69168233f,
-0.26227593f,
-1.03767522f
};
Vector<float, 3> x_check(x_check_data);
LeastSquaresSolver<float, 4, 3> qrd = LeastSquaresSolver<float, 4, 3>(A);
Vector<float, 3> x = qrd.solve(b);
TEST(isEqual(x, x_check));
return 0;
}
template<typename Type>
int test_4x4() {
// Start with an (m x n) A matrix
const Type data[16] = { 20.f, -10.f, -13.f, 21.f,
17.f, 16.f, -18.f, -14.f,
0.7f, -0.8f, 0.9f, -0.5f,
-1.f, -1.1f, -1.2f, -1.3f
};
Matrix<Type, 4, 4> A(data);
Type b_data[4] = {2.0f, 3.0f, 4.0f, 5.0f};
Vector<Type, 4> b(b_data);
Type x_check_data[4] = { 0.97893433f,
-2.80798701f,
-0.03175765f,
-2.19387649f
};
Vector<Type, 4> x_check(x_check_data);
LeastSquaresSolver<Type, 4, 4> qrd = LeastSquaresSolver<Type, 4, 4>(A);
Vector<Type, 4> x = qrd.solve(b);
TEST(isEqual(x, x_check));
return 0;
}
int test_div_zero() {
float data[4] = {0.0f, 0.0f, 0.0f, 0.0f};
Matrix<float, 2, 2> A(data);
float b_data[2] = {1.0f, 1.0f};
Vector<float, 2> b(b_data);
// Implement such that x returns zeros if it reaches div by zero
float x_check_data[2] = {0.0f, 0.0f};
Vector<float, 2> x_check(x_check_data);
LeastSquaresSolver<float, 2, 2> qrd = LeastSquaresSolver<float, 2, 2>(A);
Vector<float, 2> x = qrd.solve(b);
TEST(isEqual(x, x_check));
return 0;
}
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+260
View File
@@ -0,0 +1,260 @@
#include "test_macros.hpp"
#include <matrix/math.hpp>
using namespace matrix;
template class matrix::Matrix<float, 3, 2>;
int main()
{
Matrix3f m;
m.setZero();
m.zero();
m(0, 0) = 1;
m(0, 1) = 2;
m(0, 2) = 3;
m(1, 0) = 4;
m(1, 1) = 5;
m(1, 2) = 6;
m(2, 0) = 7;
m(2, 1) = 8;
m(2, 2) = 9;
float data[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
Matrix3f m2(data);
for(size_t i=0; i<3; i++) {
for (size_t j = 0; j < 3; j++) {
TEST(fabs(data[i*3 + j] - m2(i,j)) < FLT_EPSILON);
}
}
Matrix3f m_nan;
m_nan.setNaN();
for(size_t i=0; i<3; i++) {
for (size_t j = 0; j < 3; j++) {
TEST(isnan(m_nan(i,j)));
}
}
TEST(m_nan.isAllNan());
float data2d[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
m2 = Matrix3f(data2d);
for(size_t i=0; i<3; i++) {
for (size_t j = 0; j < 3; j++) {
TEST(fabs(data[i*3 + j] - m2(i,j)) < FLT_EPSILON);
}
}
TEST(!m2.isAllNan());
float data_times_2[9] = {2, 4, 6, 8, 10, 12, 14, 16, 18};
Matrix3f m3(data_times_2);
TEST(isEqual(m, m2));
TEST(!(isEqual(m, m3)));
m2 *= 2;
TEST(isEqual(m2, m3));
m2 /= 2;
m2 -= 1;
float data_minus_1[9] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
TEST(isEqual(Matrix3f(data_minus_1), m2));
m2 += 1;
TEST(isEqual(Matrix3f(data), m2));
m3 -= m2;
TEST(isEqual(m3, m2));
// set rows and columns to value
Matrix3f m2e(data2d);
float data2e_check1[3][3] = {
{1, 11, 3},
{4, 11, 6},
{7, 11, 9}
};
Matrix3f m2e_check1(data2e_check1);
float data2e_check2[3][3] = {
{1, 11, 3},
{4, 11, 6},
{0, 0, 0}
};
Matrix3f m2e_check2(data2e_check2);
m2e.setCol(1, 11);
TEST(isEqual(m2e, m2e_check1));
m2e.setRow(2, 0);
TEST(isEqual(m2e, m2e_check2));
float data_row_02_swap[9] = {
7, 8, 9,
4, 5, 6,
1, 2, 3,
};
float data_col_02_swap[9] = {
3, 2, 1,
6, 5, 4,
9, 8, 7
};
Matrix3f m4(data);
TEST(isEqual(-m4, m4*(-1)));
// col swap
m4.swapCols(0, 2);
TEST(isEqual(m4, Matrix3f(data_col_02_swap)));
m4.swapCols(0, 2);
// row swap
m4.swapRows(0, 2);
TEST(isEqual(m4, Matrix3f(data_row_02_swap)));
m4.swapRows(0, 2);
// swapping with same row should do nothing
m4.swapRows(0, 0);
m4.swapRows(1, 1);
m4.swapRows(2, 2);
TEST(isEqual(m4, Matrix3f(data)));
// swapping with same col should do nothing
m4.swapCols(0, 0);
m4.swapCols(1, 1);
m4.swapCols(2, 2);
TEST(isEqual(m4, Matrix3f(data)));
TEST(fabs(m4.min() - 1) < FLT_EPSILON);
TEST(fabs((-m4).min() + 9) < FLT_EPSILON);
Scalar<float> s = 1;
const Vector<float, 1> & s_vect = s;
TEST(fabs(s - 1) < FLT_EPSILON);
TEST(fabs(s_vect(0) - 1.0f) < FLT_EPSILON);
Matrix<float, 1, 1> m5 = s;
TEST(fabs(m5(0,0) - s) < FLT_EPSILON);
Matrix<float, 2, 2> m6;
m6.setRow(0, Vector2f(1, 2));
float m7_array[] = {1,2,0,0};
Matrix<float, 2, 2> m7(m7_array);
TEST(isEqual(m6, m7));
m6.setCol(0, Vector2f(3, 4));
float m8_array[] = {3,2,4,0};
Matrix<float, 2, 2> m8(m8_array);
TEST(isEqual(m6, m8));
m7.setNaN();
TEST(m7 != m8);
// min, max, constrain matrix values with scalar
float data_m9[9] = {2, 4, 6, 8, 10, 12, 14, 16, 18};
float lower_bound = 7;
float upper_bound = 11;
float data_m9_lower_bounded[9] = {7, 7, 7, 8, 10, 12, 14, 16, 18};
float data_m9_upper_bounded[9] = {2, 4, 6, 8, 10, 11, 11, 11, 11};
float data_m9_lower_constrained[9] = {7, 7, 7, 8, 10, 11, 11, 11, 11};
Matrix3f m9(data_m9);
Matrix3f m9_lower_bounded(data_m9_lower_bounded);
Matrix3f m9_upper_bounded(data_m9_upper_bounded);
Matrix3f m9_lower_upper_constrained(data_m9_lower_constrained);
TEST(isEqual(max(m9, lower_bound), m9_lower_bounded));
TEST(isEqual(max(lower_bound, m9), m9_lower_bounded));
TEST(isEqual(min(m9, upper_bound), m9_upper_bounded));
TEST(isEqual(min(upper_bound, m9), m9_upper_bounded));
TEST(isEqual(constrain(m9, lower_bound, upper_bound), m9_lower_upper_constrained));
TEST(isEqual(constrain(m9, 8.0f, 7.0f), m_nan));
// min, max, constrain matrix values with matrix of same size
float data_m10[9] = {2, 4, 6, 8, 10, 12, 14, 16, 18};
float data_m10_lower_bound[9] = {5, 7, 4, 8, 19, 10, 20, 16, 18};
float data_m10_lower_bounded_ref[9] = {5, 7, 6, 8, 19, 12, 20, 16, 18};
float data_m10_upper_bound[9] = {6, 4, 8, 18, 20, 11, 30, 16, 18};
float data_m10_upper_bounded_ref[9] = {2, 4, 6, 8, 10, 11, 14, 16, 18};
float data_m10_constrained_ref[9] = {5, NAN, 6, 8, 19, 11, 20, 16, 18};
Matrix3f m10(data_m10);
Matrix3f m10_lower_bound(data_m10_lower_bound);
Matrix3f m10_lower_bounded_ref(data_m10_lower_bounded_ref);
Matrix3f m10_upper_bound(data_m10_upper_bound);
Matrix3f m10_upper_bounded_ref(data_m10_upper_bounded_ref);
Matrix3f m10_constrained_ref(data_m10_constrained_ref);
TEST(isEqual(max(m10, m10_lower_bound), m10_lower_bounded_ref));
TEST(isEqual(max(m10_lower_bound, m10), m10_lower_bounded_ref));
TEST(isEqual(min(m10, m10_upper_bound), m10_upper_bounded_ref));
TEST(isEqual(min(m10_upper_bound, m9), m10_upper_bounded_ref));
TEST(isEqual(constrain(m10, m10_lower_bound, m10_upper_bound), m10_constrained_ref));
// min, max, constrain with NAN
TEST(isEqualF(matrix::typeFunction::min(5.f, NAN), 5.f));
TEST(isEqualF(matrix::typeFunction::min(NAN, 5.f), 5.f));
TEST(isEqualF(matrix::typeFunction::min(NAN, NAN), NAN));
TEST(isEqualF(matrix::typeFunction::max(5.f, NAN), 5.f));
TEST(isEqualF(matrix::typeFunction::max(NAN, 5.f), 5.f));
TEST(isEqualF(matrix::typeFunction::max(NAN, NAN), NAN));
TEST(isEqualF(matrix::typeFunction::constrain(NAN, 5.f, 6.f), NAN));
TEST(isEqualF(matrix::typeFunction::constrain(1.f, 5.f, 4.f), NAN));
TEST(isEqualF(matrix::typeFunction::constrain(6.f, NAN, 5.f), 5.f));
TEST(isEqualF(matrix::typeFunction::constrain(1.f, 5.f, NAN), 5.f));
Vector2f v1{NAN, 5.0f};
Vector2f v1_min = min(v1, 1.f);
Matrix3f m11 = min(m10_constrained_ref,NAN);
TEST(isEqualF(fmin(NAN, 1.f), float(v1_min(0))));
TEST(isEqual(m11, m10_constrained_ref));
// check write_string()
float comma[6] = {
1.f, 12345.123f,
12345.1228f, .1234567891011f,
12345678910.123456789f, 1234567891011.123456789101112f
};
Matrix<float, 3, 2> Comma(comma);
const size_t len = 15*2*3 + 2 + 1;
char buffer[len];
Comma.print(); // for debugging in case of failure
Comma.write_string(buffer, len);
printf("%s\n", buffer); // for debugging in case of failure
char output[] = "\t 1\t12345.123\n\t12345.123\t0.12345679\n\t1.2345679e+10\t1.234568e+12\n";
printf("%s\n", output); // for debugging in case of failure
for (size_t i = 0; i < len; i++) {
if(buffer[i] != output[i]) { // for debugging in case of failure
printf("%d: \"%c\" != \"%c\"", int(i), buffer[i], output[i]); // LCOV_EXCL_LINE only print on failure
}
TEST(buffer[i] == output[i]);
if (buffer[i] == '\0') {
break;
}
}
// check print()
// Redirect stdout
TEST(freopen("testoutput.txt", "w", stdout) != NULL);
// write
Comma.print();
fclose(stdout);
// read
FILE *fp = fopen("testoutput.txt", "r");
TEST(fp != nullptr);
TEST(!fseek(fp, 0, SEEK_SET));
for (size_t i = 0; i < len; i++) {
char c = static_cast<char>(fgetc(fp));
if (c == '\n') {
break;
}
printf("%d %d %d\n", static_cast<int>(i), output[i], c);
TEST(c == output[i]);
}
TEST(!fclose(fp));
return 0;
}
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+71
View File
@@ -0,0 +1,71 @@
#include "test_macros.hpp"
#include <matrix/math.hpp>
using namespace matrix;
int main()
{
float data[9] = {1, 0, 0, 0, 1, 0, 1, 0, 1};
Matrix3f A(data);
float data_check[9] = {1, 0, 0, 0, 1, 0, -1, 0, 1};
Matrix3f A_I(data_check);
Matrix3f I;
I.setIdentity();
Matrix3f R = A * A_I;
TEST(isEqual(R, I));
Matrix3f R2 = A;
R2 *= A_I;
TEST(isEqual(R2, I));
TEST(R2==I);
TEST(A!=A_I);
Matrix3f A2 = eye<float, 3>()*2;
Matrix3f B = A2.emult(A2);
Matrix3f B_check = eye<float, 3>()*4;
Matrix3f C_check = eye<float, 3>()*2;
TEST(isEqual(B, B_check));
Matrix3f C = B_check.edivide(C_check);
float off_diagonal_nan[9] = {2, NAN, NAN, NAN, 2, NAN, NAN, NAN, 2};
// off diagonal are NANs because division by 0
TEST(C == Matrix3f(off_diagonal_nan));
// Test non-square matrix
float data_43[12] = {1,3,2,
2,2,1,
5,2,1,
2,3,4
};
float data_32[6] = {2,3,
1,7,
5,4
};
Matrix<float, 4, 3> m43(data_43);
Matrix<float, 3, 2> m32(data_32);
Matrix<float, 4, 2> m42 = m43 * m32;
float data_42[8] = {15,32,
11,24,
17,33,
27,43
};
Matrix<float, 4, 2> m42_check(data_42);
TEST(isEqual(m42, m42_check))
float data_42_plus2[8] = {17,34,
13,26,
19,35,
29,45
};
Matrix<float, 4, 2> m42_plus2_check(data_42_plus2);
Matrix<float, 4, 2> m42_plus2 = m42 - (-2);
TEST(isEqual(m42_plus2, m42_plus2_check));
return 0;
}
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+18
View File
@@ -0,0 +1,18 @@
#include "test_macros.hpp"
#include <matrix/math.hpp>
using namespace matrix;
int main()
{
float data[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
Matrix3f A(data);
A = A * 2;
float data_check[9] = {2, 4, 6, 8, 10, 12, 14, 16, 18};
Matrix3f A_check(data_check);
TEST(isEqual(A, A_check));
return 0;
}
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+153
View File
@@ -0,0 +1,153 @@
#include "test_macros.hpp"
#include <matrix/PseudoInverse.hpp>
using namespace matrix;
static const size_t n_large = 20;
int main()
{
// 3x4 Matrix test
float data0[12] = {
0.f, 1.f, 2.f, 3.f,
4.f, 5.f, 6.f, 7.f,
8.f, 9.f, 10.f, 11.f
};
float data0_check[12] = {
-0.3375f, -0.1f, 0.1375f,
-0.13333333f, -0.03333333f, 0.06666667f,
0.07083333f, 0.03333333f, -0.00416667f,
0.275f, 0.1f, -0.075f
};
Matrix<float, 3, 4> A0(data0);
Matrix<float, 4, 3> A0_I;
bool ret = geninv(A0, A0_I);
TEST(ret);
Matrix<float, 4, 3> A0_I_check(data0_check);
TEST((A0_I - A0_I_check).abs().max() < 1e-5);
// 4x3 Matrix test
float data1[12] = {
0.f, 4.f, 8.f,
1.f, 5.f, 9.f,
2.f, 6.f, 10.f,
3.f, 7.f, 11.f
};
float data1_check[12] = {
-0.3375f, -0.13333333f, 0.07083333f, 0.275f,
-0.1f, -0.03333333f, 0.03333333f, 0.1f,
0.1375f, 0.06666667f, -0.00416667f, -0.075f
};
Matrix<float, 4, 3> A1(data1);
Matrix<float, 3, 4> A1_I;
ret = geninv(A1, A1_I);
TEST(ret);
Matrix<float, 3, 4> A1_I_check(data1_check);
TEST((A1_I - A1_I_check).abs().max() < 1e-5);
// Stess test
Matrix<float, n_large, n_large - 1> A_large;
A_large.setIdentity();
Matrix<float, n_large - 1, n_large> A_large_I;
for (size_t i = 0; i < n_large; i++) {
ret = geninv(A_large, A_large_I);
TEST(ret);
TEST(isEqual(A_large, A_large_I.T()));
}
// Square matrix test
float data2[9] = {0, 2, 3,
4, 5, 6,
7, 8, 10
};
float data2_check[9] = {
-0.4f, -0.8f, 0.6f,
-0.4f, 4.2f, -2.4f,
0.6f, -2.8f, 1.6f
};
SquareMatrix<float, 3> A2(data2);
SquareMatrix<float, 3> A2_I;
ret = geninv(A2, A2_I);
TEST(ret);
SquareMatrix<float, 3> A2_I_check(data2_check);
TEST((A2_I - A2_I_check).abs().max() < 1e-3);
// Null matrix test
Matrix<float, 6, 16> A3;
Matrix<float, 16, 6> A3_I;
ret = geninv(A3, A3_I);
TEST(ret);
Matrix<float, 16, 6> A3_I_check;
TEST((A3_I - A3_I_check).abs().max() < 1e-5);
// Mock-up effectiveness matrix
const float B_quad_w[6][16] = {
{-0.5717536f, 0.43756646f, 0.5717536f, -0.43756646f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f},
{ 0.35355328f, -0.35355328f, 0.35355328f, -0.35355328f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f},
{ 0.28323701f, 0.28323701f, -0.28323701f, -0.28323701f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f},
{ 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f},
{ 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f},
{-0.25f, -0.25f, -0.25f, -0.25f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}
};
Matrix<float, 6, 16> B = Matrix<float, 6, 16>(B_quad_w);
const float A_quad_w[16][6] = {
{ -0.495383f, 0.707107f, 0.765306f, 0.0f, 0.0f, -1.000000f },
{ 0.495383f, -0.707107f, 1.000000f, 0.0f, 0.0f, -1.000000f },
{ 0.495383f, 0.707107f, -0.765306f, 0.0f, 0.0f, -1.000000f },
{ -0.495383f, -0.707107f, -1.000000f, 0.0f, 0.0f, -1.000000f },
{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}
};
Matrix<float, 16, 6> A_check = Matrix<float, 16, 6>(A_quad_w);
Matrix<float, 16, 6> A;
ret = geninv(B, A);
TEST(ret);
TEST((A - A_check).abs().max() < 1e-5);
// Real-world test case
const float real_alloc[5][6] = {
{ 0.794079, 0.794079, 0.794079, 0.794079, 0.0000, 0.0000},
{ 0.607814, 0.607814, 0.607814, 0.607814, 1.0000, 1.0000},
{-0.672516, 0.915642, -0.915642, 0.672516, 0.0000, 0.0000},
{ 0.159704, 0.159704, 0.159704, 0.159704, -0.2500, -0.2500},
{ 0.607814, -0.607814, 0.607814, -0.607814, 1.0000, 1.0000}
};
Matrix<float, 5, 6> real ( real_alloc);
Matrix<float, 6, 5> real_pinv;
ret = geninv(real, real_pinv);
TEST(ret);
// from SVD-based inverse
const float real_pinv_expected_alloc[6][5] = {
{ 2.096205, -2.722267, 2.056547, 1.503279, 3.098087},
{ 1.612621, -1.992694, 2.056547, 1.131090, 2.275467},
{-1.062688, 2.043479, -2.056547, -0.927950, -2.275467},
{-1.546273, 2.773052, -2.056547, -1.300139, -3.098087},
{-0.293930, 0.443445, 0.000000, -0.226222, 0.000000},
{-0.293930, 0.443445, 0.000000, -0.226222, 0.000000}
};
Matrix<float, 6, 5> real_pinv_expected(real_pinv_expected_alloc);
TEST((real_pinv - real_pinv_expected).abs().max() < 1e-4);
return 0;
}
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+39
View File
@@ -0,0 +1,39 @@
#include "test_macros.hpp"
#include <matrix/math.hpp>
using namespace matrix;
int main()
{
Matrix3f A;
A.setIdentity();
for (size_t i = 0; i < 3; i++) {
for (size_t j = 0; j < 3; j++) {
if (i == j) {
TEST(fabs(A(i, j) - 1) < FLT_EPSILON);
} else {
TEST(fabs(A(i, j) - 0) < FLT_EPSILON);
}
}
}
Matrix3f B;
B.identity();
for (size_t i = 0; i < 3; i++) {
for (size_t j = 0; j < 3; j++) {
if (i == j) {
TEST(fabs(B(i, j) - 1) < FLT_EPSILON);
} else {
TEST(fabs(B(i, j) - 0) < FLT_EPSILON);
}
}
}
return 0;
}
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+235
View File
@@ -0,0 +1,235 @@
#include "test_macros.hpp"
#include <matrix/math.hpp>
using namespace matrix;
template class matrix::Slice<float, 2, 3, 4, 5>; // so that we get full coverage results
int main()
{
float data[9] = {0, 2, 3,
4, 5, 6,
7, 8, 10
};
SquareMatrix<float, 3> A(data);
// Test row slicing
Matrix<float, 2, 3> B_rowslice(A.slice<2, 3>(1, 0));
float data_check_rowslice[6] = {
4, 5, 6,
7, 8, 10
};
Matrix<float, 2, 3> B_check_rowslice(data_check_rowslice);
TEST(isEqual(B_rowslice, B_check_rowslice));
// Test column slicing
Matrix<float, 3, 2> B_colslice(A.slice<3, 2>(0, 1));
float data_check_colslice[6] = {
2, 3,
5, 6,
8, 10
};
Matrix<float, 3, 2> B_check_colslice(data_check_colslice);
TEST(isEqual(B_colslice, B_check_colslice));
// Test slicing both
Matrix<float, 2, 2> B_bothslice(A.slice<2, 2>(1, 1));
float data_check_bothslice[4] = {
5, 6,
8, 10
};
Matrix<float, 2, 2> B_check_bothslice(data_check_bothslice);
TEST(isEqual(B_bothslice, B_check_bothslice));
//Test block writing
float data_2[4] = {
11, 12,
13, 14
};
Matrix<float, 2, 2> C(data_2);
A.slice<2, 2>(1, 1) = C;
float data_2_check[9] = {
0, 2, 3,
4, 11, 12,
7, 13, 14
};
Matrix<float, 3, 3> D(data_2_check);
TEST(isEqual(A, D));
//Test writing to slices
Matrix<float, 3, 1> E;
E(0,0) = -1;
E(1,0) = 1;
E(2,0) = 3;
Matrix<float, 2, 1> F;
F(0,0) = 9;
F(1,0) = 11;
E.slice<2,1>(0,0) = F;
float data_3_check[3] = {9, 11, 3};
Matrix<float, 3, 1> G (data_3_check);
TEST(isEqual(E, G));
TEST(isEqual(E, Matrix<float,3,1>(E.slice<3,1>(0,0))));
Matrix<float, 2, 1> H = E.slice<2,1>(0,0);
TEST(isEqual(H,F));
float data_4_check[5] = {3, 11, 9, 0, 0};
{ // assigning row slices to each other
const Matrix<float, 3, 1> J (data_3_check);
Matrix<float, 5, 1> K;
K.row(2) = J.row(0);
K.row(1) = J.row(1);
K.row(0) = J.row(2);
Matrix<float, 5, 1> K_check(data_4_check);
TEST(isEqual(K, K_check));
}
{ // assigning col slices to each other
const Matrix<float, 1, 3> J (data_3_check);
Matrix<float, 1, 5> K;
K.col(2) = J.col(0);
K.col(1) = J.col(1);
K.col(0) = J.col(2);
Matrix<float, 1, 5> K_check(data_4_check);
TEST(isEqual(K, K_check));
}
// check that slice of a slice works for reading
const Matrix<float, 3, 3> cm33(data);
Matrix<float, 2, 1> topRight = cm33.slice<2,3>(0,0).slice<2,1>(0,2);
float top_right_check[2] = {3,6};
TEST(isEqual(topRight, Matrix<float, 2, 1>(top_right_check)));
// check that slice of a slice works for writing
Matrix<float, 3, 3> m33(data);
m33.slice<2,3>(0,0).slice<2,1>(0,2) = Matrix<float, 2, 1>();
const float data_check[9] = {0, 2, 0,
4, 5, 0,
7, 8, 10
};
TEST(isEqual(m33, Matrix<float, 3, 3>(data_check)));
// longerThan
Vector3f v5;
v5(0) = 3;
v5(1) = 4;
v5(2) = 9;
TEST(v5.xy().longerThan(4.99f));
TEST(!v5.xy().longerThan(5.f));
TEST(isEqualF(5.f, v5.xy().norm()));
// min/max
TEST(m33.row(1).max() == 5);
TEST(m33.col(0).min() == 0);
TEST((m33.slice<2,2>(1,1).max()) == 10);
// assign scalar value to slice
Matrix<float, 3, 1> L;
L(0,0) = -1;
L(1,0) = 1;
L(2,0) = 3;
L.slice<2,1>(0,0) = 0.0f;
float data_5_check[3] = {0, 0, 3};
Matrix<float, 3, 1> M (data_5_check);
TEST(isEqual(L, M));
// return diagonal elements
float data_6[9] = {0, 2, 3,
4, 5, 6,
7, 8, 10
};
SquareMatrix<float, 3> N(data_6);
Vector3f v6 = N.slice<3,3>(0,0).diag();
Vector3f v6_check = {0, 5, 10};
TEST(isEqual(v6,v6_check));
Vector2f v7 = N.slice<2,3>(1,0).diag();
Vector2f v7_check = {4, 8};
TEST(isEqual(v7,v7_check));
Vector2f v8 = N.slice<3,2>(0,1).diag();
Vector2f v8_check = {2, 6};
TEST(isEqual(v8,v8_check));
Vector2f v9(N.slice<1,2>(1,1));
Vector2f v9_check = {5, 6};
TEST(isEqual(v9,v9_check));
Vector3f v10(N.slice<1,3>(1,0));
Vector3f v10_check = {4, 5, 6};
TEST(isEqual(v10,v10_check));
// Different assignment operators
SquareMatrix3f O(data);
float operand_data [4] = {2, 1, -3, -1};
const SquareMatrix<float, 2> operand(operand_data);
O.slice<2,2>(1,0) += operand;
float O_check_data_1 [9] = {0, 2, 3, 6, 6, 6, 4, 7, 10};
TEST(isEqual(O, SquareMatrix3f(O_check_data_1)));
O = SquareMatrix3f(data);
O.slice<2,1>(1,1) += operand.slice<2,1>(0,0);
float O_check_data_2 [9] = {0, 2, 3, 4, 7, 6, 7, 5, 10};
TEST(isEqual(O, SquareMatrix3f(O_check_data_2)));
O = SquareMatrix3f(data);
O.slice<3,3>(0,0) += -1;
float O_check_data_3 [9] = {-1, 1, 2, 3, 4, 5, 6, 7, 9};
TEST(isEqual(O, SquareMatrix3f(O_check_data_3)));
O = SquareMatrix3f(data);
O.col(1) += Vector3f{1, -2, 3};
float O_check_data_4 [9] = {0, 3, 3, 4, 3, 6, 7, 11, 10};
TEST(isEqual(O, SquareMatrix3f(O_check_data_4)));
O = SquareMatrix3f(data);
O.slice<2,2>(1,0) -= operand;
float O_check_data_5 [9] = {0, 2, 3, 2, 4, 6, 10, 9, 10};
TEST(isEqual(O, SquareMatrix3f(O_check_data_5)));
O = SquareMatrix3f(data);
O.slice<2,1>(1,1) -= operand.slice<2,1>(0,0);
float O_check_data_6 [9] = {0, 2, 3, 4, 3, 6, 7, 11, 10};
TEST(isEqual(O, SquareMatrix3f(O_check_data_6)));
O = SquareMatrix3f(data);
O.slice<3,3>(0,0) -= -1;
float O_check_data_7 [9] = {1, 3, 4, 5, 6, 7, 8, 9, 11};
TEST(isEqual(O, SquareMatrix3f(O_check_data_7)));
O = SquareMatrix3f(data);
O.col(1) -= Vector3f{1, -2, 3};
float O_check_data_8 [9] = {0, 1, 3, 4, 7, 6, 7, 5, 10};
TEST(isEqual(O, SquareMatrix3f(O_check_data_8)));
O = SquareMatrix3f(data);
O.slice<2,1>(1,1) *= 5.f;
float O_check_data_9 [9] = {0, 2, 3, 4, 25, 6, 7, 40, 10};
TEST(isEqual(O, SquareMatrix3f(O_check_data_9)));
O = SquareMatrix3f(data);
O.slice<2,1>(1,1) /= 2.f;
float O_check_data_10 [9] = {0, 2, 3, 4, 2.5, 6, 7, 4, 10};
TEST(isEqual(O, SquareMatrix3f(O_check_data_10)));
// Different operations
O = SquareMatrix3f(data);
SquareMatrix<float, 2> res_11(O.slice<2,2>(1,1) * 2.f);
float O_check_data_11 [4] = {10, 12, 16, 20};
TEST(isEqual(res_11, SquareMatrix<float, 2>(O_check_data_11)));
O = SquareMatrix3f(data);
SquareMatrix<float, 2> res_12(O.slice<2,2>(1,1) / 2.f);
float O_check_data_12 [4] = {2.5, 3, 4, 5};
TEST(isEqual(res_12, SquareMatrix<float, 2>(O_check_data_12)));
return 0;
}
/* vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : */
+119
View File
@@ -0,0 +1,119 @@
#include <matrix/math.hpp>
#include <gtest/gtest.h>
using namespace matrix;
TEST(sparseVectorTest, defaultConstruction) {
SparseVectorf<24, 4, 6> a;
EXPECT_EQ(a.non_zeros(), 2);
EXPECT_EQ(a.index(0), 4);
EXPECT_EQ(a.index(1), 6);
a.at<4>() = 1.f;
a.at<6>() = 2.f;
}
TEST(sparseVectorTest, initializationWithData) {
const float data[3] = {1.f, 2.f, 3.f};
SparseVectorf<24, 4, 6, 22> a(data);
EXPECT_EQ(a.non_zeros(), 3);
EXPECT_EQ(a.index(0), 4);
EXPECT_EQ(a.index(1), 6);
EXPECT_EQ(a.index(2), 22);
EXPECT_FLOAT_EQ(a.at<4>(), data[0]);
EXPECT_FLOAT_EQ(a.at<6>(), data[1]);
EXPECT_FLOAT_EQ(a.at<22>(), data[2]);
}
TEST(sparseVectorTest, initialisationFromVector) {
const Vector3f vec(1.f, 2.f, 3.f);
const SparseVectorf<3, 0, 2> a(vec);
EXPECT_FLOAT_EQ(a.at<0>(), vec(0));
EXPECT_FLOAT_EQ(a.at<2>(), vec(2));
}
TEST(sparseVectorTest, accessDataWithCompressedIndices) {
const Vector3f vec(1.f, 2.f, 3.f);
SparseVectorf<3, 0, 2> a(vec);
for (size_t i = 0; i < a.non_zeros(); i++) {
a.atCompressedIndex(i) = static_cast<float>(i);
}
EXPECT_FLOAT_EQ(a.at<0>(), a.atCompressedIndex(0));
EXPECT_FLOAT_EQ(a.at<2>(), a.atCompressedIndex(1));
}
TEST(sparseVectorTest, setZero) {
const float data[3] = {1.f, 2.f, 3.f};
SparseVectorf<24, 4, 6, 22> a(data);
a.setZero();
EXPECT_FLOAT_EQ(a.at<4>(), 0.f);
EXPECT_FLOAT_EQ(a.at<6>(), 0.f);
EXPECT_FLOAT_EQ(a.at<22>(), 0.f);
}
TEST(sparseVectorTest, additionWithDenseVector) {
Vector<float, 4> dense_vec;
dense_vec.setAll(1.f);
const float data[3] = {1.f, 2.f, 3.f};
const SparseVectorf<4, 1, 2, 3> sparse_vec(data);
const Vector<float, 4> res = sparse_vec + dense_vec;
EXPECT_FLOAT_EQ(res(0), 1.f);
EXPECT_FLOAT_EQ(res(1), 2.f);
EXPECT_FLOAT_EQ(res(2), 3.f);
EXPECT_FLOAT_EQ(res(3), 4.f);
}
TEST(sparseVectorTest, addScalar) {
const float data[3] = {1.f, 2.f, 3.f};
SparseVectorf<4, 1, 2, 3> sparse_vec(data);
sparse_vec += 2.f;
EXPECT_FLOAT_EQ(sparse_vec.at<1>(), 3.f);
EXPECT_FLOAT_EQ(sparse_vec.at<2>(), 4.f);
EXPECT_FLOAT_EQ(sparse_vec.at<3>(), 5.f);
}
TEST(sparseVectorTest, dotProductWithDenseVector) {
Vector<float, 4> dense_vec;
dense_vec.setAll(3.f);
const float data[3] = {1.f, 2.f, 3.f};
const SparseVectorf<4, 1, 2, 3> sparse_vec(data);
float res = sparse_vec.dot(dense_vec);
EXPECT_FLOAT_EQ(res, 18.f);
}
TEST(sparseVectorTest, multiplicationWithDenseMatrix) {
Matrix<float, 2, 3> dense_matrix;
dense_matrix.setAll(2.f);
dense_matrix(1, 1) = 3.f;
const Vector3f dense_vec(0.f, 1.f, 5.f);
const SparseVectorf<3, 1, 2> sparse_vec(dense_vec);
const Vector<float, 2> res_sparse = dense_matrix * sparse_vec;
const Vector<float, 2> res_dense = dense_matrix * dense_vec;
EXPECT_TRUE(isEqual(res_dense, res_sparse));
}
TEST(sparseVectorTest, quadraticForm) {
float matrix_data[9] = {1, 2, 3,
2, 4, 5,
3, 5, 6
};
const SquareMatrix<float, 3> dense_matrix(matrix_data);
const Vector3f dense_vec(0.f, 1.f, 5.f);
const SparseVectorf<3, 1, 2> sparse_vec(dense_vec);
EXPECT_FLOAT_EQ(quadraticForm(dense_matrix, sparse_vec), 204.f);
}
TEST(sparseVectorTest, norms) {
const float data[2] = {3.f, 4.f};
const SparseVectorf<4, 1, 3> sparse_vec(data);
EXPECT_FLOAT_EQ(sparse_vec.norm_squared(), 25.f);
EXPECT_FLOAT_EQ(sparse_vec.norm(), 5.f);
EXPECT_TRUE(sparse_vec.longerThan(4.5f));
EXPECT_FALSE(sparse_vec.longerThan(5.5f));
}
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
std::cout << "Run SparseVector tests" << std::endl;
return RUN_ALL_TESTS();
}

Some files were not shown because too many files have changed in this diff Show More