You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ma...@apache.org on 2021/06/22 12:22:21 UTC

[commons-geometry] branch master updated: GEOMETRY-126: replacing usage of commons-numbers LinearCombination with Sum; adding VectorXD.Sum classes

This is an automated email from the ASF dual-hosted git repository.

mattjuntunen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-geometry.git


The following commit(s) were added to refs/heads/master by this push:
     new 3d138dc  GEOMETRY-126: replacing usage of commons-numbers LinearCombination with Sum; adding VectorXD.Sum classes
3d138dc is described below

commit 3d138dc6019eb2d18bb9d063e443e57a165ff9c6
Author: Matt Juntunen <ma...@apache.org>
AuthorDate: Mon Jun 21 18:14:56 2021 -0400

    GEOMETRY-126: replacing usage of commons-numbers LinearCombination with Sum; adding VectorXD.Sum classes
---
 .../euclidean/threed/SphereGenerator.java          |   2 +-
 .../enclosing/euclidean/twod/DiskGenerator.java    |   2 +-
 .../euclidean/threed/SphereGeneratorTest.java      |   4 +-
 .../euclidean/threed/WelzlEncloser3DTest.java      |   4 +-
 .../euclidean/twod/DiskGeneratorTest.java          |   4 +-
 .../geometry/euclidean/EuclideanVectorSum.java     |  51 ++++++
 .../geometry/euclidean/internal/Vectors.java       |  35 ++++
 .../commons/geometry/euclidean/oned/Vector1D.java  | 153 ++++++++--------
 .../euclidean/threed/AffineTransformMatrix3D.java  |  35 ++--
 .../geometry/euclidean/threed/EmbeddingPlane.java  |  16 +-
 .../commons/geometry/euclidean/threed/Plane.java   |   6 +-
 .../geometry/euclidean/threed/Vector3D.java        | 198 ++++++++++-----------
 .../geometry/euclidean/threed/line/Line3D.java     |   8 +-
 .../threed/rotation/QuaternionRotation.java        |  19 +-
 .../euclidean/twod/AffineTransformMatrix2D.java    |  17 +-
 .../commons/geometry/euclidean/twod/Line.java      |  14 +-
 .../commons/geometry/euclidean/twod/Vector2D.java  | 180 +++++++++----------
 .../geometry/euclidean/oned/Vector1DTest.java      |  64 ++++---
 .../euclidean/threed/EmbeddingPlaneTest.java       |   8 +-
 .../geometry/euclidean/threed/PlaneTest.java       |  10 +-
 .../euclidean/threed/RegionBSPTree3DTest.java      |   8 +-
 .../geometry/euclidean/threed/Vector3DTest.java    |  64 ++++---
 .../geometry/euclidean/threed/line/Line3DTest.java |   6 +-
 .../geometry/euclidean/twod/Vector2DTest.java      |  56 +++---
 .../twod/ConvexHullGenerator2DAbstractTest.java    |   6 +-
 .../geometry/spherical/twod/ConvexArea2S.java      |  21 +--
 .../geometry/spherical/twod/GreatCircle.java       |   4 +-
 .../geometry/spherical/twod/RegionBSPTree2S.java   |  25 +--
 28 files changed, 535 insertions(+), 485 deletions(-)

diff --git a/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/enclosing/euclidean/threed/SphereGenerator.java b/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/enclosing/euclidean/threed/SphereGenerator.java
index 0581d95..6fd1068 100644
--- a/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/enclosing/euclidean/threed/SphereGenerator.java
+++ b/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/enclosing/euclidean/threed/SphereGenerator.java
@@ -56,7 +56,7 @@ public class SphereGenerator implements SupportBallGenerator<Vector3D> {
         }
         final Vector3D vB = support.get(1);
         if (support.size() < 3) {
-            return new EnclosingBall<>(Vector3D.linearCombination(0.5, vA, 0.5, vB),
+            return new EnclosingBall<>(vA.lerp(vB, 0.5),
                                        0.5 * vA.distance(vB),
                                        Arrays.asList(vA, vB));
         }
diff --git a/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/enclosing/euclidean/twod/DiskGenerator.java b/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/enclosing/euclidean/twod/DiskGenerator.java
index 7006b29..77eb5d1 100644
--- a/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/enclosing/euclidean/twod/DiskGenerator.java
+++ b/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/enclosing/euclidean/twod/DiskGenerator.java
@@ -41,7 +41,7 @@ public class DiskGenerator implements SupportBallGenerator<Vector2D> {
         }
         final Vector2D vB = support.get(1);
         if (support.size() < 3) {
-            return new EnclosingBall<>(Vector2D.linearCombination(0.5, vA, 0.5, vB),
+            return new EnclosingBall<>(vA.lerp(vB, 0.5),
                                        0.5 * vA.distance(vB),
                                        Arrays.asList(vA, vB));
         }
diff --git a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/euclidean/threed/SphereGeneratorTest.java b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/euclidean/threed/SphereGeneratorTest.java
index 28fe2a5..df8fb86 100644
--- a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/euclidean/threed/SphereGeneratorTest.java
+++ b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/euclidean/threed/SphereGeneratorTest.java
@@ -184,10 +184,10 @@ class SphereGeneratorTest {
         for (int i = 0; i < 100; ++i) {
             final double d = 25 * random.nextDouble();
             final double refRadius = 10 * random.nextDouble();
-            final Vector3D refCenter = Vector3D.linearCombination(d, Vector3D.of(sr.nextVector()));
+            final Vector3D refCenter = Vector3D.of(sr.nextVector()).multiply(d);
             final List<Vector3D> support = new ArrayList<>();
             for (int j = 0; j < 5; ++j) {
-                support.add(Vector3D.linearCombination(1.0, refCenter, refRadius, Vector3D.of(sr.nextVector())));
+                support.add(Vector3D.Sum.of(refCenter).addScaled(refRadius, Vector3D.of(sr.nextVector())).get());
             }
 
             // act
diff --git a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/euclidean/threed/WelzlEncloser3DTest.java b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/euclidean/threed/WelzlEncloser3DTest.java
index a7a580b..9561982 100644
--- a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/euclidean/threed/WelzlEncloser3DTest.java
+++ b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/euclidean/threed/WelzlEncloser3DTest.java
@@ -119,14 +119,14 @@ class WelzlEncloser3DTest {
             // define the reference sphere we want to compute
             final double d = 25 * random.nextDouble();
             final double refRadius = 10 * random.nextDouble();
-            final Vector3D refCenter = Vector3D.linearCombination(d, Vector3D.of(sr.nextVector()));
+            final Vector3D refCenter = Vector3D.of(sr.nextVector()).multiply(d);
             // set up a large sample inside the reference sphere
             final int nbPoints = random.nextInt(1000);
 
             final List<Vector3D> points = new ArrayList<>();
             for (int i = 0; i < nbPoints; ++i) {
                 final double r = refRadius * random.nextDouble();
-                points.add(Vector3D.linearCombination(1.0, refCenter, r, Vector3D.of(sr.nextVector())));
+                points.add(Vector3D.Sum.of(refCenter).addScaled(r, Vector3D.of(sr.nextVector())).get());
             }
 
             // act/assert
diff --git a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/euclidean/twod/DiskGeneratorTest.java b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/euclidean/twod/DiskGeneratorTest.java
index e108d81..c3428d9 100644
--- a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/euclidean/twod/DiskGeneratorTest.java
+++ b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/euclidean/twod/DiskGeneratorTest.java
@@ -138,10 +138,10 @@ class DiskGeneratorTest {
         for (int i = 0; i < 500; ++i) {
             final double d = 25 * random.nextDouble();
             final double refRadius = 10 * random.nextDouble();
-            final Vector2D refCenter = Vector2D.linearCombination(d, Vector2D.of(sr.nextVector()));
+            final Vector2D refCenter = Vector2D.of(sr.nextVector()).multiply(d);
             final List<Vector2D> support = new ArrayList<>();
             for (int j = 0; j < 3; ++j) {
-                support.add(Vector2D.linearCombination(1.0, refCenter, refRadius, Vector2D.of(sr.nextVector())));
+                support.add(Vector2D.Sum.of(refCenter).addScaled(refRadius, Vector2D.of(sr.nextVector())).get());
             }
 
             // act
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanVectorSum.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanVectorSum.java
new file mode 100644
index 0000000..5c537f5
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanVectorSum.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/** Class representing a sum of Euclidean vectors.
+ * @param <V> Vector implementation type
+ */
+public abstract class EuclideanVectorSum<V extends EuclideanVector<V>>
+    implements Supplier<V>, Consumer<V> {
+
+    /** Add a vector to this instance. This method is an alias for {@link #add(EuclideanVector)}.
+     * @param vec vector to add
+     */
+    @Override
+    public void accept(final V vec) {
+        add(vec);
+    }
+
+    /** Add a vector to this instance.
+     * @param vec vector to add
+     * @return this instance
+     */
+    public abstract EuclideanVectorSum<V> add(V vec);
+
+    /** Add a scaled vector to this instance. In general, the result produced by this method
+     * will be more accurate than if the vector was scaled first and then added directly. In other
+     * words, {@code sum.addScale(scale, vec)} will generally produce a better result than
+     * {@code sum.add(vec.multiply(scale))}.
+     * @param scale scale factor
+     * @param vec vector to scale and add
+     * @return this instance
+     */
+    public abstract EuclideanVectorSum<V> addScaled(double scale, V vec);
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/Vectors.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/Vectors.java
index 817e0b0..bd77d57 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/Vectors.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/Vectors.java
@@ -18,6 +18,7 @@ package org.apache.commons.geometry.euclidean.internal;
 
 import org.apache.commons.geometry.core.Vector;
 import org.apache.commons.numbers.core.Norm;
+import org.apache.commons.numbers.core.Sum;
 
 /** This class consists exclusively of static vector utility methods.
  */
@@ -142,4 +143,38 @@ public final class Vectors {
     public static double normSq(final double x1, final double x2, final double x3) {
         return (x1 * x1) + (x2 * x2) + (x3 * x3);
     }
+
+    /** Compute the linear combination \(a_1 b_1 + a_2 b_2 \) with high accuracy.
+     * @param a1 first factor of the first term
+     * @param b1 second factor of the first term
+     * @param a2 first factor of the second term
+     * @param b2 second factor of the seconf term
+     * @return linear combination.
+     * @see Sum
+     */
+    public static double linearCombination(final double a1, final double b1,
+                                           final double a2, final double b2) {
+        return Sum.create()
+                .addProduct(a1, b1)
+                .addProduct(a2, b2).getAsDouble();
+    }
+
+    /** Compute the linear combination \(a_1 b_1 + a_2 b_2 + a_3 b_3 \) with high accuracy.
+     * @param a1 first factor of the first term
+     * @param b1 second factor of the first term
+     * @param a2 first factor of the second term
+     * @param b2 second factor of the seconf term
+     * @param a3 first factor of the third term
+     * @param b3 second factor of the third term
+     * @return linear combination.
+     * @see Sum
+     */
+    public static double linearCombination(final double a1, final double b1,
+                                           final double a2, final double b2,
+                                           final double a3, final double b3) {
+        return Sum.create()
+                .addProduct(a1, b1)
+                .addProduct(a2, b2)
+                .addProduct(a3, b3).getAsDouble();
+    }
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
index cd6506d..56cec14 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
@@ -21,8 +21,8 @@ import java.util.function.UnaryOperator;
 
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.euclidean.EuclideanVector;
+import org.apache.commons.geometry.euclidean.EuclideanVectorSum;
 import org.apache.commons.geometry.euclidean.internal.Vectors;
-import org.apache.commons.numbers.core.LinearCombination;
 import org.apache.commons.numbers.core.Precision;
 
 /** This class represents vectors and points in one-dimensional Euclidean space.
@@ -121,7 +121,9 @@ public class Vector1D extends EuclideanVector<Vector1D> {
     /** {@inheritDoc} */
     @Override
     public Vector1D lerp(final Vector1D p, final double t) {
-        return linearCombination(1.0 - t, this, t, p);
+        return Sum.create()
+                .addScaled(1.0 - t, this)
+                .addScaled(t, p).get();
     }
 
     /** {@inheritDoc} */
@@ -320,86 +322,6 @@ public class Vector1D extends EuclideanVector<Vector1D> {
         return SimpleTupleFormat.getDefault().parse(str, Vector1D::new);
     }
 
-    /** Returns a vector consisting of the linear combination of the inputs.
-     * <p>
-     * A linear combination is the sum of all of the inputs multiplied by their
-     * corresponding scale factors.
-     * </p>
-     *
-     * @param a scale factor for first vector
-     * @param c first vector
-     * @return vector calculated by {@code a * c}
-     */
-    public static Vector1D linearCombination(final double a, final Vector1D c) {
-        return new Vector1D(a * c.x);
-    }
-
-    /** Returns a vector consisting of the linear combination of the inputs.
-     * <p>
-     * A linear combination is the sum of all of the inputs multiplied by their
-     * corresponding scale factors.
-     * </p>
-     *
-     * @param a1 scale factor for first vector
-     * @param v1 first vector
-     * @param a2 scale factor for second vector
-     * @param v2 second vector
-     * @return vector calculated by {@code (a1 * v1) + (a2 * v2)}
-     */
-    public static Vector1D linearCombination(final double a1, final Vector1D v1,
-            final double a2, final Vector1D v2) {
-
-        return new Vector1D(
-                LinearCombination.value(a1, v1.x, a2, v2.x));
-    }
-
-    /** Returns a vector consisting of the linear combination of the inputs.
-     * <p>
-     * A linear combination is the sum of all of the inputs multiplied by their
-     * corresponding scale factors.
-     * </p>
-     *
-     * @param a1 scale factor for first vector
-     * @param v1 first vector
-     * @param a2 scale factor for second vector
-     * @param v2 second vector
-     * @param a3 scale factor for third vector
-     * @param v3 third vector
-     * @return vector calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3)}
-     */
-    public static Vector1D linearCombination(final double a1, final Vector1D v1,
-            final double a2, final Vector1D v2,
-            final double a3, final Vector1D v3) {
-
-        return new Vector1D(
-                LinearCombination.value(a1, v1.x, a2, v2.x, a3, v3.x));
-    }
-
-    /** Returns a vector consisting of the linear combination of the inputs.
-     * <p>
-     * A linear combination is the sum of all of the inputs multiplied by their
-     * corresponding scale factors.
-     * </p>
-     *
-     * @param a1 scale factor for first vector
-     * @param v1 first vector
-     * @param a2 scale factor for second vector
-     * @param v2 second vector
-     * @param a3 scale factor for third vector
-     * @param v3 third vector
-     * @param a4 scale factor for fourth vector
-     * @param v4 fourth vector
-     * @return vector calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3) + (a4 * v4)}
-     */
-    public static Vector1D linearCombination(final double a1, final Vector1D v1,
-            final double a2, final Vector1D v2,
-            final double a3, final Vector1D v3,
-            final double a4, final Vector1D v4) {
-
-        return new Vector1D(
-                LinearCombination.value(a1, v1.x, a2, v2.x, a3, v3.x, a4, v4.x));
-    }
-
     /**
      * Represent unit vectors.
      * This allows optimizations to be performed for certain operations.
@@ -494,4 +416,71 @@ public class Vector1D extends EuclideanVector<Vector1D> {
             return null;
         }
     }
+
+    /** Class used to create high-accuracy sums of vectors. Each vector component is
+     * summed using an instance of {@link org.apache.commons.numbers.core.Sum}.
+    *
+    * <p>This class is mutable and not thread-safe.
+    * @see org.apache.commons.numbers.core.Sum
+    */
+    public static final class Sum extends EuclideanVectorSum<Vector1D> {
+
+        /** X component sum. */
+        private final org.apache.commons.numbers.core.Sum xsum;
+
+        /** Construct a new instance with the given initial value.
+         * @param initial initial value
+         */
+        Sum(final Vector1D initial) {
+            this.xsum = org.apache.commons.numbers.core.Sum.of(initial.x);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Sum add(final Vector1D vec) {
+            xsum.add(vec.x);
+            return this;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Sum addScaled(final double scale, final Vector1D vec) {
+            xsum.addProduct(scale, vec.x);
+            return this;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Vector1D get() {
+            return Vector1D.of(xsum.getAsDouble());
+        }
+
+        /** Create a new instance with an initial value set to the {@link Vector1D#ZERO zero vector}.
+         * @return new instance set to zero
+         */
+        public static Sum create() {
+            return new Sum(Vector1D.ZERO);
+        }
+
+        /** Construct a new instance with an initial value set to the argument.
+         * @param initial initial sum value
+         * @return new instance
+         */
+        public static Sum of(final Vector1D initial) {
+            return new Sum(initial);
+        }
+
+        /** Construct a new instance from multiple values.
+         * @param first first vector
+         * @param more additional vectors
+         * @return new instance
+         */
+        public static Sum of(final Vector1D first, final Vector1D... more) {
+            final Sum s = new Sum(first);
+            for (final Vector1D v : more) {
+                s.add(v);
+            }
+            return s;
+        }
+    }
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/AffineTransformMatrix3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/AffineTransformMatrix3D.java
index d400728..a02ca3b 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/AffineTransformMatrix3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/AffineTransformMatrix3D.java
@@ -23,7 +23,6 @@ import org.apache.commons.geometry.euclidean.AbstractAffineTransformMatrix;
 import org.apache.commons.geometry.euclidean.internal.Matrices;
 import org.apache.commons.geometry.euclidean.internal.Vectors;
 import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
-import org.apache.commons.numbers.core.LinearCombination;
 
 /** Class using a matrix to represent affine transformations in 3 dimensional Euclidean space.
  *
@@ -235,7 +234,7 @@ public final class AffineTransformMatrix3D extends AbstractAffineTransformMatrix
      * @see #applyVector(Vector3D)
      */
     public double applyVectorX(final double x, final double y, final double z) {
-        return LinearCombination.value(m00, x, m01, y, m02, z);
+        return Vectors.linearCombination(m00, x, m01, y, m02, z);
     }
 
     /** Apply this transform to the given vector coordinates, ignoring translations, and
@@ -248,7 +247,7 @@ public final class AffineTransformMatrix3D extends AbstractAffineTransformMatrix
      * @see #applyVector(Vector3D)
      */
     public double applyVectorY(final double x, final double y, final double z) {
-        return LinearCombination.value(m10, x, m11, y, m12, z);
+        return Vectors.linearCombination(m10, x, m11, y, m12, z);
     }
 
     /** Apply this transform to the given vector coordinates, ignoring translations, and
@@ -261,7 +260,7 @@ public final class AffineTransformMatrix3D extends AbstractAffineTransformMatrix
      * @see #applyVector(Vector3D)
      */
     public double applyVectorZ(final double x, final double y, final double z) {
-        return LinearCombination.value(m20, x, m21, y, m22, z);
+        return Vectors.linearCombination(m20, x, m21, y, m22, z);
     }
 
     /** {@inheritDoc}
@@ -753,20 +752,20 @@ public final class AffineTransformMatrix3D extends AbstractAffineTransformMatrix
             final AffineTransformMatrix3D b) {
 
         // calculate the matrix elements
-        final double c00 = LinearCombination.value(a.m00, b.m00, a.m01, b.m10, a.m02, b.m20);
-        final double c01 = LinearCombination.value(a.m00, b.m01, a.m01, b.m11, a.m02, b.m21);
-        final double c02 = LinearCombination.value(a.m00, b.m02, a.m01, b.m12, a.m02, b.m22);
-        final double c03 = LinearCombination.value(a.m00, b.m03, a.m01, b.m13, a.m02, b.m23) + a.m03;
-
-        final double c10 = LinearCombination.value(a.m10, b.m00, a.m11, b.m10, a.m12, b.m20);
-        final double c11 = LinearCombination.value(a.m10, b.m01, a.m11, b.m11, a.m12, b.m21);
-        final double c12 = LinearCombination.value(a.m10, b.m02, a.m11, b.m12, a.m12, b.m22);
-        final double c13 = LinearCombination.value(a.m10, b.m03, a.m11, b.m13, a.m12, b.m23) + a.m13;
-
-        final double c20 = LinearCombination.value(a.m20, b.m00, a.m21, b.m10, a.m22, b.m20);
-        final double c21 = LinearCombination.value(a.m20, b.m01, a.m21, b.m11, a.m22, b.m21);
-        final double c22 = LinearCombination.value(a.m20, b.m02, a.m21, b.m12, a.m22, b.m22);
-        final double c23 = LinearCombination.value(a.m20, b.m03, a.m21, b.m13, a.m22, b.m23) + a.m23;
+        final double c00 = Vectors.linearCombination(a.m00, b.m00, a.m01, b.m10, a.m02, b.m20);
+        final double c01 = Vectors.linearCombination(a.m00, b.m01, a.m01, b.m11, a.m02, b.m21);
+        final double c02 = Vectors.linearCombination(a.m00, b.m02, a.m01, b.m12, a.m02, b.m22);
+        final double c03 = Vectors.linearCombination(a.m00, b.m03, a.m01, b.m13, a.m02, b.m23) + a.m03;
+
+        final double c10 = Vectors.linearCombination(a.m10, b.m00, a.m11, b.m10, a.m12, b.m20);
+        final double c11 = Vectors.linearCombination(a.m10, b.m01, a.m11, b.m11, a.m12, b.m21);
+        final double c12 = Vectors.linearCombination(a.m10, b.m02, a.m11, b.m12, a.m12, b.m22);
+        final double c13 = Vectors.linearCombination(a.m10, b.m03, a.m11, b.m13, a.m12, b.m23) + a.m13;
+
+        final double c20 = Vectors.linearCombination(a.m20, b.m00, a.m21, b.m10, a.m22, b.m20);
+        final double c21 = Vectors.linearCombination(a.m20, b.m01, a.m21, b.m11, a.m22, b.m21);
+        final double c22 = Vectors.linearCombination(a.m20, b.m02, a.m21, b.m12, a.m22, b.m22);
+        final double c23 = Vectors.linearCombination(a.m20, b.m03, a.m21, b.m13, a.m22, b.m23) + a.m23;
 
         return new AffineTransformMatrix3D(
                     c00, c01, c02, c03,
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/EmbeddingPlane.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/EmbeddingPlane.java
index a39c0d3..a31456c 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/EmbeddingPlane.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/EmbeddingPlane.java
@@ -127,10 +127,10 @@ public final class EmbeddingPlane extends Plane implements EmbeddingHyperplane<V
      */
     @Override
     public Vector3D toSpace(final Vector2D point) {
-        return Vector3D.linearCombination(
-                point.getX(), u,
-                point.getY(), v,
-                -getOriginOffset(), getNormal());
+        return Vector3D.Sum.create()
+                .addScaled(point.getX(), u)
+                .addScaled(point.getY(), v)
+                .addScaled(-getOriginOffset(), getNormal()).get();
     }
 
     /** Get one point from the 3D-space.
@@ -140,10 +140,10 @@ public final class EmbeddingPlane extends Plane implements EmbeddingHyperplane<V
      *         to the plane
      */
     public Vector3D pointAt(final Vector2D inPlane, final double offset) {
-        return Vector3D.linearCombination(
-                inPlane.getX(), u,
-                inPlane.getY(), v,
-                offset - getOriginOffset(), getNormal());
+        return Vector3D.Sum.create()
+                .addScaled(inPlane.getX(), u)
+                .addScaled(inPlane.getY(), v)
+                .addScaled(offset - getOriginOffset(), getNormal()).get();
     }
 
     /** Build a new reversed version of this plane, with opposite orientation.
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
index 1556b07..6c63b75 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
@@ -227,9 +227,9 @@ public class Plane extends AbstractHyperplane<Vector3D> {
         final Vector3D point = line.pointAt(0);
         final double k = -(originOffset + normal.dot(point)) / dot;
 
-        return Vector3D.linearCombination(
-                1.0, point,
-                k, direction);
+        return Vector3D.Sum.of(point)
+                .addScaled(k, direction)
+                .get();
     }
 
     /** Get the line formed by the intersection of this instance with the given plane.
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java
index 9d7f4ce..ed389f0 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java
@@ -23,9 +23,9 @@ import java.util.function.UnaryOperator;
 
 import org.apache.commons.geometry.core.internal.DoubleFunction3N;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
+import org.apache.commons.geometry.euclidean.EuclideanVectorSum;
 import org.apache.commons.geometry.euclidean.MultiDimensionalEuclideanVector;
 import org.apache.commons.geometry.euclidean.internal.Vectors;
-import org.apache.commons.numbers.core.LinearCombination;
 import org.apache.commons.numbers.core.Precision;
 
 /** This class represents vectors and points in three-dimensional Euclidean space.
@@ -167,7 +167,9 @@ public class Vector3D extends MultiDimensionalEuclideanVector<Vector3D> {
     /** {@inheritDoc} */
     @Override
     public Vector3D lerp(final Vector3D p, final double t) {
-        return linearCombination(1.0 - t, this, t, p);
+        return Sum.create()
+                .addScaled(1.0 - t, this)
+                .addScaled(t, p).get();
     }
 
     /** {@inheritDoc} */
@@ -284,11 +286,14 @@ public class Vector3D extends MultiDimensionalEuclideanVector<Vector3D> {
      * algorithms to preserve accuracy and reduce cancellation effects.
      * It should be very accurate even for nearly orthogonal vectors.
      * </p>
-     * @see LinearCombination#value(double, double, double, double, double, double)
+     * @see org.apache.commons.numbers.core.Sum
      */
     @Override
     public double dot(final Vector3D v) {
-        return LinearCombination.value(x, v.x, y, v.y, z, v.z);
+        return Vectors.linearCombination(
+                x, v.x,
+                y, v.y,
+                z, v.z);
     }
 
     /** {@inheritDoc}
@@ -372,9 +377,9 @@ public class Vector3D extends MultiDimensionalEuclideanVector<Vector3D> {
      * @return the cross product this ^ v as a new Vector3D
      */
     public Vector3D cross(final Vector3D v) {
-        return new Vector3D(LinearCombination.value(y, v.z, -z, v.y),
-                            LinearCombination.value(z, v.x, -x, v.z),
-                            LinearCombination.value(x, v.y, -y, v.x));
+        return new Vector3D(Vectors.linearCombination(y, v.z, -z, v.y),
+                            Vectors.linearCombination(z, v.x, -x, v.z),
+                            Vectors.linearCombination(x, v.y, -y, v.x));
     }
 
     /** Convenience method to apply a function to this vector. This
@@ -643,109 +648,15 @@ public class Vector3D extends MultiDimensionalEuclideanVector<Vector3D> {
      * @return the centroid of the point set
      */
     private static Vector3D computeCentroid(final Vector3D first, final Iterator<? extends Vector3D> more) {
-        double x = first.getX();
-        double y = first.getY();
-        double z = first.getZ();
-
+        final Sum sum = Sum.of(first);
         int count = 1;
 
-        Vector3D pt;
         while (more.hasNext()) {
-            pt = more.next();
-
-            x += pt.getX();
-            y += pt.getY();
-            z += pt.getZ();
-
+            sum.add(more.next());
             ++count;
         }
 
-        final double invCount = 1.0 / count;
-
-        return new Vector3D(invCount * x, invCount * y, invCount * z);
-    }
-
-    /** Returns a vector consisting of the linear combination of the inputs.
-     * <p>
-     * A linear combination is the sum of all of the inputs multiplied by their
-     * corresponding scale factors.
-     * </p>
-     *
-     * @param a scale factor for first vector
-     * @param c first vector
-     * @return vector calculated by {@code a * c}
-     */
-    public static Vector3D linearCombination(final double a, final Vector3D c) {
-        return c.multiply(a);
-    }
-
-    /** Returns a vector consisting of the linear combination of the inputs.
-     * <p>
-     * A linear combination is the sum of all of the inputs multiplied by their
-     * corresponding scale factors.
-     * </p>
-     *
-     * @param a1 scale factor for first vector
-     * @param v1 first vector
-     * @param a2 scale factor for second vector
-     * @param v2 second vector
-     * @return vector calculated by {@code (a1 * v1) + (a2 * v2)}
-     */
-    public static Vector3D linearCombination(final double a1, final Vector3D v1,
-            final double a2, final Vector3D v2) {
-        return new Vector3D(
-                LinearCombination.value(a1, v1.x, a2, v2.x),
-                LinearCombination.value(a1, v1.y, a2, v2.y),
-                LinearCombination.value(a1, v1.z, a2, v2.z));
-    }
-
-    /** Returns a vector consisting of the linear combination of the inputs.
-     * <p>
-     * A linear combination is the sum of all of the inputs multiplied by their
-     * corresponding scale factors.
-     * </p>
-     *
-     * @param a1 scale factor for first vector
-     * @param v1 first vector
-     * @param a2 scale factor for second vector
-     * @param v2 second vector
-     * @param a3 scale factor for third vector
-     * @param v3 third vector
-     * @return vector calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3)}
-     */
-    public static Vector3D linearCombination(final double a1, final Vector3D v1,
-            final double a2, final Vector3D v2,
-            final double a3, final Vector3D v3) {
-        return new Vector3D(
-                LinearCombination.value(a1, v1.x, a2, v2.x, a3, v3.x),
-                LinearCombination.value(a1, v1.y, a2, v2.y, a3, v3.y),
-                LinearCombination.value(a1, v1.z, a2, v2.z, a3, v3.z));
-    }
-
-    /** Returns a vector consisting of the linear combination of the inputs.
-     * <p>
-     * A linear combination is the sum of all of the inputs multiplied by their
-     * corresponding scale factors.
-     * </p>
-     *
-     * @param a1 scale factor for first vector
-     * @param v1 first vector
-     * @param a2 scale factor for second vector
-     * @param v2 second vector
-     * @param a3 scale factor for third vector
-     * @param v3 third vector
-     * @param a4 scale factor for fourth vector
-     * @param v4 fourth vector
-     * @return vector calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3) + (a4 * v4)}
-     */
-    public static Vector3D linearCombination(final double a1, final Vector3D v1,
-            final double a2, final Vector3D v2,
-            final double a3, final Vector3D v3,
-            final double a4, final Vector3D v4) {
-        return new Vector3D(
-                LinearCombination.value(a1, v1.x, a2, v2.x, a3, v3.x, a4, v4.x),
-                LinearCombination.value(a1, v1.y, a2, v2.y, a3, v3.y, a4, v4.y),
-                LinearCombination.value(a1, v1.z, a2, v2.z, a3, v3.z, a4, v4.z));
+        return sum.get().multiply(1.0 / count);
     }
 
     /**
@@ -910,4 +821,83 @@ public class Vector3D extends MultiDimensionalEuclideanVector<Vector3D> {
             return null;
         }
     }
+
+    /** Class used to create high-accuracy sums of vectors. Each vector component is
+     * summed using an instance of {@link org.apache.commons.numbers.core.Sum}.
+     *
+     * <p>This class is mutable and not thread-safe.
+     * @see org.apache.commons.numbers.core.Sum
+     */
+    public static final class Sum extends EuclideanVectorSum<Vector3D> {
+        /** X component sum. */
+        private final org.apache.commons.numbers.core.Sum xsum;
+        /** Y component sum. */
+        private final org.apache.commons.numbers.core.Sum ysum;
+        /** Z component sum. */
+        private final org.apache.commons.numbers.core.Sum zsum;
+
+        /** Construct a new instance with the given initial value.
+         * @param initial initial value
+         */
+        Sum(final Vector3D initial) {
+            this.xsum = org.apache.commons.numbers.core.Sum.of(initial.x);
+            this.ysum = org.apache.commons.numbers.core.Sum.of(initial.y);
+            this.zsum = org.apache.commons.numbers.core.Sum.of(initial.z);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Sum add(final Vector3D vec) {
+            xsum.add(vec.x);
+            ysum.add(vec.y);
+            zsum.add(vec.z);
+            return this;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Sum addScaled(final double scale, final Vector3D vec) {
+            xsum.addProduct(scale, vec.x);
+            ysum.addProduct(scale, vec.y);
+            zsum.addProduct(scale, vec.z);
+            return this;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Vector3D get() {
+            return Vector3D.of(
+                    xsum.getAsDouble(),
+                    ysum.getAsDouble(),
+                    zsum.getAsDouble());
+        }
+
+        /** Create a new instance with an initial value set to the {@link Vector3D#ZERO zero vector}.
+         * @return new instance set to zero
+         */
+        public static Sum create() {
+            return new Sum(Vector3D.ZERO);
+        }
+
+        /** Construct a new instance with an initial value set to the argument.
+         * @param initial initial sum value
+         * @return new instance
+         */
+        public static Sum of(final Vector3D initial) {
+            return new Sum(initial);
+        }
+
+        /** Construct a new instance from multiple values.
+         * @param first first vector
+         * @param more additional vectors
+         * @return new instance
+         */
+        public static Sum of(final Vector3D first, final Vector3D... more) {
+            final Sum s = new Sum(first);
+            for (final Vector3D v : more) {
+                s.add(v);
+            }
+            return s;
+        }
+    }
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Line3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Line3D.java
index 7d334ca..033a286 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Line3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Line3D.java
@@ -156,7 +156,9 @@ public final class Line3D implements Embedding<Vector3D, Vector1D> {
      * @return one point belonging to the line, at specified abscissa
      */
     public Vector3D pointAt(final double abscissa) {
-        return Vector3D.linearCombination(1.0, origin, abscissa, direction);
+        return Vector3D.Sum.of(origin)
+                .addScaled(abscissa, direction)
+                .get();
     }
 
     /** {@inheritDoc} */
@@ -251,7 +253,9 @@ public final class Line3D implements Embedding<Vector3D, Vector1D> {
         final double a = delta.dot(direction);
         final double b = delta.dot(line.direction);
 
-        return Vector3D.linearCombination(1, origin, (a - (b * cos)) / n, direction);
+        return Vector3D.Sum.of(origin)
+                .addScaled((a - (b * cos)) / n, direction)
+                .get();
     }
 
     /** Get the intersection point of the instance and another line.
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/rotation/QuaternionRotation.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/rotation/QuaternionRotation.java
index f00aa65..4317bd8 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/rotation/QuaternionRotation.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/rotation/QuaternionRotation.java
@@ -24,7 +24,6 @@ import org.apache.commons.geometry.euclidean.internal.Vectors;
 import org.apache.commons.geometry.euclidean.threed.AffineTransformMatrix3D;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
 import org.apache.commons.numbers.angle.Angle;
-import org.apache.commons.numbers.core.LinearCombination;
 import org.apache.commons.numbers.quaternion.Quaternion;
 import org.apache.commons.numbers.quaternion.Slerp;
 
@@ -698,17 +697,17 @@ public final class QuaternionRotation implements Rotation3D {
         // be the multiplication of the matrix composed of the column vectors d, e, f and the
         // inverse of the matrix composed of the column vectors a, b, c (which is simply the transpose since
         // it's orthogonal).
-        final double m00 = LinearCombination.value(d.getX(), a.getX(), e.getX(), b.getX(), f.getX(), c.getX());
-        final double m01 = LinearCombination.value(d.getX(), a.getY(), e.getX(), b.getY(), f.getX(), c.getY());
-        final double m02 = LinearCombination.value(d.getX(), a.getZ(), e.getX(), b.getZ(), f.getX(), c.getZ());
+        final double m00 = Vectors.linearCombination(d.getX(), a.getX(), e.getX(), b.getX(), f.getX(), c.getX());
+        final double m01 = Vectors.linearCombination(d.getX(), a.getY(), e.getX(), b.getY(), f.getX(), c.getY());
+        final double m02 = Vectors.linearCombination(d.getX(), a.getZ(), e.getX(), b.getZ(), f.getX(), c.getZ());
 
-        final double m10 = LinearCombination.value(d.getY(), a.getX(), e.getY(), b.getX(), f.getY(), c.getX());
-        final double m11 = LinearCombination.value(d.getY(), a.getY(), e.getY(), b.getY(), f.getY(), c.getY());
-        final double m12 = LinearCombination.value(d.getY(), a.getZ(), e.getY(), b.getZ(), f.getY(), c.getZ());
+        final double m10 = Vectors.linearCombination(d.getY(), a.getX(), e.getY(), b.getX(), f.getY(), c.getX());
+        final double m11 = Vectors.linearCombination(d.getY(), a.getY(), e.getY(), b.getY(), f.getY(), c.getY());
+        final double m12 = Vectors.linearCombination(d.getY(), a.getZ(), e.getY(), b.getZ(), f.getY(), c.getZ());
 
-        final double m20 = LinearCombination.value(d.getZ(), a.getX(), e.getZ(), b.getX(), f.getZ(), c.getX());
-        final double m21 = LinearCombination.value(d.getZ(), a.getY(), e.getZ(), b.getY(), f.getZ(), c.getY());
-        final double m22 = LinearCombination.value(d.getZ(), a.getZ(), e.getZ(), b.getZ(), f.getZ(), c.getZ());
+        final double m20 = Vectors.linearCombination(d.getZ(), a.getX(), e.getZ(), b.getX(), f.getZ(), c.getX());
+        final double m21 = Vectors.linearCombination(d.getZ(), a.getY(), e.getZ(), b.getY(), f.getZ(), c.getY());
+        final double m22 = Vectors.linearCombination(d.getZ(), a.getZ(), e.getZ(), b.getZ(), f.getZ(), c.getZ());
 
 
         return orthogonalRotationMatrixToQuaternion(
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/AffineTransformMatrix2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/AffineTransformMatrix2D.java
index 08d1f1a..f7428d5 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/AffineTransformMatrix2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/AffineTransformMatrix2D.java
@@ -23,7 +23,6 @@ import org.apache.commons.geometry.euclidean.AbstractAffineTransformMatrix;
 import org.apache.commons.geometry.euclidean.internal.Matrices;
 import org.apache.commons.geometry.euclidean.internal.Vectors;
 import org.apache.commons.geometry.euclidean.twod.rotation.Rotation2D;
-import org.apache.commons.numbers.core.LinearCombination;
 
 /** Class using a matrix to represent affine transformations in 2 dimensional Euclidean space.
 *
@@ -185,7 +184,7 @@ public final class AffineTransformMatrix2D extends AbstractAffineTransformMatrix
      * @see #applyVector(Vector2D)
      */
     public double applyVectorX(final double x, final double y) {
-        return LinearCombination.value(m00, x, m01, y);
+        return Vectors.linearCombination(m00, x, m01, y);
     }
 
     /** Apply this transform to the given vector coordinates, ignoring translations, and
@@ -197,7 +196,7 @@ public final class AffineTransformMatrix2D extends AbstractAffineTransformMatrix
      * @see #applyVector(Vector2D)
      */
     public double applyVectorY(final double x, final double y) {
-        return LinearCombination.value(m10, x, m11, y);
+        return Vectors.linearCombination(m10, x, m11, y);
     }
 
     /** {@inheritDoc}
@@ -702,13 +701,13 @@ public final class AffineTransformMatrix2D extends AbstractAffineTransformMatrix
     private static AffineTransformMatrix2D multiply(final AffineTransformMatrix2D a,
             final AffineTransformMatrix2D b) {
 
-        final double c00 = LinearCombination.value(a.m00, b.m00, a.m01, b.m10);
-        final double c01 = LinearCombination.value(a.m00, b.m01, a.m01, b.m11);
-        final double c02 = LinearCombination.value(a.m00, b.m02, a.m01, b.m12) + a.m02;
+        final double c00 = Vectors.linearCombination(a.m00, b.m00, a.m01, b.m10);
+        final double c01 = Vectors.linearCombination(a.m00, b.m01, a.m01, b.m11);
+        final double c02 = Vectors.linearCombination(a.m00, b.m02, a.m01, b.m12) + a.m02;
 
-        final double c10 = LinearCombination.value(a.m10, b.m00, a.m11, b.m10);
-        final double c11 = LinearCombination.value(a.m10, b.m01, a.m11, b.m11);
-        final double c12 = LinearCombination.value(a.m10, b.m02, a.m11, b.m12) + a.m12;
+        final double c10 = Vectors.linearCombination(a.m10, b.m00, a.m11, b.m10);
+        final double c11 = Vectors.linearCombination(a.m10, b.m01, a.m11, b.m11);
+        final double c12 = Vectors.linearCombination(a.m10, b.m02, a.m11, b.m12) + a.m12;
 
         return new AffineTransformMatrix2D(
                     c00, c01, c02,
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Line.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Line.java
index 0398d39..43deba2 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Line.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Line.java
@@ -23,10 +23,10 @@ import org.apache.commons.geometry.core.Transform;
 import org.apache.commons.geometry.core.partitioning.AbstractHyperplane;
 import org.apache.commons.geometry.core.partitioning.EmbeddingHyperplane;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.euclidean.internal.Vectors;
 import org.apache.commons.geometry.euclidean.oned.AffineTransformMatrix1D;
 import org.apache.commons.geometry.euclidean.oned.Vector1D;
 import org.apache.commons.numbers.angle.Angle;
-import org.apache.commons.numbers.core.LinearCombination;
 import org.apache.commons.numbers.core.Precision;
 
 /** This class represents an oriented line in the 2D plane.
@@ -298,8 +298,8 @@ public final class Line extends AbstractHyperplane<Vector2D>
         // step below given that the origin location is equal to
         // (-direction.y * originOffset, direction.x * originOffset).
         return Vector2D.of(
-                    LinearCombination.value(abscissa, direction.getX(), -originOffset, direction.getY()),
-                    LinearCombination.value(abscissa, direction.getY(), originOffset, direction.getX())
+                    Vectors.linearCombination(abscissa, direction.getX(), -originOffset, direction.getY()),
+                    Vectors.linearCombination(abscissa, direction.getY(), originOffset, direction.getX())
                 );
     }
 
@@ -316,11 +316,11 @@ public final class Line extends AbstractHyperplane<Vector2D>
             return null;
         }
 
-        final double x = LinearCombination.value(
+        final double x = Vectors.linearCombination(
                 other.direction.getX(), originOffset,
                 -direction.getX(), other.originOffset) / area;
 
-        final double y = LinearCombination.value(
+        final double y = Vectors.linearCombination(
                 other.direction.getY(), originOffset,
                 -direction.getY(), other.originOffset) / area;
 
@@ -400,8 +400,8 @@ public final class Line extends AbstractHyperplane<Vector2D>
      */
     public Vector2D pointAt(final double abscissa, final double offset) {
         final double pointOffset = offset - originOffset;
-        return Vector2D.of(LinearCombination.value(abscissa, direction.getX(),  pointOffset, direction.getY()),
-                            LinearCombination.value(abscissa, direction.getY(), -pointOffset, direction.getX()));
+        return Vector2D.of(Vectors.linearCombination(abscissa, direction.getX(),  pointOffset, direction.getY()),
+                            Vectors.linearCombination(abscissa, direction.getY(), -pointOffset, direction.getX()));
     }
 
     /** Check if the line contains a point.
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
index ab32f9b..10b0ff8 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
@@ -23,9 +23,9 @@ import java.util.function.UnaryOperator;
 
 import org.apache.commons.geometry.core.internal.DoubleFunction2N;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
+import org.apache.commons.geometry.euclidean.EuclideanVectorSum;
 import org.apache.commons.geometry.euclidean.MultiDimensionalEuclideanVector;
 import org.apache.commons.geometry.euclidean.internal.Vectors;
-import org.apache.commons.numbers.core.LinearCombination;
 import org.apache.commons.numbers.core.Precision;
 
 /** This class represents vectors and points in two-dimensional Euclidean space.
@@ -145,7 +145,9 @@ public class Vector2D extends MultiDimensionalEuclideanVector<Vector2D> {
     /** {@inheritDoc} */
     @Override
     public Vector2D lerp(final Vector2D p, final double t) {
-        return linearCombination(1.0 - t, this, t, p);
+        return Sum.create()
+                .addScaled(1.0 - t, this)
+                .addScaled(t, p).get();
     }
 
     /** {@inheritDoc} */
@@ -240,7 +242,7 @@ public class Vector2D extends MultiDimensionalEuclideanVector<Vector2D> {
     /** {@inheritDoc} */
     @Override
     public double dot(final Vector2D v) {
-        return LinearCombination.value(x, v.x, y, v.y);
+        return Vectors.linearCombination(x, v.x, y, v.y);
     }
 
     /** {@inheritDoc}
@@ -258,7 +260,7 @@ public class Vector2D extends MultiDimensionalEuclideanVector<Vector2D> {
         final double threshold = normProduct * 0.9999;
         if ((dot < -threshold) || (dot > threshold)) {
             // the vectors are almost aligned, compute using the sine
-            final double n = Math.abs(LinearCombination.value(x, v.y, -y, v.x));
+            final double n = Math.abs(Vectors.linearCombination(x, v.y, -y, v.x));
             if (dot >= 0) {
                 return Math.asin(n / normProduct);
             }
@@ -315,7 +317,7 @@ public class Vector2D extends MultiDimensionalEuclideanVector<Vector2D> {
      * @return the signed area of the parallelogram formed by this instance and the given vector
      */
     public double signedArea(final Vector2D v) {
-        return LinearCombination.value(
+        return Vectors.linearCombination(
                 x, v.y,
                 -y, v.x);
     }
@@ -578,104 +580,15 @@ public class Vector2D extends MultiDimensionalEuclideanVector<Vector2D> {
      * @return the centroid of the point set
      */
     private static Vector2D computeCentroid(final Vector2D first, final Iterator<? extends Vector2D> more) {
-        double x = first.getX();
-        double y = first.getY();
-
+        final Sum sum = Sum.of(first);
         int count = 1;
 
-        Vector2D pt;
         while (more.hasNext()) {
-            pt = more.next();
-
-            x += pt.getX();
-            y += pt.getY();
-
+            sum.add(more.next());
             ++count;
         }
 
-        final double invCount = 1.0 / count;
-
-        return new Vector2D(invCount * x, invCount * y);
-    }
-
-    /** Returns a vector consisting of the linear combination of the inputs.
-     * <p>
-     * A linear combination is the sum of all of the inputs multiplied by their
-     * corresponding scale factors.
-     * </p>
-     *
-     * @param a scale factor for first vector
-     * @param c first vector
-     * @return vector calculated by {@code a * c}
-     */
-    public static Vector2D linearCombination(final double a, final Vector2D c) {
-        return new Vector2D(a * c.x, a * c.y);
-    }
-
-    /** Returns a vector consisting of the linear combination of the inputs.
-     * <p>
-     * A linear combination is the sum of all of the inputs multiplied by their
-     * corresponding scale factors.
-     * </p>
-     *
-     * @param a1 scale factor for first vector
-     * @param v1 first vector
-     * @param a2 scale factor for second vector
-     * @param v2 second vector
-     * @return vector calculated by {@code (a1 * v1) + (a2 * v2)}
-     */
-    public static Vector2D linearCombination(final double a1, final Vector2D v1,
-            final double a2, final Vector2D v2) {
-        return new Vector2D(
-                LinearCombination.value(a1, v1.x, a2, v2.x),
-                LinearCombination.value(a1, v1.y, a2, v2.y));
-    }
-
-    /** Returns a vector consisting of the linear combination of the inputs.
-     * <p>
-     * A linear combination is the sum of all of the inputs multiplied by their
-     * corresponding scale factors.
-     * </p>
-     *
-     * @param a1 scale factor for first vector
-     * @param v1 first vector
-     * @param a2 scale factor for second vector
-     * @param v2 second vector
-     * @param a3 scale factor for third vector
-     * @param v3 third vector
-     * @return vector calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3)}
-     */
-    public static Vector2D linearCombination(final double a1, final Vector2D v1,
-            final double a2, final Vector2D v2,
-            final double a3, final Vector2D v3) {
-        return new Vector2D(
-                LinearCombination.value(a1, v1.x, a2, v2.x, a3, v3.x),
-                LinearCombination.value(a1, v1.y, a2, v2.y, a3, v3.y));
-    }
-
-    /** Returns a vector consisting of the linear combination of the inputs.
-     * <p>
-     * A linear combination is the sum of all of the inputs multiplied by their
-     * corresponding scale factors.
-     * </p>
-     *
-     * @param a1 scale factor for first vector
-     * @param v1 first vector
-     * @param a2 scale factor for second vector
-     * @param v2 second vector
-     * @param a3 scale factor for third vector
-     * @param v3 third vector
-     * @param a4 scale factor for fourth vector
-     * @param v4 fourth vector
-     * @return vector calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3) + (a4 * v4)}
-     */
-    public static Vector2D linearCombination(final double a1, final Vector2D v1,
-                                             final double a2, final Vector2D v2,
-                                             final double a3, final Vector2D v3,
-                                             final double a4, final Vector2D v4) {
-        return new Vector2D(
-                LinearCombination.value(a1, v1.x, a2, v2.x, a3, v3.x, a4, v4.x),
-                LinearCombination.value(a1, v1.y, a2, v2.y, a3, v3.y, a4, v4.y));
+        return sum.get().multiply(1.0 / count);
     }
 
     /**
@@ -830,4 +743,77 @@ public class Vector2D extends MultiDimensionalEuclideanVector<Vector2D> {
             return null;
         }
     }
+
+    /** Class used to create high-accuracy sums of vectors. Each vector component is
+     * summed using an instance of {@link org.apache.commons.numbers.core.Sum}.
+     *
+     * <p>This class is mutable and not thread-safe.
+     * @see org.apache.commons.numbers.core.Sum
+     */
+    public static final class Sum extends EuclideanVectorSum<Vector2D> {
+        /** X component sum. */
+        private final org.apache.commons.numbers.core.Sum xsum;
+        /** Y component sum. */
+        private final org.apache.commons.numbers.core.Sum ysum;
+
+        /** Construct a new instance with the given initial value.
+         * @param initial initial value
+         */
+        Sum(final Vector2D initial) {
+            this.xsum = org.apache.commons.numbers.core.Sum.of(initial.x);
+            this.ysum = org.apache.commons.numbers.core.Sum.of(initial.y);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Sum add(final Vector2D vec) {
+            xsum.add(vec.x);
+            ysum.add(vec.y);
+            return this;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Sum addScaled(final double scale, final Vector2D vec) {
+            xsum.addProduct(scale, vec.x);
+            ysum.addProduct(scale, vec.y);
+            return this;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Vector2D get() {
+            return Vector2D.of(
+                    xsum.getAsDouble(),
+                    ysum.getAsDouble());
+        }
+
+        /** Create a new instance with an initial value set to the {@link Vector2D#ZERO zero vector}.
+         * @return new instance set to zero
+         */
+        public static Sum create() {
+            return new Sum(Vector2D.ZERO);
+        }
+
+        /** Construct a new instance with an initial value set to the argument.
+         * @param initial initial sum value
+         * @return new instance
+         */
+        public static Sum of(final Vector2D initial) {
+            return new Sum(initial);
+        }
+
+        /** Construct a new instance from multiple values.
+         * @param first first vector
+         * @param more additional vectors
+         * @return new instance
+         */
+        public static Sum of(final Vector2D first, final Vector2D... more) {
+            final Sum s = new Sum(first);
+            for (final Vector2D v : more) {
+                s.add(v);
+            }
+            return s;
+        }
+    }
 }
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java
index ced992f..4bd8e61 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java
@@ -16,7 +16,9 @@
  */
 package org.apache.commons.geometry.euclidean.oned;
 
+import java.util.Arrays;
 import java.util.Comparator;
+import java.util.List;
 import java.util.regex.Pattern;
 
 import org.apache.commons.geometry.core.GeometryTestUtils;
@@ -706,49 +708,45 @@ class Vector1DTest {
     }
 
     @Test
-    void testLinearCombination() {
+    void testSum_factoryMethods() {
         // act/assert
-        checkVector(Vector1D.linearCombination(2, Vector1D.of(3)), 6);
-        checkVector(Vector1D.linearCombination(-2, Vector1D.of(3)), -6);
+        checkVector(Vector1D.Sum.create().get(), 0);
+        checkVector(Vector1D.Sum.of(Vector1D.of(2)).get(), 2);
+        checkVector(Vector1D.Sum.of(
+                Vector1D.of(-2),
+                Vector1D.Unit.PLUS).get(), -1);
     }
 
     @Test
-    void testLinearCombination2() {
-        // act/assert
-        checkVector(Vector1D.linearCombination(
-                2, Vector1D.of(3),
-                5, Vector1D.of(7)), 41);
-        checkVector(Vector1D.linearCombination(
-                2, Vector1D.of(3),
-                -5, Vector1D.of(7)), -29);
-    }
+    void testSum_instanceMethods() {
+        // arrange
+        final Vector1D p1 = Vector1D.of(-1);
+        final Vector1D p2 = Vector1D.of(4);
 
-    @Test
-    void testLinearCombination3() {
         // act/assert
-        checkVector(Vector1D.linearCombination(
-                2, Vector1D.of(3),
-                5, Vector1D.of(7),
-                11, Vector1D.of(13)), 184);
-        checkVector(Vector1D.linearCombination(
-                2, Vector1D.of(3),
-                5, Vector1D.of(7),
-                -11, Vector1D.of(13)), -102);
+        checkVector(Vector1D.Sum.create()
+                .add(p1)
+                .addScaled(0.5, p2)
+                .get(), 1);
     }
 
     @Test
-    void testLinearCombination4() {
+    void testSum_accept() {
+        // arrange
+        final Vector1D p1 = Vector1D.of(2);
+        final Vector1D p2 = Vector1D.of(-3);
+
+        final List<Vector1D.Unit> units = Arrays.asList(
+                Vector1D.Unit.MINUS);
+
+        final Vector1D.Sum s = Vector1D.Sum.create();
+
         // act/assert
-        checkVector(Vector1D.linearCombination(
-                2, Vector1D.of(3),
-                5, Vector1D.of(7),
-                11, Vector1D.of(13),
-                17, Vector1D.of(19)), 507);
-        checkVector(Vector1D.linearCombination(
-                2, Vector1D.of(3),
-                5, Vector1D.of(7),
-                11, Vector1D.of(13),
-                -17, Vector1D.of(19)), -139);
+        Arrays.asList(p1, Vector1D.ZERO, p2).forEach(s);
+        units.forEach(s);
+
+        // assert
+        checkVector(s.get(), -2);
     }
 
     @Test
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/EmbeddingPlaneTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/EmbeddingPlaneTest.java
index 7f01fb7..6f35074 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/EmbeddingPlaneTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/EmbeddingPlaneTest.java
@@ -280,17 +280,19 @@ class EmbeddingPlaneTest {
         EmbeddingPlane plane  = Planes.fromPoints(p1, p2, p3, TEST_PRECISION).getEmbedding();
 
         // act/assert
-        plane = plane.translate(Vector3D.linearCombination(2.0, plane.getU(), -1.5, plane.getV()));
+        plane = plane.translate(Vector3D.Sum.create()
+                .addScaled(2.0, plane.getU())
+                .addScaled(-1.5, plane.getV()).get());
         Assertions.assertTrue(plane.contains(p1));
         Assertions.assertTrue(plane.contains(p2));
         Assertions.assertTrue(plane.contains(p3));
 
-        plane = plane.translate(Vector3D.linearCombination(-1.2, plane.getNormal()));
+        plane = plane.translate(plane.getNormal().multiply(-1.2));
         Assertions.assertFalse(plane.contains(p1));
         Assertions.assertFalse(plane.contains(p2));
         Assertions.assertFalse(plane.contains(p3));
 
-        plane = plane.translate(Vector3D.linearCombination(+1.2, plane.getNormal()));
+        plane = plane.translate(plane.getNormal().multiply(+1.2));
         Assertions.assertTrue(plane.contains(p1));
         Assertions.assertTrue(plane.contains(p2));
         Assertions.assertTrue(plane.contains(p3));
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneTest.java
index 17d7a8b..b9393ba 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneTest.java
@@ -490,10 +490,10 @@ class PlaneTest {
         Assertions.assertEquals(-5.0, plane.offset(Vector3D.of(-4, 0, 0)), TEST_EPS);
         Assertions.assertEquals(+5.0, plane.offset(Vector3D.of(6, 10, -12)), TEST_EPS);
         Assertions.assertEquals(0.3,
-                            plane.offset(Vector3D.linearCombination(1.0, p1, 0.3, plane.getNormal())),
+                            plane.offset(Vector3D.Sum.of(p1).addScaled(0.3, plane.getNormal()).get()),
                             TEST_EPS);
         Assertions.assertEquals(-0.3,
-                            plane.offset(Vector3D.linearCombination(1.0, p1, -0.3, plane.getNormal())),
+                            plane.offset(Vector3D.Sum.of(p1).addScaled(-0.3, plane.getNormal()).get()),
                             TEST_EPS);
     }
 
@@ -701,17 +701,17 @@ class PlaneTest {
         Plane plane  = Planes.fromPoints(p1, p2, p3, TEST_PRECISION);
 
         // act/assert
-        plane = plane.translate(Vector3D.linearCombination(2.0, plane.getNormal().orthogonal()));
+        plane = plane.translate(plane.getNormal().orthogonal().multiply(2.0));
         Assertions.assertTrue(plane.contains(p1));
         Assertions.assertTrue(plane.contains(p2));
         Assertions.assertTrue(plane.contains(p3));
 
-        plane = plane.translate(Vector3D.linearCombination(-1.2, plane.getNormal()));
+        plane = plane.translate(plane.getNormal().multiply(-1.2));
         Assertions.assertFalse(plane.contains(p1));
         Assertions.assertFalse(plane.contains(p2));
         Assertions.assertFalse(plane.contains(p3));
 
-        plane = plane.translate(Vector3D.linearCombination(+1.2, plane.getNormal()));
+        plane = plane.translate(plane.getNormal().multiply(+1.2));
         Assertions.assertTrue(plane.contains(p1));
         Assertions.assertTrue(plane.contains(p2));
         Assertions.assertTrue(plane.contains(p3));
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3DTest.java
index 7364465..2f6f0b8 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3DTest.java
@@ -1246,10 +1246,10 @@ class RegionBSPTree3DTest {
         final double third = 1.0 / 3.0;
         EuclideanTestUtils.assertRegionLocation(tree, RegionLocation.BOUNDARY,
             vertex1, vertex2, vertex3, vertex4,
-            Vector3D.linearCombination(third, vertex1, third, vertex2, third, vertex3),
-            Vector3D.linearCombination(third, vertex2, third, vertex3, third, vertex4),
-            Vector3D.linearCombination(third, vertex3, third, vertex4, third, vertex1),
-            Vector3D.linearCombination(third, vertex4, third, vertex1, third, vertex2)
+            Vector3D.Sum.create().addScaled(third, vertex1).addScaled(third, vertex2).addScaled(third, vertex3).get(),
+            Vector3D.Sum.create().addScaled(third, vertex2).addScaled(third, vertex3).addScaled(third, vertex4).get(),
+            Vector3D.Sum.create().addScaled(third, vertex3).addScaled(third, vertex4).addScaled(third, vertex1).get(),
+            Vector3D.Sum.create().addScaled(third, vertex4).addScaled(third, vertex1).addScaled(third, vertex2).get()
         );
         EuclideanTestUtils.assertRegionLocation(tree, RegionLocation.OUTSIDE,
             Vector3D.of(1, 2, 4),
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java
index b1446e6..02e5642 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.List;
 import java.util.regex.Pattern;
 
 import org.apache.commons.geometry.core.GeometryTestUtils;
@@ -670,8 +671,8 @@ class Vector3DTest {
         checkVector(v1.cross(v2), -1, 2, 1);
 
         final double scale    = Math.scalb(1.0, 100);
-        final Vector3D big1   = Vector3D.linearCombination(scale, v1);
-        final Vector3D small2 = Vector3D.linearCombination(1 / scale, v2);
+        final Vector3D big1   = v1.multiply(scale);
+        final Vector3D small2 = v2.multiply(1 / scale);
         checkVector(big1.cross(small2), -1, 2, 1);
     }
 
@@ -1315,54 +1316,49 @@ class Vector3DTest {
     }
 
     @Test
-    void testLinearCombination1() {
-        // arrange
-        final Vector3D p1 = Vector3D.of(1, 2, 3);
-
+    void testSum_factoryMethods() {
         // act/assert
-        checkVector(Vector3D.linearCombination(0, p1), 0, 0, 0);
-
-        checkVector(Vector3D.linearCombination(1, p1), 1, 2, 3);
-        checkVector(Vector3D.linearCombination(-1, p1), -1, -2, -3);
-
-        checkVector(Vector3D.linearCombination(0.5, p1), 0.5, 1, 1.5);
-        checkVector(Vector3D.linearCombination(-0.5, p1), -0.5, -1, -1.5);
+        checkVector(Vector3D.Sum.create().get(), 0, 0, 0);
+        checkVector(Vector3D.Sum.of(Vector3D.of(1, 2, 3)).get(), 1, 2, 3);
+        checkVector(Vector3D.Sum.of(
+                Vector3D.of(1, 2, 3),
+                Vector3D.Unit.PLUS_X,
+                Vector3D.Unit.PLUS_Y,
+                Vector3D.Unit.PLUS_Z).get(), 2, 3, 4);
     }
 
     @Test
-    void testLinearCombination2() {
+    void testSum_instanceMethods() {
         // arrange
         final Vector3D p1 = Vector3D.of(1, 2, 3);
-        final Vector3D p2 = Vector3D.of(-3, -4, -5);
+        final Vector3D p2 = Vector3D.of(4, 6, 8);
 
         // act/assert
-        checkVector(Vector3D.linearCombination(2, p1, -3, p2), 11, 16, 21);
-        checkVector(Vector3D.linearCombination(-3, p1, 2, p2), -9, -14, -19);
+        checkVector(Vector3D.Sum.create()
+                .add(p1)
+                .addScaled(0.5, p2)
+                .get(), 3, 5, 7);
     }
 
     @Test
-    void testLinearCombination3() {
+    void testSum_accept() {
         // arrange
-        final Vector3D p1 = Vector3D.of(1, 2, 3);
-        final Vector3D p2 = Vector3D.of(-3, -4, -5);
-        final Vector3D p3 = Vector3D.of(5, 6, 7);
+        final Vector3D p1 = Vector3D.of(1, 2, -3);
+        final Vector3D p2 = Vector3D.of(3, -6, 8);
 
-        // act/assert
-        checkVector(Vector3D.linearCombination(2, p1, -3, p2, 4, p3), 31, 40, 49);
-        checkVector(Vector3D.linearCombination(-3, p1, 2, p2, -4, p3), -29, -38, -47);
-    }
+        final List<Vector3D.Unit> units = Arrays.asList(
+                Vector3D.Unit.PLUS_X,
+                Vector3D.Unit.PLUS_Y,
+                Vector3D.Unit.PLUS_Z);
 
-    @Test
-    void testLinearCombination4() {
-        // arrange
-        final Vector3D p1 = Vector3D.of(1, 2, 3);
-        final Vector3D p2 = Vector3D.of(-3, -4, -5);
-        final Vector3D p3 = Vector3D.of(5, 6, 7);
-        final Vector3D p4 = Vector3D.of(-7, -8, 9);
+        final Vector3D.Sum s = Vector3D.Sum.create();
 
         // act/assert
-        checkVector(Vector3D.linearCombination(2, p1, -3, p2, 4, p3, -5, p4), 66, 80, 4);
-        checkVector(Vector3D.linearCombination(-3, p1, 2, p2, -4, p3, 5, p4), -64, -78, -2);
+        Arrays.asList(p1, Vector3D.ZERO, p2).forEach(s);
+        units.forEach(s);
+
+        // assert
+        checkVector(s.get(), 5, -3, 6);
     }
 
     @Test
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/line/Line3DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/line/Line3DTest.java
index 71fa6f9..684d3d0 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/line/Line3DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/line/Line3DTest.java
@@ -232,12 +232,12 @@ class Line3DTest {
         final Vector3D p1 = Vector3D.of(0, 0, 1);
         final Line3D l = Lines3D.fromPoints(p1, Vector3D.of(0, 0, 2), TEST_PRECISION);
         Assertions.assertTrue(l.contains(p1));
-        Assertions.assertTrue(l.contains(Vector3D.linearCombination(1.0, p1, 0.3, l.getDirection())));
+        Assertions.assertTrue(l.contains(Vector3D.Sum.of(p1).addScaled(0.3, l.getDirection()).get()));
         final Vector3D u = l.getDirection().orthogonal();
         final Vector3D v = l.getDirection().cross(u);
         for (double alpha = 0; alpha < 2 * Math.PI; alpha += 0.3) {
-            Assertions.assertFalse(l.contains(p1.add(Vector3D.linearCombination(Math.cos(alpha), u,
-                    Math.sin(alpha), v))));
+            Assertions.assertFalse(l.contains(p1.add(Vector3D.Sum.create().addScaled(Math.cos(alpha), u).addScaled(
+                    Math.sin(alpha), v).get())));
         }
     }
 
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java
index 90d2f1c..72eb3ae 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.List;
 import java.util.regex.Pattern;
 
 import org.apache.commons.geometry.core.GeometryTestUtils;
@@ -1118,54 +1119,47 @@ class Vector2DTest {
     }
 
     @Test
-    void testLinearCombination1() {
-        // arrange
-        final Vector2D p1 = Vector2D.of(1, 2);
-
+    void testSum_factoryMethods() {
         // act/assert
-        checkVector(Vector2D.linearCombination(0, p1), 0, 0);
-
-        checkVector(Vector2D.linearCombination(1, p1), 1, 2);
-        checkVector(Vector2D.linearCombination(-1, p1), -1, -2);
-
-        checkVector(Vector2D.linearCombination(0.5, p1), 0.5, 1);
-        checkVector(Vector2D.linearCombination(-0.5, p1), -0.5, -1);
+        checkVector(Vector2D.Sum.create().get(), 0, 0);
+        checkVector(Vector2D.Sum.of(Vector2D.of(1, 2)).get(), 1, 2);
+        checkVector(Vector2D.Sum.of(
+                Vector2D.of(1, 2),
+                Vector2D.Unit.PLUS_X,
+                Vector2D.Unit.PLUS_Y).get(), 2, 3);
     }
 
     @Test
-    void testLinearCombination2() {
+    void testSum_instanceMethods() {
         // arrange
         final Vector2D p1 = Vector2D.of(1, 2);
-        final Vector2D p2 = Vector2D.of(-3, -4);
+        final Vector2D p2 = Vector2D.of(4, 6);
 
         // act/assert
-        checkVector(Vector2D.linearCombination(2, p1, -3, p2), 11, 16);
-        checkVector(Vector2D.linearCombination(-3, p1, 2, p2), -9, -14);
+        checkVector(Vector2D.Sum.create()
+                .add(p1)
+                .addScaled(0.5, p2)
+                .get(), 3, 5);
     }
 
     @Test
-    void testLinearCombination3() {
+    void testSum_accept() {
         // arrange
         final Vector2D p1 = Vector2D.of(1, 2);
-        final Vector2D p2 = Vector2D.of(-3, -4);
-        final Vector2D p3 = Vector2D.of(5, 6);
+        final Vector2D p2 = Vector2D.of(3, -6);
 
-        // act/assert
-        checkVector(Vector2D.linearCombination(2, p1, -3, p2, 4, p3), 31, 40);
-        checkVector(Vector2D.linearCombination(-3, p1, 2, p2, -4, p3), -29, -38);
-    }
+        final List<Vector2D.Unit> units = Arrays.asList(
+                Vector2D.Unit.PLUS_X,
+                Vector2D.Unit.PLUS_Y);
 
-    @Test
-    void testLinearCombination4() {
-        // arrange
-        final Vector2D p1 = Vector2D.of(1, 2);
-        final Vector2D p2 = Vector2D.of(-3, -4);
-        final Vector2D p3 = Vector2D.of(5, 6);
-        final Vector2D p4 = Vector2D.of(-7, -8);
+        final Vector2D.Sum s = Vector2D.Sum.create();
 
         // act/assert
-        checkVector(Vector2D.linearCombination(2, p1, -3, p2, 4, p3, -5, p4), 66, 80);
-        checkVector(Vector2D.linearCombination(-3, p1, 2, p2, -4, p3, 5, p4), -64, -78);
+        Arrays.asList(p1, Vector2D.ZERO, p2).forEach(s);
+        units.forEach(s);
+
+        // assert
+        checkVector(s.get(), 5, -3);
     }
 
     @Test
diff --git a/commons-geometry-hull/src/test/java/org/apache/commons/geometry/hull/euclidean/twod/ConvexHullGenerator2DAbstractTest.java b/commons-geometry-hull/src/test/java/org/apache/commons/geometry/hull/euclidean/twod/ConvexHullGenerator2DAbstractTest.java
index 6488d6b..e30d5cc 100644
--- a/commons-geometry-hull/src/test/java/org/apache/commons/geometry/hull/euclidean/twod/ConvexHullGenerator2DAbstractTest.java
+++ b/commons-geometry-hull/src/test/java/org/apache/commons/geometry/hull/euclidean/twod/ConvexHullGenerator2DAbstractTest.java
@@ -25,8 +25,8 @@ import org.apache.commons.geometry.core.Region;
 import org.apache.commons.geometry.core.RegionLocation;
 import org.apache.commons.geometry.euclidean.twod.ConvexArea;
 import org.apache.commons.geometry.euclidean.twod.Vector2D;
-import org.apache.commons.numbers.core.LinearCombination;
 import org.apache.commons.numbers.core.Precision;
+import org.apache.commons.numbers.core.Sum;
 import org.apache.commons.rng.UniformRandomProvider;
 import org.apache.commons.rng.simple.RandomSource;
 import org.junit.jupiter.api.Assertions;
@@ -468,7 +468,9 @@ public abstract class ConvexHullGenerator2DAbstractTest {
             Assertions.assertTrue(d1.norm() > 1e-10);
             Assertions.assertTrue(d2.norm() > 1e-10);
 
-            final double cross = LinearCombination.value(d1.getX(), d2.getY(), -d1.getY(), d2.getX());
+            final double cross = Sum.create()
+                    .addProduct(d1.getX(), d2.getY())
+                    .addProduct(-d1.getY(), d2.getX()).getAsDouble();
             final int cmp = Precision.compareTo(cross, 0.0, TEST_EPS);
 
             if (sign != 0 && cmp != sign) {
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/ConvexArea2S.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/ConvexArea2S.java
index e0bc3ee..170d39f 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/ConvexArea2S.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/ConvexArea2S.java
@@ -367,16 +367,13 @@ public final class ConvexArea2S extends AbstractConvexHyperplaneBoundedRegion<Po
      * @see #computeTriangleFanWeightedCentroidVector(List)
      */
     private static Vector3D computeArcPoleWeightedCentroidVector(final List<GreatArc> arcs) {
-        Vector3D centroid = Vector3D.ZERO;
+        final Vector3D.Sum centroid = Vector3D.Sum.create();
 
-        Vector3D arcContribution;
         for (final GreatArc arc : arcs) {
-            arcContribution = arc.getCircle().getPole().withNorm(arc.getSize());
-
-            centroid = centroid.add(arcContribution);
+            centroid.addScaled(arc.getSize(), arc.getCircle().getPole());
         }
 
-        return centroid;
+        return centroid.get();
     }
 
     /** Compute the weighted centroid vector for the triangle or polygon formed by the given arcs
@@ -403,7 +400,7 @@ public final class ConvexArea2S extends AbstractConvexHyperplaneBoundedRegion<Po
         final Point2S p0 = arcIt.next().getStartPoint();
         final Vector3D.Unit v0 = p0.getVector();
 
-        Vector3D areaCentroid = Vector3D.ZERO;
+        final Vector3D.Sum areaCentroid = Vector3D.Sum.create();
 
         GreatArc arc;
         Point2S p1;
@@ -422,17 +419,21 @@ public final class ConvexArea2S extends AbstractConvexHyperplaneBoundedRegion<Po
                 v1 = p1.getVector();
                 v2 = p2.getVector();
 
-                triangleCentroid = v0.add(v1).add(v2).normalize();
+                triangleCentroid = Vector3D.Sum.create()
+                        .add(v0)
+                        .add(v1)
+                        .add(v2)
+                        .get().normalize();
                 triangleCentroidLen =
                         computeArcCentroidContribution(v0, v1, triangleCentroid) +
                         computeArcCentroidContribution(v1, v2, triangleCentroid) +
                         computeArcCentroidContribution(v2, v0, triangleCentroid);
 
-                areaCentroid = areaCentroid.add(triangleCentroid.withNorm(triangleCentroidLen));
+                areaCentroid.addScaled(triangleCentroidLen, triangleCentroid);
             }
         }
 
-        return areaCentroid;
+        return areaCentroid.get();
     }
 
     /** Compute the contribution made by a single arc to a weighted centroid vector.
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircle.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircle.java
index 6bd7a5b..e858558 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircle.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircle.java
@@ -169,7 +169,9 @@ public final class GreatCircle extends AbstractHyperplane<Point2S>
      * @return the point on the great circle with the given phase angle
      */
     public Vector3D vectorAt(final double azimuth) {
-        return Vector3D.linearCombination(Math.cos(azimuth), u, Math.sin(azimuth), v);
+        return Vector3D.Sum.create()
+                .addScaled(Math.cos(azimuth), u)
+                .addScaled(Math.sin(azimuth), v).get();
     }
 
     /** {@inheritDoc} */
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/RegionBSPTree2S.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/RegionBSPTree2S.java
index 906c5ab..026cd9f 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/RegionBSPTree2S.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/RegionBSPTree2S.java
@@ -28,6 +28,7 @@ import org.apache.commons.geometry.core.partitioning.bsp.AbstractBSPTree;
 import org.apache.commons.geometry.core.partitioning.bsp.AbstractRegionBSPTree;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
 import org.apache.commons.numbers.core.Precision;
+import org.apache.commons.numbers.core.Sum;
 
 /** BSP tree representing regions in 2D spherical space.
  */
@@ -169,35 +170,37 @@ public class RegionBSPTree2S extends AbstractRegionBSPTree<Point2S, RegionBSPTre
         final List<ConvexArea2S> areas = toConvex();
         final Precision.DoubleEquivalence precision = ((GreatArc) getRoot().getCut()).getPrecision();
 
-        double sizeSum = 0;
-        Vector3D centroidVectorSum = Vector3D.ZERO;
+        final Sum sizeSum = Sum.create();
+        final Vector3D.Sum centroidVectorSum = Vector3D.Sum.create();
 
-        Vector3D centroidVector;
         double maxCentroidVectorWeightSq = 0.0;
 
         for (final ConvexArea2S area : areas) {
-            sizeSum += area.getSize();
+            sizeSum.add(area.getSize());
 
-            centroidVector = area.getWeightedCentroidVector();
-            maxCentroidVectorWeightSq = Math.max(maxCentroidVectorWeightSq, centroidVector.normSq());
+            final Vector3D areaCentroidVector = area.getWeightedCentroidVector();
+            maxCentroidVectorWeightSq = Math.max(maxCentroidVectorWeightSq, areaCentroidVector.normSq());
 
-            centroidVectorSum = centroidVectorSum.add(centroidVector);
+            centroidVectorSum.add(areaCentroidVector);
         }
 
+        final double size = sizeSum.getAsDouble();
+        final Vector3D centroidVector = centroidVectorSum.get();
+
         // Convert the weighted centroid vector to a point on the sphere surface. If the centroid vector
         // length is less than the max length of the combined convex areas and the vector itself is
         // equivalent to zero, then we know that there are opposing and approximately equal areas in the
         // region, resulting in an indeterminate centroid. This would occur, for example, if there were
         // equal areas around each pole.
         final Point2S centroid;
-        if (centroidVectorSum.normSq() < maxCentroidVectorWeightSq &&
-                centroidVectorSum.eq(Vector3D.ZERO, precision)) {
+        if (centroidVector.normSq() < maxCentroidVectorWeightSq &&
+                centroidVector.eq(Vector3D.ZERO, precision)) {
             centroid = null;
         } else {
-            centroid = Point2S.from(centroidVectorSum);
+            centroid = Point2S.from(centroidVector);
         }
 
-        return new RegionSizeProperties<>(sizeSum, centroid);
+        return new RegionSizeProperties<>(size, centroid);
     }
 
     /** {@inheritDoc} */