Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions docs/ReleaseNotes_GeopackV2.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,34 @@ This PR adds dependency injection support for the Geopack library by introducing
- Added AddExternalFieldModels() extension method to register IT89 implementation
- Added Microsoft.Extensions.DependencyInjection.Abstractions package dependency (version 9.0.10)

## Issue #46 (PR #70):

This PR implements comprehensive code optimizations focused on improving computational performance across the Geopack library.
The optimizations target mathematical operations, memory allocation, and leverage modern .NET features including SIMD vectorization.

Key changes:

- Replaced Math.Pow(x, 2) with direct multiplication x * x for better performance;
- Adopted Math.SinCos() to compute sine and cosine simultaneously, reducing redundant calculations;
- Extracted magic numbers into well-documented constants in GeopackConstants.cs;
- Implemented SIMD vectorization using Vector<double> for IGRF coefficient interpolation and extrapolation;
- Refactored Newton's method iteration into a separate method for better code organization;
- Optimized list initialization with capacity hints to reduce allocations.

| File | Description |
|------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|
| `src/Geopack/Geopack.Trace.cs` | Optimized list capacity, dot product calculation |
| `src/Geopack/Geopack.T96Mgnp.cs` | Replaced Math.Pow with multiplication, used Math.SinCos, extracted pressure factor constant |
| `src/Geopack/Geopack.Sun.cs` | Extracted constants, adopted Math.SinCos |
| `src/Geopack/Geopack.ShuMgnp.cs` | Extracted model coefficients as constants, refactored Newton's method to private method, optimized trigonometric calculations |
| `src/Geopack/Geopack.Recalc.cs` | Implemented SIMD vectorization for coefficient processing, eliminated zero multiplications, cached repeated calculations |
| `src/Geopack/Geopack.IgrfGsw.cs` | Replaced Math.Pow with direct multiplication |
| `src/Geopack/Geopack.IgrfGeo.cs` | Adopted Math.SinCos for simultaneous trigonometric calculations |
| `src/Geopack/Geopack.Dip.cs` | Replaced Math.Pow with multiplication, cached repeated array accesses |
| `src/ExternalFieldModels/T89/T89.cs` | Adopted Math.SinCos |
| `src/Contracts/Spherical/SphericalVector.cs` | Minor formatting adjustment |
| `src/Contracts/Spherical/SphericalLocation.cs` | Cached trigonometric calculations to avoid recomputation |
| `src/Contracts/Engine/GeopackConstants.cs` | Added numerous well-documented constants for physics calculations and algorithm parameters |
| `src/Contracts/Coordinates/GeodeticCoordinates.cs` | Extracted WGS84 constants, replaced Math.Pow, adopted Math.SinCos |
| `src/Contracts/Coordinates/GeocentricCoordinates.cs` | Optimized iterative calculation with constant extraction and Math.SinCos |
| `src/Contracts/Cartesian/CartesianVector.cs` | Replaced Math.Pow with direct multiplication |
4 changes: 2 additions & 2 deletions src/Contracts/Cartesian/CartesianLocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ public SphericalLocation ToSpherical()
{
double phi;
double theta;
double sq = X * X + Y * Y;
double r = Math.Sqrt(sq + Z * Z);
double sq = Math.FusedMultiplyAdd(X, X, Y * Y);
double r = Math.Sqrt(Math.FusedMultiplyAdd(Z, Z, sq));

if (Math.Abs(sq) > double.Epsilon)
{
Expand Down
10 changes: 5 additions & 5 deletions src/Contracts/Cartesian/CartesianVector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ private CartesianVector(double x, double y, double z, CoordinateSystem coordinat
/// </remarks>
public SphericalVector<TVector> ToSphericalVector(CartesianLocation location)
{
double rho2 = Math.Pow(location.X, 2.0D) + Math.Pow(location.Y, 2.0D);
double rho2 = location.X * location.X + location.Y * location.Y;
double rho = Math.Sqrt(rho2);

double r = Math.Sqrt(rho2 + Math.Pow(location.Z, 2.0D));
double r = Math.Sqrt(rho2 + location.Z * location.Z);

if (Math.Abs(r) <= double.Epsilon)
{
Expand All @@ -64,9 +64,9 @@ public SphericalVector<TVector> ToSphericalVector(CartesianLocation location)
double ct = location.Z / r;
double st = rho / r;

double br = (location.X * X + location.Y * Y + location.Z * Z) / r;
double btheta = (X * cphi + Y * sphi) * ct - Z * st;
double bphi = Y * cphi - X * sphi;
double br = Math.FusedMultiplyAdd(location.X, X, Math.FusedMultiplyAdd(location.Y, Y, location.Z * Z)) / r;
double btheta = Math.FusedMultiplyAdd(Math.FusedMultiplyAdd(X, cphi, Y * sphi), ct, -Z * st);
double bphi = Math.FusedMultiplyAdd(Y, cphi, -X * sphi);

return SphericalVector<TVector>.New(br, btheta, bphi, CoordinateSystem);
}
Expand Down
33 changes: 18 additions & 15 deletions src/Contracts/Coordinates/GeocentricCoordinates.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using AuroraScienceHub.Geopack.Contracts.Engine;

namespace AuroraScienceHub.Geopack.Contracts.Coordinates;

/// <summary>
Expand Down Expand Up @@ -37,32 +39,33 @@ public GeocentricCoordinates(double r, double theta)
/// </remarks>
public GeodeticCoordinates ToGeodetic()
{
const double r_eq = 6378.137D;
const double beta = 6.73949674228e-3;
const double tol = 1e-6;

int n = 0;
double phi = 1.570796327D - Theta;
double phi = GeopackConstants.HalfPi - Theta;
double phi1 = phi;
double r2 = R * R;

double xmus, rs, cosfims, h, z, x, rr, dphi;
double xmus, h, dphi;

do
{
double sp = Math.Sin(phi1);
double arg = sp * (1.0D + beta) / Math.Sqrt(1.0D + beta * (2.0D + beta) * Math.Pow(sp, 2));
(double sinPhi, double cosPhi) = Math.SinCos(phi1);
double sp2 = sinPhi * sinPhi;
double arg = sinPhi * GeopackConstants.WGS84Ex / Math.Sqrt(1.0D + GeopackConstants.WGS84FirstEx * sp2);
double rs = GeopackConstants.REq / Math.Sqrt(1.0D + GeopackConstants.WGS84Beta * sp2);
double rs2 = rs * rs;
xmus = Math.Asin(arg);
rs = r_eq / Math.Sqrt(1.0D + beta * Math.Pow(Math.Sin(phi1), 2));
cosfims = Math.Cos(phi1 - xmus);
h = Math.Sqrt(rs * cosfims * rs * cosfims + R * R - rs * rs) - rs * cosfims;
z = rs * Math.Sin(phi1) + h * Math.Sin(xmus);
x = rs * Math.Cos(phi1) + h * Math.Cos(xmus);
rr = Math.Sqrt(Math.Pow(x, 2) + Math.Pow(z, 2));
(double sinXmus, double cosXmus) = Math.SinCos(xmus);

double cosfims = Math.Cos(phi1 - xmus);
h = Math.Sqrt(rs2 * cosfims * cosfims + r2 - rs2) - rs * cosfims;
double z = rs * sinPhi + h * sinXmus;
double x = rs * cosPhi + h * cosXmus;
double rr = Math.Sqrt(x * x + z * z);
dphi = Math.Asin(z / rr) - phi;
phi1 -= dphi;
n++;
}
while (Math.Abs(dphi) > tol && n < 100);
while (Math.Abs(dphi) > GeopackConstants.CoordinateConvergenceTolerance && n < 100);

return new GeodeticCoordinates(xmus, h);
}
Expand Down
16 changes: 8 additions & 8 deletions src/Contracts/Coordinates/GeodeticCoordinates.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using AuroraScienceHub.Geopack.Contracts.Engine;

namespace AuroraScienceHub.Geopack.Contracts.Coordinates;

/// <summary>
Expand Down Expand Up @@ -34,18 +36,16 @@ public GeodeticCoordinates(double geodLatitude, double altitude)
/// </remarks>
public GeocentricCoordinates ToGeocentric()
{
const double r_eq = 6378.137D;
const double beta = 6.73949674228e-3;

double cosxmu = Math.Cos(Latitude);
double sinxmu = Math.Sin(Latitude);
double den = Math.Sqrt(Math.Pow(cosxmu, 2) + Math.Pow(sinxmu / (1.0D + beta), 2));
double cosxmu = Math.Cos(Latitude);
double sinxmuBeta = sinxmu / GeopackConstants.WGS84Ex;
double den = Math.Sqrt(cosxmu * cosxmu + sinxmuBeta * sinxmuBeta);
double coslam = cosxmu / den;
double sinlam = sinxmu / (den * (1.0D + beta));
double rs = r_eq / Math.Sqrt(1.0D + beta * Math.Pow(sinlam, 2));
double sinlam = sinxmu / (den * GeopackConstants.WGS84Ex);
double rs = GeopackConstants.REq / Math.Sqrt(1.0D + GeopackConstants.WGS84Beta * sinlam * sinlam);
double x = rs * coslam + Altitude * cosxmu;
double z = rs * sinlam + Altitude * sinxmu;
double r = Math.Sqrt(Math.Pow(x, 2) + Math.Pow(z, 2));
double r = Math.Sqrt(x * x + z * z);
double theta = Math.Acos(z / r);

return new GeocentricCoordinates(r, theta);
Expand Down
107 changes: 107 additions & 0 deletions src/Contracts/Engine/GeopackConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ public static class GeopackConstants
/// </summary>
public const double Pi = 3.141592654D;

/// <summary>
/// PI/2 value from original Geopack (lower precision for compatibility)
/// </summary>
public const double HalfPi = 1.570796327D;

/// <summary>
/// 2π value from original Geopack
/// </summary>
Expand All @@ -19,4 +24,106 @@ public static class GeopackConstants
/// Radians to degrees conversion factor (180/π)
/// </summary>
public const double Rad = 57.295779513D;

/// <summary>
/// Equatorial Earth's radius in kilometers
/// </summary>
public const double REq = 6378.137D;

/// <summary>
/// WGS84 ellipsoid flattening coefficient (Beta = (a-b)/a where a is equatorial radius, b is polar radius)
/// </summary>
public const double WGS84Beta = 6.73949674228e-3;

/// <summary>
/// Convergence tolerance for iterative coordinate transformations
/// </summary>
public const double CoordinateConvergenceTolerance = 1e-6;

/// <summary>
/// WGS84 ellipsoid parameter: 1 + Beta
/// </summary>
public const double WGS84Ex = 1.0 + WGS84Beta;

/// <summary>
/// WGS84 ellipsoid parameter: Beta * (2 + Beta)
/// </summary>
public const double WGS84FirstEx = WGS84Beta * (2.0 + WGS84Beta);

/// <summary>
/// Earth's axial tilt at J2000 epoch (obliquity of the ecliptic) in degrees
/// </summary>
public const double EarthObliquityJ2000 = 23.45229;

/// <summary>
/// Rate of change of Earth's obliquity per Julian century in degrees
/// </summary>
public const double EarthObliquityRatePerCentury = 0.0130125;

/// <summary>
/// Number of days in a Julian century
/// </summary>
public const double DaysPerJulianCentury = 36525.0;

/// <summary>
/// Solar wind proton mass density conversion factor (converts n*v^2 to pressure in nPa)
/// Formula: 1.6726e-27 kg (proton mass) * 1e15 (km^3 to m^3) * 1e9 (Pa to nPa) = 1.6726e-6
/// But empirical value used in models is 1.94e-6
/// </summary>
public const double SolarWindDynamicPressureFactor = 1.94e-6;

/// <summary>
/// Seconds per hour
/// </summary>
public const double SecondsPerHour = 3600.0;

/// <summary>
/// Seconds per day
/// </summary>
public const double SecondsPerDay = 86400.0;

/// <summary>
/// Degrees in a full circle
/// </summary>
public const double DegreesPerCircle = 360.0;

/// <summary>
/// Degrees in a half circle (semicircle)
/// </summary>
public const double DegreesPerSemicircle = 180.0;

/// <summary>
/// Average number of days in a year (accounting for leap years)
/// </summary>
public const double DaysPerYear = 365.25;

/// <summary>
/// Reciprocal of IGRF interpolation interval (1/5 = 0.2) for optimization
/// </summary>
public const double IgrfInterpolationIntervalReciprocal = 0.2;

/// <summary>
/// Total number of IGRF coefficients
/// </summary>
public const int IgrfCoefficientCount = 105;

/// <summary>
/// Number of IGRF delta coefficients used in extrapolation
/// </summary>
public const int IgrfDeltaCoefficientCount = 45;

/// <summary>
/// IGRF extrapolation base year
/// </summary>
public const int IgrfExtrapolationBaseYear = 2025;

/// <summary>
/// Maximum iterations for Newton's method convergence
/// </summary>
public const int NewtonMaxIterations = 1000;

/// <summary>
/// Convergence tolerance for Newton's iterative methods
/// </summary>
public const double NewtonConvergenceTolerance = 1e-4;
}
12 changes: 8 additions & 4 deletions src/Contracts/Spherical/SphericalLocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,14 @@ public static SphericalLocation New(double r, double theta, double phi, Coordina
/// </remarks>
public CartesianLocation ToCartesian()
{
double sq = R * Math.Sin(Theta);
double x = sq * Math.Cos(Phi);
double y = sq * Math.Sin(Phi);
double z = R * Math.Cos(Theta);
double sinTheta = Math.Sin(Theta);
double cosTheta = Math.Cos(Theta);
double sinPhi = Math.Sin(Phi);
double cosPhi = Math.Cos(Phi);
double sq = R * sinTheta;
double x = sq * cosPhi;
double y = sq * sinPhi;
double z = R * cosTheta;

return CartesianLocation.New(x, y, z, CoordinateSystem);
}
Expand Down
9 changes: 5 additions & 4 deletions src/Contracts/Spherical/SphericalVector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ public CartesianVector<TVector> ToCartesianVector(SphericalLocation location)
double c = Math.Cos(location.Theta);
double sf = Math.Sin(location.Phi);
double cf = Math.Cos(location.Phi);
double be = R * s + Theta * c;
double bx = be * cf - Phi * sf;
double by = be * sf + Phi * cf;
double bz = R * c - Theta * s;

double be = Math.FusedMultiplyAdd(R, s, Theta * c);
double bx = Math.FusedMultiplyAdd(be, cf, -Phi * sf);
double by = Math.FusedMultiplyAdd(be, sf, Phi * cf);
double bz = Math.FusedMultiplyAdd(R, c, -Theta * s);

return CartesianVector<TVector>.New(bx, by, bz, CoordinateSystem);
}
Expand Down
7 changes: 3 additions & 4 deletions src/ExternalFieldModels/T89/T89.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ public CartesianVector<MagneticField> Calculate(int IOPT, double[] PARMOD, doubl
YND = 2.0D * YN;
}

double SPS = Math.Sin(PS);
double CPS = Math.Cos(PS);
(double SPS, double CPS) = Math.SinCos(PS);

double X2 = location.X * location.X;
double Y2 = location.Y * location.Y;
Expand Down Expand Up @@ -249,8 +248,8 @@ public CartesianVector<MagneticField> Calculate(int IOPT, double[] PARMOD, doubl
double FXMN = -location.X * FXYM;
double FYPL = location.Y * FXYP;
double FYMN = -location.Y * FXYM;
double FZPL = WCSP + XYWC * SZRP;
double FZMN = WCSM + XYWC * SZRM;
double FZPL = Math.FusedMultiplyAdd(WCSP, 1.0, XYWC * SZRP);
double FZMN = Math.FusedMultiplyAdd(WCSM, 1.0, XYWC * SZRM);
double DER13 = FXPL + FXMN;
double DER14 = (FXPL - FXMN) * SPS;
double DER23 = FYPL + FYMN;
Expand Down
11 changes: 7 additions & 4 deletions src/Geopack/Geopack.Dip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ public CartesianVector<MagneticField> Dip(ComputationContext context, CartesianL
throw new InvalidOperationException("Location must be in GSW coordinate system.");
}

double dipmom = Math.Sqrt(Math.Pow(context.G[1], 2) + Math.Pow(context.G[2], 2) + Math.Pow(context.H[2], 2));
double g1 = context.G[1];
double g2 = context.G[2];
double h2 = context.H[2];
double dipmom = Math.Sqrt(g1 * g1 + g2 * g2 + h2 * h2);

double p = Math.Pow(location.X, 2);
double u = Math.Pow(location.Z, 2);
double p = location.X * location.X;
double u = location.Z * location.Z;
double v = 3.0D * location.Z * location.X;
double t = Math.Pow(location.Y, 2);
double t = location.Y * location.Y;

if (p + t + u is 0D)
{
Expand Down
8 changes: 4 additions & 4 deletions src/Geopack/Geopack.GeiGeo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ private static T GeiGeoInternal<T>(ComputationContext context, T components, Ope
{
OperationType.Direct => components.CoordinateSystem is CoordinateSystem.GEI
? T.New(
components.X * context.CGST + components.Y * context.SGST,
components.Y * context.CGST - components.X * context.SGST,
Math.FusedMultiplyAdd(components.X, context.CGST, components.Y * context.SGST),
Math.FusedMultiplyAdd(components.Y, context.CGST, -components.X * context.SGST),
components.Z,
CoordinateSystem.GEO)
: throw new InvalidOperationException("Input coordinates must be in GEI system."),

OperationType.Reversed => components.CoordinateSystem is CoordinateSystem.GEO
? T.New(
components.X * context.CGST - components.Y * context.SGST,
components.Y * context.CGST + components.X * context.SGST,
Math.FusedMultiplyAdd(components.X, context.CGST, -components.Y * context.SGST),
Math.FusedMultiplyAdd(components.Y, context.CGST, components.X * context.SGST),
components.Z,
CoordinateSystem.GEI)
: throw new InvalidOperationException("Input coordinates must be in GEO system."),
Expand Down
12 changes: 6 additions & 6 deletions src/Geopack/Geopack.GeoGsw.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ private static T GeoGswInternal<T>(ComputationContext context, T components, Ope
{
OperationType.Direct => components.CoordinateSystem is CoordinateSystem.GEO
? T.New(
context.A11 * components.X + context.A12 * components.Y + context.A13 * components.Z,
context.A21 * components.X + context.A22 * components.Y + context.A23 * components.Z,
context.A31 * components.X + context.A32 * components.Y + context.A33 * components.Z,
Math.FusedMultiplyAdd(context.A11, components.X, Math.FusedMultiplyAdd(context.A12, components.Y, context.A13 * components.Z)),
Math.FusedMultiplyAdd(context.A21, components.X, Math.FusedMultiplyAdd(context.A22, components.Y, context.A23 * components.Z)),
Math.FusedMultiplyAdd(context.A31, components.X, Math.FusedMultiplyAdd(context.A32, components.Y, context.A33 * components.Z)),
CoordinateSystem.GSW)
: throw new InvalidOperationException("Input coordinates must be in GEO system."),

OperationType.Reversed => components.CoordinateSystem is CoordinateSystem.GSW
? T.New(
context.A11 * components.X + context.A21 * components.Y + context.A31 * components.Z,
context.A12 * components.X + context.A22 * components.Y + context.A32 * components.Z,
context.A13 * components.X + context.A23 * components.Y + context.A33 * components.Z,
Math.FusedMultiplyAdd(context.A11, components.X, Math.FusedMultiplyAdd(context.A21, components.Y, context.A31 * components.Z)),
Math.FusedMultiplyAdd(context.A12, components.X, Math.FusedMultiplyAdd(context.A22, components.Y, context.A32 * components.Z)),
Math.FusedMultiplyAdd(context.A13, components.X, Math.FusedMultiplyAdd(context.A23, components.Y, context.A33 * components.Z)),
CoordinateSystem.GEO)
: throw new InvalidOperationException("Input coordinates must be in GSW system."),
_ => throw new NotSupportedException($"Specify correct OperationType: {operation}. Available types are Direct and Reversed.")
Expand Down
Loading