Compare commits

..

4 Commits

Author SHA1 Message Date
Quinn Henthorne
0b55d29376 Working on getting the QR decomposition to compile 2024-12-18 16:30:53 -05:00
Quinn Henthorne
0f76e8511e Added an untested eigen function 2024-12-16 10:36:02 -05:00
Quinn Henthorne
002f3ac314 Added a function for SVD in python 2024-12-16 10:23:21 -05:00
Quinn Henthorne
6c47a491ea Added an example QR decomposition function in python 2024-12-16 09:18:43 -05:00
27 changed files with 675 additions and 1321 deletions

Binary file not shown.

View File

@@ -1,102 +0,0 @@
name: Merge-Checker
on:
pull_request:
branches: ["**"]
jobs:
build_and_test:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v3
with:
persist-credentials: true
fetch-depth: 0
- name: Install dependencies (CMake + Ninja + build tools)
run: |
sudo apt-get update
sudo apt-get install -y cmake ninja-build build-essential time git
- name: Configure project with CMake
run: cmake -G Ninja -S . -B build/
- name: Build with Ninja
run: ninja -C build/
- name: Run all unit tests except matrix-timing-tests
run: |
for test_exec in build/unit-tests/matrix-tests build/unit-tests/quaternion-tests build/unit-tests/vector-3d-tests; do
if [ -x "$test_exec" ]; then
echo "Running $test_exec"
"$test_exec"
else
echo "Warning: $test_exec not found or not executable"
fi
done
- name: Run matrix-timing-tests
run: |
mkdir -p unit-tests/timing-results
if [ -x build/unit-tests/matrix-timing-tests ]; then
echo "Running matrix-timing-tests with timing"
/usr/bin/time -v build/unit-tests/matrix-timing-tests -d yes &> unit-tests/timing-results/matrix-timing-tests.txt
cat unit-tests/timing-results/matrix-timing-tests.txt
else
echo "matrix-timing-tests executable not found or not executable"
exit 1
fi
- name: Compare timing results
id: check_diff
run: |
git show origin/${{ github.event.pull_request.head.ref }}:unit-tests/timing-results/matrix-timing-tests.txt > old.txt || echo "" > old.txt
cp unit-tests/timing-results/matrix-timing-tests.txt new.txt
echo "Comparing timing results for changes ≥ 0.1s (ignoring 'Timing Tests' lines)..."
changed=0
awk -v changed_ref=/tmp/timings_changed.flag '
BEGIN {
change_threshold = 0.1
}
FILENAME == "old.txt" && /^[0-9]+\.[0-9]+ s: / {
label = substr($0, index($0, ":") + 2)
if (label != "Timing Tests") {
label_times[label] = $1
}
}
FILENAME == "new.txt" && /^[0-9]+\.[0-9]+ s: / {
new_time = $1
label = substr($0, index($0, ":") + 2)
if (label == "Timing Tests") next
old_time = label_times[label]
delta = new_time - old_time
if (delta < 0) delta = -delta
if (old_time != "" && delta >= change_threshold) {
printf "⚠️ %.3f s → %.3f s: %s (Δ=%.3f s)\n", old_time, new_time, label, delta
system("touch " changed_ref)
} else if (old_time == "") {
printf "🆕 New timing entry: %.3f s: %s\n", new_time, label
system("touch " changed_ref)
}
}
END {
if (!system("test -f " changed_ref)) {
exit 0
} else {
print "✅ Timings havent changed significantly (Δ < 0.1s)."
exit 0
}
}
' old.txt new.txt
if [ -f /tmp/timings_changed.flag ]; then
echo "timings_changed=true" >> $GITHUB_OUTPUT
else
echo "timings_changed=false" >> $GITHUB_OUTPUT
fi

2
.gitignore vendored
View File

@@ -1,2 +1,2 @@
build/ build/
.cache/ venv/

17
.vscode/launch.json vendored
View File

@@ -27,25 +27,16 @@
"internalConsoleOptions": "openOnSessionStart" "internalConsoleOptions": "openOnSessionStart"
}, },
{ {
"name": "Debug Quaternion Unit Tests", "name": "Run Matrix Unit Tests",
"type": "cppdbg", "type": "cpp",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/build/unit-tests/quaternion-tests", "program": "${workspaceFolder}/build/unit-tests/matrix-tests",
"args": [], "args": [],
"stopAtEntry": false, "stopAtEntry": false,
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"environment": [], "environment": [],
"externalConsole": false, "externalConsole": false,
"MIMode": "gdb", "preLaunchTask": "build_tests", // Compile unit tests before running
"miDebuggerPath": "/usr/bin/gdb", // Adjust to your debugger path
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "build_tests", // Task to compile unit tests
"internalConsoleOptions": "openOnSessionStart" "internalConsoleOptions": "openOnSessionStart"
} }
] ]

13
.vscode/settings.json vendored
View File

@@ -1,5 +1,8 @@
{ {
"C_Cpp.intelliSenseEngine": "default", "C_Cpp.intelliSenseEngine": "default",
"clangd.arguments": [
"--include-directory=build/unit-tests"
],
"C_Cpp.default.intelliSenseMode": "linux-gcc-x64", "C_Cpp.default.intelliSenseMode": "linux-gcc-x64",
"files.associations": { "files.associations": {
"*.h": "cpp", "*.h": "cpp",
@@ -68,12 +71,8 @@
"typeinfo": "cpp", "typeinfo": "cpp",
"variant": "cpp", "variant": "cpp",
"shared_mutex": "cpp", "shared_mutex": "cpp",
"charconv": "cpp", "complex": "cpp"
"format": "cpp",
"csignal": "cpp",
"span": "cpp"
}, },
"clangd.enable": true, "clangd.enable": false,
"C_Cpp.dimInactiveRegions": false, "C_Cpp.dimInactiveRegions": false
"editor.defaultFormatter": "xaver.clang-format"
} }

6
.vscode/tasks.json vendored
View File

@@ -4,14 +4,12 @@
{ {
"label": "build_tests", "label": "build_tests",
"type": "shell", "type": "shell",
"command": "cd build && ninja", "command": "cd build && ninja matrix-tests",
"group": { "group": {
"kind": "build", "kind": "build",
"isDefault": true "isDefault": true
}, },
"problemMatcher": [ "problemMatcher": ["$gcc"],
"$gcc"
],
"detail": "Generated task to build unit test executable" "detail": "Generated task to build unit test executable"
} }
] ]

View File

@@ -1,19 +1,40 @@
cmake_minimum_required (VERSION 3.11) cmake_minimum_required(VERSION 3.6)
project(Vector3D) project(Vector3D)
add_subdirectory(src)
add_subdirectory(unit-tests) add_subdirectory(unit-tests)
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)
add_compile_options(-fdiagnostics-color=always -Wall -Wextra -Wpedantic) add_compile_options(-fdiagnostics-color=always)
include(FetchContent) # Vector3d
add_library(Vector3D
FetchContent_Declare( STATIC
Catch2 Vector3D.hpp
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.8.0 # or a later release
) )
FetchContent_MakeAvailable(Catch2) set_target_properties(Vector3D
PROPERTIES
LINKER_LANGUAGE CXX
)
target_include_directories(Vector3D PUBLIC
include
)
# Matrix
add_library(Matrix
STATIC
Matrix.hpp
Matrix.cpp
)
set_target_properties(Matrix
PROPERTIES
LINKER_LANGUAGE CXX
)
target_include_directories(Matrix
PUBLIC
.
)

View File

