You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@commons.apache.org by "Alex Herbert (Jira)" <ji...@apache.org> on 2021/04/07 15:34:00 UTC

[jira] [Comment Edited] (GEOMETRY-119) Vector normalizeOrDefault() method

    [ https://issues.apache.org/jira/browse/GEOMETRY-119?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17316430#comment-17316430 ] 

Alex Herbert edited comment on GEOMETRY-119 at 4/7/21, 3:33 PM:
----------------------------------------------------------------

I think there is a case for:
{code:java}
public final class SafeNorm {
    public static double value(double x, double y, double z);
}
{code}
Currently there is only the generic:
{code:java}
public static double value(double[] v);
{code}
The two argument version is provided by Math.hypot(double, double). Adding a specialisation for 3D coordinates is a common use case. At the moment SafeNorm can take an array of any length and appears to use constants for dwarf and giant number that are based around single precision:
{code:java}
    /** Constant. Roughly 2^-66 */
    private static final double R_DWARF = 3.834e-20;
    /** Constant. Roughly 2^63 */
    private static final double R_GIANT = 1.304e+19;
{code}
So R_DWARF^2 and R_GIANT^2 may fit in single precision (float), but I have not checked.

A simple (not checked) version based on the above normalisation code would be:
{code:java}
private static Unit value(final double x, final double y, final double z) {
    final double max = Math.max(Math.max(Math.abs(x), Math.abs(y)), Math.abs(z));
    double scale = 1.0;
    if (max > 0x1.0p500) {
        // Scale down
        x *= 0x1.0p-600;
        y *= 0x1.0p-600;
        z *= 0x1.0p-600;
        scale = 0x1.0p600;
    } else if (max < 0x1.0p-500) {
        // Scale up
        x *= 0x1.0p600;
        y *= 0x1.0p600;
        z *= 0x1.0p600;
        scale = 0x1.0p-600;
    }
    return Math.sqrt(x * x + y * y + z * z) * scale;
}
{code}
This does not handle edge cases with infinite or NaN. It could be updated to use a similar method to Math.hypot:
 * If any argument is infinite, then the result is positive infinity.
 * If any argument is NaN and no other argument is infinite, then the result is NaN.

I also think that hypot(x, y) must equal hypot(y, x). In the case of a 3 argument version this would be achieved by a partial sort of XYZ. Summing from small to high would create the best accuracy to carry smaller bits up to the final total sum. It would also make permutations of XYZ return the same result.

 


was (Author: alexherbert):
I think there is a case for:
{code:java}
public final class SafeNorm {
    public static double norm(double x, double y, double z);
}
{code}
Currently there is only the generic:
{code:java}
public static double value(double[] v);
{code}
The two argument version is is provided by Math.hypot(double, double). Adding a specialisation for 3D coordinates is a common use case. At the moment SafeNorm can take an array of any length and appears to use constants for dwarf and giant number that are based around single precision:
{code:java}
    /** Constant. Roughly 2^-66 */
    private static final double R_DWARF = 3.834e-20;
    /** Constant. Roughly 2^63 */
    private static final double R_GIANT = 1.304e+19;
{code}
So R_DWARF^2 and R_GIANT^2 may fit in single precision (float), but I have not checked.

A simple (not checked) version based on the above normalisation code would be:
{code:java}
private static Unit norm(final double x, final double y, final double z) {
    final double max = Math.max(Math.max(Math.abs(x), Math.abs(y)), Math.abs(z));
    double scale = 1.0;
    if (max > 0x1.0p500) {
        // Scale down
        x *= 0x1.0p-600;
        y *= 0x1.0p-600;
        z *= 0x1.0p-600;
        scale = 0x1.0p600;
    } else if (max < 0x1.0p-500) {
        // Scale up
        x *= 0x1.0p600;
        y *= 0x1.0p600;
        z *= 0x1.0p600;
        scale = 0x1.0p-600;
    }
    return Math.sqrt(x * x + y * y + z * z) * scale;
}
{code}
This does not handle edge cases with infinite or NaN. It could be updated to use a similar method to Math.hypot:
 * If any argument is infinite, then the result is positive infinity.
 * If any argument is NaN and no other argument is infinite, then the result is NaN.

I also think that hypot(x, y) must equal hypot(y, x). In the case of a 3 argument version this would be achieved by a partial sort of XYZ. Summing from small to high would create the best accuracy to carry smaller bits up to the final total sum. It would also make permutations of XYZ return the same result.

 

> Vector normalizeOrDefault() method
> ----------------------------------
>
>                 Key: GEOMETRY-119
>                 URL: https://issues.apache.org/jira/browse/GEOMETRY-119
>             Project: Apache Commons Geometry
>          Issue Type: Improvement
>            Reporter: Matt Juntunen
>            Priority: Major
>
> A frequent use case when working with vectors, especially vectors coming from external data, is attempting to normalize the vector, and if this is not possible, to use an alternative value. For example, the {{QuaternionRotation}} code [here|https://github.com/apache/commons-geometry/blob/master/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/rotation/QuaternionRotation.java#L86] does exactly this; it attempts to normalize a vector and failing that (ie, if the vector is exactly zero), it returns a substitute value. The {{QuaternionRotation}} class is able to take advantage of our internal {{Vectors.tryNormalize()}} but callers outside of the library are not able to do so and so are left with 2 choices:
> 1. wrap the {{normalize()}} call in a try-catch and handle the exception thrown on illegal norm values, or
> 2. compute and test the norm prior to calling {{normalize()}} to ensure that the call won't fail, resulting in 2 computations of the norm.
> Neither of these options are very good.
> I propose adding a new method to the Euclidean Vector classes to handle this situation: {{normalizeOrDefault()}}. The method would accept a default value (possibly null) to return if the vector cannot be normalized. The normal would then only need to be computed once and an exception would not need to be thrown in case of failure. The behavior of the current {{normalize}} method would be the same.
> Examples:
> {code:java}
> // get some kind of normal, preferably vec but +z will also do
> Vector3D.Unit norm = vec.normalizeOrDefault(Vector3D.Unit.PLUS_Z);
> {code}
> {code:java}
> // throw a very use-case specific error message
> Vector3D norm = vec.normalizeOrDefault(null);
> if (norm == null) {
>     throw new Exception("Invalid triangle at index " + i + ": cannot compute normal.");
> } 
> {code}



--
This message was sent by Atlassian Jira
(v8.3.4#803005)