View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.commons.geometry.euclidean.oned;
    18
    19  import java.util.Comparator;
    20  import java.util.function.UnaryOperator;
    21
    22  import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
    23  import org.apache.commons.geometry.euclidean.EuclideanVector;
    24  import org.apache.commons.geometry.euclidean.EuclideanVectorSum;
    25  import org.apache.commons.geometry.euclidean.internal.Vectors;
    26  import org.apache.commons.numbers.core.Precision;
    27
    28  /** This class represents vectors and points in one-dimensional Euclidean space.
    29   * Instances of this class are guaranteed to be immutable.
    30   */
    31  public class Vector1D extends EuclideanVector<Vector1D> {
    32
    33      /** Zero vector (coordinates: 0). */
    34      public static final Vector1D ZERO = new Vector1D(0.0);
    35
    36      /** A vector with all coordinates set to NaN. */
    37      public static final Vector1D NaN = new Vector1D(Double.NaN);
    38
    39      /** A vector with all coordinates set to positive infinity. */
    40      public static final Vector1D POSITIVE_INFINITY =
    41          new Vector1D(Double.POSITIVE_INFINITY);
    42
    43      /** A vector with all coordinates set to negative infinity. */
    44      public static final Vector1D NEGATIVE_INFINITY =
    45          new Vector1D(Double.NEGATIVE_INFINITY);
    46
    47      /** Comparator that sorts vectors in component-wise ascending order.
    48       * Vectors are only considered equal if their coordinates match exactly.
    49       * Null arguments are evaluated as being greater than non-null arguments.
    50       */
    51      public static final Comparator<Vector1D> COORDINATE_ASCENDING_ORDER = (a, b) -> {
    52          int cmp = 0;
    53
    54          if (a != null && b != null) {
    55              cmp = Double.compare(a.getX(), b.getX());
    56          } else if (a != null) {
    57              cmp = -1;
    58          } else if (b != null) {
    59              cmp = 1;
    60          }
    61
    62          return cmp;
    63      };
    64
    65      /** Abscissa (coordinate value). */
    66      private final double x;
    67
    68      /** Simple constructor.
    69       * @param x abscissa (coordinate value)
    70       */
    71      private Vector1D(final double x) {
    72          this.x = x;
    73      }
    74
    75      /**
    76       * Returns the abscissa (coordinate value) of the instance.
    77       * @return the abscissa value
    78       */
    79      public double getX() {
    80          return x;
    81      }
    82
    83      /** {@inheritDoc} */
    84      @Override
    85      public int getDimension() {
    86          return 1;
    87      }
    88
    89      /** {@inheritDoc} */
    90      @Override
    91      public boolean isNaN() {
    92          return Double.isNaN(x);
    93      }
    94
    95      /** {@inheritDoc} */
    96      @Override
    97      public boolean isInfinite() {
    98          return !isNaN() && Double.isInfinite(x);
    99      }
    100
    101     /** {@inheritDoc} */
    102     @Override
    103     public boolean isFinite() {
    104         return Double.isFinite(x);
    105     }
    106
    107     /** {@inheritDoc} */
    108     @Override
    109     public Vector1D vectorTo(final Vector1D v) {
    110         return v.subtract(this);
    111     }
    112
    113     /** {@inheritDoc} */
    114     @Override
    115     public Unit directionTo(final Vector1D v) {
    116         return vectorTo(v).normalize();
    117     }
    118
    119     /** {@inheritDoc} */
    120     @Override
    121     public Vector1D lerp(final Vector1D p, final double t) {
    122         return Sum.create()
    123                 .addScaled(1.0 - t, this)
    124                 .addScaled(t, p).get();
    125     }
    126
    127     /** {@inheritDoc} */
    128     @Override
    129     public Vector1D getZero() {
    130         return ZERO;
    131     }
    132
    133     /** {@inheritDoc} */
    134     @Override
    135     public double norm() {
    136         return Vectors.norm(x);
    137     }
    138
    139     /** {@inheritDoc} */
    140     @Override
    141     public double normSq() {
    142         return Vectors.normSq(x);
    143     }
    144
    145     /** {@inheritDoc} */
    146     @Override
    147     public Vector1D withNorm(final double magnitude) {
    148         getCheckedNorm(); // validate our norm value
    149         return (x > 0.0) ? new Vector1D(magnitude) : new Vector1D(-magnitude);
    150     }
    151
    152     /** {@inheritDoc} */
    153     @Override
    154     public Vector1D add(final Vector1D v) {
    155         return new Vector1D(x + v.x);
    156     }
    157
    158     /** {@inheritDoc} */
    159     @Override
    160     public Vector1D add(final double factor, final Vector1D v) {
    161         return new Vector1D(x + (factor * v.x));
    162     }
    163
    164     /** {@inheritDoc} */
    165     @Override
    166     public Vector1D subtract(final Vector1D v) {
    167         return new Vector1D(x - v.x);
    168     }
    169
    170     /** {@inheritDoc} */
    171     @Override
    172     public Vector1D subtract(final double factor, final Vector1D v) {
    173         return new Vector1D(x - (factor * v.x));
    174     }
    175
    176     /** {@inheritDoc} */
    177     @Override
    178     public Vector1D negate() {
    179         return new Vector1D(-x);
    180     }
    181
    182     /** {@inheritDoc} */
    183     @Override
    184     public Unit normalize() {
    185         return Unit.from(x);
    186     }
    187
    188     /** {@inheritDoc} */
    189     @Override
    190     public Unit normalizeOrNull() {
    191         return Unit.tryCreateNormalized(x, false);
    192     }
    193
    194     /** {@inheritDoc} */
    195     @Override
    196     public Vector1D multiply(final double a) {
    197         return new Vector1D(a * x);
    198     }
    199
    200     /** {@inheritDoc} */
    201     @Override
    202     public double distance(final Vector1D v) {
    203         return Vectors.norm(x - v.x);
    204     }
    205
    206     /** {@inheritDoc} */
    207     @Override
    208     public double distanceSq(final Vector1D v) {
    209         return Vectors.normSq(x - v.x);
    210     }
    211
    212     /** {@inheritDoc} */
    213     @Override
    214     public double dot(final Vector1D v) {
    215         return x * v.x;
    216     }
    217
    218     /** {@inheritDoc}
    219      * <p>For the one-dimensional case, this method returns 0 if the vector x values have
    220      * the same sign and {@code pi} if they are opposite.</p>
    221      */
    222     @Override
    223     public double angle(final Vector1D v) {
    224         // validate the norm values
    225         getCheckedNorm();
    226         v.getCheckedNorm();
    227
    228         final double sig1 = Math.signum(x);
    229         final double sig2 = Math.signum(v.x);
    230
    231         // the angle is 0 if the x value signs are the same and pi if not
    232         return (sig1 == sig2) ? 0.0 : Math.PI;
    233     }
    234
    235     /** Convenience method to apply a function to this vector. This
    236      * can be used to transform the vector inline with other methods.
    237      * @param fn the function to apply
    238      * @return the transformed vector
    239      */
    240     public Vector1D transform(final UnaryOperator<Vector1D> fn) {
    241         return fn.apply(this);
    242     }
    243
    244     /** {@inheritDoc} */
    245     @Override
    246     public boolean eq(final Vector1D vec, final Precision.DoubleEquivalence precision) {
    247         return precision.eq(x, vec.x);
    248     }
    249
    250     /**
    251      * Get a hashCode for the vector.
    252      * <p>All NaN values have the same hash code.</p>
    253      *
    254      * @return a hash code value for this object
    255      */
    256     @Override
    257     public int hashCode() {
    258         if (isNaN()) {
    259             return 857;
    260         }
    261         return 403 * Double.hashCode(x);
    262     }
    263
    264     /**
    265      * Test for the equality of two vectors.
    266      * <p>
    267      * If all coordinates of two vectors are exactly the same, and none are
    268      * <code>Double.NaN</code>, the two vectors are considered to be equal.
    269      * </p>
    270      * <p>
    271      * <code>NaN</code> coordinates are considered to globally affect the vector
    272      * and be equal to each other - i.e, if either (or all) coordinates of the
    273      * vector are equal to <code>Double.NaN</code>, the vector is equal to
    274      * {@link #NaN}.
    275      * </p>
    276      *
    277      * @param other Object to test for equality to this
    278      * @return true if two vector objects are equal, false if
    279      *         object is null, not an instance of Vector1D, or
    280      *         not equal to this Vector1D instance
    281      *
    282      */
    283     @Override
    284     public boolean equals(final Object other) {
    285         if (this == other) {
    286             return true;
    287         }
    288         if (other instanceof Vector1D) {
    289             final Vector1D rhs = (Vector1D) other;
    290             if (rhs.isNaN()) {
    291                 return this.isNaN();
    292             }
    293
    294             return Double.compare(x, rhs.x) == 0;
    295         }
    296         return false;
    297     }
    298
    299     /** {@inheritDoc} */
    300     @Override
    301     public String toString() {
    302         return SimpleTupleFormat.getDefault().format(x);
    303     }
    304
    305     /** Returns a vector with the given coordinate value.
    306      * @param x vector coordinate
    307      * @return vector instance
    308      */
    309     public static Vector1D of(final double x) {
    310         return new Vector1D(x);
    311     }
    312
    313     /** Parses the given string and returns a new vector instance. The expected string
    314      * format is the same as that returned by {@link #toString()}.
    315      * @param str the string to parse
    316      * @return vector instance represented by the string
    317      * @throws IllegalArgumentException if the given string has an invalid format
    318      */
    319     public static Vector1D parse(final String str) {
    320         return SimpleTupleFormat.getDefault().parse(str, Vector1D::new);
    321     }
    322
    323     /**
    324      * Represent unit vectors.
    325      * This allows optimizations to be performed for certain operations.
    326      */
    327     public static final class Unit extends Vector1D {
    328         /** Unit vector (coordinates: 1). */
    329         public static final Unit PLUS  = new Unit(1d);
    330         /** Negation of unit vector (coordinates: -1). */
    331         public static final Unit MINUS = new Unit(-1d);
    332
    333         /** Simple constructor. Callers are responsible for ensuring that the given
    334          * values represent a normalized vector.
    335          * @param x abscissa (first coordinate value)
    336          */
    337         private Unit(final double x) {
    338             super(x);
    339         }
    340
    341         /** {@inheritDoc} */
    342         @Override
    343         public double norm() {
    344             return 1;
    345         }
    346
    347         /** {@inheritDoc} */
    348         @Override
    349         public double normSq() {
    350             return 1;
    351         }
    352
    353         /** {@inheritDoc} */
    354         @Override
    355         public Unit normalize() {
    356             return this;
    357         }
    358
    359         /** {@inheritDoc} */
    360         @Override
    361         public Unit normalizeOrNull() {
    362             return this;
    363         }
    364
    365         /** {@inheritDoc} */
    366         @Override
    367         public Vector1D withNorm(final double mag) {
    368             return multiply(mag);
    369         }
    370
    371         /** {@inheritDoc} */
    372         @Override
    373         public Vector1D negate() {
    374             return this == PLUS ? MINUS : PLUS;
    375         }
    376
    377         /** Create a normalized vector.
    378          * @param x Vector coordinate.
    379          * @return a vector whose norm is 1.
    380          * @throws IllegalArgumentException if the norm of the given value is zero, NaN, or infinite
    381          */
    382         public static Unit from(final double x) {
    383             return tryCreateNormalized(x, true);
    384         }
    385
    386         /** Create a normalized vector.
    387          * @param v Vector.
    388          * @return a vector whose norm is 1.
    389          * @throws IllegalArgumentException if the norm of the given value is zero, NaN, or infinite
    390          */
    391         public static Unit from(final Vector1D v) {
    392             return v instanceof Unit ?
    393                 (Unit) v :
    394                 from(v.getX());
    395         }
    396
    397         /** Attempt to create a normalized vector from the given coordinate values. If {@code throwOnFailure}
    398          * is true, an exception is thrown if a normalized vector cannot be created. Otherwise, null
    399          * is returned.
    400          * @param x x coordinate
    401          * @param throwOnFailure if true, an exception will be thrown if a normalized vector cannot be created
    402          * @return normalized vector or null if one cannot be created and {@code throwOnFailure}
    403          *      is false
    404          * @throws IllegalArgumentException if the computed norm is zero, NaN, or infinite
    405          */
    406         private static Unit tryCreateNormalized(final double x, final boolean throwOnFailure) {
    407             final double norm = Vectors.norm(x);
    408
    409             if (Vectors.isRealNonZero(norm)) {
    410                 return x > 0 ? PLUS : MINUS;
    411             } else if (throwOnFailure) {
    412                 throw Vectors.illegalNorm(norm);
    413             }
    414             return null;
    415         }
    416     }
    417
    418     /** Class used to create high-accuracy sums of vectors. Each vector component is
    419      * summed using an instance of {@link org.apache.commons.numbers.core.Sum}.
    420     *
    421     * <p>This class is mutable and not thread-safe.
    422     * @see org.apache.commons.numbers.core.Sum
    423     */
    424     public static final class Sum extends EuclideanVectorSum<Vector1D> {
    425
    426         /** X component sum. */
    427         private final org.apache.commons.numbers.core.Sum xsum;
    428
    429         /** Construct a new instance with the given initial value.
    430          * @param initial initial value
    431          */
    432         Sum(final Vector1D initial) {
    433             this.xsum = org.apache.commons.numbers.core.Sum.of(initial.x);
    434         }
    435
    436         /** {@inheritDoc} */
    437         @Override
    438         public Sum add(final Vector1D vec) {
    439             xsum.add(vec.x);
    440             return this;
    441         }
    442
    443         /** {@inheritDoc} */
    444         @Override
    445         public Sum addScaled(final double scale, final Vector1D vec) {
    446             xsum.addProduct(scale, vec.x);
    447             return this;
    448         }
    449
    450         /** {@inheritDoc} */
    451         @Override
    452         public Vector1D get() {
    453             return Vector1D.of(xsum.getAsDouble());
    454         }
    455
    456         /** Create a new instance with an initial value set to the {@link Vector1D#ZERO zero vector}.
    457          * @return new instance set to zero
    458          */
    459         public static Sum create() {
    460             return new Sum(Vector1D.ZERO);
    461         }
    462
    463         /** Construct a new instance with an initial value set to the argument.
    464          * @param initial initial sum value
    465          * @return new instance
    466          */
    467         public static Sum of(final Vector1D initial) {
    468             return new Sum(initial);
    469         }
    470
    471         /** Construct a new instance from multiple values.
    472          * @param first first vector
    473          * @param more additional vectors
    474          * @return new instance
    475          */
    476         public static Sum of(final Vector1D first, final Vector1D... more) {
    477             final Sum s = new Sum(first);
    478             for (final Vector1D v : more) {
    479                 s.add(v);
    480             }
    481             return s;
    482         }
    483     }
    484 }