Skip to content

Commit

Permalink
Support rotors and translators in PGA3 and improve build configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremyong committed Oct 7, 2019
1 parent 0e29568 commit 2080e2c
Show file tree
Hide file tree
Showing 21 changed files with 403 additions and 129 deletions.
39 changes: 32 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,25 +1,50 @@
cmake_minimum_required(VERSION 3.15)
project(gal LANGUAGES C CXX)

if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(GAL_STANDALONE ON)
else()
set(GAL_STANDALONE OFF)
endif()

# NOTE: When GAL is included as a transitive dependency, these switches are disabled
option(GAL_TESTS_ENABLED "Enable GAL test compilation" ON)
option(GAL_SAMPLES_ENABLED "Enable GAL samples compilation" ON)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# NEVER mutate global cmake state unless we are building as a standalone project
if (GAL_STANDALONE)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY})

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
endif()

# Requires libfmt
add_subdirectory(src)

if (GAL_TESTS_ENABLED)
if (GAL_TESTS_ENABLED AND GAL_STANDALONE)
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
conan_basic_setup(NO_OUTPUT_DIRS TARGETS SKIP_STD KEEP_RPATHS SKIP_FPIC)

enable_testing()
add_subdirectory(test)
endif()

if (GAL_SAMPLES_ENABLED)
if (GAL_SAMPLES_ENABLED AND GAL_STANDALONE)
add_subdirectory(samples)
endif()

42 changes: 34 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,32 @@ GAL is in the early stages of development, so please stay tuned for more!

Being a template-library, GAL is header-only and can be installed by either linking the `gal` interface target via cmake or by copying the files in `src` to a known include path.

To build the tests, you need a C++ compiler (currently untested via MSVC) that supports C++17 or greater.
Conan is used to easily fetch `doctest` and `fmt` as additional dependencies. Neither are required for GAL
to function but you will need to link `fmt` in your own code if you wish to include `formatters.hpp` for
pretty type printing.

```sh
# From the root directory of this project
mkdir build
cd build

# If you don't have conan installed already, the easiest way to get it is with `pip install conan` with
# a working python 3 installation (use `pip install conan --user` for a local installation)

# Install test dependencies
conan install ..

# Optionally supply release type, flags, etc and pick your favorite generator
cmake .. -G Ninja

# ... or whichever (ideally multicore-friendly) build system you choose
ninja

# Run the tests
./bin/gal_test
```

## Motivation

Geometric Algebra promises (and fulfills) a unified algebraic system for manipulating geometric objects
Expand All @@ -37,7 +63,7 @@ produced it. In GA, such information is encoded by higher ordered basis elements
proferred in the typical R3 vector space. This is what makes GA a *graded* algebra. The second important
(and necessary) piece that gives GA its power are the operations between elements of this algebra. Without
going into much detail, the geometric product encodes both projections and rejections nicely within the
framework, allow the *conjugate* (aka "sandwich") operator to elegantly supply interpolatable (read.
framework supplies us with the *conjugate* (aka "sandwich") operator to elegantly supply interpolatable (read.
differentiable) rotations and translations of any geometric object. All of this is to say, the state
space of the *code* needed to express a vast range of computation is compressed significantly.

Expand All @@ -57,14 +83,14 @@ to act on higher-order elements than just vectors). Under CGA, the contraction o
the compiler would be unable to optimize this as such in general. GAL makes the following code possible:

```c++
using gal::scalar;
using gal::cga::point;
using scalar = gal::cga::scalar<float>;
using point = gal::cga::point<float>;

float construct_plane(point<float> p)
float construct_plane(point p)
{
gal::engine engine{p};

return engine.compute<scalar<float>>([](auto p)
return engine.compute<scalar>([](auto p)
{
// Contract a cga point back onto itself
// Note that p here contains no actual data! It is just the type that represents a CGA point
Expand Down Expand Up @@ -144,12 +170,12 @@ CHECK_EQ(p2.x * p.x + p2.y * p.y + p2.z * p.z + p.d, epsilon);
CHECK_EQ(p3.x * p.x + p3.y * p.y + p3.z * p.z + p.d, epsilon);
```
### Projective Geometric Algebra
## TODO
- Implement additional geometric objects (even sub-algebra versors, etc)
- Implement additional operators (exponential maps and logarithms)
- Implement the Conformal Geometric Algebra
- Implement logarithms/derivatives
- Flesh out metrics that aren't PGA3 with the same functionality (rotors/translators)
- Add sample applications and provide benchmark
- Add an additional engine that compiles expressions to SPIR-V or shader code
- Add additional documentation
Expand Down
1 change: 1 addition & 0 deletions conanfile.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[requires]
doctest/2.3.4@bincrafters/stable
fmt/6.0.0

