Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recommended way to create 3 dimensional magnetic field? #780

Open
krwq opened this issue Apr 25, 2020 · 24 comments
Open

Recommended way to create 3 dimensional magnetic field? #780

krwq opened this issue Apr 25, 2020 · 24 comments
Labels
enhancement pinned Issues that should not be auto-closed due to inactivity.

Comments

@krwq
Copy link
Contributor

krwq commented Apr 25, 2020

We're currently considering to use this library in https://github.com/dotnet/iot/ . We have initially started developing very similar APIs and then noticed this library so we decided to give it a try since it already solves many problems we were about to hit.

We have converted our Temperature/Pressure structs to this library without any issues (see dotnet/iot#1052) but now we're trying to use 3-dimensional magnetic field which is the output of IMU (i.e. BNO055 sensor). We haven't ourselves developed such unit yet and used Vector3.

What's the recommended way to achieve 3-dimensional magnetic field?

@pgrawehr
Copy link
Contributor

Do I understand correctly that you would like to have a Vector3<MagneticField> or Vector3<Speed> instance? I guess that will end up with the same problems that were also discussed in #695.

Consider this code:

public class Vector3<T>
{
    private T _value;
    public Vector3(T value)
    {
        _value = value;
    }
    
    public static Vector3<T> operator+(Vector3<T> a, Vector3<T> b)
    {
        return new Vector3(a._value + b._value); // ERROR: T does not have an operator +
    }
    // ....
}

Unfortunatelly the above doesn't work, because the generic type "T" (aka any type) does not have a defined + operator. There's no way to add a type constraint on "something that is a number". And declaring interfaces (IImplementsAddition) for operator methods is also not possible, because in C# they need to be static by definition 😢 ...

This works:

    public static Vector3<T> operator+(Vector3<T> a, Vector3<T> b)
    {
        dynamic a1 = a;
        dynamic b1 = b;
        return new Vector3(a1._value + b1._value); // Works!
    }

But of course, this comes with a performance penalty (I don't know how big it is) and the downside that problems will pop up only at runtime and not at compile time.

@angularsen
Copy link
Owner

Hi,

UnitsNet does not currently support XYZ-dimensional quantities in a generic way.
Some discussion in #695 on why this was not straight forward to add, but it is absolutely possible if someone wants to champion it and a performant proposal is outlined in #666.

You can, however, easily add your own wrapper types for specific quantities.
Something like this:

public struct MagneticField3
{
    public MagneticField X { get; }
    public MagneticField Y { get; }
    public MagneticField Z { get; }
    
    public MagneticField3(MagneticField x, MagneticField y, MagneticField z) { ... }

    // Operator overloads for arithmetic
    public static MagneticField3 operator +(MagneticField3 left, MagneticField3 right) { ... }
    public static MagneticField3 operator -(MagneticField3 left, MagneticField3 right) { ... }
    public static MagneticField3 operator *(double left, MagneticField3 right) { ... }
    public static MagneticField3 operator /(MagneticField3 left, double right) { ... }

Did that answer your question?

@krwq
Copy link
Contributor Author

krwq commented Apr 26, 2020

@pgrawehr not necessarily, you could alternatively have MagneticField3 struct. Generally speaking the magnetic field definition talks about vectors and not scalars.

I agree it's not ideal that above cannot be expressed too well in C# and generic vector would be a perfect solution.

@tmilnthorp
Copy link
Collaborator

tmilnthorp commented Apr 26, 2020

We could use the code waiting in #698 to do this in a generic way, and without a large performance hit.

@pgrawehr
Copy link
Contributor

Sounds good, but would apparently also need the definition of the Vector and Matrix classes to be useful, right? A matrix multiplication would be a good test case for the performance impact.

@krwq
Copy link
Contributor Author

krwq commented Apr 27, 2020

Honestly for units I only care about vector. Matrices are probably only used in more advanced physics (i.e. jacobian) which usually need super high performance in which case you'd likely write your own version anyway. Good idea for completeness but I'd personally skip it for now.

@angularsen that should be fine for now although this is something I'd really love to see here in the future (although understand the limitations and why not yet)

@tmilnthorp
Copy link
Collaborator

I created #801 as a POC. I assume you would expect to write something as follows:

            var length1X = Length.FromMeters(1.0);
            var length1Y = Length.FromMeters(2.0);
            var length1Z = Length.FromMeters(3.0);
            var length2X = Length.FromMeters(4.0);
            var length2Y = Length.FromMeters(5.0);
            var length2Z = Length.FromMeters(6.0);

            var vector1 = new Vector3<Length>(length1X, length1Y, length1Z);
            var vector2 = new Vector3<Length>(length2X, length2Y, length2Z);

            var result = vector1 + vector2;

            var expectedX = Length.FromMeters(5.0);
            var expectedY = Length.FromMeters(7.0);
            var expectedZ = Length.FromMeters(9.0);

            Assert.Equal( new Vector3<Length>( expectedX, expectedY, expectedZ ), result );

@pgrawehr
Copy link
Contributor

Nice start, and nice that this works with base types, too:


        [Fact]
        public void WorksWithBaseTypes()
        {
            double length1X = 1.0;
            double length1Y = 2.0;
            double length1Z = 3.0;
            double length2X = 4.0;
            double length2Y = 5.0;
            double length2Z = 6.0;

            var vector1 = new Vector3<double>(length1X, length1Y, length1Z);
            var vector2 = new Vector3<double>(length2X, length2Y, length2Z);

            var result = vector1 + vector2;

            double expectedX = 5.0;
            double expectedY = 7.0;
            double expectedZ = 9.0;

            Assert.Equal(new Vector3<double>(expectedX, expectedY, expectedZ), result);
        }

@pgrawehr
Copy link
Contributor

This doesn't make a lot of sense, but I just tried to do:

        [Fact]
        public void ThisIsWeird()
        {
            var vector1 = new Vector3<String>("A", "B", "C");
            var vector2 = new Vector3<String>("D", "E", "F");

            Assert.Equal("AD", (vector1 + vector2).X);
        }

Not that I would have a meaningful usage for this, but this failed with

System.TypeInitializationException : The type initializer for 'AddImplementation`3' threw an exception.
---- System.InvalidOperationException : The binary operator Add is not defined for the types 'System.String' and 'System.String'.
   at UnitsNet.CompiledLambdas.AddImplementation`3.Invoke(TLeft left, TRight right) in C:\projects\UnitsNet\UnitsNet\CompiledLambdas.cs:line 224
   at UnitsNet.CompiledLambdas.Add[T](T left, T right) in C:\projects\UnitsNet\UnitsNet\CompiledLambdas.cs:line 61
   at UnitsNet.Vector3`1.op_Addition(Vector3`1 left, Vector3`1 right) in C:\projects\UnitsNet\UnitsNet\Vector3.cs:line 59
   at UnitsNet.Tests.VectorTests.ThisIsWeird() in C:\projects\UnitsNet\UnitsNet.Tests\VectorTests.cs:line 73

String does have a binary + operator, so this message confuses me a bit and I'm worried we're overlooking some meaningful use case here?

@tmilnthorp
Copy link
Collaborator

String does not have a + operator 😃

It is syntactic sugar for the compiler to generate string.Concat.

@tmilnthorp tmilnthorp linked a pull request May 29, 2020 that will close this issue
@stale
Copy link

stale bot commented Jul 27, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Jul 27, 2020
@stale stale bot closed this as completed Aug 3, 2020
@tmilnthorp tmilnthorp added pinned Issues that should not be auto-closed due to inactivity. and removed wontfix labels Aug 4, 2020
@tmilnthorp tmilnthorp reopened this Aug 4, 2020
@tmilnthorp
Copy link
Collaborator

@krwq @pgrawehr What methods/operators would you like to see? I assume something similar to Vector3?

@krwq
Copy link
Contributor Author

krwq commented Sep 9, 2020

@tmilnthorp yes, something similar would be perfect. At minimum I think:

  • add
  • subtract
  • multiply by scalar
  • divide by scalar
  • perhaps easy way to do conversion to Vector3 and back
  • Length/Magnitude
  • Direction (Normalize returning Vector3 or similar)

Note that things like Normalize and similar could not have units anymore so they would have to return regular Vector3

@pgrawehr
Copy link
Contributor

pgrawehr commented Sep 9, 2020

Yea, that sounds good. The Vector is not limited to 3 dimensions though, right?
Shall we have the Matrix, too? If desired, I can provide the implementation for it. Seeing that System.Numerics doesn't seem to provide an arbitrary matrix class, this would be a plus.

@krwq
Copy link
Contributor Author

krwq commented Sep 9, 2020

@pgrawehr Vector in .NET is more about SIMD instructions. I currently don't see any larger short term benefits of having Matrix in IoT repo but Vector3 is being used quite a bit

@pgrawehr
Copy link
Contributor

pgrawehr commented Sep 9, 2020

Right, IOT will not necessarily benefit, but having a generic matrix class could come in handy in other places. Due to the limits in the .NET generics system (for which we seem to have a good workaround now), this probably hasn't been done yet.

@dschuermans
Copy link
Contributor

This sparked my interest, since we're also using 3D vectors in our code (geometry related)

What I don't quite understand, is the added benefit of adding a vector object to UnitsNet?
As @angularsen pointed out, it is very easy to create your own vector wrapper around the units that you need.

That is what we're doing now too and on top, we are wrapping MathNet.Spatial Vector3D along with it:

    /// <summary>
    /// Represents a vector in 3D space.
    /// A vector describes the change in XYZ between two points in 3D space.
    /// </summary>
    public sealed class Vector : IEquatable<Vector>
    {
        private readonly Vector3D _vector3D;

        private Vector(Vector3D vector)
        {
            _vector3D = vector;
            IsUnitVector = false;
        }

        private Vector(UnitVector3D vector)
        {
            _vector3D = new Vector3D(vector.X, vector.Y, vector.Z);
            IsUnitVector = true;
        }

        private Vector(Point3D point)
        {
            _vector3D = new Vector3D(point.X, point.Y, point.Z);
        }

        public Vector(double x, double y, double z)
            : this(Length.FromMeters(x), Length.FromMeters(y), Length.FromMeters(z)) { }

        public Vector(Length? x, Length? y, Length? z)
            : this(x ?? Length.Zero, y ?? Length.Zero, z ?? Length.Zero) { }

        public Vector(Length x, Length y, Length z)
        {
            _vector3D = new Vector3D(x.Meters, y.Meters, z.Meters);
            IsUnitVector = false;
        }

        public Length X => Length.FromMeters(_vector3D.X);
        public Length Y => Length.FromMeters(_vector3D.Y);
        public Length Z => Length.FromMeters(_vector3D.Z);

        public bool IsUnitVector { get; }

        public double Magnitude => _vector3D.Length;

        public Vector Orthogonal => new Vector(_vector3D.Orthogonal);

        public Vector UnitVector
        {
            get
            {
                if (this.IsUnitVector)
                {
                    return this;
                }

                return new Vector(_vector3D.Normalize());
            }
        }

        internal object GetUnderlyingObject()
        {
            return _vector3D;
        }

        public bool IsVertical
        {
            get
            {
                return Math.Abs(X.Meters) < 1e-3 && Math.Abs(Y.Meters) < 1e-3;
            }
        }

        public Vector Rotate(Vector about, Angle angle)
        {
            UnitVector3D normalizedAbout = about.IsUnitVector ? UnitVector3D.Create(about.X.Meters, about.Y.Meters, about.Z.Meters) : ((Vector3D)about.GetUnderlyingObject()).Normalize();

            Vector3D rotatedVector =
                ((Vector3D) GetUnderlyingObject()).Rotate(normalizedAbout,
                    MathNet.Spatial.Units.Angle.FromRadians(angle.Radians));

            return new Vector(rotatedVector);
        }

        public static Vector Zero => new Vector(0, 0, 0);

        #region Operators

        /// <summary>
        /// Add 2 vectors together, resulting in a new vector
        /// </summary>
        /// <param name="first">A vector</param>
        /// <param name="second">Another vector</param>
        /// <returns>A new vector which is the result of both vectors combined.
        /// This vector describes the change in X, Y and Z to go from the start of the first vector to the end of the second vector.</returns>
        public static Vector operator +(Vector first, Vector second)
        {
            var vector1 = (Vector3D) first.GetUnderlyingObject();
            var vector2 = (Vector3D) second.GetUnderlyingObject();
            Vector3D result = vector1 + vector2;

            return new Vector(result);
        }

        /// <summary>
        /// Subtract 2 vectors from each other, resulting in a new vector
        /// </summary>
        /// <param name="first">A vector</param>
        /// <param name="second">Another vector</param>
        /// <returns>A new vector which is the result of both vectors subtracted from eachother.
        /// This vector describes the change in X, Y and Z to go from the end of the second vector to the end of the first vector</returns>
        public static Vector operator -(Vector first, Vector second)
        {
            var vector1 = (Vector3D)first.GetUnderlyingObject();
            var vector2 = (Vector3D)second.GetUnderlyingObject();
            Vector3D result = vector1 - vector2;

            return new Vector(result);
        }

        /// <summary>
        /// Negate a vector, reversing its direction
        /// </summary>
        /// <param name="vectorToNegate">The vector to negate</param>
        /// <returns></returns>
        public static Vector operator -(Vector vectorToNegate)
        {
            var vector = (Vector3D) vectorToNegate.GetUnderlyingObject();

            Vector3D result = vector.Negate();

            return new Vector(result);
        }

        /// <summary>
        /// Create a unit vector from the provided vector. (= vector normalization)
        /// </summary>
        /// <param name="vectorToNormalize">The vector that needs to be normalized</param>
        /// <returns>A new vector which is the unit vector of the provided vector.
        /// This vector will have a magnitude / size of 1 but the same direction as the provided vector.</returns>
        public static Vector operator !(Vector vectorToNormalize)
        {
            if (vectorToNormalize.IsUnitVector)
            {
                return vectorToNormalize;
            }

            var vector = (Vector3D) vectorToNormalize.GetUnderlyingObject();
            UnitVector3D result = vector.Normalize();

            return new Vector(result);
        }

        /// <summary>
        /// Perform scalar division on a vector (= scale a vector)
        /// If the scalar value is bigger than 1, it will change the magnitude of the vector (make it shorter)
        /// If the scalar value is lesser than 1 but bigger than 0, it will change the magnitude of the vector (make it longer)
        /// If the scalar value is bigger than -1 but lesser than 0, it will reverse the direction and change its magnitude (make it longer)
        /// If the scalar value is -1, it will reverse the direction of the vector but not change its magnitude
        /// If the scalar value is lesser than -1, it will reverse the direction and change its magnitude (make it shorter)
        /// </summary>
        /// <param name="vectorToScale">The vector to scale</param>
        /// <param name="scalar">The scalar value to scale the vector with</param>
        /// <returns>A new vector which is the scaled version of the provided vector</returns>
        public static Vector operator /(Vector vectorToScale, double scalar)
        {
            var vector = (Vector3D)vectorToScale.GetUnderlyingObject();
            Vector3D result = vector.ScaleBy(1/scalar);

            return new Vector(result);
        }

        /// <summary>
        /// Perform scalar multiplication on a vector (= scale a vector).
        /// If the scalar value is bigger than 1, it will change the magnitude of the vector (make it longer).
        /// If the scalar value is -1, it will reverse the direction of the vector but not change its magnitude
        /// If the scalar value is lesser than -1, it will reverse the direction and change the magnitude of the vector (make it longer).
        /// </summary>
        /// <param name="vectorToScale">The vector to scale</param>
        /// <param name="scalar">The scalar value to scale the vector with</param>
        /// <returns>A new vector which is the scaled version of the provided vector</returns>
        public static Vector operator *(Vector vectorToScale, double scalar)
        {
            var vector = (Vector3D) vectorToScale.GetUnderlyingObject();

            Vector3D result = vector.ScaleBy(scalar);

            return new Vector(result);
        }

        /// <summary>
        /// Perform scalar multiplication on a vector (= scale a vector).
        /// If the scalar value is bigger than 1, it will change the magnitude of the vector (make it longer).
        /// If the scalar value is -1, it will reverse the direction of the vector but not change its magnitude
        /// If the scalar value is lesser than -1, it will reverse the direction and change the magnitude of the vector (make it longer).
        /// </summary>
        /// <param name="scalar">The scalar value to scale the vector with</param>
        /// <param name="vectorToScale">The vector to scale</param>
        /// <returns>A new vector which is the scaled version of the provided vector</returns>
        public static Vector operator *(double scalar, Vector vectorToScale)
        {
            return vectorToScale * scalar;
        }

        /// <summary>
        /// Performs the vector cross product operation.
        /// 
        /// </summary>
        /// <param name="first">The first vector to use in the vector cross product operation</param>
        /// <param name="second">The second vector to use in the vector cross product operation</param>
        /// <returns>A new vector which is orthogonal (90° angle) to both vectors</returns>
        public static Vector operator *(Vector first, Vector second)
        {
            var vector1 = (Vector3D) first.GetUnderlyingObject();
            var vector2 = (Vector3D) second.GetUnderlyingObject();

            Vector3D result = vector1.CrossProduct(vector2);

            return new Vector(result);
        }

        /// <summary>
        /// Calculate the magnitude (= size) of the provided vector
        /// </summary>
        /// <param name="vectorToCalculateSizeFor">The vector of which we want to know the size</param>
        /// <returns>A scalar value representing the magnitude of the vector</returns>
        public static double operator ~(Vector vectorToCalculateSizeFor)
        {
            var vector = (Vector3D)vectorToCalculateSizeFor.GetUnderlyingObject();

            return vector.Length;
        }

        /// <summary>
        /// Performs the vector dot product operation
        /// </summary>
        /// <param name="first">The first vector to use in the vector dot product operation</param>
        /// <param name="second">The second vector to use in th vector dot product operation</param>
        /// <returns>A scalar value
        /// If the value is zero, it means the angle between the 2 provided vectors is 90° (which means they are orthogonal / perpendicular)
        /// </returns>
        public static double operator |(Vector first, Vector second)
        {
            var vector1 = (Vector3D)first.GetUnderlyingObject();
            var vector2 = (Vector3D)second.GetUnderlyingObject();

            return vector1.DotProduct(vector2);
        }

        /// <summary>
        /// Convert a Point to a vector
        /// </summary>
        /// <param name="point">The point to convert to a vector</param>
        public static explicit operator Vector(Point point)
        {
            var point3d = (Point3D) point.GetUnderlyingObject();

            return new Vector(point3d);
        }

        public static explicit operator Vector(Vector3D mathNetVector)
        {
            return new Vector(mathNetVector);
        }

        /// <summary>
        /// Checks if both vectors are equal
        /// </summary>
        /// <param name="first">The first vector to compare with</param>
        /// <param name="second">The second vector to compare against</param>
        /// <returns>True if both vectors are equal, false otherwise</returns>
        public static bool operator ==(Vector first, Vector second)
        {
            if (ReferenceEquals(first, null))
            {
                return ReferenceEquals(second, null);
            }

            return first.Equals(second);
        }

        /// <summary>
        /// Checks if both vectors are not equal
        /// </summary>
        /// <param name="first">The first vector to compare with</param>
        /// <param name="second">The second vector to compare against</param>
        /// <returns>True if both vector are not equal, false otherwise</returns>
        public static bool operator !=(Vector first, Vector second)
        {
            if (ReferenceEquals(first, null))
            {
                return !ReferenceEquals(second, null);
            }
            return !first.Equals(second);
        }
        #endregion

        #region IEquatable implementation

        /// <summary>
        /// Checks if this instance is equal to the provided instance
        /// </summary>
        /// <param name="other">The other instance to compare against</param>
        /// <returns>True if both instances are equal</returns>
        public bool Equals(Vector other)
        {
            if (other is null)
            {
                return false;
            }

            var otherVector3D = (Vector3D) other.GetUnderlyingObject();

            return _vector3D.Equals(otherVector3D, 1E-2);
        }

        /// <summary>
        /// Checks if this instance is equal to the provided object
        /// </summary>
        /// <param name="obj">The object to compare against</param>
        /// <returns>True if both instances are equal</returns>
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
            {
                return false;
            }

            if (ReferenceEquals(this, obj))
            {
                return true;
            }

            if (obj.GetType() != this.GetType())
            {
                return false;
            }

            return Equals((Vector) obj);
        }

        /// <summary>
        /// Calculates the hashcode of this instance.
        /// </summary>
        /// <returns>The hashcode of this object</returns>
        public override int GetHashCode()
        {
            return _vector3D.GetHashCode();
        }

        #endregion

        /// <summary>
        /// Returns a user-friendly string representation of this vector in 3D space (X, Y, Z)
        /// </summary>
        /// <returns>A string</returns>
        public override string ToString()
        {
            return $"({X}, {Y}, {Z})";
        }
    }

@pgrawehr
Copy link
Contributor

pgrawehr commented Oct 1, 2020

@dschuermans Your implementation is not generic (but only for Lenght in this case). We want to be able to define Vector<T>, which is not a simple task, due to limitations of how .NET generics work.

@dschuermans
Copy link
Contributor

@pgrawehr So my point remains: what's the benefit of adding a vector object to UnitsNet?
In all other libraries that implement a Vector object, it is unitless. If there is any unit assigned to it, it would be length
It's impossible to have a vector in which X,Y or Z values are different units.

As far as I understand Vectors, it is their values that matter and the result of mathematic operations on them.
Whether you say that the X, Y and Z values are in meters, magnetic field or hell, even cows doesn't matter for the functionality of the vector. The resulting numbers will remain the same.
I'd even dare to say, that in the end it all boils down to Length:

Vectors are a geometric way of representing quantities that have direction as well as magnitude. An example of a vector is force. If we are to fully describe a force on an object we need to specify not only how much force is applied, but also in which direction. Another example of a vector quantity is velocity -- an object that is traveling at ten meters per second to the east has a different velocity than an object that is traveling ten meters per second to the west. This vector is a special case, however, sometimes people are interested in only the magnitude of the velocity of an object. This quantity, a scalar, is called speed which has magnitude but no given direction.
When vectors are written, they are represented by a single letter in bold type or with an arrow above the letter. Some examples of vectors are displacement (e.g. 120 cm at 30°) and velocity (e.g. 12 meters per second north). The only basic SI unit that is a vector is the meter. All others are scalars. Derived quantities can be vector or scalar, but every vector quantity must involve meters in its definition and unit.

Source

So wouldn't a solution for this issue be that you have a unitless vector, yet with some functionality to convert it to a set of X, Y and Z values in a specific unit?

using System;
using UnitsNet;
					
public class Program
{
	public static void Main()
	{
		var vector1 = new Vector(1, 1, 1);
		var vector2 = new Vector(1, 1, 1);
		var result = vector1 + vector2;
		
		Console.WriteLine(result.AsUnit<Length>());
		Console.WriteLine(result.AsUnit<MagneticField>());
	}
}

public sealed class Vector {
	
	public double X {get;set;}
	public double Y {get;set;}
	public double Z {get;set;}
	
	public Vector(double x, double y, double z){
		X = x;
		Y = y;
		Z = z;
	}
	
	public (T X, T Y, T Z) AsUnit<T>()
		where T: IQuantity
	{
			var temp = Activator.CreateInstance<T>();
			
			T x = (T)Quantity.From(X, temp.QuantityInfo.BaseUnit);
			T y = (T)Quantity.From(X, temp.QuantityInfo.BaseUnit);
			T z = (T)Quantity.From(X, temp.QuantityInfo.BaseUnit);
			
			return (x, y , z);
		
	}
	
	public static Vector operator +(Vector first, Vector second)
	{
		return new Vector(first.X + second.X, first.Y + second.Y, first.Z + second.Z);	
	}
}

Output:

(2 m, 2 m, 2 m)
(2 T, 2 T, 2 T)

With a unitless vector, you can fallback onto libraries that specialize in this stuff (e.g. MathNet), instead of reinventing the wheel and creating your own Vector implementation again.

@pgrawehr
Copy link
Contributor

pgrawehr commented Oct 3, 2020

The suggestion above could solve the original issue, that's true. I'll take a look at that MathNet library (I don't know that one), whether it otherwise supports what we need. However, I disagree that it's always length. I've used vectors at least for length, speed, orientation (angles), and force.

Having a generic Vector<T> type on the other hand does have some advantages, though:

  1. It is type safe: It's not possible to accidentally reinterpret a length as magnetic field. Neither is it possible to mix up feet and meters.
  2. It may save memory: It makes a difference whether I allocate thousands of double-typed vectors or float-typed vectors (if I only need the later).

@angularsen
Copy link
Owner

Earlier in this issue there are several links to proof of concepts and proposals for a generic way to do arithmetic of quantities, which would be transferable to vectors of quantities. It seems doable, but someone needs to champion it and start a PR to drive that forward.

Without this, there are today several feasible alternatives:

  • Create your own custom Length3, Mass3 vector types and implement the overloads
  • Use unitless vectors, provided with libraries like Math.NET. This would be the most performant anyway and more interoperable with other types of computations like matrices.

@oysteinkrog
Copy link

@angularsen Now that .net 6 supports generic math, maybe it is easier to do this kind of stuff?
https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/

@angularsen
Copy link
Owner

angularsen commented Nov 2, 2021

Well hello there Mr Krog :D

Yes I think so. We touched on it in #977, but it's always hard to see the possibilities before trying a bit.

I threw up #984 just to play with it.

  • The static abstracts in interfaces is pretty cool and goes way beyond math.
  • INumber does not seem like a good match for quantities, it's the whole kitchen sink and then some.
    • Quantities do not always have a natural Min/Max value. What unit should be used? Also, very susceptible to overflows.
    • Quantities do not always have a Zero value (Temperature have different zero points for Kelvin, Celsius and Fahrenheit). We can opt-in for quantities that do support it, by adding the interface only to those.
    • Quantities do not have a One value (What is one Length? We could use the base unit, but it's not intuitive and feels weird in the semantics of INumber.One.)
    • Increment/decrement could manipulate Value, but again, crossing into non-intuitive behavior.

Fortunately they have split that up into fine grained interfaces like IAdditionOperators and IAdditiveIdentity and also supports different types of TSelf and TResult. Nice!

I'm pretty sure we will make use of this when it leaves preview.

@krwq
Copy link
Contributor Author

krwq commented Nov 4, 2021

cc: @tannergooding

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement pinned Issues that should not be auto-closed due to inactivity.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants