MadsKirkFoged / EngineeringUnits

Working with units made easy with automatic unit-check and converting between units
MIT License
42 stars 11 forks source link

Help convertint class from UnitsNet #23

Closed wiwalsh closed 2 years ago

wiwalsh commented 2 years ago

I have a vector and matrix class that I had written for UnitsNet that I am trying to convert over to EngineeringUnits and having some trouble.

I have included the whole class, but there are two basic areas I am having trouble understanding how to implement. Since this is a generic class, I am having trouble initializing the values of the array to a value. I am also having trouble assigning the values of the EngineeringUnits overloaded operators back to . I get

Cannot implicitly convert type 'EngineeringUnits.UnknownUnit' to 'T'. An explicit conversion exists (are you missing a cast?)

using System;
using System.Globalization;
using UnitsNet;

namespace Tidal.Support.DataStructures
{
    public class UVector<T> : ICloneable where T : IQuantity, new()
    {
        private T[] _vector;

        public UVector(int nDim)
        {
            GetVectorSize = nDim;
            _vector = new T[nDim];
            for (var i = 0; i < nDim; i++) _vector[i] = Zero();
        }

        public UVector(T[] vector)
        {
            GetVectorSize = vector.Length;
            _vector = vector;
        }

        public T this[int i]
        {
            get
            {
                if (i < 0 || i > GetVectorSize) throw new ArgumentException("Requested vector index is out of range.");

                return _vector[i];
            }
            set => _vector[i] = value;
        }

        public int GetVectorSize { get; }

        object ICloneable.Clone()
        {
            return Clone();
        }

        private T Zero()
        {
            QuantityValue v = 0.0;
            var value = Quantity.FromQuantityInfo(_vector[0].QuantityInfo, v);
            return (T) value;
        }

        public UVector<T> Clone()
        {
            var v = new UVector<T>(_vector);
            v._vector = (T[]) _vector.Clone();
            return v;
        }

        public UVector<T> SwapVectorEntries(int m, int n)
        {
            var temp = _vector[m];
            _vector[m] = _vector[n];
            _vector[n] = temp;
            return new UVector<T>(_vector);
        }

        public override string ToString()
        {
            var str = "(";
            for (var i = 0; i < GetVectorSize - 1; i++) str += _vector[i].ToString(CultureInfo.InvariantCulture) + ", ";

            str += _vector[GetVectorSize - 1].ToString(CultureInfo.InvariantCulture) + ")";
            return str;
        }

        public override bool Equals(object obj)
        {
            return obj is UVector<T> && Equals((UVector<T>) obj);
        }

        public bool Equals(UVector<T> v)
        {
            return _vector == v._vector;
        }

        public override int GetHashCode()
        {
            return _vector.GetHashCode();
        }

        public static bool operator ==(UVector<T> v1, UVector<T> v2)
        {
            return v1.Equals(v2);
        }

        public static bool operator !=(UVector<T> v1, UVector<T> v2)
        {
            return !v1.Equals(v2);
        }

        public static UVector<T> operator +(UVector<T> v)
        {
            return v;
        }

        public static UVector<T> operator +(UVector<T> v1, UVector<T> v2)
        {
            var result = new UVector<T>(v1.GetVectorSize);

            var dim = v1[0].Dimensions;
            QuantityValue val = 0.0;
            var a = v1[0].QuantityInfo.BaseUnitInfo.Value;
            for (var i = 0; i < v1.GetVectorSize; i++)
            {
                val = v1[i].As(a) + v2[i].As(a);
                result[i] = (T) Quantity.From(val, a);
            }

            return result;
        }

        public static UVector<T> operator -(UVector<T> v1)
        {
            var result = new UVector<T>(v1.GetVectorSize);

            var dim = v1[0].Dimensions;
            QuantityValue val = 0.0;
            var a = v1[0].QuantityInfo.BaseUnitInfo.Value;
            for (var i = 0; i < v1.GetVectorSize; i++)
            {
                val = -v1[i].As(a);
                result[i] = (T) Quantity.From(val, a);
            }

            return result;
        }

        public static UVector<T> operator -(UVector<T> v1, UVector<T> v2)
        {
            var result = new UVector<T>(v1.GetVectorSize);

            var dim = v1[0].Dimensions;
            QuantityValue val = 0.0;
            var a = v1[0].QuantityInfo.BaseUnitInfo.Value;
            for (var i = 0; i < v1.GetVectorSize; i++)
            {
                val = v1[i].As(a) - v2[i].As(a);
                result[i] = (T) Quantity.From(val, a);
            }

            return result;
        }

        public static UVector<T> operator *(UVector<T> v1, double d)
        {
            var result = new UVector<T>(v1.GetVectorSize);

            var dim = v1[0].Dimensions;
            QuantityValue val = 0.0;
            var a = v1[0].QuantityInfo.BaseUnitInfo.Value;
            for (var i = 0; i < v1.GetVectorSize; i++)
            {
                val = v1[i].As(a) * d;
                result[i] = (T) Quantity.From(val, a);
            }

            return result;
        }

        public static UVector<T> operator *(double d, UVector<T> v)
        {
            return v * d;
        }