[generators]
cmake
4 changes: 2 additions & 2 deletions samples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
add_executable(gal_samples main.cpp)
target_link_libraries(gal_samples PRIVATE gal)
target_link_libraries(gal_samples PRIVATE gal CONAN_PKG::fmt)

target_compile_options(gal_samples PRIVATE -save-temps=obj -O3 -DNDEBUG)
# target_compile_options(gal_samples PRIVATE -save-temps=obj -O3 -DNDEBUG)
4 changes: 2 additions & 2 deletions samples/main.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include <pga.hpp>
#include <formatters.hpp>

int main(int argc, char** argv)
{
// TODO
fmt::print("IMPLEMENT ME\n");
return 0;
}
3 changes: 2 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
add_library(gal INTERFACE)
target_include_directories(gal INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(gal INTERFACE fmt)
target_link_libraries(gal INTERFACE fmt)
target_compile_features(gal INTERFACE cxx_std_17)
13 changes: 3 additions & 10 deletions src/cga.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace cga
// The CGA is a graded algebra with 32 basis elements
using algebra = ga::algebra<metric>;

GAL_OPERATORS(algebra)
GAL_OPERATORS(algebra);

template <int X, int Y, int Z>
using point_t = multivector<void,
Expand All @@ -29,6 +29,7 @@ namespace cga
struct alignas(16) point
{
using value_t = T;
constexpr static size_t size = 3;

template <typename Q, size_t ID>
using p2
Expand Down Expand Up @@ -68,15 +69,7 @@ namespace cga
T w;
};

[[nodiscard]] constexpr const T& operator[](size_t index) const noexcept
{
return *(reinterpret_cast<const T*>(this) + index);
}

[[nodiscard]] constexpr T& operator[](size_t index) noexcept
{
return *(reinterpret_cast<T*>(this) + index);
}
GAL_ACCESSORS

template <typename Engine, typename... I>
[[nodiscard]] constexpr static point<T> convert(const Engine& engine, multivector<void, I...> mv) noexcept
Expand Down
2 changes: 1 addition & 1 deletion src/ega.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace ega

using algebra = ga::algebra<metric>;

GAL_OPERATORS(algebra)
GAL_OPERATORS(algebra);

template <size_t ID>
using t = term<element<0>, monomial<one, generator<tag<ID>>>>;
Expand Down
51 changes: 47 additions & 4 deletions src/engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ namespace gal
// TODO: SIMD engine
// TODO: constraint evaluation

// Memoization trie used to store and retrieve results of subexpressions that repeat during a computation
// The number of combinations possible of multiplications between N factors scales as the factorial of N, so it is
// unreasonable to use a dense representation for memoized products. However, a total ordering exists between the
// generators so intermediate products are amenable to storage in a trie-like structure.

template <typename... T>
struct mem_trie
{
};

template <typename... I>
class engine
{
Expand Down Expand Up @@ -68,14 +78,47 @@ class engine
[[nodiscard]] constexpr T evaluate(generator<Tag, Degree, Order>) const noexcept
{
static_assert(!Tag::untagged, "A generator in the field field of an expression is unlabeled");
// TODO: permit substitution of a different power function for more exotic types
return std::pow(get<T, Tag::id, Tag::index>(), Degree::value);
// TODO: leverage sub-expression memoization table to accelerate compile times and unoptimized codegen
if constexpr (Degree::value == 0)
{
return T{1};
}
else
{
const auto value = get<T, Tag::id, Tag::index>();
constexpr bool is_derived
= std::is_same<typename std::decay<decltype(value)>::type, derived_generator<T>>::value;
// TODO: memoize derived results
if constexpr (Degree::value == 1)
{
if constexpr (is_derived)
{
return value.value;
}
else
{
return value;
}
}
else
{
// TODO: permit substitution of a different power function for more exotic types
if constexpr (is_derived)
{
return std::pow(value.value, Degree::value);
}
else
{
return std::pow(value, Degree::value);
}
}
}
}

template <typename T, size_t ID, size_t Index>
[[nodiscard]] constexpr const auto& get() const noexcept
[[nodiscard]] constexpr auto get() const noexcept
{
return std::get<ID>(data)[Index];
return std::get<ID>(data).template get<Index>();
}

template <size_t... N>
Expand Down
8 changes: 4 additions & 4 deletions src/formatters.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ struct formatter<gal::term<E, Monomials...>>
{
if constexpr (sizeof...(Monomials) == 0)
{
return ctx.out();
return format_to(ctx.out(), "0");
}

format_to(ctx.out(), "[");
Expand Down Expand Up @@ -253,7 +253,7 @@ struct formatter<gal::term<E, Monomials...>>
};

template <typename... Terms>
struct formatter<gal::multivector<void, Terms...>>
struct formatter<::gal::multivector<void, Terms...>>
{
template <typename PC>
constexpr auto parse(PC& ctx)
Expand Down Expand Up @@ -284,7 +284,7 @@ struct formatter<gal::multivector<void, Terms...>>
{
if (!first)
{
format_to(ctx.out(), " + {}", T{});
format_to(ctx.out(), "\n + {}", T{});
}
else
{
Expand All @@ -307,7 +307,7 @@ struct formatter<gal::multivector<void, Terms...>>
}
else
{
format_to(ctx.out(), " + {}", T{});
format_to(ctx.out(), "\n + {}", T{});
}
}
}
Expand Down
33 changes: 28 additions & 5 deletions src/ga.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -373,12 +373,12 @@ struct scalar

#define GAL_OPERATORS(Algebra) \
template <typename... I, typename... J> \
[[nodiscard]] constexpr auto operator>>(::gal::multivector<void, I...> lhs, ::gal::multivector<void, J...> rhs) noexcept \
[[nodiscard]] constexpr auto operator>>(multivector<void, I...> lhs, multivector<void, J...> rhs) noexcept \
{\
return ::gal::detail::product<Algebra::contract>(lhs, rhs);\
}\
template <typename... I, typename... J>\
[[nodiscard]] constexpr auto operator^(::gal::multivector<void, I...> lhs, ::gal::multivector<void, J...> rhs) noexcept\
[[nodiscard]] constexpr auto operator^(multivector<void, I...> lhs, multivector<void, J...> rhs) noexcept\
{\
return ::gal::detail::product<Algebra::exterior>(lhs, rhs);\
}\
Expand All @@ -401,7 +401,30 @@ struct scalar
[[nodiscard]] constexpr auto operator&(M1 lhs, M2 rhs) noexcept\
{\
return !(!lhs ^ !rhs);\
}


}\
template <typename T = float> using scalar = ::gal::scalar<T>;\
using ::gal::simplify;\
using ::gal::scale

#define GAL_ACCESSORS \
[[nodiscard]] constexpr const T& operator[](size_t index) const noexcept\
{\
return *(reinterpret_cast<const T*>(this) + index);\
}\
[[nodiscard]] constexpr T& operator[](size_t index) noexcept\
{\
return *(reinterpret_cast<T*>(this) + index);\
}\
template <size_t I>\
[[nodiscard]] constexpr auto get() const noexcept\
{\
if constexpr (I < size)\
{\
return *(reinterpret_cast<const T*>(this) + I);\
}\
else\
{\
return derived_generator<T>{get_special(std::integral_constant<size_t, I>{})};\
}\
}
} // namespace gal
Loading

0 comments on commit 2080e2c

Please sign in to comment.