Skip to content

Conversation

@Jullija
Copy link

@Jullija Jullija commented Oct 26, 2025

Added

Types

  • cartesian_vector — fixed 3D vector
  • cartesian_tensor<T,R,C> — matrix/tensor with R,C ∈ [1..3]

Vector ops (a,b : cartesian_vector ; s : scalar)

  • a + b — elementwise addition
  • a - b — elementwise subtraction
  • a % b — elementwise modulo (% for integral, fmod for floating)
  • a * s, s * a — scalar multiply
  • a / s — scalar divide
  • a ⋅ b (scalar_product, alias dot) — dot product → scalar
  • a × b (vector_product, alias cross) — cross product → vector
  • |a| (magnitude, norm) — Euclidean length (floating reps only)
  • a/|a| (unit) — unit vector (floating reps only)

Tensor ops (A,B : cartesian_tensor<R,C> ; M:N, matching dims; s: scalar)

  • A + B, A - B — elementwise
  • A % B — elementwise modulo (% / fmod as above)
  • A * s, s * A, A / s — scalar scale
  • A · x (matvec) — 3×3 tensor–vector product → vector
  • A · B (matmul) — matrix multiply (R×K)*(K×C) → (R×C)
  • A : B (double_contraction) — scalar (sum of elementwise products)
  • a ⊗ b (outer_numeric) — vector outer product → 3×3 tensor

@Jullija Jullija changed the title Implement simple vector and tensor types [DRAFT] Implement simple vector and tensor types Oct 26, 2025
@Jullija Jullija marked this pull request as draft October 26, 2025 16:29
@Jullija Jullija force-pushed the feature/la-vectors branch from 20a6db0 to 7bc1e18 Compare October 26, 2025 16:33
Copy link
Owner

@mpusz mpusz left a comment

Choose a reason for hiding this comment

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

Hi!

Thanks for contributing! I had time to review only cartesian_vector changes and provided some feedback that I believe will also be meaningful to another type as well.

Comment on lines -25 to +26
#include <mp-units/bits/requires_hosted.h>
//
#include <mp-units/bits/module_macros.h>
#include <mp-units/bits/requires_hosted.h>
Copy link
Owner

Choose a reason for hiding this comment

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

We put requires_hosted.h as the first header in all HOSTED implementations. Do not use alphabetical order for this header.

Copy link
Owner

Choose a reason for hiding this comment

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

Still left to resolve.