        public static UVector<T> operator /(UVector<T> v1, double d)
        {
            var result = new UVector<T>(v1.GetVectorSize);

            var dim = v1[0].Dimensions;
            QuantityValue val = 0.0;
            var a = v1[0].QuantityInfo.BaseUnitInfo.Value;
            for (var i = 0; i < v1.GetVectorSize; i++)
            {
                val = v1[i].As(a) / d;
                result[i] = (T) Quantity.From(val, a);
            }

            return result;
        }

        public static UVector<T> operator /(double d, UVector<T> v)
        {
            throw new Exception("Matrix dimensions must agree.");
        }

        public static T DotProduct(UVector<T> v1, UVector<T> v2)
        {
            var dim = v1[0].Dimensions;
            QuantityValue val = 0.0;
            var temp = 0.0;
            var a = v1[0].QuantityInfo.BaseUnitInfo.Value;
            for (var i = 0; i < v1.GetVectorSize; i++) temp += v1[i].As(a) * v2[i].As(a);

            val = temp;
            return (T) Quantity.From(val, a);
        }

        /// <summary>
        ///     ///////////////////////
        /// </summary>
        /// <returns></returns>
        public T GetNorm()
        {
            var unit = GetNormSquare();
            var dim = _vector[0].Dimensions;
            var a = _vector[0].QuantityInfo.BaseUnitInfo.Value;

            return (T) Quantity.From(Math.Sqrt(unit.As(a)), a);
        }

        /// <summary>
        ///     need GetNormSquare to be private as units are not consistent.
        /// </summary>
        /// <returns></returns>
        private T GetNormSquare()
        {
            var dim = _vector[0].Dimensions;
            QuantityValue result = 0.0;
            var temp = 0.0;
            var a = _vector[0].QuantityInfo.BaseUnitInfo.Value;
            //  double result = 0.0;
            for (var i = 0; i < GetVectorSize; i++) temp += _vector[i].As(a) * _vector[i].As(a);

            result = temp;
            return (T) Quantity.From(result, a);
            ;
        }

        public void Normalize()
        {
            var norm = GetNorm();
            var a = _vector[0].QuantityInfo.BaseUnitInfo.Value;
            if (norm.As(a) == 0) throw new Exception("Tried to normalize a vector with a norm of zero.");

            for (var i = 0; i < GetVectorSize; i++) _vector[i] = (T) Quantity.From(_vector[i].As(a) / norm.As(a), a);
        }

        public UVector<T> GetUnitVector()
        {
            var result = new UVector<T>(_vector);
            result.Normalize();
            return result;
        }

        public RVector ToUnit(Enum unit)
        {
            var result = new RVector(GetVectorSize);
            for (var i = 0; i < GetVectorSize; i++) result[i] = _vector[i].As(unit);

            return result;
        }

        public static UVector<T> CrossProduct(UVector<T> v1, UVector<T> v2)
        {
            if (v1.GetVectorSize != 3) throw new Exception("Vector v1 must be 3 dimensional.");

            if (v2.GetVectorSize != 3) throw new Exception("Vector v2 must be 3 dimensional.");

            var a = v1[0].QuantityInfo.BaseUnitInfo.Value;

            var result = new UVector<T>(3);
            result[0] = (T) Quantity.From(v1[1].As(a) * v2[2].As(a) - v1[2].As(a) * v2[1].As(a), a);
            result[1] = (T) Quantity.From(v1[2].As(a) * v2[0].As(a) - v1[0].As(a) * v2[2].As(a), a);
            result[2] = (T) Quantity.From(v1[0].As(a) * v2[1].As(a) - v1[1].As(a) * v2[0].As(a), a);
            return result;
        }
    }
}
wiwalsh commented 2 years ago

OK. Let me ask a more concise question. If I wanted to use a generic class to instantiate a unit to a value, how would I do that (for some silly reason)? Once I get the above classes working, I don't mind sharing, but I'm not sure if it is of interest to anyone.

Function Call

var instance = new TestClass<Length>(4.5)

Somehow in the below class I need to assign the value to whatever the default unit is for that unit type. I'm semi lost between generics and how this library works.

Class

    public class TestClass<T> where T : BaseUnit
    {
        private T singleValue;
        public TestClass(double input)
        {
            singleValue = new T(input);
        }
    }
MadsKirkFoged commented 2 years ago

That will be great with a vector and matrix class! You dont really need a generic class for this, we do it a bit differently here.

I have started on a vector class for you in Math ->Vector.cs Just a quick go I have added this:

            Power p1 = new Power(5,PowerUnit.SI);
            Power p2 = new Power(10, PowerUnit.SI);
            Power p3 = new Power(15, PowerUnit.SI);

            UVector v1 = new UVector(p1, p2);
            UVector v2 = v1 + v1;
            Power PDot = UVector.DotProduct(v1, v1);

            UVector v3 = new UVector(p1, p2, p3);
            UVector v4 = new UVector(p2, p2, p2);
            UVector PCross = UVector.CrossProduct(v3, v4);

Feel free to change it or add to it all you want and also check if what I have done is even correct.

wiwalsh commented 2 years ago

Awesome! I'll start adding things and my tests for those functions.

MadsKirkFoged commented 2 years ago

Let me know if you need help with anything!