@@ -6,24 +6,30 @@
#include <cmath> #include <cmath>
#include <cstdlib> #include <cstdlib>
#include <type_traits> #include <type_traits>
#include <cstring>
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows, columns>::Matrix(float value) Matrix<rows, columns>::Matrix(float value) {
{
this->Fill(value); this->Fill(value);
} }
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows, columns>::Matrix(const std::array<float, rows * columns> &array) Matrix<rows, columns>::Matrix(const std::array<float, rows * columns> &array) {
{
this->setMatrixToArray(array); this->setMatrixToArray(array);
} }
template <uint8_t rows, uint8_t columns>
Matrix<rows, columns>::Matrix(const Matrix<rows, columns> &other) {
for (uint8_t row_idx{0}; row_idx < rows; row_idx++) {
for (uint8_t column_idx{0}; column_idx < columns; column_idx++) {
this->matrix[row_idx * columns + column_idx] =
other.Get(row_idx, column_idx);
}
}
}
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
template <typename... Args> template <typename... Args>
Matrix<rows, columns>::Matrix(Args... args) Matrix<rows, columns>::Matrix(Args... args) {
{
constexpr uint16_t arraySize{static_cast<uint16_t>(rows) * constexpr uint16_t arraySize{static_cast<uint16_t>(rows) *
static_cast<uint16_t>(columns)}; static_cast<uint16_t>(columns)};
@@ -34,46 +40,17 @@ Matrix<rows, columns>::Matrix(Args... args)
memcpy(this->matrix.begin(), initList.begin(), minSize * sizeof(float)); memcpy(this->matrix.begin(), initList.begin(), minSize * sizeof(float));
} }
template <uint8_t rows, uint8_t columns>
void Matrix<rows, columns>::Identity()
{
this->Fill(0);
for (uint8_t idx{0}; idx < rows; idx++)
{
this->matrix[idx * columns + idx] = 1;
}
}
template <uint8_t rows, uint8_t columns>
Matrix<rows, columns>::Matrix(const Matrix<rows, columns> &other)
{
for (uint8_t row_idx{0}; row_idx < rows; row_idx++)
{
for (uint8_t column_idx{0}; column_idx < columns; column_idx++)
{
this->matrix[row_idx * columns + column_idx] =
other.Get(row_idx, column_idx);
}
}
}
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
void Matrix<rows, columns>::setMatrixToArray( void Matrix<rows, columns>::setMatrixToArray(
const std::array<float, rows * columns> &array) const std::array<float, rows * columns> &array) {
{ for (uint8_t row_idx{0}; row_idx < rows; row_idx++) {
for (uint8_t row_idx{0}; row_idx < rows; row_idx++) for (uint8_t column_idx{0}; column_idx < columns; column_idx++) {
{
for (uint8_t column_idx{0}; column_idx < columns; column_idx++)
{
uint16_t array_idx = uint16_t array_idx =
static_cast<uint16_t>(row_idx) * static_cast<uint16_t>(columns) + static_cast<uint16_t>(row_idx) * static_cast<uint16_t>(columns) +
static_cast<uint16_t>(column_idx); static_cast<uint16_t>(column_idx);
if (array_idx < array.size()) if (array_idx < array.size()) {
{
this->matrix[row_idx * columns + column_idx] = array[array_idx]; this->matrix[row_idx * columns + column_idx] = array[array_idx];
} } else {
else
{
this->matrix[row_idx * columns + column_idx] = 0; this->matrix[row_idx * columns + column_idx] = 0;
} }
} }
@@ -83,12 +60,9 @@ void Matrix<rows, columns>::setMatrixToArray(
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows, columns> & Matrix<rows, columns> &
Matrix<rows, columns>::Add(const Matrix<rows, columns> &other, Matrix<rows, columns>::Add(const Matrix<rows, columns> &other,
Matrix<rows, columns> &result) const Matrix<rows, columns> &result) const {
{ for (uint8_t row_idx{0}; row_idx < rows; row_idx++) {
for (uint8_t row_idx{0}; row_idx < rows; row_idx++) for (uint8_t column_idx{0}; column_idx < columns; column_idx++) {
{
for (uint8_t column_idx{0}; column_idx < columns; column_idx++)
{
result[row_idx][column_idx] = result[row_idx][column_idx] =
this->Get(row_idx, column_idx) + other.Get(row_idx, column_idx); this->Get(row_idx, column_idx) + other.Get(row_idx, column_idx);
} }
@@ -99,12 +73,9 @@ Matrix<rows, columns>::Add(const Matrix<rows, columns> &other,
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows, columns> & Matrix<rows, columns> &
Matrix<rows, columns>::Sub(const Matrix<rows, columns> &other, Matrix<rows, columns>::Sub(const Matrix<rows, columns> &other,
Matrix<rows, columns> &result) const Matrix<rows, columns> &result) const {
{ for (uint8_t row_idx{0}; row_idx < rows; row_idx++) {
for (uint8_t row_idx{0}; row_idx < rows; row_idx++) for (uint8_t column_idx{0}; column_idx < columns; column_idx++) {
{
for (uint8_t column_idx{0}; column_idx < columns; column_idx++)
{
result[row_idx][column_idx] = result[row_idx][column_idx] =
this->Get(row_idx, column_idx) - other.Get(row_idx, column_idx); this->Get(row_idx, column_idx) - other.Get(row_idx, column_idx);
} }
@@ -117,24 +88,24 @@ template <uint8_t rows, uint8_t columns>
template <uint8_t other_columns> template <uint8_t other_columns>
Matrix<rows, other_columns> & Matrix<rows, other_columns> &
Matrix<rows, columns>::Mult(const Matrix<columns, other_columns> &other, Matrix<rows, columns>::Mult(const Matrix<columns, other_columns> &other,
Matrix<rows, other_columns> &result) const Matrix<rows, other_columns> &result) const {
{
// allocate some buffers for all of our dot products // allocate some buffers for all of our dot products
Matrix<1, columns> this_row; Matrix<1, columns> this_row;
Matrix<columns, 1> other_column; Matrix<rows, 1> other_column;
Matrix<1, rows> other_column_t;
for (uint8_t row_idx{0}; row_idx < rows; row_idx++) for (uint8_t row_idx{0}; row_idx < rows; row_idx++) {
{
// get our row // get our row
this->GetRow(row_idx, this_row); this->GetRow(row_idx, this_row);
for (uint8_t column_idx{0}; column_idx < columns; column_idx++) for (uint8_t column_idx{0}; column_idx < columns; column_idx++) {
{
// get the other matrix'ss column // get the other matrix'ss column
other.GetColumn(column_idx, other_column); other.GetColumn(column_idx, other_column);
// transpose the other matrix's column
other_column.Transpose(other_column_t);
// the result's index is equal to the dot product of these two vectors // the result's index is equal to the dot product of these two vectors
result[row_idx][column_idx] = result[row_idx][column_idx] =
Matrix<rows, columns>::DotProduct(this_row, other_column.Transpose()); Matrix<rows, columns>::dotProduct(this_row, other_column_t);
} }
} }
@@ -143,12 +114,9 @@ Matrix<rows, columns>::Mult(const Matrix<columns, other_columns> &other,
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows, columns> & Matrix<rows, columns> &
Matrix<rows, columns>::Mult(float scalar, Matrix<rows, columns> &result) const Matrix<rows, columns>::Mult(float scalar, Matrix<rows, columns> &result) const {
{ for (uint8_t row_idx{0}; row_idx < rows; row_idx++) {
for (uint8_t row_idx{0}; row_idx < rows; row_idx++) for (uint8_t column_idx{0}; column_idx < columns; column_idx++) {
{
for (uint8_t column_idx{0}; column_idx < columns; column_idx++)
{
result[row_idx][column_idx] = this->Get(row_idx, column_idx) * scalar; result[row_idx][column_idx] = this->Get(row_idx, column_idx) * scalar;
} }
} }
@@ -157,20 +125,17 @@ Matrix<rows, columns>::Mult(float scalar, Matrix<rows, columns> &result) const
} }
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows, columns> Matrix<rows, columns> &
Matrix<rows, columns>::Invert() const Matrix<rows, columns>::Invert(Matrix<rows, columns> &result) const {
{
// since all matrix sizes have to be statically specified at compile time we // since all matrix sizes have to be statically specified at compile time we
// can do this // can do this
static_assert(rows == columns, static_assert(rows == columns,
"Your matrix isn't square and can't be inverted"); "Your matrix isn't square and can't be inverted");
Matrix<rows, columns> result{};
// unfortunately we can't calculate this at compile time so we'll just reurn // unfortunately we can't calculate this at compile time so we'll just reurn
// zeros // zeros
float determinant{this->Det()}; float determinant{this->Det()};
if (determinant == 0) if (determinant == 0) {
{
// you can't invert a matrix with a negative determinant // you can't invert a matrix with a negative determinant
result.Fill(0); result.Fill(0);
return result; return result;
@@ -195,14 +160,10 @@ Matrix<rows, columns>::Invert() const
} }
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<columns, rows> Matrix<columns, rows> &
Matrix<rows, columns>::Transpose() const Matrix<rows, columns>::Transpose(Matrix<columns, rows> &result) const {
{ for (uint8_t column_idx{0}; column_idx < rows; column_idx++) {
Matrix<columns, rows> result{}; for (uint8_t row_idx{0}; row_idx < columns; row_idx++) {
for (uint8_t column_idx{0}; column_idx < rows; column_idx++)
{
for (uint8_t row_idx{0}; row_idx < columns; row_idx++)
{
result[row_idx][column_idx] = this->Get(column_idx, row_idx); result[row_idx][column_idx] = this->Get(column_idx, row_idx);
} }
} }
@@ -212,26 +173,20 @@ Matrix<rows, columns>::Transpose() const
// explicitly define the determinant for a 2x2 matrix because it is definitely // explicitly define the determinant for a 2x2 matrix because it is definitely
// the fastest way to calculate a 2x2 matrix determinant // the fastest way to calculate a 2x2 matrix determinant
// template <> template <> float Matrix<0, 0>::Det() const { return 1e+6; }
// inline float Matrix<0, 0>::Det() const { return 1e+6; } template <> float Matrix<1, 1>::Det() const { return this->matrix[0]; }
template <> template <> float Matrix<2, 2>::Det() const {
inline float Matrix<1, 1>::Det() const { return this->matrix[0]; }
template <>
inline float Matrix<2, 2>::Det() const
{
return this->matrix[0] * this->matrix[3] - this->matrix[1] * this->matrix[2]; return this->matrix[0] * this->matrix[3] - this->matrix[1] * this->matrix[2];
} }
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
float Matrix<rows, columns>::Det() const float Matrix<rows, columns>::Det() const {
{
static_assert(rows == columns, static_assert(rows == columns,
"You can't take the determinant of a non-square matrix."); "You can't take the determinant of a non-square matrix.");
Matrix<rows - 1, columns - 1> MinorMatrix{}; Matrix<rows - 1, columns - 1> MinorMatrix{};
float determinant{0}; float determinant{0};
for (uint8_t column_idx{0}; column_idx < columns; column_idx++) for (uint8_t column_idx{0}; column_idx < columns; column_idx++) {
{
// for odd indices the sign is negative // for odd indices the sign is negative
float sign = (column_idx % 2 == 0) ? 1 : -1; float sign = (column_idx % 2 == 0) ? 1 : -1;
determinant += sign * this->matrix[column_idx] * determinant += sign * this->matrix[column_idx] *
@@ -244,12 +199,9 @@ float Matrix<rows, columns>::Det() const
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows, columns> & Matrix<rows, columns> &
Matrix<rows, columns>::ElementMultiply(const Matrix<rows, columns> &other, Matrix<rows, columns>::ElementMultiply(const Matrix<rows, columns> &other,
Matrix<rows, columns> &result) const Matrix<rows, columns> &result) const {
{ for (uint8_t row_idx{0}; row_idx < rows; row_idx++) {
for (uint8_t row_idx{0}; row_idx < rows; row_idx++) for (uint8_t column_idx{0}; column_idx < columns; column_idx++) {
{
for (uint8_t column_idx{0}; column_idx < columns; column_idx++)
{
result[row_idx][column_idx] = result[row_idx][column_idx] =
this->Get(row_idx, column_idx) * other.Get(row_idx, column_idx); this->Get(row_idx, column_idx) * other.Get(row_idx, column_idx);
} }
@@ -261,12 +213,9 @@ Matrix<rows, columns>::ElementMultiply(const Matrix<rows, columns> &other,
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows, columns> & Matrix<rows, columns> &
Matrix<rows, columns>::ElementDivide(const Matrix<rows, columns> &other, Matrix<rows, columns>::ElementDivide(const Matrix<rows, columns> &other,
Matrix<rows, columns> &result) const Matrix<rows, columns> &result) const {
{ for (uint8_t row_idx{0}; row_idx < rows; row_idx++) {
for (uint8_t row_idx{0}; row_idx < rows; row_idx++) for (uint8_t column_idx{0}; column_idx < columns; column_idx++) {
{
for (uint8_t column_idx{0}; column_idx < columns; column_idx++)
{
result[row_idx][column_idx] = result[row_idx][column_idx] =
this->Get(row_idx, column_idx) / other.Get(row_idx, column_idx); this->Get(row_idx, column_idx) / other.Get(row_idx, column_idx);
} }
@@ -277,10 +226,8 @@ Matrix<rows, columns>::ElementDivide(const Matrix<rows, columns> &other,
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
float Matrix<rows, columns>::Get(uint8_t row_index, float Matrix<rows, columns>::Get(uint8_t row_index,
uint8_t column_index) const uint8_t column_index) const {
{ if (row_index > rows - 1 || column_index > columns - 1) {
if (row_index > rows - 1 || column_index > columns - 1)
{
return 1e+10; // TODO: We should throw something here instead of failing return 1e+10; // TODO: We should throw something here instead of failing
// quietly // quietly
} }
@@ -290,8 +237,7 @@ float Matrix<rows, columns>::Get(uint8_t row_index,
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<1, columns> & Matrix<1, columns> &
Matrix<rows, columns>::GetRow(uint8_t row_index, Matrix<rows, columns>::GetRow(uint8_t row_index,
Matrix<1, columns> &row) const Matrix<1, columns> &row) const {
{
memcpy(&(row[0]), this->matrix.begin() + row_index * columns, memcpy(&(row[0]), this->matrix.begin() + row_index * columns,
columns * sizeof(float)); columns * sizeof(float));
@@ -301,10 +247,8 @@ Matrix<rows, columns>::GetRow(uint8_t row_index,
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows, 1> & Matrix<rows, 1> &
Matrix<rows, columns>::GetColumn(uint8_t column_index, Matrix<rows, columns>::GetColumn(uint8_t column_index,
Matrix<rows, 1> &column) const Matrix<rows, 1> &column) const {
{ for (uint8_t row_idx{0}; row_idx < rows; row_idx++) {
for (uint8_t row_idx{0}; row_idx < rows; row_idx++)
{
column[row_idx][0] = this->Get(row_idx, column_index); column[row_idx][0] = this->Get(row_idx, column_index);
} }
@@ -312,17 +256,13 @@ Matrix<rows, columns>::GetColumn(uint8_t column_index,
} }
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
void Matrix<rows, columns>::ToString(std::string &stringBuffer) const void Matrix<rows, columns>::ToString(std::string &stringBuffer) const {
{ for (uint8_t row_idx{0}; row_idx < rows; row_idx++) {
for (uint8_t row_idx{0}; row_idx < rows; row_idx++)
{
stringBuffer += "|"; stringBuffer += "|";
for (uint8_t column_idx{0}; column_idx < columns; column_idx++) for (uint8_t column_idx{0}; column_idx < columns; column_idx++) {
{
stringBuffer += stringBuffer +=
std::to_string(this->matrix[row_idx * columns + column_idx]); std::to_string(this->matrix[row_idx * columns + column_idx]);
if (column_idx != columns - 1) if (column_idx != columns - 1) {
{
stringBuffer += "\t"; stringBuffer += "\t";
} }
} }
@@ -332,10 +272,8 @@ void Matrix<rows, columns>::ToString(std::string &stringBuffer) const
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
std::array<float, columns> &Matrix<rows, columns>:: std::array<float, columns> &Matrix<rows, columns>::
operator[](uint8_t row_index) operator[](uint8_t row_index) {
{ if (row_index > rows - 1) {
if (row_index > rows - 1)
{
// TODO: We should throw something here instead of failing quietly. // TODO: We should throw something here instead of failing quietly.
row_index = 0; row_index = 0;
} }
@@ -347,19 +285,20 @@ operator[](uint8_t row_index)
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows, columns> &Matrix<rows, columns>:: Matrix<rows, columns> &Matrix<rows, columns>::
operator=(const Matrix<rows, columns> &other) operator=(const Matrix<rows, columns> &other) {
{ for (uint8_t row_idx{0}; row_idx < rows; row_idx++) {
memcpy(this->matrix.begin(), other.matrix.begin(), for (uint8_t column_idx{0}; column_idx < columns; column_idx++) {
rows * columns * sizeof(float)); this->matrix[row_idx * columns + column_idx] =
other.Get(row_idx, column_idx);
}
}
// return a reference to ourselves so you can chain together these functions // return a reference to ourselves so you can chain together these functions
return *this; return *this;
} }
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows, columns> Matrix<rows, columns>:: Matrix<rows, columns> Matrix<rows, columns>::
operator+(const Matrix<rows, columns> &other) const operator+(const Matrix<rows, columns> &other) const {
{
Matrix<rows, columns> buffer{}; Matrix<rows, columns> buffer{};
this->Add(other, buffer); this->Add(other, buffer);
return buffer; return buffer;
@@ -367,26 +306,22 @@ operator+(const Matrix<rows, columns> &other) const
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows, columns> Matrix<rows, columns>:: Matrix<rows, columns> Matrix<rows, columns>::
operator-(const Matrix<rows, columns> &other) const operator-(const Matrix<rows, columns> &other) const {
{
Matrix<rows, columns> buffer{}; Matrix<rows, columns> buffer{};
this->Sub(other, buffer); this->Sub(other, buffer);
return buffer; return buffer;
} }
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
template <uint8_t other_columns> Matrix<rows, columns> Matrix<rows, columns>::
Matrix<rows, other_columns> Matrix<rows, columns>:: operator*(const Matrix<rows, columns> &other) const {
operator*(const Matrix<columns, other_columns> &other) const Matrix<rows, columns> buffer{};
{
Matrix<rows, other_columns> buffer{};
this->Mult(other, buffer); this->Mult(other, buffer);
return buffer; return buffer;
} }
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows, columns> Matrix<rows, columns>::operator*(float scalar) const Matrix<rows, columns> Matrix<rows, columns>::operator*(float scalar) const {
{
Matrix<rows, columns> buffer{}; Matrix<rows, columns> buffer{};
this->Mult(scalar, buffer); this->Mult(scalar, buffer);
return buffer; return buffer;
@@ -394,12 +329,10 @@ Matrix<rows, columns> Matrix<rows, columns>::operator*(float scalar) const
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
template <uint8_t vector_size> template <uint8_t vector_size>
float Matrix<rows, columns>::DotProduct(const Matrix<1, vector_size> &vec1, float Matrix<rows, columns>::dotProduct(const Matrix<1, vector_size> &vec1,
const Matrix<1, vector_size> &vec2) const Matrix<1, vector_size> &vec2) {
{
float sum{0}; float sum{0};
for (uint8_t i{0}; i < vector_size; i++) for (uint8_t i{0}; i < vector_size; i++) {
{
sum += vec1.Get(0, i) * vec2.Get(0, i); sum += vec1.Get(0, i) * vec2.Get(0, i);
} }
@@ -408,12 +341,10 @@ float Matrix<rows, columns>::DotProduct(const Matrix<1, vector_size> &vec1,
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
template <uint8_t vector_size> template <uint8_t vector_size>
float Matrix<rows, columns>::DotProduct(const Matrix<vector_size, 1> &vec1, float Matrix<rows, columns>::dotProduct(const Matrix<vector_size, 1> &vec1,
const Matrix<vector_size, 1> &vec2) const Matrix<vector_size, 1> &vec2) {
{
float sum{0}; float sum{0};
for (uint8_t i{0}; i < vector_size; i++) for (uint8_t i{0}; i < vector_size; i++) {
{
sum += vec1.Get(i, 0) * vec2.Get(i, 0); sum += vec1.Get(i, 0) * vec2.Get(i, 0);
} }
@@ -421,27 +352,17 @@ float Matrix<rows, columns>::DotProduct(const Matrix<vector_size, 1> &vec1,
} }
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
void Matrix<rows, columns>::Fill(float value) void Matrix<rows, columns>::Fill(float value) {
{ this->matrix.fill(value);
for (uint8_t row_idx{0}; row_idx < rows; row_idx++)
{
for (uint8_t column_idx{0}; column_idx < columns; column_idx++)
{
this->matrix[row_idx * columns + column_idx] = value;
}
}
} }
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows, columns> & Matrix<rows, columns> &
Matrix<rows, columns>::MatrixOfMinors(Matrix<rows, columns> &result) const Matrix<rows, columns>::MatrixOfMinors(Matrix<rows, columns> &result) const {
{
Matrix<rows - 1, columns - 1> MinorMatrix{}; Matrix<rows - 1, columns - 1> MinorMatrix{};
for (uint8_t row_idx{0}; row_idx < rows; row_idx++) for (uint8_t row_idx{0}; row_idx < rows; row_idx++) {
{ for (uint8_t column_idx{0}; column_idx < columns; column_idx++) {
for (uint8_t column_idx{0}; column_idx < columns; column_idx++)
{
this->MinorMatrix(MinorMatrix, row_idx, column_idx); this->MinorMatrix(MinorMatrix, row_idx, column_idx);
result[row_idx][column_idx] = MinorMatrix.Det(); result[row_idx][column_idx] = MinorMatrix.Det();
} }
@@ -453,20 +374,15 @@ Matrix<rows, columns>::MatrixOfMinors(Matrix<rows, columns> &result) const
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows - 1, columns - 1> & Matrix<rows - 1, columns - 1> &
Matrix<rows, columns>::MinorMatrix(Matrix<rows - 1, columns - 1> &result, Matrix<rows, columns>::MinorMatrix(Matrix<rows - 1, columns - 1> &result,
uint8_t row_idx, uint8_t column_idx) const uint8_t row_idx, uint8_t column_idx) const {
{
std::array<float, (rows - 1) * (columns - 1)> subArray{}; std::array<float, (rows - 1) * (columns - 1)> subArray{};
uint16_t array_idx{0}; uint16_t array_idx{0};
for (uint8_t row_iter{0}; row_iter < rows; row_iter++) for (uint8_t row_iter{0}; row_iter < rows; row_iter++) {
{ if (row_iter == row_idx) {
if (row_iter == row_idx)
{
continue; continue;
} }
for (uint8_t column_iter{0}; column_iter < columns; column_iter++) for (uint8_t column_iter{0}; column_iter < columns; column_iter++) {
{ if (column_iter == column_idx) {
if (column_iter == column_idx)
{
continue; continue;
} }
subArray[array_idx] = this->Get(row_iter, column_iter); subArray[array_idx] = this->Get(row_iter, column_iter);
@@ -480,12 +396,9 @@ Matrix<rows, columns>::MinorMatrix(Matrix<rows - 1, columns - 1> &result,
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows, columns> & Matrix<rows, columns> &
Matrix<rows, columns>::adjugate(Matrix<rows, columns> &result) const Matrix<rows, columns>::adjugate(Matrix<rows, columns> &result) const {
{ for (uint8_t row_iter{0}; row_iter < rows; row_iter++) {
for (uint8_t row_iter{0}; row_iter < rows; row_iter++) for (uint8_t column_iter{0}; column_iter < columns; column_iter++) {
{
for (uint8_t column_iter{0}; column_iter < columns; column_iter++)
{
float sign = ((row_iter + 1) % 2) == 0 ? -1 : 1; float sign = ((row_iter + 1) % 2) == 0 ? -1 : 1;
sign *= ((column_iter + 1) % 2) == 0 ? -1 : 1; sign *= ((column_iter + 1) % 2) == 0 ? -1 : 1;
result[column_iter][row_iter] = this->Get(row_iter, column_iter) * sign; result[column_iter][row_iter] = this->Get(row_iter, column_iter) * sign;
@@ -497,20 +410,16 @@ Matrix<rows, columns>::adjugate(Matrix<rows, columns> &result) const
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
Matrix<rows, columns> & Matrix<rows, columns> &
Matrix<rows, columns>::Normalize(Matrix<rows, columns> &result) const Matrix<rows, columns>::Normalize(Matrix<rows, columns> &result) const {
{
float sum{0}; float sum{0};
for (uint8_t row_idx{0}; row_idx < rows; row_idx++) for (uint8_t row_idx{0}; row_idx < rows; row_idx++) {
{ for (uint8_t column_idx{0}; column_idx < columns; column_idx++) {
for (uint8_t column_idx{0}; column_idx < columns; column_idx++)
{
float val{this->Get(row_idx, column_idx)}; float val{this->Get(row_idx, column_idx)};
sum += val * val; sum += val * val;
} }
} }
if (sum == 0) if (sum == 0) {
{
// this wouldn't do anything anyways // this wouldn't do anything anyways
result.Fill(1e+6); result.Fill(1e+6);
return result; return result;
@@ -518,10 +427,8 @@ Matrix<rows, columns>::Normalize(Matrix<rows, columns> &result) const
sum = sqrt(sum); sum = sqrt(sum);
for (uint8_t row_idx{0}; row_idx < rows; row_idx++) for (uint8_t row_idx{0}; row_idx < rows; row_idx++) {
{ for (uint8_t column_idx{0}; column_idx < columns; column_idx++) {
for (uint8_t column_idx{0}; column_idx < columns; column_idx++)
{
result[row_idx][column_idx] = this->Get(row_idx, column_idx) / sum; result[row_idx][column_idx] = this->Get(row_idx, column_idx) / sum;
} }
} }
@@ -530,43 +437,71 @@ Matrix<rows, columns>::Normalize(Matrix<rows, columns> &result) const
} }
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
template <uint8_t sub_rows, uint8_t sub_columns, uint8_t row_offset, uint8_t column_offset> Matrix<rows, rows> Matrix<rows, columns>::Eye() {
Matrix<sub_rows, sub_columns> Matrix<rows, columns>::SubMatrix() const Matrix<rows, rows> i_matrix;
{ i_matrix.Fill(0);
// static assert that sub_rows + row_offset <= rows for (uint8_t i{0}; i < rows; i++) {
// static assert that sub_columns + column_offset <= columns i_matrix[i][i] = 1;
static_assert(sub_rows + row_offset <= rows,
"The submatrix you're trying to get is out of bounds (rows)");
static_assert(sub_columns + column_offset <= columns,
"The submatrix you're trying to get is out of bounds (columns)");
Matrix<sub_rows, sub_columns> buffer{};
for (uint8_t row_idx{0}; row_idx < sub_rows; row_idx++)
{
for (uint8_t column_idx{0}; column_idx < sub_columns; column_idx++)
{
buffer[row_idx][column_idx] =
this->Get(row_idx + row_offset, column_idx + column_offset);
} }
} return i_matrix;
return buffer;
} }
template <uint8_t rows, uint8_t columns> template <uint8_t rows, uint8_t columns>
template <uint8_t sub_rows, uint8_t sub_columns, uint8_t row_offset, uint8_t column_offset> void Matrix<rows, columns>::QR_Decomposition(Matrix<rows, columns> &Q,
void Matrix<rows, columns>::SetSubMatrix(const Matrix<sub_rows, sub_columns> &sub_matrix) Matrix<rows, columns> &R) const {
{ Q = Matrix<rows, columns>::Eye(); // Q starts as the identity matrix
static_assert(sub_rows + row_offset <= rows, R = *this; // R starts as a copy of this matrix (For this algorithm we'll call
"The submatrix you're trying to set is out of bounds (rows)"); // this matrix A)
static_assert(sub_columns + column_offset <= columns,
"The submatrix you're trying to set is out of bounds (columns)");
for (uint8_t row_idx{0}; row_idx < sub_rows; row_idx++) for (uint8_t row{0}; row < rows; row++) {
{ // compute the householder vector
for (uint8_t column_idx{0}; column_idx < sub_columns; column_idx++) const uint8_t houseHoldVectorSize{rows - row};
{ const uint8_t subMatrixSize{columns - row};
this->matrix[(row_idx + row_offset) * columns + column_idx + column_offset] = sub_matrix.Get(row_idx, column_idx); Matrix<houseHoldVectorSize, 1> x{};
this->SubMatrix(row, row, x);
Matrix<houseHoldVectorSize, 1> e1{};
e1.Fill(0);
if (x[0][0] >= 0) {
e1[0][0] = x.Norm();
} else {
e1[0][0] = -x.Norm();
} }
Matrix<houseHoldVectorSize, 1> v = x + e1;
v = v * (1 / v.Norm()); // normalize V
// ************************************
// Apply the reflection to the R matrix
// ************************************
// initialize R's submatrix
Matrix<houseHoldVectorSize, subMatrixSize> R_subMatrix{};
R.SubMatrix(row, row, R_subMatrix);
// create some temporary buffers
Matrix<1, subMatrixSize> vR{};
Matrix<1, houseHoldVectorSize> v_T{};
v.Transpose(v_T);
Matrix<houseHoldVectorSize, subMatrixSize> vR_outer{};
// calculate the reflection
R_subMatrix =
R_subMatrix - 2 * Matrix<rows, columns>::OuterProduct(
v_T, v_T.Mult(R_subMatrix, vR), vR_outer);
// save the reflection back to R
R.CopySubMatrixInto(row, row, R_subMatrix);
// ************************************
// Apply the reflection to the Q matrix
// ************************************
// initialize Q's submatrix
Matrix<rows, houseHoldVectorSize> Q_subMatrix{};
Q.SubMatrix(0, row, Q_subMatrix);
// create some temporary buffers
Matrix<rows, 1> Qv{};
Matrix<rows, houseHoldVectorSize> Qv_outer{};
Q_subMatrix = Q_subMatrix - 2 * Matrix<rows, columns>::OuterProduct(
Q_subMatrix.Mult(v, Qv), v, Qv_outer);
Q.CopySubMatrixInto(0, row, Q_subMatrix);
} }
} }

View File

@@ -3,7 +3,6 @@
#include <array> #include <array>
#include <cstdint> #include <cstdint>
#include <string>
// TODO: Add a function to calculate eigenvalues/vectors // TODO: Add a function to calculate eigenvalues/vectors
// TODO: Add a function to compute RREF // TODO: Add a function to compute RREF
@@ -12,8 +11,6 @@
template <uint8_t rows, uint8_t columns> class Matrix { template <uint8_t rows, uint8_t columns> class Matrix {
public: public:
static_assert(rows > 0, "Template error: rows must be greater than 0.");
static_assert(columns > 0, "Template error: columns must be greater than 0.");
/** /**
* @brief create a matrix but leave all of its values unitialized * @brief create a matrix but leave all of its values unitialized
*/ */
@@ -39,11 +36,6 @@ public:
*/ */
template <typename... Args> Matrix(Args... args); template <typename... Args> Matrix(Args... args);
/**
* @brief set the matrix diagonals to 1 and all other values to 0
*/
void Identity();
/** /**
* @brief Set all elements in this to value * @brief Set all elements in this to value
*/ */
@@ -120,13 +112,13 @@ public:
* @param result A buffer to store the result into * @param result A buffer to store the result into
* @warning this is super slow! Only call it if you absolutely have to!!! * @warning this is super slow! Only call it if you absolutely have to!!!
*/ */
Matrix<rows, columns> Invert() const; Matrix<rows, columns> &Invert(Matrix<rows, columns> &result) const;
/** /**
* @brief Transpose this matrix * @brief Transpose this matrix
* @param result A buffer to store the result into * @param result A buffer to store the result into
*/ */
Matrix<columns, rows> Transpose() const; Matrix<columns, rows> &Transpose(Matrix<columns, rows> &result) const;
/** /**
* @brief reduce the matrix so the sum of its elements equal 1 * @brief reduce the matrix so the sum of its elements equal 1
@@ -134,6 +126,66 @@ public:
*/ */
Matrix<rows, columns> &Normalize(Matrix<rows, columns> &result) const; Matrix<rows, columns> &Normalize(Matrix<rows, columns> &result) const;
/**
* @brief return an identity matrix of the specified size
*/
static Matrix<rows, rows> Eye();
/**
* @brief write a copy of a sub matrix into the given result matrix.
* @param rowIndex The row index to start the copy from
* @param columnIndex the column index to start the copy from
* @param result the matrix buffer to write the sub matrix into. The size of
* the matrix buffer allows the function to determine the end indices of the
* sub matrix
*/
template <uint8_t subRows, uint8_t subColumns>
Matrix<subRows, subColumns> &
SubMatrix(uint8_t rowIndex, uint8_t columnIndex,
Matrix<subRows, subColumns> &result) const {
return result;
}
/**
* @brief write a copy of a sub matrix into this matrix starting at the given
* idnex.
* @param rowIndex The row index to start the copy from
* @param columnIndex the column index to start the copy from
* @param subMatrix The submatrix to copy into this matrix. The size of
* the matrix buffer allows the function to determine the end indices of the
* sub matrix
*/
template <uint8_t subRows, uint8_t subColumns>
void CopySubMatrixInto(uint8_t rowIndex, uint8_t columnIndex,
const Matrix<subRows, subColumns> &subMatrix) {}
/**
* @brief Returns the norm of the matrix
*/
float Norm() { return 0; }
template <uint8_t vec1Length, uint8_t vec2Length>
static Matrix<vec1Length, vec2Length> &
OuterProduct(const Matrix<1, vec1Length> &vec1,
const Matrix<1, vec2Length> &vec2,
Matrix<vec1Length, vec2Length> &result) {
return result;
}
template <uint8_t vec1Length, uint8_t vec2Length>
static Matrix<vec1Length, vec2Length> &
OuterProduct(const Matrix<vec1Length, 1> &vec1,
const Matrix<vec2Length, 1> &vec2,
Matrix<vec1Length, vec2Length> &result) {
return result;
}
/**
* @brief Calulcate the QR decomposition of a matrix
* @param Q the
*/
void QR_Decomposition(Matrix<rows, columns> &Q,
Matrix<rows, columns> &R) const;
/** /**
* @brief Get a row from the matrix * @brief Get a row from the matrix
* @param row_index the row index to get * @param row_index the row index to get
@@ -169,6 +221,10 @@ public:
*/ */
float Get(uint8_t row_index, uint8_t column_index) const; float Get(uint8_t row_index, uint8_t column_index) const;
// *******************************************************
// ************** OPERATOR OVERRIDES *********************
// *******************************************************
/** /**
* @brief get the specified row of the matrix returned as a reference to the * @brief get the specified row of the matrix returned as a reference to the
* internal array * internal array
@@ -187,42 +243,27 @@ public:
Matrix<rows, columns> operator-(const Matrix<rows, columns> &other) const; Matrix<rows, columns> operator-(const Matrix<rows, columns> &other) const;
template <uint8_t other_columns> Matrix<rows, columns> operator*(const Matrix<rows, columns> &other) const;
Matrix<rows, other_columns>
operator*(const Matrix<columns, other_columns> &other) const;
Matrix<rows, columns> operator*(float scalar) const; Matrix<rows, columns> operator*(float scalar) const;
template <uint8_t sub_rows, uint8_t sub_columns, uint8_t row_offset, private:
uint8_t column_offset>
Matrix<sub_rows, sub_columns> SubMatrix() const;
template <uint8_t sub_rows, uint8_t sub_columns, uint8_t row_offset,
uint8_t column_offset>
void SetSubMatrix(const Matrix<sub_rows, sub_columns> &sub_matrix);
/** /**
* @brief take the dot product of the two vectors * @brief take the dot product of the two vectors
*/ */
template <uint8_t vector_size> template <uint8_t vector_size>
static float DotProduct(const Matrix<1, vector_size> &vec1, static float dotProduct(const Matrix<1, vector_size> &vec1,
const Matrix<1, vector_size> &vec2); const Matrix<1, vector_size> &vec2);
template <uint8_t vector_size> template <uint8_t vector_size>
static float DotProduct(const Matrix<vector_size, 1> &vec1, static float dotProduct(const Matrix<vector_size, 1> &vec1,
const Matrix<vector_size, 1> &vec2); const Matrix<vector_size, 1> &vec2);
static float DotProduct(const Matrix<1, 1> &vec1, const Matrix<1, 1> &vec2) {
return vec1.Get(0, 0) * vec2.Get(0, 0);
}
protected:
std::array<float, rows * columns> matrix;
private:
Matrix<rows, columns> &adjugate(Matrix<rows, columns> &result) const; Matrix<rows, columns> &adjugate(Matrix<rows, columns> &result) const;
void setMatrixToArray(const std::array<float, rows * columns> &array); void setMatrixToArray(const std::array<float, rows * columns> &array);
std::array<float, rows * columns> matrix;
}; };
#include "Matrix.cpp" #include "Matrix.cpp"

View File

@@ -1,5 +1 @@
# Introduction A Simple matrix math library focused on embedded development which avoids and heap memory allocation unless you explicitly ask for it.
This matrix math library is focused on embedded development and avoids any heap memory allocation unless you explicitly ask for it.
It uses templates to pre-allocate matrices on the stack.
There are still several operations that are works in progress

81
Vector3D.hpp Normal file
View File

@@ -0,0 +1,81 @@
#pragma once
#include <cstdint>
#include <cmath>
#include <type_traits>
template <typename Type>
class V3D{
public:
constexpr V3D(const V3D& other):
x(other.x),
y(other.y),
z(other.z){
static_assert(std::is_arithmetic<Type>::value, "Type must be a number");
}
constexpr V3D(Type x=0, Type y=0, Type z=0):
x(x),
y(y),
z(z){
static_assert(std::is_arithmetic<Type>::value, "Type must be a number");
}
template <typename OtherType>
constexpr V3D(const V3D<OtherType> other):
x(static_cast<Type>(other.x)),
y(static_cast<Type>(other.y)),
z(static_cast<Type>(other.z)){
static_assert(std::is_arithmetic<Type>::value, "Type must be a number");
static_assert(std::is_arithmetic<OtherType>::value, "OtherType must be a number");
}
V3D& operator=(const V3D &other){
this->x = other.x;
this->y = other.y;
this->z = other.z;
return *this;
}
V3D& operator+=(const V3D &other){
this->x += other.x;
this->y += other.y;
this->z += other.z;
return *this;
}
V3D& operator-=(const V3D &other){
this->x -= other.x;
this->y -= other.y;
this->z -= other.z;
return *this;
}
V3D& operator/=(const Type scalar){
if(scalar == 0){
return *this;
}
this->x /= scalar;
this->y /= scalar;
this->z /= scalar;
return *this;
}
V3D& operator*=(const Type scalar){
this->x *= scalar;
this->y *= scalar;
this->z *= scalar;
return *this;
}
bool operator==(const V3D &other){
return this->x == other.x && this->y == other.y && this->z == other.z;
}
float magnitude(){
return std::sqrt(static_cast<float>(this->x * this->x + this->y * this->y + this->z * this->z));
}
Type x;
Type y;
Type z;
};

View File

@@ -1,20 +0,0 @@
{
"name": "Vector3D",
"version": "1.0.0",
"description": "Contains a V3D object for easy 3d vector math and a Matrix object for more complicated linear algebra operations.",
"keywords": "linear algebra, vector, matrix, 3D",
"repository": {
"type": "git",
"url": "https://github.com/Cynopolis/Vector3D.git"
},
"authors": [
{
"name": "Cynopolis",
"email": "megaveganzombie@gmail.com",
"url": "https://github.com/Cynopolis"
}
],
"license": "None Yet",
"frameworks": "*",
"platforms": "*"
}

159
qr-decom.py Normal file
View File

@@ -0,0 +1,159 @@
import numpy as np
# QR decomposition using the householder reflection method
def householder_reflection(A):
"""
Perform QR decomposition using Householder reflection.
Arguments:
A -- A matrix to be decomposed (m x n).
Returns:
Q -- Orthogonal matrix (m x m).
R -- Upper triangular matrix (m x n).
"""
A = A.astype(float) # Ensure the matrix is of type float
m, n = A.shape
Q = np.eye(m) # Initialize Q as an identity matrix
R = A.copy() # R starts as a copy of A
# Apply Householder reflections for each column
for k in range(n):
# Step 1: Compute the Householder vector
x = R[k:m, k]
e1 = np.zeros_like(x)
e1[0] = np.linalg.norm(x) if x[0] >= 0 else -np.linalg.norm(x)
v = x + e1
v = v / np.linalg.norm(v) # Normalize v
# Step 2: Apply the reflection to the matrix
R[k:m, k:n] = R[k:m, k:n] - 2 * np.outer(v, v.T @ R[k:m, k:n])
# Step 3: Apply the reflection to Q
Q[:, k:m] = Q[:, k:m] - 2 * np.outer(Q[:, k:m] @ v, v)
# The resulting Q and R are the QR decomposition
return Q, R
# Example usage
A = np.array([[12, -51, 4],
[6, 167, -68],
[-4, 24, -41]])
Q, R = householder_reflection(A)
print("Q matrix:")
print(Q)
print("\nR matrix:")
print(R)
print("Multiplied Together:")
print(Q@R)
def svd_decomposition(A):
"""
Perform Singular Value Decomposition (SVD) from scratch.
Arguments:
A -- The matrix to be decomposed (m x n).
Returns:
U -- Orthogonal matrix of left singular vectors (m x m).
Sigma -- Diagonal matrix of singular values (m x n).
Vt -- Orthogonal matrix of right singular vectors (n x n).
"""
# Step 1: Compute A^T A
AtA = np.dot(A.T, A) # A transpose multiplied by A
# Step 2: Compute the eigenvalues and eigenvectors of A^T A
eigenvalues, V = np.linalg.eig(AtA)
# Step 3: Sort eigenvalues in descending order and sort V accordingly
sorted_indices = np.argsort(eigenvalues)[::-1] # Indices to sort eigenvalues in descending order
eigenvalues = eigenvalues[sorted_indices]
V = V[:, sorted_indices]
# Step 4: Compute the singular values (sqrt of eigenvalues)
singular_values = np.sqrt(eigenvalues)
# Step 5: Construct the Sigma matrix
m, n = A.shape
Sigma = np.zeros((m, n)) # Initialize Sigma as a zero matrix
for i in range(min(m, n)):
Sigma[i, i] = singular_values[i] # Place the singular values on the diagonal
# Step 6: Compute the U matrix using A * V = U * Sigma
U = np.dot(A, V) # A * V gives us the unnormalized U
# Normalize the columns of U
for i in range(U.shape[1]):
U[:, i] = U[:, i] / singular_values[i] # Normalize each column by the corresponding singular value
# Step 7: Return U, Sigma, Vt
return U, Sigma, V.T # V.T is the transpose of V
# Example usage
A = np.array([[12, -51, 4],
[6, 167, -68],
[-4, 24, -41]])
U, Sigma, Vt = svd_decomposition(A)
print("\nSVD DECOMPOSITION\nU matrix:")
print(U)
print("\nSigma matrix:")
print(Sigma)
print("\nVt matrix:")
print(Vt)
print("Multiplied together:")
print(U@Sigma@Vt)
def eigen_decomposition_qr(A, max_iter=1000, tol=1e-9):
"""
Compute the eigenvalues and eigenvectors of a matrix A using the QR algorithm
with QR decomposition.
Arguments:
A -- A square matrix (n x n).
max_iter -- Maximum number of iterations for convergence (default 1000).
tol -- Tolerance for convergence (default 1e-9).
Returns:
eigenvalues -- List of eigenvalues.
eigenvectors -- Matrix of eigenvectors.
"""
# Make a copy of A to perform the iteration
A_copy = A.copy()
n = A_copy.shape[0]
# Initialize the matrix for eigenvectors (this will accumulate the Q matrices)
eigenvectors = np.eye(n)
# Perform QR iterations
for _ in range(max_iter):
# Perform QR decomposition on A_copy
Q, R = householder_reflection(A_copy)
# Update A_copy to be R * Q (QR algorithm step)
A_copy = R @ Q
# Accumulate the eigenvectors
eigenvectors = eigenvectors @ Q
# Check for convergence: if the off-diagonal elements are small enough, we stop
off_diagonal_norm = np.linalg.norm(np.tril(A_copy, -1)) # Norm of the lower triangle (off-diagonal)
if off_diagonal_norm < tol:
break
# The eigenvalues are the diagonal elements of the matrix A_copy
eigenvalues = np.diag(A_copy)
return eigenvalues, eigenvectors
# Example usage
A = np.array([[12, -51, 4],
[6, 167, -68],
[-4, 24, -41]])
eigenvalues, eigenvectors = eigen_decomposition_qr(A)
print("\n\nEigenvalues:", eigenvalues)
print("Eigenvectors:\n", eigenvectors)

View File

@@ -1,59 +0,0 @@
# Quaternion Interface
add_library(vector-3d-intf
INTERFACE
)
target_include_directories(vector-3d-intf
INTERFACE
.
)
target_link_libraries(vector-3d-intf
INTERFACE
)
# Quaternion
add_library(quaternion
STATIC
Quaternion.cpp
)
target_link_libraries(quaternion
PUBLIC
vector-3d-intf
PRIVATE
)
set_target_properties(quaternion
PROPERTIES
LINKER_LANGUAGE CXX
)
# Vector3d
add_library(vector-3d
STATIC
Vector3D.cpp
)
target_link_libraries(vector-3d
PUBLIC
vector-3d-intf
PRIVATE
)
# Matrix
add_library(matrix
STATIC
Matrix.cpp
)
target_link_libraries(matrix
PUBLIC
vector-3d-intf
PRIVATE
)
set_target_properties(matrix
PROPERTIES
LINKER_LANGUAGE CXX
)

View File

@@ -1,120 +0,0 @@
#include "Quaternion.h"
#include <cmath>
/**
* @brief Create a quaternion from an angle and axis
* @param angle The angle to rotate by
* @param axis The axis to rotate around
*/
Quaternion Quaternion::FromAngleAndAxis(float angle, const Matrix<1, 3> &axis)
{
const float halfAngle = angle / 2;
const float sinHalfAngle = sin(halfAngle);
Matrix<1, 3> normalizedAxis{};
axis.Normalize(normalizedAxis);
return Quaternion{
static_cast<float>(cos(halfAngle)),
normalizedAxis.Get(0, 0) * sinHalfAngle,
normalizedAxis.Get(0, 1) * sinHalfAngle,
normalizedAxis.Get(0, 2) * sinHalfAngle};
}
float Quaternion::operator[](uint8_t index) const
{
if (index < 4)
{
return this->matrix[index];
}
// index out of bounds
return 1e+6;
}
void Quaternion::operator=(const Quaternion &other)
{
memcpy(&(this->matrix), &(other.matrix), 4 * sizeof(float));
}
Quaternion Quaternion::operator*(const Quaternion &other) const
{
Quaternion result{};
this->Q_Mult(other, result);
return result;
}
Quaternion Quaternion::operator*(float scalar) const
{
return Quaternion{this->w * scalar, this->v1 * scalar, this->v2 * scalar, this->v3 * scalar};
}
Quaternion Quaternion::operator+(const Quaternion &other) const
{
return Quaternion{this->w + other.w, this->v1 + other.v1, this->v2 + other.v2, this->v3 + other.v3};
}
Quaternion &
Quaternion::Q_Mult(const Quaternion &other, Quaternion &buffer) const
{
// eq. 6
buffer.w = (other.w * this->w - other.v1 * this->v1 - other.v2 * this->v2 - other.v3 * this->v3);
buffer.v1 = (other.w * this->v1 + other.v1 * this->w - other.v2 * this->v3 + other.v3 * this->v2);
buffer.v2 = (other.w * this->v2 + other.v1 * this->v3 + other.v2 * this->w - other.v3 * this->v1);
buffer.v3 = (other.w * this->v3 - other.v1 * this->v2 + other.v2 * this->v1 + other.v3 * this->w);
return buffer;
}
Quaternion &Quaternion::Rotate(Quaternion &other, Quaternion &buffer) const
{
Quaternion prime{this->w, -this->v1, -this->v2, -this->v3};
buffer.v1 = other.v1;
buffer.v2 = other.v2;
buffer.v3 = other.v3;
buffer.w = 0;
Quaternion temp{};
this->Q_Mult(buffer, temp);
temp.Q_Mult(prime, buffer);
return buffer;
}
void Quaternion::Normalize()
{
float magnitude = sqrt(this->v1 * this->v1 + this->v2 * this->v2 + this->v3 * this->v3 + this->w * this->w);
if (magnitude == 0)
{
return;
}
this->v1 /= magnitude;
this->v2 /= magnitude;
this->v3 /= magnitude;
this->w /= magnitude;
}
Matrix<3, 3> Quaternion::ToRotationMatrix() const
{
float xx = this->v1 * this->v1;
float yy = this->v2 * this->v2;
float zz = this->v3 * this->v3;
Matrix<3, 3> rotationMatrix{
1 - 2 * (yy - zz), 2 * (this->v1 * this->v2 - this->v3 * this->w), 2 * (this->v1 * this->v3 + this->v2 * this->w),
2 * (this->v1 * this->v2 + this->v3 * this->w), 1 - 2 * (xx - zz), 2 * (this->v2 * this->v3 - this->v1 * this->w),
2 * (this->v1 * this->v3 - this->v2 * this->w), 2 * (this->v2 * this->v3 + this->v1 * this->w), 1 - 2 * (xx - yy)};
return rotationMatrix;
};
Matrix<3, 1> Quaternion::ToEulerAngle() const
{
float sqv1 = this->v1 * this->v1;
float sqv2 = this->v2 * this->v2;
float sqv3 = this->v3 * this->v3;
float sqw = this->w * this->w;
Matrix<3, 1> eulerAngle;
{
atan2(2.0 * (this->v1 * this->v2 + this->v3 * this->w), (sqv1 - sqv2 - sqv3 + sqw));
asin(-2.0 * (this->v1 * this->v3 - this->v2 * this->w) / (sqv1 + sqv2 + sqv3 + sqw));
atan2(2.0 * (this->v2 * this->v3 + this->v1 * this->w), (-sqv1 - sqv2 + sqv3 + sqw));
};
return eulerAngle;
}

View File

@@ -1,91 +0,0 @@
#ifndef QUATERNION_H_
#define QUATERNION_H_
#include "Matrix.hpp"
class Quaternion : public Matrix<1, 4>
{
public:
Quaternion() : Matrix<1, 4>() {}
Quaternion(float fillValue) : Matrix<1, 4>(fillValue) {}
Quaternion(float w, float v1, float v2, float v3) : Matrix<1, 4>(w, v1, v2, v3) {}
Quaternion(const Quaternion &q) : Matrix<1, 4>(q.w, q.v1, q.v2, q.v3) {}
Quaternion(const Matrix<1, 4> &matrix) : Matrix<1, 4>(matrix) {}
Quaternion(const std::array<float, 4> &array) : Matrix<1, 4>(array) {}
/**
* @brief Create a quaternion from an angle and axis
* @param angle The angle to rotate by
* @param axis The axis to rotate around
*/
static Quaternion FromAngleAndAxis(float angle, const Matrix<1, 3> &axis);
/**
* @brief Access the elements of the quaternion
* @param index The index of the element to access
* @return The value of the element at the index
*/
float operator[](uint8_t index) const;
/**
* @brief Assign one quaternion to another
*/
void operator=(const Quaternion &other);
/**
* @brief Do quaternion multiplication
*/
Quaternion operator*(const Quaternion &other) const;
/**
* @brief Multiply the quaternion by a scalar
*/
Quaternion operator*(float scalar) const;
/**
* @brief Add two quaternions together
* @param other The quaternion to add to this one
* @return The net quaternion
*/
Quaternion operator+(const Quaternion &other) const;
/**
* @brief Q_Mult a quaternion by another quaternion
* @param other The quaternion to rotate by
* @param buffer The buffer to store the result in
* @return A reference to the buffer
*/
Quaternion &Q_Mult(const Quaternion &other, Quaternion &buffer) const;
/**
* @brief Rotate a quaternion by this quaternion
* @param other The quaternion to rotate
* @param buffer The buffer to store the result in
*
*/
Quaternion &Rotate(Quaternion &other, Quaternion &buffer) const;
/**
* @brief Normalize the quaternion to a magnitude of 1
*/
void Normalize();
/**
* @brief Convert the quaternion to a rotation matrix
* @return The rotation matrix
*/
Matrix<3, 3> ToRotationMatrix() const;
/**
* @brief Convert the quaternion to an Euler angle representation
* @return The Euler angle representation of the quaternion
*/
Matrix<3, 1> ToEulerAngle() const;
// Give people an easy way to access the elements
float &w{matrix[0]};
float &v1{matrix[1]};
float &v2{matrix[2]};
float &v3{matrix[3]};
};
#endif // QUATERNION_H_

View File

@@ -1,162 +0,0 @@
#ifdef VECTOR3D_H_ // since the .cpp file has to be included by the .hpp file this
// will evaluate to true
#include <cmath>
#include <type_traits>
#include <string>
template <typename Type>
V3D<Type>::V3D(const Matrix<1, 3> &other)
{
this->x = other.Get(0, 0);
this->y = other.Get(0, 1);
this->z = other.Get(0, 2);
}
template <typename Type>
V3D<Type>::V3D(const Matrix<3, 1> &other)
{
this->x = other.Get(0, 0);
this->y = other.Get(1, 0);
this->z = other.Get(2, 0);
}
template <typename Type>
V3D<Type>::V3D(const V3D &other) : x(other.x),
y(other.y),
z(other.z)
{
static_assert(std::is_arithmetic<Type>::value, "Type must be a number");
}
template <typename Type>
V3D<Type>::V3D(Type x, Type y, Type z) : x(x),
y(y),
z(z)
{
static_assert(std::is_arithmetic<Type>::value, "Type must be a number");
}
template <typename Type>
template <typename OtherType>
V3D<Type>::V3D(const V3D<OtherType> &other)
{
static_assert(std::is_arithmetic<Type>::value, "Type must be a number");
static_assert(std::is_arithmetic<OtherType>::value, "OtherType must be a number");
this->x = static_cast<Type>(other.x);
this->y = static_cast<Type>(other.y);
this->z = static_cast<Type>(other.z);
}
template <typename Type>
std::array<Type, 3> V3D<Type>::ToArray() const
{
return {this->x, this->y, this->z};
}
template <typename Type>
void V3D<Type>::operator=(const V3D<Type> &other)
{
this->x = other.x;
this->y = other.y;
this->z = other.z;
}
template <typename Type>
V3D<Type> V3D<Type>::operator+(Type other) const
{
return V3D<Type>{this->x + other, this->y + other, this->z + other};
}
template <typename Type>
V3D<Type> V3D<Type>::operator+(const V3D<Type> &other) const
{
return V3D<Type>{this->x + other.x, this->y + other.y, this->z + other.z};
}
template <typename Type>
V3D<Type> V3D<Type>::operator-(Type other) const
{
return V3D<Type>{this->x - other, this->y - other, this->z - other};
}
template <typename Type>
V3D<Type> V3D<Type>::operator-(const V3D<Type> &other) const
{
return V3D<Type>{this->x - other.x, this->y - other.y, this->z - other.z};
}
template <typename Type>
V3D<Type> V3D<Type>::operator*(Type scalar) const
{
return V3D<Type>{this->x * scalar, this->y * scalar, this->z * scalar};
}
template <typename Type>
V3D<Type> V3D<Type>::operator/(Type scalar) const
{
return V3D<Type>{this->x / scalar, this->y / scalar, this->z / scalar};
}
template <typename Type>
V3D<Type> &V3D<Type>::operator+=(Type other)
{
*this = *this + other;
return *this;
}
template <typename Type>
V3D<Type> &V3D<Type>::operator+=(const V3D<Type> &other)
{
*this = *this + other;
return *this;
}
template <typename Type>
V3D<Type> &V3D<Type>::operator-=(Type other)
{
*this = *this - other;
return *this;
}
template <typename Type>
V3D<Type> &V3D<Type>::operator-=(const V3D<Type> &other)
{
*this = *this - other;
return *this;
}
template <typename Type>
V3D<Type> &V3D<Type>::operator/=(Type scalar)
{
if (scalar == 0)
{
return *this;
}
this->x /= scalar;
this->y /= scalar;
this->z /= scalar;
return *this;
}
template <typename Type>
V3D<Type> &V3D<Type>::operator*=(Type scalar)
{
this->x *= scalar;
this->y *= scalar;
this->z *= scalar;
return *this;
}
template <typename Type>
bool V3D<Type>::operator==(const V3D<Type> &other)
{
return this->x == other.x && this->y == other.y && this->z == other.z;
}
template <typename Type>
float V3D<Type>::magnitude()
{
return std::sqrt(static_cast<float>(this->x * this->x + this->y * this->y + this->z * this->z));
}
#endif // VECTOR3D_H_

View File

@@ -1,58 +0,0 @@
#ifndef VECTOR3D_H_
#define VECTOR3D_H_
#include <cstdint>
#include "Matrix.hpp"
template <typename Type>
class V3D
{
public:
V3D(const Matrix<1, 3> &other);
V3D(const Matrix<3, 1> &other);
V3D(const V3D &other);
V3D(Type x = 0, Type y = 0, Type z = 0);
template <typename OtherType>
V3D(const V3D<OtherType> &other);
template <typename OtherType>
operator OtherType() const;
std::array<Type, 3> ToArray() const;
V3D<Type> operator+(Type other) const;
V3D<Type> operator+(const V3D<Type> &other) const;
V3D<Type> operator-(Type other) const;
V3D<Type> operator-(const V3D<Type> &other) const;
V3D<Type> operator*(Type scalar) const;
V3D<Type> operator/(Type scalar) const;
void operator=(const V3D<Type> &other);
V3D<Type> &operator+=(Type other);
V3D<Type> &operator+=(const V3D<Type> &other);
V3D<Type> &operator-=(Type other);
V3D<Type> &operator-=(const V3D<Type> &other);
V3D<Type> &operator/=(Type scalar);
V3D<Type> &operator*=(Type scalar);
bool operator==(const V3D<Type> &other);
float magnitude();
Type x;
Type y;
Type z;
};
#include "Vector3D.cpp"
#endif // VECTOR3D_H_

View File

@@ -1,35 +1,21 @@
# Quaternion tests cmake_minimum_required (VERSION 3.11)
add_executable(quaternion-tests quaternion-tests.cpp)
target_link_libraries(quaternion-tests project ("test_driver")
PRIVATE
quaternion include(FetchContent)
Catch2::Catch2WithMain
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.0.1 # or a later release
) )
# matrix tests FetchContent_MakeAvailable(Catch2)
add_executable(matrix-tests matrix-tests.cpp) add_executable(matrix-tests matrix-tests.cpp)
target_link_libraries(matrix-tests target_link_libraries(matrix-tests
PRIVATE PRIVATE
matrix Matrix
Catch2::Catch2WithMain
)
# matrix timing tests
add_executable(matrix-timing-tests matrix-timing-tests.cpp)
target_link_libraries(matrix-timing-tests
PRIVATE
matrix
Catch2::Catch2WithMain
)
# Vector 3D Tests
add_executable(vector-3d-tests vector-tests.cpp)
target_link_libraries(vector-3d-tests
PRIVATE
vector-3d
Catch2::Catch2WithMain Catch2::Catch2WithMain
) )

View File

@@ -0,0 +1,14 @@
Addition: 0.419 s
Subtraction: 0.421 s
Multiplication: 3.297 s
Scalar Multiplication: 0.329 s
Element Multiply: 0.306 s
Element Divide: 0.302 s
Minor Matrix: 0.331 s
Determinant: 0.177 s
Matrix of Minors: 0.766 s
Invert: 0.183 s
Transpose: 0.215 s
Normalize: 0.315 s
GET ROW: 0.008 s
GET COLUMN: 0.43 s

View File

@@ -118,8 +118,6 @@ TEST_CASE("Elementary Matrix Operations", "Matrix") {
REQUIRE(mat3.Get(0, 1) == 22); REQUIRE(mat3.Get(0, 1) == 22);
REQUIRE(mat3.Get(1, 0) == 43); REQUIRE(mat3.Get(1, 0) == 43);
REQUIRE(mat3.Get(1, 1) == 50); REQUIRE(mat3.Get(1, 1) == 50);
// TODO: You need to add non-square multiplications to this.
} }
SECTION("Scalar Multiplication") { SECTION("Scalar Multiplication") {
@@ -184,6 +182,18 @@ TEST_CASE("Elementary Matrix Operations", "Matrix") {
REQUIRE(minorMat4.Get(1, 0) == 4); REQUIRE(minorMat4.Get(1, 0) == 4);
REQUIRE(minorMat4.Get(1, 1) == 5); REQUIRE(minorMat4.Get(1, 1) == 5);
} }
SECTION("Identity Matrix") {
Matrix<3, 3> mat4{Matrix<3, 3>::Eye()};
REQUIRE(mat4.Get(0, 0) == 1);
REQUIRE(mat4.Get(0, 1) == 0);
REQUIRE(mat4.Get(0, 2) == 0);
REQUIRE(mat4.Get(1, 0) == 0);
REQUIRE(mat4.Get(1, 1) == 1);
REQUIRE(mat4.Get(1, 2) == 0);
REQUIRE(mat4.Get(2, 0) == 0);
REQUIRE(mat4.Get(2, 1) == 0);
REQUIRE(mat4.Get(2, 2) == 1);
}
SECTION("Determinant") { SECTION("Determinant") {
float det1 = mat1.Det(); float det1 = mat1.Det();
@@ -224,7 +234,7 @@ TEST_CASE("Elementary Matrix Operations", "Matrix") {
} }
SECTION("Invert") { SECTION("Invert") {
mat3 = mat1.Invert(); mat1.Invert(mat3);
REQUIRE_THAT(mat3.Get(0, 0), Catch::Matchers::WithinRel(-2.0F, 1e-6f)); REQUIRE_THAT(mat3.Get(0, 0), Catch::Matchers::WithinRel(-2.0F, 1e-6f));
REQUIRE_THAT(mat3.Get(0, 1), Catch::Matchers::WithinRel(1.0F, 1e-6f)); REQUIRE_THAT(mat3.Get(0, 1), Catch::Matchers::WithinRel(1.0F, 1e-6f));
REQUIRE_THAT(mat3.Get(1, 0), Catch::Matchers::WithinRel(1.5F, 1e-6f)); REQUIRE_THAT(mat3.Get(1, 0), Catch::Matchers::WithinRel(1.5F, 1e-6f));
@@ -233,7 +243,7 @@ TEST_CASE("Elementary Matrix Operations", "Matrix") {
SECTION("Transpose") { SECTION("Transpose") {
// transpose a square matrix // transpose a square matrix
mat3 = mat1.Transpose(); mat1.Transpose(mat3);
REQUIRE(mat3.Get(0, 0) == 1); REQUIRE(mat3.Get(0, 0) == 1);
REQUIRE(mat3.Get(0, 1) == 3); REQUIRE(mat3.Get(0, 1) == 3);
@@ -244,7 +254,7 @@ TEST_CASE("Elementary Matrix Operations", "Matrix") {
Matrix<2, 3> mat4{1, 2, 3, 4, 5, 6}; Matrix<2, 3> mat4{1, 2, 3, 4, 5, 6};
Matrix<3, 2> mat5{}; Matrix<3, 2> mat5{};
mat5 = mat4.Transpose(); mat4.Transpose(mat5);
REQUIRE(mat5.Get(0, 0) == 1); REQUIRE(mat5.Get(0, 0) == 1);
REQUIRE(mat5.Get(0, 1) == 4); REQUIRE(mat5.Get(0, 1) == 4);
@@ -295,62 +305,113 @@ TEST_CASE("Elementary Matrix Operations", "Matrix") {
REQUIRE(mat1Columns.Get(0, 0) == 2); REQUIRE(mat1Columns.Get(0, 0) == 2);
REQUIRE(mat1Columns.Get(1, 0) == 4); REQUIRE(mat1Columns.Get(1, 0) == 4);
} }
SECTION("Get Sub-Matrices") {
Matrix<3, 3> mat4{1, 2, 3, 4, 5, 6, 7, 8, 9};
Matrix<2, 2> mat5 = mat4.SubMatrix<2, 2, 0, 0>();
REQUIRE(mat5.Get(0, 0) == 1);
REQUIRE(mat5.Get(0, 1) == 2);
REQUIRE(mat5.Get(1, 0) == 4);
REQUIRE(mat5.Get(1, 1) == 5);
mat5 = mat4.SubMatrix<2, 2, 1, 1>();
REQUIRE(mat5.Get(0, 0) == 5);
REQUIRE(mat5.Get(0, 1) == 6);
REQUIRE(mat5.Get(1, 0) == 8);
REQUIRE(mat5.Get(1, 1) == 9);
Matrix<3, 1> mat6 = mat4.SubMatrix<3, 1, 0, 0>();
REQUIRE(mat6.Get(0, 0) == 1);
REQUIRE(mat6.Get(1, 0) == 4);
REQUIRE(mat6.Get(2, 0) == 7);
Matrix<1, 3> mat7 = mat4.SubMatrix<1, 3, 0, 0>();
REQUIRE(mat7.Get(0, 0) == 1);
REQUIRE(mat7.Get(0, 1) == 2);
REQUIRE(mat7.Get(0, 2) == 3);
} }
SECTION("Set Sub-Matrices") { // basically re-run all of the previous tests with huge matrices and time the
Matrix<3, 3> startMatrix{1, 2, 3, 4, 5, 6, 7, 8, 9}; // results.
Matrix<3, 3> mat4 = startMatrix; TEST_CASE("Timing Tests", "Matrix") {
std::array<float, 50 * 50> arr1{};
for (uint16_t i{0}; i < 50 * 50; i++) {
arr1[i] = i;
}
std::array<float, 50 * 50> arr2{5, 6, 7, 8};
for (uint16_t i{50 * 50}; i < 2 * 50 * 50; i++) {
arr2[i] = i;
}
Matrix<50, 50> mat1{arr1};
Matrix<50, 50> mat2{arr2};
Matrix<50, 50> mat3{};
Matrix<2, 2> mat5{10, 11, 12, 13}; // A smaller matrix to use for really badly optimized operations
mat4.SetSubMatrix<2, 2, 0, 0>(mat5); Matrix<4, 4> mat4{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
REQUIRE(mat4.Get(0, 0) == 10); Matrix<4, 4> mat5{};
REQUIRE(mat4.Get(0, 1) == 11);
REQUIRE(mat4.Get(1, 0) == 12);
REQUIRE(mat4.Get(1, 1) == 13);
mat4 = startMatrix; SECTION("Addition") {
mat4.SetSubMatrix<2, 2, 1, 1>(mat5); for (uint32_t i{0}; i < 10000; i++) {
REQUIRE(mat4.Get(1, 1) == 10); mat3 = mat1 + mat2;
REQUIRE(mat4.Get(1, 2) == 11); }
REQUIRE(mat4.Get(2, 1) == 12); }
REQUIRE(mat4.Get(2, 2) == 13);
SECTION("Subtraction") {
Matrix<3, 1> mat6{10, 11, 12}; for (uint32_t i{0}; i < 10000; i++) {
mat4.SetSubMatrix<3, 1, 0, 0>(mat6); mat3 = mat1 - mat2;
REQUIRE(mat4.Get(0, 0) == 10); }
REQUIRE(mat4.Get(1, 0) == 11); }
REQUIRE(mat4.Get(2, 0) == 12);
SECTION("Multiplication") {
Matrix<1, 3> mat7{10, 11, 12}; for (uint32_t i{0}; i < 1000; i++) {
mat4.SetSubMatrix<1, 3, 0, 0>(mat7); mat3 = mat1 * mat2;
REQUIRE(mat4.Get(0, 0) == 10); }
REQUIRE(mat4.Get(0, 1) == 11); }
REQUIRE(mat4.Get(0, 2) == 12);
SECTION("Scalar Multiplication") {
for (uint32_t i{0}; i < 10000; i++) {
mat3 = mat1 * 3;
}
}
SECTION("Element Multiply") {
for (uint32_t i{0}; i < 10000; i++) {
mat1.ElementMultiply(mat2, mat3);
}
}
SECTION("Element Divide") {
for (uint32_t i{0}; i < 10000; i++) {
mat1.ElementDivide(mat2, mat3);
}
}
SECTION("Minor Matrix") {
// what about matrices of 0,0 or 1,1?
// minor matrix for 2x2 matrix
Matrix<49, 49> minorMat1{};
for (uint32_t i{0}; i < 10000; i++) {
mat1.MinorMatrix(minorMat1, 0, 0);
}
}
SECTION("Determinant") {
for (uint32_t i{0}; i < 100000; i++) {
float det1 = mat4.Det();
}
}
SECTION("Matrix of Minors") {
for (uint32_t i{0}; i < 100000; i++) {
mat4.MatrixOfMinors(mat5);
}
}
SECTION("Invert") {
for (uint32_t i{0}; i < 100000; i++) {
mat4.Invert(mat5);
}
};
SECTION("Transpose") {
for (uint32_t i{0}; i < 10000; i++) {
mat1.Transpose(mat3);
}
}
SECTION("Normalize") {
for (uint32_t i{0}; i < 10000; i++) {
mat1.Normalize(mat3);
}
}
SECTION("GET ROW") {
Matrix<1, 50> mat1Rows{};
for (uint32_t i{0}; i < 1000000; i++) {
mat1.GetRow(0, mat1Rows);
}
}
SECTION("GET COLUMN") {
Matrix<50, 1> mat1Columns{};
for (uint32_t i{0}; i < 1000000; i++) {
mat1.GetColumn(0, mat1Columns);
}
} }
} }

View File

@@ -1,119 +0,0 @@
// include the unit test framework first
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
// include the module you're going to test next
#include "Matrix.hpp"
// any other libraries
#include <array>
#include <cmath>
// basically re-run all of the matrix tests with huge matrices and time the
// results.
TEST_CASE("Timing Tests", "Matrix") {
std::array<float, 50 * 50> arr1{};
for (uint16_t i{0}; i < 50 * 50; i++) {
arr1[i] = i;
}
std::array<float, 50 * 50> arr2{5, 6, 7, 8};
for (uint16_t i{50 * 50}; i < 2 * 50 * 50; i++) {
arr2[i] = i;
}
Matrix<50, 50> mat1{arr1};
Matrix<50, 50> mat2{arr2};
Matrix<50, 50> mat3{};
// A smaller matrix to use for really badly optimized operations
Matrix<4, 4> mat4{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
Matrix<4, 4> mat5{};
SECTION("Addition") {
for (uint32_t i{0}; i < 10000; i++) {
mat3 = mat1 + mat2;
}
}
SECTION("Subtraction") {
for (uint32_t i{0}; i < 10000; i++) {
mat3 = mat1 - mat2;
}
}
SECTION("Multiplication") {
for (uint32_t i{0}; i < 1000; i++) {
mat3 = mat1 * mat2;
}
}
SECTION("Scalar Multiplication") {
for (uint32_t i{0}; i < 10000; i++) {
mat3 = mat1 * 3;
}
}
SECTION("Element Multiply") {
for (uint32_t i{0}; i < 10000; i++) {
mat1.ElementMultiply(mat2, mat3);
}
}
SECTION("Element Divide") {
for (uint32_t i{0}; i < 10000; i++) {
mat1.ElementDivide(mat2, mat3);
}
}
SECTION("Minor Matrix") {
// what about matrices of 0,0 or 1,1?
// minor matrix for 2x2 matrix
Matrix<49, 49> minorMat1{};
for (uint32_t i{0}; i < 10000; i++) {
mat1.MinorMatrix(minorMat1, 0, 0);
}
}
SECTION("Determinant") {
for (uint32_t i{0}; i < 100000; i++) {
float det1 = mat4.Det();
}
}
SECTION("Matrix of Minors") {
for (uint32_t i{0}; i < 100000; i++) {
mat4.MatrixOfMinors(mat5);
}
}
SECTION("Invert") {
for (uint32_t i{0}; i < 100000; i++) {
mat5 = mat4.Invert();
}
};
SECTION("Transpose") {
for (uint32_t i{0}; i < 10000; i++) {
mat3 = mat1.Transpose();
}
}
SECTION("Normalize") {
for (uint32_t i{0}; i < 10000; i++) {
mat1.Normalize(mat3);
}
}
SECTION("GET ROW") {
Matrix<1, 50> mat1Rows{};
for (uint32_t i{0}; i < 1000000; i++) {
mat1.GetRow(0, mat1Rows);
}
}
SECTION("GET COLUMN") {
Matrix<50, 1> mat1Columns{};
for (uint32_t i{0}; i < 1000000; i++) {
mat1.GetColumn(0, mat1Columns);
}
}
}

View File

@@ -1,103 +0,0 @@
// include the unit test framework first
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
// include the module you're going to test next
#include "Quaternion.h"
// any other libraries
#include <array>
#include <cmath>
#include <iostream>
TEST_CASE("Vector Math", "Vector")
{
Quaternion q1{1, 2, 3, 4};
Quaternion q2{5, 6, 7, 8};
SECTION("Initialization")
{
// explicit initialization
REQUIRE(q1.w == 1);
REQUIRE(q1.v1 == 2);
REQUIRE(q1.v2 == 3);
REQUIRE(q1.v3 == 4);
// fill initialization
Quaternion q3{0};
REQUIRE(q3.w == 0);
REQUIRE(q3.v1 == 0);
REQUIRE(q3.v2 == 0);
REQUIRE(q3.v3 == 0);
// copy initialization
Quaternion q4{q1};
REQUIRE(q4.w == 1);
REQUIRE(q4.v1 == 2);
REQUIRE(q4.v2 == 3);
REQUIRE(q4.v3 == 4);
// matrix initialization
Matrix<1, 4> m1{1, 2, 3, 4};
Quaternion q5{m1};
REQUIRE(q5.w == 1);
REQUIRE(q5.v1 == 2);
REQUIRE(q5.v2 == 3);
REQUIRE(q5.v3 == 4);
// array initialization
Quaternion q6{std::array<float, 4>{1, 2, 3, 4}};
REQUIRE(q6.w == 1);
REQUIRE(q6.v1 == 2);
REQUIRE(q6.v2 == 3);
REQUIRE(q6.v3 == 4);
}
SECTION("Equals")
{
Quaternion q3{0, 0, 0, 0};
q3 = q1;
REQUIRE(q3.w == 1);
REQUIRE(q3.v1 == 2);
REQUIRE(q3.v2 == 3);
REQUIRE(q3.v3 == 4);
}
SECTION("Array access")
{
REQUIRE(q1[0] == 1);
REQUIRE(q1[1] == 2);
REQUIRE(q1[2] == 3);
REQUIRE(q1[3] == 4);
}
SECTION("Addition")
{
Quaternion q3 = q1 + q2;
REQUIRE(q3.w == 6);
REQUIRE(q3.v1 == 8);
REQUIRE(q3.v2 == 10);
REQUIRE(q3.v3 == 12);
}
SECTION("Multiplication")
{
Quaternion q3;
q1.Q_Mult(q2, q3);
REQUIRE(q3.w == -60);
REQUIRE(q3.v1 == 12);
REQUIRE(q3.v2 == 30);
REQUIRE(q3.v3 == 24);
}
SECTION("Rotation")
{
Quaternion q3{Quaternion::FromAngleAndAxis(M_PI / 2, Matrix<1, 3>{0, 0, 1})};
Quaternion q4{0, 1, 0, 0};
Quaternion q5;
q3.Rotate(q4, q5);
REQUIRE_THAT(q5.v1, Catch::Matchers::WithinRel(0.0f, 1e-6f));
REQUIRE_THAT(q5.v2, Catch::Matchers::WithinRel(1.0f, 1e-6f));
REQUIRE_THAT(q5.v3, Catch::Matchers::WithinRel(0.0f, 1e-6f));
}
}

View File

@@ -0,0 +1,7 @@
# be in the root folder of this project when you run this
cd build/
ninja matrix-tests
echo "Running tests. This will take a while."
./unit-tests/matrix-tests -n "Timing Tests" -d yes > ../unit-tests/matrix-test-timings-temp.txt
cd ../unit-tests/
python3 test-timing-post-process.py

View File

@@ -1,56 +0,0 @@
Randomness seeded to: 2444679151
0.180 s: Addition
0.180 s: Timing Tests
0.177 s: Subtraction
0.177 s: Timing Tests
1.868 s: Multiplication
1.868 s: Timing Tests
0.127 s: Scalar Multiplication
0.127 s: Timing Tests
0.173 s: Element Multiply
0.173 s: Timing Tests
0.178 s: Element Divide
0.178 s: Timing Tests
0.172 s: Minor Matrix
0.172 s: Timing Tests
0.103 s: Determinant
0.103 s: Timing Tests
0.411 s: Matrix of Minors
0.411 s: Timing Tests
0.109 s: Invert
0.109 s: Timing Tests
0.122 s: Transpose
0.122 s: Timing Tests
0.190 s: Normalize
0.190 s: Timing Tests
0.006 s: GET ROW
0.006 s: Timing Tests
0.235 s: GET COLUMN
0.235 s: Timing Tests
===============================================================================
test cases: 1 | 1 passed
assertions: - none -
Command being timed: "build/unit-tests/matrix-timing-tests -d yes"
User time (seconds): 4.05
System time (seconds): 0.00
Percent of CPU this job got: 100%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:04.05
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 3200
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 184
Minor (reclaiming a frame) page faults: 171
Voluntary context switches: 1
Involuntary context switches: 26
Swaps: 0
File system inputs: 12
File system outputs: 1
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0

View File

@@ -1,45 +0,0 @@
// include the unit test framework first
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
// include the module you're going to test next
#include "Vector3D.hpp"
#include "Matrix.hpp"
// any other libraries
#include <array>
#include <cmath>
#include <iostream>
TEST_CASE("Vector Math", "Vector")
{
V3D<float> v1{1, 2, 3};
V3D<float> v2{4, 5, 6};
V3D<float> v3{};
SECTION("Initialization")
{
// list initialization
REQUIRE(v1.x == 1);
REQUIRE(v1.y == 2);
REQUIRE(v1.z == 3);
// copy initialization
V3D<float> v4{v2};
REQUIRE(v4.x == 4);
REQUIRE(v4.y == 5);
REQUIRE(v4.z == 6);
// empty initialization
REQUIRE(v3.x == 0);
REQUIRE(v3.y == 0);
REQUIRE(v3.z == 0);
// matrix initialization
Matrix<1, 3> mat1{v1.ToArray()};
V3D<float> v5{mat1};
REQUIRE(v5.x == v1.x);
REQUIRE(v5.y == v1.y);
REQUIRE(v5.z == v1.z);
}
}