namespace mp_units {

MP_UNITS_EXPORT template<detail::Scalar T>
MP_UNITS_EXPORT template<detail::Scalar T = double>
Copy link
Owner

Choose a reason for hiding this comment

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

It is better to provide template defaults in the implementation rather than in one of (possibly many) forward declarations.

Comment on lines 57 to 58
requires requires(T t, U u) { t + u; }
requires requires(const T& t, const U& u) { t + u; }
Copy link
Owner

Choose a reason for hiding this comment

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

Does it make any difference?

Copy link
Author

Choose a reason for hiding this comment

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

I've read that adding const doesn't mutate an input which is desired in arithmetic operations (https://en.cppreference.com/w/cpp/language/operator_arithmetic.html)

Copy link
Owner

Choose a reason for hiding this comment

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

You are right: https://godbolt.org/z/3zW8ns6YP. Thanks!

namespace detail {

struct cartesian_vector_iface {
// A + B
Copy link
Owner

Choose a reason for hiding this comment

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

Such comments do not improve anything.

Comment on lines 76 to 95
// A % B (integral: %, floating: fmod into CT)
template<typename T, typename U>
requires requires(T t, U u) { t * u; }
[[nodiscard]] friend constexpr auto operator*(const cartesian_vector<T>& lhs, const U& rhs)
requires(requires(const T& t, const U& u) { t % u; }) || (std::floating_point<T> && std::floating_point<U>)
[[nodiscard]] friend constexpr auto operator%(const cartesian_vector<T>& lhs, const cartesian_vector<U>& rhs)
{
using CT = std::common_type_t<T, U>;
if constexpr (std::floating_point<T> && std::floating_point<U>) {
using std::fmod;
return ::mp_units::cartesian_vector<CT>{static_cast<CT>(fmod(static_cast<long double>(lhs._coordinates_[0]),
static_cast<long double>(rhs._coordinates_[0]))),
static_cast<CT>(fmod(static_cast<long double>(lhs._coordinates_[1]),
static_cast<long double>(rhs._coordinates_[1]))),
static_cast<CT>(fmod(static_cast<long double>(lhs._coordinates_[2]),
static_cast<long double>(rhs._coordinates_[2])))};
} else {
return ::mp_units::cartesian_vector<CT>{static_cast<CT>(lhs._coordinates_[0] % rhs._coordinates_[0]),
static_cast<CT>(lhs._coordinates_[1] % rhs._coordinates_[1]),
static_cast<CT>(lhs._coordinates_[2] % rhs._coordinates_[2])};
}
}
Copy link
Owner

Choose a reason for hiding this comment

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

There is no op% for floating-point types for a reason. We should behave like fundamental types, so it should not be available as well for floating-point representation types. If we care about modulo for such types, we should provide fmod non-member function. See fmod from math.h for more info.

Comment on lines 288 to 333
template<typename FormatContext>
auto format(const mp_units::cartesian_vector<T>& vec, FormatContext& ctx) const
template<typename Ctx>
auto format(const mp_units::cartesian_vector<T>& v, Ctx& ctx) const
{
return format_to(ctx.out(), "[{}, {}, {}]", vec[0], vec[1], vec[2]);
return format_to(ctx.out(), "[{}, {}, {}]", v[0], v[1], v[2]);
Copy link
Owner

Choose a reason for hiding this comment

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

Please restore the previous code. FormatContext provides more info and is used consistently in the entire project for all formatters.

v is too short as an identifier and often causes some stupid shadowing errors in MSVC.

import mp_units;
#else
#include <mp-units/cartesian_vector.h>
#if MP_UNITS_HOSTED
Copy link
Owner

Choose a reason for hiding this comment

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

Runtime tests are only run for HOSTED so no need to check here.

Comment on lines 44 to 46
REQUIRE(v[0] == 0);
REQUIRE(v[1] == 0);
REQUIRE(v[2] == 0);
Copy link
Owner

Choose a reason for hiding this comment

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

Should be garbage

Copy link
Owner

Choose a reason for hiding this comment

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

This is why I didn't provide default construction.


SECTION("zero arguments")
{
cartesian_vector<double> v{};
Copy link
Owner

Choose a reason for hiding this comment

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

This is how zeros should be set, but this collides with your default-constructor now

using namespace Catch::Matchers;
using Catch::Matchers::WithinAbs;

TEST_CASE("cartesian_vector initialization and access", "[vector]")
Copy link
Owner

Choose a reason for hiding this comment

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

I personally prefer to have fewer TEST_CASES in catch, so it is easier to manage. For example, if I want to check if all vector tests still work, it can check the status of one easy-to-find test rather than searching for many different strings.

Filtering by a tag is not often an option.

@Jullija
Copy link
Author

Jullija commented Oct 26, 2025

Hi! Thank you so much! I'll start reading and implementing changes - thank you and sorry in advance for any problems! :)

Comment on lines 149 to 173
cartesian_vector() = default;
cartesian_vector(const cartesian_vector&) = default;
cartesian_vector(cartesian_vector&&) = default;
cartesian_vector& operator=(const cartesian_vector&) = default;
cartesian_vector& operator=(cartesian_vector&&) = default;

template<typename... Args>
requires(... && std::constructible_from<T, Args>)
requires(sizeof...(Args) <= 3) && (... && std::constructible_from<T, Args>)
constexpr explicit(!(... && std::convertible_to<Args, T>)) cartesian_vector(Args&&... args) :
_coordinates_{static_cast<T>(std::forward<Args>(args))...}
{
}

template<typename U>
requires std::constructible_from<T, U>
requires std::constructible_from<T, const U&>
constexpr explicit(!std::convertible_to<U, T>) cartesian_vector(const cartesian_vector<U>& other) :
_coordinates_{static_cast<T>(other[0]), static_cast<T>(other[1]), static_cast<T>(other[2])}
{
}

template<typename U>
requires std::constructible_from<T, U>
requires std::constructible_from<T, U&&>
constexpr explicit(!std::convertible_to<U, T>) cartesian_vector(cartesian_vector<U>&& other) :
_coordinates_{static_cast<T>(std::move(other[0])), static_cast<T>(std::move(other[1])),
static_cast<T>(std::move(other[2]))}
Copy link
Owner

Choose a reason for hiding this comment

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

Let's remove all of those. This will result in an aggregate type (such as std::array), which should solve all our issues. Please, change the comment next to a public member to reflect that.

@Jullija Jullija force-pushed the feature/la-vectors branch 9 times, most recently from f3f2391 to 00b2e74 Compare November 10, 2025 20:46
@Jullija
Copy link
Author

Jullija commented Nov 11, 2025

@mpusz could you help me fix the pipeline? I tried in some many ways and I have no idea how to fix it :c

@mpusz
Copy link
Owner

mpusz commented Nov 12, 2025

I am currently on vacation in Hawaii, so I don't have much time to look into it. I will check it out when I am back.

@Jullija Jullija force-pushed the feature/la-vectors branch 2 times, most recently from 1618ca3 to e564751 Compare November 20, 2025 18:41
@Jullija Jullija changed the title [DRAFT] Implement simple vector and tensor types Implement simple vector and tensor types Nov 20, 2025
@Jullija Jullija marked this pull request as ready for review November 20, 2025 19:21
@Jullija Jullija requested a review from mpusz November 20, 2025 19:38
Comment on lines -25 to +26
#include <mp-units/bits/requires_hosted.h>
//
#include <mp-units/bits/module_macros.h>
#include <mp-units/bits/requires_hosted.h>
Copy link
Owner

Choose a reason for hiding this comment

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

Still left to resolve.

#pragma once

#include <mp-units/bits/module_macros.h>
#include <mp-units/bits/requires_hosted.h>
Copy link
Owner

Choose a reason for hiding this comment

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

Should be the first include

Comment on lines +157 to +164
MP_UNITS_EXPORT template<typename T, typename U>
requires requires(const T& t, const U& u) { t + u; }
[[nodiscard]] constexpr auto operator+(const cartesian_vector<T>& lhs, const cartesian_vector<U>& rhs)
{
return ::mp_units::cartesian_vector{lhs._coordinates_[0] + rhs._coordinates_[0],
lhs._coordinates_[1] + rhs._coordinates_[1],
lhs._coordinates_[2] + rhs._coordinates_[2]};
}
Copy link
Owner

Choose a reason for hiding this comment

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

All of the operators and other customization points should be hidden friends as they were before.

{
cartesian_vector<double> v2 = v1;
cartesian_vector v1{1.0, 2.0, 3.0};
cartesian_vector<double> v2 = v1; // Use initialization instead of assignment
Copy link
Owner

Choose a reason for hiding this comment

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

This is not an assignment

static_assert(std::constructible_from<cartesian_vector<double>, quantity<one, cartesian_vector<double>>>);
static_assert(!std::convertible_to<quantity<one, double>, cartesian_vector<double>>);
static_assert(std::constructible_from<cartesian_vector<double>, quantity<one, double>>);
static_assert(std::is_aggregate_v<cartesian_vector<double>>);
Copy link
Owner

Choose a reason for hiding this comment

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

This change makes no sense and removes proper test

#else
#include <mp-units/cartesian_vector.h>
#include <mp-units/ext/format.h>
#include <sstream>
Copy link
Owner

Choose a reason for hiding this comment

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

Included twice

#endif

#ifndef MP_UNITS_MODULES
#include <sstream>
Copy link
Owner

Choose a reason for hiding this comment

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

import std; missing.

Comment on lines +223 to +226
REQUIRE_THAT(u.magnitude(), WithinAbs(1.0, 1e-12));
REQUIRE_THAT(u[0], WithinAbs(3.0 / 5.0, 1e-12));
REQUIRE_THAT(u[1], WithinAbs(4.0 / 5.0, 1e-12));
REQUIRE_THAT(u[2], WithinAbs(0.0, 1e-12));
Copy link
Owner

Choose a reason for hiding this comment

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

Use AlmostEquals instead

}
}

TEST_CASE("cartesian_vector text output", "[vector][fmt][ostream]")
Copy link
Owner

Choose a reason for hiding this comment

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

Why did you remove my tests that were testing each text output separately for different representation types?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants