diff --git a/Directory.Build.props b/Directory.Build.props
index ac6779f..df34357 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -28,7 +28,7 @@
- 1.0.3
+ 2.0.0
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 47abbfa..87a645c 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -14,6 +14,7 @@
+
diff --git a/FMA_Optimizations_Summary.md b/FMA_Optimizations_Summary.md
new file mode 100644
index 0000000..e69de29
diff --git a/Geopack.slnx b/Geopack.slnx
index 9c759ba..4ec08df 100644
--- a/Geopack.slnx
+++ b/Geopack.slnx
@@ -5,6 +5,7 @@
+
@@ -31,4 +32,4 @@
-
\ No newline at end of file
+
diff --git a/NuGet.Config b/NuGet.Config
index 25fdb36..24d718f 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -8,10 +8,11 @@
-
+
+
-
+
diff --git a/UnitTests/Extensions/TestExtensions.cs b/UnitTests/Extensions/TestExtensions.cs
new file mode 100644
index 0000000..f0cff4b
--- /dev/null
+++ b/UnitTests/Extensions/TestExtensions.cs
@@ -0,0 +1,14 @@
+using Shouldly;
+
+namespace AuroraScienceHub.Geopack.UnitTests.Extensions;
+
+///
+/// Test extensions
+///
+public static class TestExtensions
+{
+ private const double MinimalTestsPrecision = 0.000000000008d;
+
+ internal static void ShouldApproximatelyBe(this double actual, double expected)
+ => actual.ShouldBe(expected, MinimalTestsPrecision);
+}
diff --git a/UnitTests/ExternalFieldModels/ExternalFieldModelsTests.T89.cs b/UnitTests/ExternalFieldModels/ExternalFieldModelsTests.T89.cs
index 33afef6..2ee583d 100644
--- a/UnitTests/ExternalFieldModels/ExternalFieldModelsTests.T89.cs
+++ b/UnitTests/ExternalFieldModels/ExternalFieldModelsTests.T89.cs
@@ -1,4 +1,6 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
using Shouldly;
namespace AuroraScienceHub.Geopack.UnitTests.ExternalFieldModels;
@@ -20,12 +22,15 @@ public void T89_ShouldReturnCorrectValues(
double x, double y, double z,
double expectedBx, double expectedBy, double expectedBz)
{
+ // Arrange
+ CartesianLocation location = CartesianLocation.New(x, y, z, CoordinateSystem.GSW);
+
// Act
- CartesianFieldVector resultField = _t89.Calculate(iopt, new double[10], ps, x, y, z);
+ CartesianVector resultField = _t89.Calculate(iopt, new double[10], ps, location);
// Assert
- resultField.Bx.ShouldBe(expectedBx, MinimalTestsPrecision);
- resultField.By.ShouldBe(expectedBy, MinimalTestsPrecision);
- resultField.Bz.ShouldBe(expectedBz, MinimalTestsPrecision);
+ resultField.X.ShouldBe(expectedBx, MinimalTestsPrecision);
+ resultField.Y.ShouldBe(expectedBy, MinimalTestsPrecision);
+ resultField.Z.ShouldBe(expectedBz, MinimalTestsPrecision);
}
}
diff --git a/UnitTests/Geopack/GeopackTests.BCarSph_08.cs b/UnitTests/Geopack/GeopackTests.BCarSph_08.cs
deleted file mode 100644
index b98d542..0000000
--- a/UnitTests/Geopack/GeopackTests.BCarSph_08.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-using Shouldly;
-
-namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
-
-public partial class GeopackTests
-{
- [Theory(DisplayName = "Iterate BCARSP_08 setups and verify result")]
- [InlineData(-1.2, 1, 1.5, 1, 11, -21, -10.020128994909539344, 19.492502934797215630, -9.090618475235615392)]
- [InlineData(1, 0, 0, 1, 1, 1, 1, -1, 1)]
- [InlineData(0, 1, 0, 1, 1, 1, 1, -1, -1)]
- [InlineData(0, 0, 1, 1, 1, 1, 1, 1, 1)]
- [InlineData(1, 1, 1, 1, 1, 1, 1.7320508075688776, 0, 0)]
- [InlineData(1, 1, 1, 1, 0, 0, 0.577350269189625842, 0.408248290463863017, -0.707106781186547462)]
- [InlineData(1, 1, 1, 0, 1, 0, 0.577350269189625842, 0.408248290463863017, 0.707106781186547462)]
- [InlineData(1, 1, 1, 0, 0, 1, 0.577350269189625842, -0.816496580927726145, 0)]
- [InlineData(1, 1, 1, 0, 0, 0, 0, 0, 0)]
- public void BCarSph_ShouldReturnsCorrectValues(
- double x, double y, double z,
- double bx, double by, double bz,
- double br, double btheta, double bphi)
- {
- // Act
- SphericalFieldVector fieldVector = _geopack.BCarSph_08(x, y, z, bx, by, bz);
-
- // Assert
- fieldVector.Br.ShouldBe(br, MinimalTestsPrecision);
- fieldVector.Btheta.ShouldBe(btheta, MinimalTestsPrecision);
- fieldVector.Bphi.ShouldBe(bphi, MinimalTestsPrecision);
- }
-
- [Fact(DisplayName = "BCarSph: NaN check (identical to original Geopack-2008 behavior)")]
- public void BCarSph_ShouldReturnNaND_IfDivideByZero()
- {
- // Act
- SphericalFieldVector fieldVector = _geopack.BCarSph_08(0, 0, 0, 1, 1, 1);
-
- // Assert
- fieldVector.Br.ShouldBe(double.NaN);
- fieldVector.Btheta.ShouldBe(double.NaN);
- fieldVector.Bphi.ShouldBe(1);
- }
-}
diff --git a/UnitTests/Geopack/GeopackTests.BSphCar_08.cs b/UnitTests/Geopack/GeopackTests.BSphCar_08.cs
deleted file mode 100644
index 7bad5f3..0000000
--- a/UnitTests/Geopack/GeopackTests.BSphCar_08.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-using AuroraScienceHub.Geopack.UnitTests.Utils;
-using Shouldly;
-
-namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
-
-public partial class GeopackTests
-{
- [Fact(DisplayName = "Iterate BSPCAR_08 setups and verify result")]
- public async Task BSphCar_ReturnsCorrectValues()
- {
- // Arrange
- string rawData = await EmbeddedResourceReader.ReadTextAsync(BSpCarDatasetFileName);
- string[] line = rawData.SplitParametersLine();
-
- double theta = line[1].ParseDouble();
- double phi = line[3].ParseDouble();
- SphericalFieldVector approvedSphericalFieldVector = new SphericalFieldVector(
- line[5].ParseDouble(),
- line[7].ParseDouble(),
- line[9].ParseDouble(),
- null);
-
- CartesianFieldVector expectedVector = new CartesianFieldVector(
- line[11].ParseDouble(),
- line[13].ParseDouble(),
- line[15].ParseDouble(),
- null);
-
- // Act
- CartesianFieldVector fieldVector = _geopack.BSphCar_08(
- theta, phi,
- approvedSphericalFieldVector.Br, approvedSphericalFieldVector.Btheta, approvedSphericalFieldVector.Bphi);
-
- // Assert
- fieldVector.Bx.ShouldBe(expectedVector.Bx, MinimalTestsPrecision);
- fieldVector.By.ShouldBe(expectedVector.By, MinimalTestsPrecision);
- fieldVector.Bz.ShouldBe(expectedVector.Bz, MinimalTestsPrecision);
- }
-
- [Fact(DisplayName = "BSphCar: Zero angles B-field coordinates conversion")]
- public void BSphCar_ZeroAngles_ReturnsExpectedValues()
- {
- // Act
- CartesianFieldVector fieldVector = _geopack.BSphCar_08(0.0, 0.0, 1.0, 1.0, 1.0);
-
- // Assert
- fieldVector.Bx.ShouldBe(1.0, MinimalTestsPrecision);
- fieldVector.By.ShouldBe(1.0, MinimalTestsPrecision);
- fieldVector.Bz.ShouldBe(1.0, MinimalTestsPrecision);
- }
-
- [Fact(DisplayName = "BSphCar: Negative angles B-field coordinates conversion")]
- public void BSphCar_NegativeAngles_ReturnsExpectedValues()
- {
- // Act
- CartesianFieldVector fieldVector = _geopack.BSphCar_08(-Math.PI / 4, -Math.PI / 4, 1.0, 1.0, 1.0);
-
- // Assert
- fieldVector.Bx.ShouldBe(0.70710678118654757, MinimalTestsPrecision);
- fieldVector.By.ShouldBe(0.70710678118654746, MinimalTestsPrecision);
- fieldVector.Bz.ShouldBe(1.4142135623730949, MinimalTestsPrecision);
- }
-
- [Fact(DisplayName = "BSphCar: Large angles B-field coordinates conversion")]
- public void BSphCar_LargeAngles_ReturnsExpectedValues()
- {
- // Act
- CartesianFieldVector fieldVector = _geopack.BSphCar_08(2 * Math.PI, 2 * Math.PI, 1.0, 1.0, 1.0);
-
- // Assert
- fieldVector.Bx.ShouldBe(1.0, MinimalTestsPrecision);
- fieldVector.By.ShouldBe(1.0, MinimalTestsPrecision);
- fieldVector.Bz.ShouldBe(1.0, MinimalTestsPrecision);
- }
-
- [Fact(DisplayName = "BSphCar: Small angles B-field coordinates conversion")]
- public void BSphCar_SmallAngles_ReturnsExpectedValues()
- {
- // Act
- CartesianFieldVector fieldVector = _geopack.BSphCar_08(1e-10, 1e-10, 1.0, 1.0, 1.0);
-
- // Assert
- fieldVector.Bx.ShouldBe(1.0, MinimalTestsPrecision);
- fieldVector.By.ShouldBe(1.0000000001, MinimalTestsPrecision);
- fieldVector.Bz.ShouldBe(0.99999999989999999, MinimalTestsPrecision);
- }
-}
diff --git a/UnitTests/Geopack/GeopackTests.Recalc.cs b/UnitTests/Geopack/GeopackTests.Recalc.cs
new file mode 100644
index 0000000..70cb130
--- /dev/null
+++ b/UnitTests/Geopack/GeopackTests.Recalc.cs
@@ -0,0 +1,81 @@
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
+using AuroraScienceHub.Geopack.UnitTests.Geopack.TestData.Models;
+using AuroraScienceHub.Geopack.UnitTests.Utils;
+using Shouldly;
+
+namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
+
+public partial class GeopackTests
+{
+ [Fact(DisplayName = "Basic test: Computation context should be correct")]
+ public async Task RecalcCommonBlocks_ShouldBeCorrect()
+ {
+ // Act
+ ComputationContext context = s_geopack.Recalc(
+ fixture.InputData.DateTime,
+ CartesianVector.New(fixture.InputData.VGSEX, fixture.InputData.VGSEY, fixture.InputData.VGSEZ,
+ CoordinateSystem.GSE));
+
+ // Assert
+ string rawData = await EmbeddedResourceReader.ReadTextAsync(CommonsDataFileName);
+ GeopackCommons approvedData = GeopackDataParser.ParseRecalcCommons(rawData);
+
+ for (int i = 0; i < context.G.Length; i++)
+ {
+ context.G[i].ShouldApproximatelyBe(approvedData.G![i]);
+ context.H[i].ShouldApproximatelyBe(approvedData.H![i]);
+ context.REC[i].ShouldApproximatelyBe(approvedData.REC![i]);
+ }
+
+ context.ST0.ShouldApproximatelyBe(approvedData.ST0);
+ context.CT0.ShouldApproximatelyBe(approvedData.CT0);
+ context.SL0.ShouldApproximatelyBe(approvedData.SL0);
+ context.CL0.ShouldApproximatelyBe(approvedData.CL0);
+ context.CTCL.ShouldApproximatelyBe(approvedData.CTCL);
+ context.STCL.ShouldApproximatelyBe(approvedData.STCL);
+ context.CTSL.ShouldApproximatelyBe(approvedData.CTSL);
+ context.STSL.ShouldApproximatelyBe(approvedData.STSL);
+ context.SFI.ShouldApproximatelyBe(approvedData.SFI);
+ context.CFI.ShouldApproximatelyBe(approvedData.CFI);
+ context.SPS.ShouldApproximatelyBe(approvedData.SPS);
+ context.CPS.ShouldApproximatelyBe(approvedData.CPS);
+ context.CGST.ShouldApproximatelyBe(approvedData.CGST);
+ context.SGST.ShouldApproximatelyBe(approvedData.SGST);
+ context.PSI.ShouldApproximatelyBe(approvedData.PSI);
+ context.A11.ShouldApproximatelyBe(approvedData.A11);
+ context.A21.ShouldApproximatelyBe(approvedData.A21);
+ context.A31.ShouldApproximatelyBe(approvedData.A31);
+ context.A12.ShouldApproximatelyBe(approvedData.A12);
+ context.A22.ShouldApproximatelyBe(approvedData.A22);
+ context.A32.ShouldApproximatelyBe(approvedData.A32);
+ context.A13.ShouldApproximatelyBe(approvedData.A13);
+ context.A23.ShouldApproximatelyBe(approvedData.A23);
+ context.A33.ShouldApproximatelyBe(approvedData.A33);
+ context.E11.ShouldApproximatelyBe(approvedData.E11);
+ context.E21.ShouldApproximatelyBe(approvedData.E21);
+ context.E31.ShouldApproximatelyBe(approvedData.E31);
+ context.E12.ShouldApproximatelyBe(approvedData.E12);
+ context.E22.ShouldApproximatelyBe(approvedData.E22);
+ context.E32.ShouldApproximatelyBe(approvedData.E32);
+ context.E13.ShouldApproximatelyBe(approvedData.E13);
+ context.E23.ShouldApproximatelyBe(approvedData.E23);
+ context.E33.ShouldApproximatelyBe(approvedData.E33);
+ }
+
+ [Fact(DisplayName = "Recalc should throw if incorrect sw velocity coordinate system")]
+ public void Recalc_Throw()
+ {
+ // Act
+ Action act = () => s_geopack.Recalc(
+ fixture.InputData.DateTime,
+ CartesianVector.New(fixture.InputData.VGSEX, fixture.InputData.VGSEY, fixture.InputData.VGSEZ,
+ CoordinateSystem.GSW));
+
+ // Assert
+ act.ShouldThrow();
+ }
+}
diff --git a/UnitTests/Geopack/GeopackTests.Recalc_08.cs b/UnitTests/Geopack/GeopackTests.Recalc_08.cs
deleted file mode 100644
index d795ca8..0000000
--- a/UnitTests/Geopack/GeopackTests.Recalc_08.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using AuroraScienceHub.Geopack.UnitTests.Geopack.TestData.Models;
-using AuroraScienceHub.Geopack.UnitTests.Utils;
-using Shouldly;
-
-namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
-
-public partial class GeopackTests
-{
- [Fact(DisplayName = "Basic test: Recalc Common1 & Common2 should be correct")]
- public async Task RecalcCommonBlocks_ShouldBeCorrect()
- {
- // Act
- (Common1 common1, Common2 common2) = _geopack.Recalc_08(
- fixture.InputData.DateTime,
- fixture.InputData.VGSEX,
- fixture.InputData.VGSEY,
- fixture.InputData.VGSEZ);
-
- // Assert
- string rawData = await EmbeddedResourceReader.ReadTextAsync(CommonsDataFileName);
- GeopackCommons approvedData = GeopackDataParser.ParseRecalcCommons(rawData);
-
- for (int i = 0; i < common2.G.Length; i++)
- {
- common2.G[i].ShouldBe(approvedData.G![i], MinimalTestsPrecision);
- common2.H[i].ShouldBe(approvedData.H![i], MinimalTestsPrecision);
- common2.REC[i].ShouldBe(approvedData.REC![i], MinimalTestsPrecision);
- }
- common1.ST0.ShouldBe(approvedData.ST0, MinimalTestsPrecision);
- common1.CT0.ShouldBe(approvedData.CT0, MinimalTestsPrecision);
- common1.SL0.ShouldBe(approvedData.SL0, MinimalTestsPrecision);
- common1.CL0.ShouldBe(approvedData.CL0, MinimalTestsPrecision);
- common1.CTCL.ShouldBe(approvedData.CTCL, MinimalTestsPrecision);
- common1.STCL.ShouldBe(approvedData.STCL, MinimalTestsPrecision);
- common1.CTSL.ShouldBe(approvedData.CTSL, MinimalTestsPrecision);
- common1.STSL.ShouldBe(approvedData.STSL, MinimalTestsPrecision);
- common1.SFI.ShouldBe(approvedData.SFI, MinimalTestsPrecision);
- common1.CFI.ShouldBe(approvedData.CFI, MinimalTestsPrecision);
- common1.SPS.ShouldBe(approvedData.SPS, MinimalTestsPrecision);
- common1.CPS.ShouldBe(approvedData.CPS, MinimalTestsPrecision);
- common1.DS3.ShouldBe(approvedData.DS3, MinimalTestsPrecision);
- common1.CGST.ShouldBe(approvedData.CGST, MinimalTestsPrecision);
- common1.SGST.ShouldBe(approvedData.SGST, MinimalTestsPrecision);
- common1.PSI.ShouldBe(approvedData.PSI, MinimalTestsPrecision);
- common1.A11.ShouldBe(approvedData.A11, MinimalTestsPrecision);
- common1.A21.ShouldBe(approvedData.A21, MinimalTestsPrecision);
- common1.A31.ShouldBe(approvedData.A31, MinimalTestsPrecision);
- common1.A12.ShouldBe(approvedData.A12, MinimalTestsPrecision);
- common1.A22.ShouldBe(approvedData.A22, MinimalTestsPrecision);
- common1.A32.ShouldBe(approvedData.A32, MinimalTestsPrecision);
- common1.A13.ShouldBe(approvedData.A13, MinimalTestsPrecision);
- common1.A23.ShouldBe(approvedData.A23, MinimalTestsPrecision);
- common1.A33.ShouldBe(approvedData.A33, MinimalTestsPrecision);
- common1.E11.ShouldBe(approvedData.E11, MinimalTestsPrecision);
- common1.E21.ShouldBe(approvedData.E21, MinimalTestsPrecision);
- common1.E31.ShouldBe(approvedData.E31, MinimalTestsPrecision);
- common1.E12.ShouldBe(approvedData.E12, MinimalTestsPrecision);
- common1.E22.ShouldBe(approvedData.E22, MinimalTestsPrecision);
- common1.E32.ShouldBe(approvedData.E32, MinimalTestsPrecision);
- common1.E13.ShouldBe(approvedData.E13, MinimalTestsPrecision);
- common1.E23.ShouldBe(approvedData.E23, MinimalTestsPrecision);
- common1.E33.ShouldBe(approvedData.E33, MinimalTestsPrecision);
- }
-}
diff --git a/UnitTests/Geopack/GeopackTests.Sun_08.cs b/UnitTests/Geopack/GeopackTests.Sun.cs
similarity index 74%
rename from UnitTests/Geopack/GeopackTests.Sun_08.cs
rename to UnitTests/Geopack/GeopackTests.Sun.cs
index 4aebb44..7a48001 100644
--- a/UnitTests/Geopack/GeopackTests.Sun_08.cs
+++ b/UnitTests/Geopack/GeopackTests.Sun.cs
@@ -1,4 +1,5 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.PhysicalObjects;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
using Shouldly;
namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
@@ -18,13 +19,13 @@ public void SUN_08_VariousDates_ReturnsExpectedValues(
DateTime date = new(year, month, day, hour, minute, second);
// Act
- Sun sun = _geopack.Sun_08(date);
+ Sun sun = s_geopack.Sun(date);
// Assert
sun.DateTime.ShouldBe(date);
- sun.Gst.ShouldBe(gst, MinimalTestsPrecision);
- sun.Slong.ShouldBe(slong, MinimalTestsPrecision);
- sun.Srasn.ShouldBe(srasn, MinimalTestsPrecision);
- sun.Sdec.ShouldBe(sdec, MinimalTestsPrecision);
+ sun.Gst.ShouldApproximatelyBe(gst);
+ sun.Slong.ShouldApproximatelyBe(slong);
+ sun.Srasn.ShouldApproximatelyBe(srasn);
+ sun.Sdec.ShouldApproximatelyBe(sdec);
}
}
diff --git a/UnitTests/Geopack/GeopackTests.ToCartesianVector.cs b/UnitTests/Geopack/GeopackTests.ToCartesianVector.cs
new file mode 100644
index 0000000..d4ea9a2
--- /dev/null
+++ b/UnitTests/Geopack/GeopackTests.ToCartesianVector.cs
@@ -0,0 +1,105 @@
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
+using AuroraScienceHub.Geopack.Contracts.Spherical;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
+using AuroraScienceHub.Geopack.UnitTests.Utils;
+using Shouldly;
+
+namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
+
+public partial class GeopackTests
+{
+ [Fact(DisplayName = "Iterate BSPCAR_08 setups and verify result")]
+ public async Task ToCartesian_ReturnsCorrectValues()
+ {
+ // Arrange
+ string rawData = await EmbeddedResourceReader.ReadTextAsync(BSpCarDatasetFileName);
+ string[] line = rawData.SplitParametersLine();
+
+ SphericalLocation sphLocation = SphericalLocation.New(1D, line[1].ParseDouble(), line[3].ParseDouble(), CoordinateSystem.GSW);
+ SphericalVector testSphericalFieldVector = SphericalVector.New(
+ line[5].ParseDouble(),
+ line[7].ParseDouble(),
+ line[9].ParseDouble(),
+ CoordinateSystem.GSW);
+
+ CartesianVector expectedVector = CartesianVector.New(
+ line[11].ParseDouble(),
+ line[13].ParseDouble(),
+ line[15].ParseDouble(),
+ CoordinateSystem.GSW);
+
+ // Act
+ CartesianVector calculatedVector = testSphericalFieldVector.ToCartesianVector(sphLocation);
+
+ // Assert
+ calculatedVector.X.ShouldApproximatelyBe(expectedVector.X);
+ calculatedVector.Y.ShouldApproximatelyBe(expectedVector.Y);
+ calculatedVector.Z.ShouldApproximatelyBe(expectedVector.Z);
+ }
+
+ [Fact(DisplayName = "BSphCar: Zero angles B-field coordinates conversion")]
+ public void ToCartesian_ZeroAngles_ReturnsExpectedValues()
+ {
+ // Arrange
+ SphericalLocation location = SphericalLocation.New(0, 0, 0, CoordinateSystem.GSW);
+ SphericalVector testField = SphericalVector.New(1, 1, 1, CoordinateSystem.GSW);
+
+ // Act
+ CartesianVector fieldVector = testField.ToCartesianVector(location);
+
+ // Assert
+ fieldVector.X.ShouldApproximatelyBe(1.0);
+ fieldVector.Y.ShouldApproximatelyBe(1.0);
+ fieldVector.Z.ShouldApproximatelyBe(1.0);
+ }
+
+ [Fact(DisplayName = "BSphCar: Negative angles B-field coordinates conversion")]
+ public void BSphCar_NegativeAngles_ReturnsExpectedValues()
+ {
+ // Arrange
+ SphericalLocation location = SphericalLocation.New(0, -Math.PI / 4, -Math.PI / 4, CoordinateSystem.GSW);
+ SphericalVector testField = SphericalVector.New(1, 1, 1, CoordinateSystem.GSW);
+
+ // Act
+ CartesianVector fieldVector = testField.ToCartesianVector(location);
+
+ // Assert
+ fieldVector.X.ShouldApproximatelyBe(0.70710678118654757);
+ fieldVector.Y.ShouldApproximatelyBe(0.70710678118654746);
+ fieldVector.Z.ShouldApproximatelyBe(1.4142135623730949);
+ }
+
+ [Fact(DisplayName = "BSphCar: Large angles B-field coordinates conversion")]
+ public void BSphCar_LargeAngles_ReturnsExpectedValues()
+ {
+ // Arrange
+ SphericalLocation location = SphericalLocation.New(0, 2 * Math.PI, 2 * Math.PI, CoordinateSystem.GSW);
+ SphericalVector testField = SphericalVector.New(1, 1, 1, CoordinateSystem.GSW);
+
+ // Act
+ CartesianVector fieldVector = testField.ToCartesianVector(location);
+
+ // Assert
+ fieldVector.X.ShouldApproximatelyBe(1.0);
+ fieldVector.Y.ShouldApproximatelyBe(1.0);
+ fieldVector.Z.ShouldApproximatelyBe(1.0);
+ }
+
+ [Fact(DisplayName = "BSphCar: Small angles B-field coordinates conversion")]
+ public void BSphCar_SmallAngles_ReturnsExpectedValues()
+ {
+ // Arrange
+ SphericalLocation location = SphericalLocation.New(0, 1e-10, 1e-10, CoordinateSystem.GSW);
+ SphericalVector testField = SphericalVector.New(1, 1, 1, CoordinateSystem.GSW);
+
+ // Act
+ CartesianVector fieldVector = testField.ToCartesianVector(location);
+
+ // Assert
+ fieldVector.X.ShouldApproximatelyBe(1.0);
+ fieldVector.Y.ShouldApproximatelyBe(1.0000000001);
+ fieldVector.Z.ShouldApproximatelyBe(0.99999999989999999);
+ }
+}
diff --git a/UnitTests/Geopack/GeopackTests.ToSphericalVector.cs b/UnitTests/Geopack/GeopackTests.ToSphericalVector.cs
new file mode 100644
index 0000000..89b426d
--- /dev/null
+++ b/UnitTests/Geopack/GeopackTests.ToSphericalVector.cs
@@ -0,0 +1,52 @@
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
+using AuroraScienceHub.Geopack.Contracts.Spherical;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
+using Shouldly;
+
+namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
+
+public partial class GeopackTests
+{
+ [Theory(DisplayName = "Iterate BCARSP_08 setups and verify result")]
+ [InlineData(-1.2, 1, 1.5, 1, 11, -21, -10.020128994909539344, 19.492502934797215630, -9.090618475235615392)]
+ [InlineData(1, 0, 0, 1, 1, 1, 1, -1, 1)]
+ [InlineData(0, 1, 0, 1, 1, 1, 1, -1, -1)]
+ [InlineData(0, 0, 1, 1, 1, 1, 1, 1, 1)]
+ [InlineData(1, 1, 1, 1, 1, 1, 1.7320508075688776, 0, 0)]
+ [InlineData(1, 1, 1, 1, 0, 0, 0.577350269189625842, 0.408248290463863017, -0.707106781186547462)]
+ [InlineData(1, 1, 1, 0, 1, 0, 0.577350269189625842, 0.408248290463863017, 0.707106781186547462)]
+ [InlineData(1, 1, 1, 0, 0, 1, 0.577350269189625842, -0.816496580927726145, 0)]
+ [InlineData(1, 1, 1, 0, 0, 0, 0, 0, 0)]
+ public void ToSphericalVector_ShouldReturnsCorrectValues(double x, double y, double z,
+ double bx, double by, double bz,
+ double br, double btheta, double bphi)
+ {
+ // Arrange
+ CartesianLocation location = CartesianLocation.New(x, y, z, CoordinateSystem.GSW);
+ CartesianVector cartesianVector = CartesianVector.New(bx, by, bz, CoordinateSystem.GSW);
+
+ // Act
+ SphericalVector sphericalVector = cartesianVector.ToSphericalVector(location);
+
+ // Assert
+ sphericalVector.R.ShouldApproximatelyBe(br);
+ sphericalVector.Theta.ShouldApproximatelyBe(btheta);
+ sphericalVector.Phi.ShouldApproximatelyBe(bphi);
+ }
+
+ [Fact(DisplayName = "BCarSph: NaN check (identical to original Geopack-2008 behavior)")]
+ public void ToSphericalVector_ShouldReturnNaND_IfDivideByZero()
+ {
+ // Arrange
+ CartesianLocation location = CartesianLocation.New(0, 0, 0, CoordinateSystem.GSW);
+ CartesianVector cartesianVector = CartesianVector.New(1, 1, 1, CoordinateSystem.GSW);
+
+ // Act
+ Action act = () => cartesianVector.ToSphericalVector(location);
+
+ // Assert
+ act.ShouldThrow();
+ }
+}
diff --git a/UnitTests/Geopack/GeopackTests.Trace_08.cs b/UnitTests/Geopack/GeopackTests.Trace.cs
similarity index 55%
rename from UnitTests/Geopack/GeopackTests.Trace_08.cs
rename to UnitTests/Geopack/GeopackTests.Trace.cs
index 4c80eb4..238ae35 100644
--- a/UnitTests/Geopack/GeopackTests.Trace_08.cs
+++ b/UnitTests/Geopack/GeopackTests.Trace.cs
@@ -1,6 +1,10 @@
using AuroraScienceHub.Geopack.Contracts;
-using AuroraScienceHub.Geopack.Contracts.Interfaces;
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+using AuroraScienceHub.Geopack.Contracts.PhysicalObjects;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
using AuroraScienceHub.Geopack.UnitTests.Utils;
using Shouldly;
@@ -12,7 +16,9 @@ public partial class GeopackTests
public async Task TraceFieldLineFromNorthToSouth()
{
// Arrange
- InternalFieldModel internalField = _geopack.IgrfGsw_08;
+ CartesianVector swVelocity = CartesianVector.New(-304.0D, -16.0D + 29.78D, 4.0D, CoordinateSystem.GSE);
+ ComputationContext context = s_geopack.Recalc(fixture.InputData.DateTime, swVelocity);
+ InternalFieldModel internalField = s_geopack.IgrfGsw;
string rawData = await EmbeddedResourceReader.ReadTextAsync(TraceNSResultFileName);
string[] lines = rawData.SplitLines();
@@ -27,13 +33,10 @@ public async Task TraceFieldLineFromNorthToSouth()
int lmax = 500;
// Act
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, -16.0D + 29.78D, 4.0D);
- double XGSW = -1.02D;
- double YGSW = 0.8D;
- double ZGSW = 0.9D;
+ CartesianLocation startingPoint = CartesianLocation.New(-1.02D, 0.8D, 0.9D, CoordinateSystem.GSW);
- FieldLine fieldLine = _geopack.Trace_08(
- XGSW, YGSW, ZGSW,
+ FieldLine fieldLine = s_geopack.Trace(context,
+ startingPoint,
dir, dsmax, err, rlim, r0,
iopt, parmod,
_t89, internalField,
@@ -45,9 +48,9 @@ public async Task TraceFieldLineFromNorthToSouth()
for (int i = 0; i < lines.Length; i++)
{
string[] pars = lines[i].SplitParametersLine();
- fieldLine.Points[i].X.ShouldBe(pars[0].ParseDouble(), MinimalTestsPrecision);
- fieldLine.Points[i].Y.ShouldBe(pars[1].ParseDouble(), MinimalTestsPrecision);
- fieldLine.Points[i].Z.ShouldBe(pars[2].ParseDouble(), MinimalTestsPrecision);
+ fieldLine.Points[i].X.ShouldApproximatelyBe(pars[0].ParseDouble());
+ fieldLine.Points[i].Y.ShouldApproximatelyBe(pars[1].ParseDouble());
+ fieldLine.Points[i].Z.ShouldApproximatelyBe(pars[2].ParseDouble());
}
}
@@ -55,7 +58,8 @@ public async Task TraceFieldLineFromNorthToSouth()
public async Task TraceFieldLineFromSouthToNorth()
{
// Arrange
- InternalFieldModel internalField = _geopack.IgrfGsw_08;
+ ComputationContext context = s_geopack.Recalc(fixture.InputData.DateTime);
+ InternalFieldModel internalField = s_geopack.IgrfGsw;
string rawData = await EmbeddedResourceReader.ReadTextAsync(TraceSNResultFileName);
string[] lines = rawData.SplitLines();
@@ -70,13 +74,10 @@ public async Task TraceFieldLineFromSouthToNorth()
int lmax = 500;
// Act
- _geopack.Recalc_08(fixture.InputData.DateTime);
- double XGSW = -1.02D;
- double YGSW = 0.8D;
- double ZGSW = -0.9D;
+ CartesianLocation startingPoint = CartesianLocation.New(-1.02D, 0.8D, -0.9D, CoordinateSystem.GSW);
- FieldLine fieldLine = _geopack.Trace_08(
- XGSW, YGSW, ZGSW,
+ FieldLine fieldLine = s_geopack.Trace(context,
+ startingPoint,
dir, dsmax, err, rlim, r0,
iopt, parmod,
_t89, internalField,
@@ -88,9 +89,9 @@ public async Task TraceFieldLineFromSouthToNorth()
for (int i = 0; i < lines.Length; i++)
{
string[] pars = lines[i].SplitParametersLine();
- fieldLine.Points[i].X.ShouldBe(pars[0].ParseDouble(), MinimalTestsPrecision);
- fieldLine.Points[i].Y.ShouldBe(pars[1].ParseDouble(), MinimalTestsPrecision);
- fieldLine.Points[i].Z.ShouldBe(pars[2].ParseDouble(), MinimalTestsPrecision);
+ fieldLine.Points[i].X.ShouldApproximatelyBe(pars[0].ParseDouble());
+ fieldLine.Points[i].Y.ShouldApproximatelyBe(pars[1].ParseDouble());
+ fieldLine.Points[i].Z.ShouldApproximatelyBe(pars[2].ParseDouble());
}
}
}
diff --git a/UnitTests/Geopack/GeopackTests.cs b/UnitTests/Geopack/GeopackTests.cs
index f38ee27..ff1df2f 100644
--- a/UnitTests/Geopack/GeopackTests.cs
+++ b/UnitTests/Geopack/GeopackTests.cs
@@ -1,4 +1,8 @@
-using AuroraScienceHub.Geopack.Contracts.Interfaces;
+using AuroraScienceHub.Geopack.Contracts;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
using AuroraScienceHub.Geopack.ExternalFieldModels.T89;
using AuroraScienceHub.Geopack.UnitTests.Geopack.Fixtures;
@@ -10,13 +14,14 @@ public class TestDataCollection : ICollectionFixture;
[Collection("Geopack")]
public partial class GeopackTests(TestDataFixture fixture)
{
- private readonly AuroraScienceHub.Geopack.Geopack _geopack = new();
+ private static readonly AuroraScienceHub.Geopack.Geopack s_geopack = new();
- private readonly IExternalFieldModel _t89 = new T89();
+ private readonly ComputationContext _context = s_geopack.Recalc(
+ fixture.InputData.DateTime, CartesianVector.New(-304.0D, 13.0D, 4.0D, CoordinateSystem.GSE));
- private const double Rad = 57.295779513D;
+ private static readonly IExternalFieldModel _t89 = new T89();
- private const double MinimalTestsPrecision = 0.000000000008d;
+ private const double Rad = 57.295779513D;
private const string CommonsDataFileName =
"AuroraScienceHub.Geopack.UnitTests.Geopack.TestData.CommonsDataSet.dat";
diff --git a/UnitTests/Geopack/GepackTests.Dip_08.cs b/UnitTests/Geopack/GepackTests.Dip.cs
similarity index 59%
rename from UnitTests/Geopack/GepackTests.Dip_08.cs
rename to UnitTests/Geopack/GepackTests.Dip.cs
index d1d392a..971bcc3 100644
--- a/UnitTests/Geopack/GepackTests.Dip_08.cs
+++ b/UnitTests/Geopack/GepackTests.Dip.cs
@@ -1,4 +1,7 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
using Shouldly;
namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
@@ -17,26 +20,28 @@ public void Dip_ShouldReturnCorrectValues(
double xgsw, double ygsw, double zgsw,
double expectedBx, double expectedBy, double expectedBz)
{
+ // Arrange
+ CartesianLocation location = CartesianLocation.New(xgsw, ygsw, zgsw, CoordinateSystem.GSW);
+
// Act
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
- CartesianFieldVector resultField = _geopack.Dip_08(xgsw, ygsw, zgsw);
+ CartesianVector resultField = s_geopack.Dip(_context, location);
// Assert
- resultField.Bx.ShouldBe(expectedBx, MinimalTestsPrecision);
- resultField.By.ShouldBe(expectedBy, MinimalTestsPrecision);
- resultField.Bz.ShouldBe(expectedBz, MinimalTestsPrecision);
+ resultField.X.ShouldApproximatelyBe(expectedBx);
+ resultField.Y.ShouldApproximatelyBe(expectedBy);
+ resultField.Z.ShouldApproximatelyBe(expectedBz);
}
[Fact(DisplayName = "DIP_08 is NaN if Zero coordinates")]
- public void Dip_ShouldReturnNaNValues_IfZeroCoordinates()
+ public void Dip_ShouldThrow_IfZeroCoordinates()
{
+ // Arrange
+ CartesianLocation location = CartesianLocation.New(0D, 0D, 0D, CoordinateSystem.GSW);
+
// Act
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
- CartesianFieldVector resultField = _geopack.Dip_08(0.0D, 0.0D, 0.0D);
+ Action act = () => s_geopack.Dip(_context, location);
// Assert
- resultField.Bx.ShouldBe(double.NaN);
- resultField.By.ShouldBe(double.NaN);
- resultField.Bz.ShouldBe(double.NaN);
+ act.ShouldThrow();
}
}
diff --git a/UnitTests/Geopack/GepackTests.GeiGeo.cs b/UnitTests/Geopack/GepackTests.GeiGeo.cs
new file mode 100644
index 0000000..4ff5863
--- /dev/null
+++ b/UnitTests/Geopack/GepackTests.GeiGeo.cs
@@ -0,0 +1,78 @@
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
+using AuroraScienceHub.Geopack.UnitTests.Utils;
+using Shouldly;
+
+namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
+
+public partial class GeopackTests
+{
+ [Fact(DisplayName = "GeiGeo: convert to correct values")]
+ public async Task GeiGeo_ReturnsCorrectValues()
+ {
+ // Arrange
+ CartesianVector swVelocity = CartesianVector.New(-304.0D, 13.0D, 4.0D, CoordinateSystem.GSE);
+ ComputationContext ctx = s_geopack.Recalc(fixture.InputData.DateTime, swVelocity);
+ string rawData = await EmbeddedResourceReader.ReadTextAsync(GeiGeoDatasetFileName);
+ string[] lines = rawData.SplitLines();
+
+ foreach (string line in lines)
+ {
+ string[] coordinatesString = line.SplitParametersLine();
+ CartesianLocation locationGei = CartesianLocation.New(
+ coordinatesString[1].ParseDouble(),
+ coordinatesString[3].ParseDouble(),
+ coordinatesString[5].ParseDouble(),
+ CoordinateSystem.GEI);
+
+ CartesianLocation locationGeo = CartesianLocation.New(
+ coordinatesString[7].ParseDouble(),
+ coordinatesString[9].ParseDouble(),
+ coordinatesString[11].ParseDouble(),
+ CoordinateSystem.GEO);
+
+ // Act
+ CartesianLocation calculatedGeo = s_geopack.GeiToGeo(ctx, locationGei);
+
+ // Assert
+ calculatedGeo.X.ShouldApproximatelyBe(locationGeo.X);
+ calculatedGeo.Y.ShouldApproximatelyBe(locationGeo.Y);
+ calculatedGeo.Z.ShouldApproximatelyBe(locationGeo.Z);
+ calculatedGeo.CoordinateSystem.ShouldBe(CoordinateSystem.GEO);
+ }
+ }
+
+ [Fact(DisplayName = "GeoGei: convert to correct values")]
+ public async Task GeoGei_ReturnsCorrectValues()
+ {
+ // Arrange
+ string rawData = await EmbeddedResourceReader.ReadTextAsync(GeoGeiDatasetFileName);
+ string[] lines = rawData.SplitLines();
+
+ foreach (string line in lines)
+ {
+ string[] coordinatesString = line.SplitParametersLine();
+ CartesianLocation locationGeo = CartesianLocation.New(
+ coordinatesString[1].ParseDouble(),
+ coordinatesString[3].ParseDouble(),
+ coordinatesString[5].ParseDouble(),
+ CoordinateSystem.GEO);
+
+ double x = coordinatesString[7].ParseDouble();
+ double y = coordinatesString[9].ParseDouble();
+ double z = coordinatesString[11].ParseDouble();
+
+ // Act
+ CartesianLocation calculatedLocation = s_geopack.GeoToGei(_context, locationGeo);
+
+ // Assert
+ calculatedLocation.X.ShouldApproximatelyBe(x);
+ calculatedLocation.Y.ShouldApproximatelyBe(y);
+ calculatedLocation.Z.ShouldApproximatelyBe(z);
+ calculatedLocation.CoordinateSystem.ShouldBe(CoordinateSystem.GEI);
+ }
+ }
+}
diff --git a/UnitTests/Geopack/GepackTests.GeiGeo_08.cs b/UnitTests/Geopack/GepackTests.GeiGeo_08.cs
deleted file mode 100644
index 125b7f5..0000000
--- a/UnitTests/Geopack/GepackTests.GeiGeo_08.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-using AuroraScienceHub.Geopack.UnitTests.Utils;
-using Shouldly;
-
-namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
-
-public partial class GeopackTests
-{
- [Fact(DisplayName = "GeiGeo: convert to correct values")]
- public async Task GeiGeo_ReturnsCorrectValues()
- {
- // Arrange
- string rawData = await EmbeddedResourceReader.ReadTextAsync(GeiGeoDatasetFileName);
- string[] lines = rawData.SplitLines();
-
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
-
- foreach (string line in lines)
- {
- string[] coordinatesString = line.SplitParametersLine();
- double xgei = coordinatesString[1].ParseDouble();
- double ygei = coordinatesString[3].ParseDouble();
- double zgei = coordinatesString[5].ParseDouble();
-
- double xgeo = coordinatesString[7].ParseDouble();
- double ygeo = coordinatesString[9].ParseDouble();
- double zgeo = coordinatesString[11].ParseDouble();
-
- // Act
- CartesianLocation location = _geopack.GeiGeo_08(xgei, ygei, zgei);
-
- // Assert
- location.X.ShouldBe(xgeo, MinimalTestsPrecision);
- location.Y.ShouldBe(ygeo, MinimalTestsPrecision);
- location.Z.ShouldBe(zgeo, MinimalTestsPrecision);
- location.CoordinateSystem.ShouldBe(CoordinateSystem.GEO);
- }
- }
-
- [Fact(DisplayName = "GeoGei: convert to correct values")]
- public async Task GeoGei_ReturnsCorrectValues()
- {
- // Arrange
- string rawData = await EmbeddedResourceReader.ReadTextAsync(GeoGeiDatasetFileName);
- string[] lines = rawData.SplitLines();
-
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
-
- foreach (string line in lines)
- {
- string[] coordinatesString = line.SplitParametersLine();
- double xgeo = coordinatesString[1].ParseDouble();
- double ygeo = coordinatesString[3].ParseDouble();
- double zgeo = coordinatesString[5].ParseDouble();
-
- double xgei = coordinatesString[7].ParseDouble();
- double ygei = coordinatesString[9].ParseDouble();
- double zgei = coordinatesString[11].ParseDouble();
-
- // Act
- CartesianLocation location = _geopack.GeoGei_08(xgeo, ygeo, zgeo);
-
- // Assert
- location.X.ShouldBe(xgei, MinimalTestsPrecision);
- location.Y.ShouldBe(ygei, MinimalTestsPrecision);
- location.Z.ShouldBe(zgei, MinimalTestsPrecision);
- location.CoordinateSystem.ShouldBe(CoordinateSystem.GEI);
- }
- }
-}
diff --git a/UnitTests/Geopack/GepackTests.GeoGsw_08.cs b/UnitTests/Geopack/GepackTests.GeoGsw.cs
similarity index 58%
rename from UnitTests/Geopack/GepackTests.GeoGsw_08.cs
rename to UnitTests/Geopack/GepackTests.GeoGsw.cs
index c28bed5..b8ca190 100644
--- a/UnitTests/Geopack/GepackTests.GeoGsw_08.cs
+++ b/UnitTests/Geopack/GepackTests.GeoGsw.cs
@@ -1,4 +1,6 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
using AuroraScienceHub.Geopack.UnitTests.Utils;
using Shouldly;
@@ -13,26 +15,25 @@ public async Task GeoGsw_ReturnsCorrectValues()
string rawData = await EmbeddedResourceReader.ReadTextAsync(GeoGswDatasetFileName);
string[] lines = rawData.SplitLines();
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
-
foreach (string line in lines)
{
string[] coordinatesString = line.SplitParametersLine();
- double xgeo = coordinatesString[1].ParseDouble();
- double ygeo = coordinatesString[3].ParseDouble();
- double zgeo = coordinatesString[5].ParseDouble();
+ CartesianLocation geoLocation = CartesianLocation.New(coordinatesString[1].ParseDouble(),
+ coordinatesString[3].ParseDouble(),
+ coordinatesString[5].ParseDouble(),
+ CoordinateSystem.GEO);
double xgsw = coordinatesString[7].ParseDouble();
double ygsw = coordinatesString[9].ParseDouble();
double zgsw = coordinatesString[11].ParseDouble();
// Act
- CartesianLocation location = _geopack.GeoGsw_08(xgeo, ygeo, zgeo);
+ CartesianLocation location = s_geopack.GeoToGsw(_context, geoLocation);
// Assert
- location.X.ShouldBe(xgsw, MinimalTestsPrecision);
- location.Y.ShouldBe(ygsw, MinimalTestsPrecision);
- location.Z.ShouldBe(zgsw, MinimalTestsPrecision);
+ location.X.ShouldApproximatelyBe(xgsw);
+ location.Y.ShouldApproximatelyBe(ygsw);
+ location.Z.ShouldApproximatelyBe(zgsw);
location.CoordinateSystem.ShouldBe(CoordinateSystem.GSW);
}
}
@@ -44,26 +45,25 @@ public async Task GswGeo_ReturnsCorrectValues()
string rawData = await EmbeddedResourceReader.ReadTextAsync(GswGeoDatasetFileName);
string[] lines = rawData.SplitLines();
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
-
foreach (string line in lines)
{
string[] coordinatesString = line.SplitParametersLine();
- double xgsw = coordinatesString[1].ParseDouble();
- double ygsw = coordinatesString[3].ParseDouble();
- double zgsw = coordinatesString[5].ParseDouble();
+ CartesianLocation gswLocation = CartesianLocation.New(coordinatesString[1].ParseDouble(),
+ coordinatesString[3].ParseDouble(),
+ coordinatesString[5].ParseDouble(),
+ CoordinateSystem.GSW);
double xgeo = coordinatesString[7].ParseDouble();
double ygeo = coordinatesString[9].ParseDouble();
double zgeo = coordinatesString[11].ParseDouble();
// Act
- CartesianLocation location = _geopack.GswGeo_08(xgsw, ygsw, zgsw);
+ CartesianLocation location = s_geopack.GswToGeo(_context, gswLocation);
// Assert
- location.X.ShouldBe(xgeo, MinimalTestsPrecision);
- location.Y.ShouldBe(ygeo, MinimalTestsPrecision);
- location.Z.ShouldBe(zgeo, MinimalTestsPrecision);
+ location.X.ShouldApproximatelyBe(xgeo);
+ location.Y.ShouldApproximatelyBe(ygeo);
+ location.Z.ShouldApproximatelyBe(zgeo);
location.CoordinateSystem.ShouldBe(CoordinateSystem.GEO);
}
}
diff --git a/UnitTests/Geopack/GepackTests.GeoMag_08.cs b/UnitTests/Geopack/GepackTests.GeoMag.cs
similarity index 58%
rename from UnitTests/Geopack/GepackTests.GeoMag_08.cs
rename to UnitTests/Geopack/GepackTests.GeoMag.cs
index 5cd6ef4..15d6a3c 100644
--- a/UnitTests/Geopack/GepackTests.GeoMag_08.cs
+++ b/UnitTests/Geopack/GepackTests.GeoMag.cs
@@ -1,4 +1,6 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
using AuroraScienceHub.Geopack.UnitTests.Utils;
using Shouldly;
@@ -13,26 +15,25 @@ public async Task GeoMag_ReturnsCorrectValues()
string rawData = await EmbeddedResourceReader.ReadTextAsync(GeoMagDatasetFileName);
string[] lines = rawData.SplitLines();
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
-
foreach (string line in lines)
{
string[] coordinatesString = line.SplitParametersLine();
- double xgeo = coordinatesString[1].ParseDouble();
- double ygeo = coordinatesString[3].ParseDouble();
- double zgeo = coordinatesString[5].ParseDouble();
+ CartesianLocation geoLocation = CartesianLocation.New(coordinatesString[1].ParseDouble(),
+ coordinatesString[3].ParseDouble(),
+ coordinatesString[5].ParseDouble(),
+ CoordinateSystem.GEO);
double xmag = coordinatesString[7].ParseDouble();
double ymag = coordinatesString[9].ParseDouble();
double zmag = coordinatesString[11].ParseDouble();
// Act
- CartesianLocation location = _geopack.GeoMag_08(xgeo, ygeo, zgeo);
+ CartesianLocation location = s_geopack.GeoToMag(_context, geoLocation);
// Assert
- location.X.ShouldBe(xmag, MinimalTestsPrecision);
- location.Y.ShouldBe(ymag, MinimalTestsPrecision);
- location.Z.ShouldBe(zmag, MinimalTestsPrecision);
+ location.X.ShouldApproximatelyBe(xmag);
+ location.Y.ShouldApproximatelyBe(ymag);
+ location.Z.ShouldApproximatelyBe(zmag);
location.CoordinateSystem.ShouldBe(CoordinateSystem.MAG);
}
}
@@ -44,26 +45,25 @@ public async Task MagGeo_ReturnsCorrectValues()
string rawData = await EmbeddedResourceReader.ReadTextAsync(MagGeoDatasetFileName);
string[] lines = rawData.SplitLines();
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
-
foreach (string line in lines)
{
string[] coordinatesString = line.SplitParametersLine();
- double xmag = coordinatesString[1].ParseDouble();
- double ymag = coordinatesString[3].ParseDouble();
- double zmag = coordinatesString[5].ParseDouble();
+ CartesianLocation magLocation = CartesianLocation.New(coordinatesString[1].ParseDouble(),
+ coordinatesString[3].ParseDouble(),
+ coordinatesString[5].ParseDouble(),
+ CoordinateSystem.MAG);
double xgeo = coordinatesString[7].ParseDouble();
double ygeo = coordinatesString[9].ParseDouble();
double zgeo = coordinatesString[11].ParseDouble();
// Act
- CartesianLocation location = _geopack.MagGeo_08(xmag, ymag, zmag);
+ CartesianLocation location = s_geopack.MagToGeo(_context, magLocation);
// Assert
- location.X.ShouldBe(xgeo, MinimalTestsPrecision);
- location.Y.ShouldBe(ygeo, MinimalTestsPrecision);
- location.Z.ShouldBe(zgeo, MinimalTestsPrecision);
+ location.X.ShouldApproximatelyBe(xgeo);
+ location.Y.ShouldApproximatelyBe(ygeo);
+ location.Z.ShouldApproximatelyBe(zgeo);
location.CoordinateSystem.ShouldBe(CoordinateSystem.GEO);
}
}
diff --git a/UnitTests/Geopack/GepackTests.GeodGeo.cs b/UnitTests/Geopack/GepackTests.GeodGeo.cs
new file mode 100644
index 0000000..2ee7bf0
--- /dev/null
+++ b/UnitTests/Geopack/GepackTests.GeodGeo.cs
@@ -0,0 +1,53 @@
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
+using AuroraScienceHub.Geopack.UnitTests.Utils;
+using Shouldly;
+
+namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
+
+public partial class GeopackTests
+{
+ [Fact(DisplayName = "ToPolar: convert to correct values")]
+ public async Task ToPolar_ReturnsCorrectValues()
+ {
+ // Arrange
+ string rawData = await EmbeddedResourceReader.ReadTextAsync(GeodGeoDatasetFileName);
+ string[] lines = rawData.SplitLines();
+
+ foreach (string line in lines)
+ {
+ string[] coordinatesString = line.SplitParametersLine();
+ GeodeticCoordinates testLocation = new(coordinatesString[3].ParseDouble(), coordinatesString[1].ParseDouble());
+ GeocentricCoordinates approvedLocation = new(coordinatesString[5].ParseDouble(), coordinatesString[7].ParseDouble());
+
+ // Act
+ GeocentricCoordinates result = testLocation.ToGeocentric();
+
+ // Assert
+ result.R.ShouldApproximatelyBe(approvedLocation.R);
+ result.Theta.ShouldApproximatelyBe(approvedLocation.Theta);
+ }
+ }
+
+ [Fact(DisplayName = "ToGeodetic: convert to correct values")]
+ public async Task ToGeodetic_ReturnsCorrectValues()
+ {
+ // Arrange
+ string rawData = await EmbeddedResourceReader.ReadTextAsync(GeoGeodDatasetFileName);
+ string[] lines = rawData.SplitLines();
+
+ foreach (string line in lines)
+ {
+ string[] coordinatesString = line.SplitParametersLine();
+ GeocentricCoordinates testLocation = new(coordinatesString[1].ParseDouble(), coordinatesString[3].ParseDouble());
+ GeodeticCoordinates approvedLocation = new(coordinatesString[7].ParseDouble(), coordinatesString[5].ParseDouble());
+
+ // Act
+ GeodeticCoordinates result = testLocation.ToGeodetic();
+
+ // Assert
+ result.Altitude.ShouldApproximatelyBe(approvedLocation.Altitude);
+ result.Latitude.ShouldApproximatelyBe(approvedLocation.Latitude);
+ }
+ }
+}
diff --git a/UnitTests/Geopack/GepackTests.GeodGeo_08.cs b/UnitTests/Geopack/GepackTests.GeodGeo_08.cs
deleted file mode 100644
index cc0764a..0000000
--- a/UnitTests/Geopack/GepackTests.GeodGeo_08.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-using AuroraScienceHub.Geopack.UnitTests.Utils;
-using Shouldly;
-
-namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
-
-public partial class GeopackTests
-{
- [Fact(DisplayName = "GeodGeo: convert to correct values")]
- public async Task GeodGeo_ReturnsCorrectValues()
- {
- // Arrange
- string rawData = await EmbeddedResourceReader.ReadTextAsync(GeodGeoDatasetFileName);
- string[] lines = rawData.SplitLines();
-
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
-
- foreach (string line in lines)
- {
- string[] coordinatesString = line.SplitParametersLine();
- double h = coordinatesString[1].ParseDouble();
- double xmu = coordinatesString[3].ParseDouble();
-
- double r = coordinatesString[5].ParseDouble();
- double theta = coordinatesString[7].ParseDouble();
-
- // Act
- GeodeticGeocentricCoordinates location = _geopack.GeodGeo_08(h, xmu);
-
- // Assert
- location.H.ShouldBe(h, MinimalTestsPrecision);
- location.Xmu.ShouldBe(xmu, MinimalTestsPrecision);
- location.R.ShouldBe(r, MinimalTestsPrecision);
- location.Theta.ShouldBe(theta, MinimalTestsPrecision);
- }
- }
-
- [Fact(DisplayName = "GeoGeod: convert to correct values")]
- public async Task GeoGeod_ReturnsCorrectValues()
- {
- // Arrange
- string rawData = await EmbeddedResourceReader.ReadTextAsync(GeoGeodDatasetFileName);
- string[] lines = rawData.SplitLines();
-
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
-
- foreach (string line in lines)
- {
- string[] coordinatesString = line.SplitParametersLine();
- double r = coordinatesString[1].ParseDouble();
- double theta = coordinatesString[3].ParseDouble();
-
- double h = coordinatesString[5].ParseDouble();
- double xmu = coordinatesString[7].ParseDouble();
-
- // Act
- GeodeticGeocentricCoordinates location = _geopack.GeoGeod_08(r, theta);
-
- // Assert
- location.H.ShouldBe(h, MinimalTestsPrecision);
- location.Xmu.ShouldBe(xmu, MinimalTestsPrecision);
- location.R.ShouldBe(r, MinimalTestsPrecision);
- location.Theta.ShouldBe(theta, MinimalTestsPrecision);
- }
- }
-}
diff --git a/UnitTests/Geopack/GepackTests.GswGse_08.cs b/UnitTests/Geopack/GepackTests.GswGse.cs
similarity index 58%
rename from UnitTests/Geopack/GepackTests.GswGse_08.cs
rename to UnitTests/Geopack/GepackTests.GswGse.cs
index cbf248d..f79b9b2 100644
--- a/UnitTests/Geopack/GepackTests.GswGse_08.cs
+++ b/UnitTests/Geopack/GepackTests.GswGse.cs
@@ -1,4 +1,6 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
using AuroraScienceHub.Geopack.UnitTests.Utils;
using Shouldly;
@@ -13,26 +15,25 @@ public async Task GswGse_ReturnsCorrectValues()
string rawData = await EmbeddedResourceReader.ReadTextAsync(GswGseDatasetFileName);
string[] lines = rawData.SplitLines();
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
-
foreach (string line in lines)
{
string[] coordinatesString = line.SplitParametersLine();
- double xgsw = coordinatesString[1].ParseDouble();
- double ygsw = coordinatesString[3].ParseDouble();
- double zgsw = coordinatesString[5].ParseDouble();
+ CartesianLocation gswLocation = CartesianLocation.New(coordinatesString[1].ParseDouble(),
+ coordinatesString[3].ParseDouble(),
+ coordinatesString[5].ParseDouble(),
+ CoordinateSystem.GSW);
double xgse = coordinatesString[7].ParseDouble();
double ygse = coordinatesString[9].ParseDouble();
double zgse = coordinatesString[11].ParseDouble();
// Act
- CartesianLocation location = _geopack.GswGse_08(xgsw, ygsw, zgsw);
+ CartesianLocation location = s_geopack.GswToGse(_context, gswLocation);
// Assert
- location.X.ShouldBe(xgse, MinimalTestsPrecision);
- location.Y.ShouldBe(ygse, MinimalTestsPrecision);
- location.Z.ShouldBe(zgse, MinimalTestsPrecision);
+ location.X.ShouldApproximatelyBe(xgse);
+ location.Y.ShouldApproximatelyBe(ygse);
+ location.Z.ShouldApproximatelyBe(zgse);
location.CoordinateSystem.ShouldBe(CoordinateSystem.GSE);
}
}
@@ -44,26 +45,25 @@ public async Task GseGsw_ReturnsCorrectValues()
string rawData = await EmbeddedResourceReader.ReadTextAsync(GseGswDatasetFileName);
string[] lines = rawData.SplitLines();
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
-
foreach (string line in lines)
{
string[] coordinatesString = line.SplitParametersLine();
- double xgse = coordinatesString[1].ParseDouble();
- double ygse = coordinatesString[3].ParseDouble();
- double zgse = coordinatesString[5].ParseDouble();
+ CartesianLocation gseLocation = CartesianLocation.New(coordinatesString[1].ParseDouble(),
+ coordinatesString[3].ParseDouble(),
+ coordinatesString[5].ParseDouble(),
+ CoordinateSystem.GSE);
double xgsw = coordinatesString[7].ParseDouble();
double ygsw = coordinatesString[9].ParseDouble();
double zgsw = coordinatesString[11].ParseDouble();
// Act
- CartesianLocation location = _geopack.GseGsw_08(xgse, ygse, zgse);
+ CartesianLocation location = s_geopack.GseToGsw(_context, gseLocation);
// Assert
- location.X.ShouldBe(xgsw, MinimalTestsPrecision);
- location.Y.ShouldBe(ygsw, MinimalTestsPrecision);
- location.Z.ShouldBe(zgsw, MinimalTestsPrecision);
+ location.X.ShouldApproximatelyBe(xgsw);
+ location.Y.ShouldApproximatelyBe(ygsw);
+ location.Z.ShouldApproximatelyBe(zgsw);
location.CoordinateSystem.ShouldBe(CoordinateSystem.GSW);
}
}
diff --git a/UnitTests/Geopack/GepackTests.IgrfGeo_08.cs b/UnitTests/Geopack/GepackTests.IgrfGeo.cs
similarity index 67%
rename from UnitTests/Geopack/GepackTests.IgrfGeo_08.cs
rename to UnitTests/Geopack/GepackTests.IgrfGeo.cs
index 81d4af8..2b988b9 100644
--- a/UnitTests/Geopack/GepackTests.IgrfGeo_08.cs
+++ b/UnitTests/Geopack/GepackTests.IgrfGeo.cs
@@ -1,4 +1,7 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
+using AuroraScienceHub.Geopack.Contracts.Spherical;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
using Shouldly;
namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
@@ -23,31 +26,25 @@ public void IgrfGeo_ShouldReturnCorrectValues(
double expectedBr, double expectedBtheta, double expectedBphi)
{
// Arrange
- double coLat = (90.0 - xLat) / Rad;
- double lon = xLon / Rad;
+ SphericalLocation testLocation = SphericalLocation.New(r, (90.0 - xLat) / Rad, xLon / Rad, CoordinateSystem.GEO);
// Act
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
- SphericalFieldVector resultField = _geopack.IgrfGeo_08(r, coLat, lon);
+ SphericalVector resultField = s_geopack.IgrfGeo(_context, testLocation);
// Assert
- resultField.Br.ShouldBe(expectedBr, MinimalTestsPrecision);
- resultField.Btheta.ShouldBe(expectedBtheta, MinimalTestsPrecision);
- resultField.Bphi.ShouldBe(expectedBphi, MinimalTestsPrecision);
+ resultField.R.ShouldApproximatelyBe(expectedBr);
+ resultField.Theta.ShouldApproximatelyBe(expectedBtheta);
+ resultField.Phi.ShouldApproximatelyBe(expectedBphi);
resultField.CoordinateSystem.ShouldBe(CoordinateSystem.GEO);
}
[Fact(DisplayName = "IGRF_GEO_08 is NaN if Zero coordinates")]
- public void IgrfGeo_ShouldReturnNaNValues_IfZeroCoordinates()
+ public void IgrfGeo_ShouldThrow_IfZeroCoordinates()
{
// Act
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
- SphericalFieldVector resultField = _geopack.IgrfGeo_08(0.0D, 0.0D, 0.0D);
+ Action act = () => s_geopack.IgrfGeo(_context, SphericalLocation.New(0D, 1D, 1D, CoordinateSystem.GEO));
// Assert
- resultField.Br.ShouldBe(double.NaN);
- resultField.Btheta.ShouldBe(double.NaN);
- resultField.Bphi.ShouldBe(double.NaN);
- resultField.CoordinateSystem.ShouldBe(CoordinateSystem.GEO);
+ act.ShouldThrow();
}
}
diff --git a/UnitTests/Geopack/GepackTests.IgrfGsw_08.cs b/UnitTests/Geopack/GepackTests.IgrfGsw.cs
similarity index 64%
rename from UnitTests/Geopack/GepackTests.IgrfGsw_08.cs
rename to UnitTests/Geopack/GepackTests.IgrfGsw.cs
index f7f99b3..18c0067 100644
--- a/UnitTests/Geopack/GepackTests.IgrfGsw_08.cs
+++ b/UnitTests/Geopack/GepackTests.IgrfGsw.cs
@@ -1,4 +1,7 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
using Shouldly;
namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
@@ -18,13 +21,12 @@ public void IgrfGsw_ShouldReturnCorrectValues(
double expectedBx, double expectedBy, double expectedBz)
{
// Act
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
- CartesianFieldVector resultField = _geopack.IgrfGsw_08(x, y, z);
+ CartesianVector resultField = s_geopack.IgrfGsw(_context, CartesianLocation.New(x, y, z, CoordinateSystem.GSW));
// Assert
- resultField.Bx.ShouldBe(expectedBx, MinimalTestsPrecision);
- resultField.By.ShouldBe(expectedBy, MinimalTestsPrecision);
- resultField.Bz.ShouldBe(expectedBz, MinimalTestsPrecision);
+ resultField.X.ShouldApproximatelyBe(expectedBx);
+ resultField.Y.ShouldApproximatelyBe(expectedBy);
+ resultField.Z.ShouldApproximatelyBe(expectedBz);
resultField.CoordinateSystem.ShouldBe(CoordinateSystem.GSW);
}
@@ -32,13 +34,9 @@ public void IgrfGsw_ShouldReturnCorrectValues(
public void IgrfGsw_ShouldReturnNaNValues_IfZeroCoordinates()
{
// Act
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
- CartesianFieldVector resultField = _geopack.IgrfGsw_08(0.0D, 0.0D, 0.0D);
+ Action act = () => s_geopack.IgrfGsw(_context, CartesianLocation.New(0.0D, 0.0D, 0.0D, CoordinateSystem.GSW));
// Assert
- resultField.Bx.ShouldBe(double.NaN);
- resultField.By.ShouldBe(double.NaN);
- resultField.Bz.ShouldBe(double.NaN);
- resultField.CoordinateSystem.ShouldBe(CoordinateSystem.GSW);
+ act.ShouldThrow();
}
}
diff --git a/UnitTests/Geopack/GepackTests.MagSm_08.cs b/UnitTests/Geopack/GepackTests.MagSm.cs
similarity index 58%
rename from UnitTests/Geopack/GepackTests.MagSm_08.cs
rename to UnitTests/Geopack/GepackTests.MagSm.cs
index b674edc..06c2c74 100644
--- a/UnitTests/Geopack/GepackTests.MagSm_08.cs
+++ b/UnitTests/Geopack/GepackTests.MagSm.cs
@@ -1,4 +1,6 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
using AuroraScienceHub.Geopack.UnitTests.Utils;
using Shouldly;
@@ -13,26 +15,25 @@ public async Task MagSm_ReturnsCorrectValues()
string rawData = await EmbeddedResourceReader.ReadTextAsync(MagSmDatasetFileName);
string[] lines = rawData.SplitLines();
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
-
foreach (string line in lines)
{
string[] coordinatesString = line.SplitParametersLine();
- double xmag = coordinatesString[1].ParseDouble();
- double ymag = coordinatesString[3].ParseDouble();
- double zmag = coordinatesString[5].ParseDouble();
+ CartesianLocation magLocation = CartesianLocation.New(coordinatesString[1].ParseDouble(),
+ coordinatesString[3].ParseDouble(),
+ coordinatesString[5].ParseDouble(),
+ CoordinateSystem.MAG);
double xsm = coordinatesString[7].ParseDouble();
double ysm = coordinatesString[9].ParseDouble();
double zsm = coordinatesString[11].ParseDouble();
// Act
- CartesianLocation location = _geopack.MagSm_08(xmag, ymag, zmag);
+ CartesianLocation location = s_geopack.MagToSm(_context, magLocation);
// Assert
- location.X.ShouldBe(xsm, MinimalTestsPrecision);
- location.Y.ShouldBe(ysm, MinimalTestsPrecision);
- location.Z.ShouldBe(zsm, MinimalTestsPrecision);
+ location.X.ShouldApproximatelyBe(xsm);
+ location.Y.ShouldApproximatelyBe(ysm);
+ location.Z.ShouldApproximatelyBe(zsm);
location.CoordinateSystem.ShouldBe(CoordinateSystem.SM);
}
}
@@ -44,26 +45,25 @@ public async Task SmMag_ReturnsCorrectValues()
string rawData = await EmbeddedResourceReader.ReadTextAsync(SmMagDatasetFileName);
string[] lines = rawData.SplitLines();
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
-
foreach (string line in lines)
{
string[] coordinatesString = line.SplitParametersLine();
- double xsm = coordinatesString[1].ParseDouble();
- double ysm = coordinatesString[3].ParseDouble();
- double zsm = coordinatesString[5].ParseDouble();
+ CartesianLocation smLocation = CartesianLocation.New(coordinatesString[1].ParseDouble(),
+ coordinatesString[3].ParseDouble(),
+ coordinatesString[5].ParseDouble(),
+ CoordinateSystem.SM);
double xmag = coordinatesString[7].ParseDouble();
double ymag = coordinatesString[9].ParseDouble();
double zmag = coordinatesString[11].ParseDouble();
// Act
- CartesianLocation location = _geopack.SmMag_08(xsm, ysm, zsm);
+ CartesianLocation location = s_geopack.SmToMag(_context, smLocation);
// Assert
- location.X.ShouldBe(xmag, MinimalTestsPrecision);
- location.Y.ShouldBe(ymag, MinimalTestsPrecision);
- location.Z.ShouldBe(zmag, MinimalTestsPrecision);
+ location.X.ShouldApproximatelyBe(xmag);
+ location.Y.ShouldApproximatelyBe(ymag);
+ location.Z.ShouldApproximatelyBe(zmag);
location.CoordinateSystem.ShouldBe(CoordinateSystem.MAG);
}
}
diff --git a/UnitTests/Geopack/GepackTests.ShuMgnp_08.cs b/UnitTests/Geopack/GepackTests.ShuMgnp.cs
similarity index 68%
rename from UnitTests/Geopack/GepackTests.ShuMgnp_08.cs
rename to UnitTests/Geopack/GepackTests.ShuMgnp.cs
index 8795a32..7f47ed7 100644
--- a/UnitTests/Geopack/GepackTests.ShuMgnp_08.cs
+++ b/UnitTests/Geopack/GepackTests.ShuMgnp.cs
@@ -1,4 +1,7 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.PhysicalObjects;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
using Shouldly;
namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
@@ -18,13 +21,13 @@ public void ShuMgnp_ShouldReturnCorrectValues(
MagnetopausePosition position)
{
// Act
- Magnetopause resultField = _geopack.ShuMgnp_08(xnPd, vel, bzimf, x, y, z);
+ Magnetopause resultField = s_geopack.ShuMgnp(xnPd, vel, bzimf, CartesianLocation.New(x, y, z, CoordinateSystem.GSW));
// Assert
- resultField.X.ShouldBe(xmgnp, MinimalTestsPrecision);
- resultField.Y.ShouldBe(ymgnp, MinimalTestsPrecision);
- resultField.Z.ShouldBe(zmgnp, MinimalTestsPrecision);
- resultField.Dist.ShouldBe(dist, MinimalTestsPrecision);
+ resultField.BoundaryLocation.X.ShouldApproximatelyBe(xmgnp);
+ resultField.BoundaryLocation.Y.ShouldApproximatelyBe(ymgnp);
+ resultField.BoundaryLocation.Z.ShouldApproximatelyBe(zmgnp);
+ resultField.Dist.ShouldApproximatelyBe(dist);
resultField.Position.ShouldBe(position);
}
@@ -38,13 +41,9 @@ public void ShuMgnp_ShouldNaN(
double x, double y, double z)
{
// Act
- Magnetopause resultField = _geopack.ShuMgnp_08(xnPd, vel, bzImf, x, y, z);
+ Action act = () => s_geopack.ShuMgnp(xnPd, vel, bzImf, CartesianLocation.New(x, y, z, CoordinateSystem.GSW));
// Assert
- resultField.X.ShouldBe(double.NaN);
- resultField.Y.ShouldBe(double.NaN);
- resultField.Z.ShouldBe(double.NaN);
- resultField.Dist.ShouldBe(double.NaN);
- resultField.Position.ShouldBe(MagnetopausePosition.NotDefined);
+ act.ShouldThrow();
}
}
diff --git a/UnitTests/Geopack/GepackTests.SmGsw_08.cs b/UnitTests/Geopack/GepackTests.SmGsw.cs
similarity index 58%
rename from UnitTests/Geopack/GepackTests.SmGsw_08.cs
rename to UnitTests/Geopack/GepackTests.SmGsw.cs
index da0d85a..7d2a658 100644
--- a/UnitTests/Geopack/GepackTests.SmGsw_08.cs
+++ b/UnitTests/Geopack/GepackTests.SmGsw.cs
@@ -1,4 +1,6 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
using AuroraScienceHub.Geopack.UnitTests.Utils;
using Shouldly;
@@ -13,26 +15,25 @@ public async Task SmGsw_ReturnsCorrectValues()
string rawData = await EmbeddedResourceReader.ReadTextAsync(SmGswDatasetFileName);
string[] lines = rawData.SplitLines();
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
-
foreach (string line in lines)
{
string[] coordinatesString = line.SplitParametersLine();
- double xsm = coordinatesString[1].ParseDouble();
- double ysm = coordinatesString[3].ParseDouble();
- double zsm = coordinatesString[5].ParseDouble();
+ CartesianLocation smLocation = CartesianLocation.New(coordinatesString[1].ParseDouble(),
+ coordinatesString[3].ParseDouble(),
+ coordinatesString[5].ParseDouble(),
+ CoordinateSystem.SM);
double xgsw = coordinatesString[7].ParseDouble();
double ygsw = coordinatesString[9].ParseDouble();
double zgsw = coordinatesString[11].ParseDouble();
// Act
- CartesianLocation location = _geopack.SmGsw_08(xsm, ysm, zsm);
+ CartesianLocation location = s_geopack.SmToGsw(_context, smLocation);
// Assert
- location.X.ShouldBe(xgsw, MinimalTestsPrecision);
- location.Y.ShouldBe(ygsw, MinimalTestsPrecision);
- location.Z.ShouldBe(zgsw, MinimalTestsPrecision);
+ location.X.ShouldApproximatelyBe(xgsw);
+ location.Y.ShouldApproximatelyBe(ygsw);
+ location.Z.ShouldApproximatelyBe(zgsw);
location.CoordinateSystem.ShouldBe(CoordinateSystem.GSW);
}
}
@@ -44,26 +45,25 @@ public async Task GswSm_ReturnsCorrectValues()
string rawData = await EmbeddedResourceReader.ReadTextAsync(GswSmDatasetFileName);
string[] lines = rawData.SplitLines();
- _geopack.Recalc_08(fixture.InputData.DateTime, -304.0D, 13.0D, 4.0D);
-
foreach (string line in lines)
{
string[] coordinatesString = line.SplitParametersLine();
- double xgsw = coordinatesString[1].ParseDouble();
- double ygsw = coordinatesString[3].ParseDouble();
- double zgsw = coordinatesString[5].ParseDouble();
+ CartesianLocation gswLocation = CartesianLocation.New(coordinatesString[1].ParseDouble(),
+ coordinatesString[3].ParseDouble(),
+ coordinatesString[5].ParseDouble(),
+ CoordinateSystem.GSW);
double xsm = coordinatesString[7].ParseDouble();
double ysm = coordinatesString[9].ParseDouble();
double zsm = coordinatesString[11].ParseDouble();
// Act
- CartesianLocation location = _geopack.GswSm_08(xgsw, ygsw, zgsw);
+ CartesianLocation location = s_geopack.GswToSm(_context, gswLocation);
// Assert
- location.X.ShouldBe(xsm, MinimalTestsPrecision);
- location.Y.ShouldBe(ysm, MinimalTestsPrecision);
- location.Z.ShouldBe(zsm, MinimalTestsPrecision);
+ location.X.ShouldApproximatelyBe(xsm);
+ location.Y.ShouldApproximatelyBe(ysm);
+ location.Z.ShouldApproximatelyBe(zsm);
location.CoordinateSystem.ShouldBe(CoordinateSystem.SM);
}
}
diff --git a/UnitTests/Geopack/GepackTests.T96Mgnp_08.cs b/UnitTests/Geopack/GepackTests.T96Mgnp.cs
similarity index 69%
rename from UnitTests/Geopack/GepackTests.T96Mgnp_08.cs
rename to UnitTests/Geopack/GepackTests.T96Mgnp.cs
index 6a40c87..da06cf1 100644
--- a/UnitTests/Geopack/GepackTests.T96Mgnp_08.cs
+++ b/UnitTests/Geopack/GepackTests.T96Mgnp.cs
@@ -1,4 +1,7 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.PhysicalObjects;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
using Shouldly;
namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
@@ -18,13 +21,13 @@ public void T96Mgnp_ShouldReturnCorrectValues(
double dist, MagnetopausePosition position)
{
// Act
- Magnetopause resultField = _geopack.T96Mgnp_08(xnPd, vel, x, y, z);
+ Magnetopause resultField = s_geopack.T96Mgnp(xnPd, vel, CartesianLocation.New(x, y, z, CoordinateSystem.GSW));
// Assert
- resultField.X.ShouldBe(xmgnp, MinimalTestsPrecision);
- resultField.Y.ShouldBe(ymgnp, MinimalTestsPrecision);
- resultField.Z.ShouldBe(zmgnp, MinimalTestsPrecision);
- resultField.Dist.ShouldBe(dist, MinimalTestsPrecision);
+ resultField.BoundaryLocation.X.ShouldApproximatelyBe(xmgnp);
+ resultField.BoundaryLocation.Y.ShouldApproximatelyBe(ymgnp);
+ resultField.BoundaryLocation.Z.ShouldApproximatelyBe(zmgnp);
+ resultField.Dist.ShouldApproximatelyBe(dist);
resultField.Position.ShouldBe(position);
}
@@ -37,13 +40,9 @@ public void T96Mgnp_ShouldNaN(
double x, double y, double z)
{
// Act
- Magnetopause resultField = _geopack.T96Mgnp_08(xnPd, vel, x, y, z);
+ Action act = () => s_geopack.T96Mgnp(xnPd, vel, CartesianLocation.New(x, y, z, CoordinateSystem.GSW));
// Assert
- resultField.X.ShouldBe(double.NaN);
- resultField.Y.ShouldBe(double.NaN);
- resultField.Z.ShouldBe(double.NaN);
- resultField.Dist.ShouldBe(double.NaN);
- resultField.Position.ShouldBe(MagnetopausePosition.NotDefined);
+ act.ShouldThrow();
}
}
diff --git a/UnitTests/Geopack/GepackTests.SphCar_08.cs b/UnitTests/Geopack/GepackTests.ToCartesian.cs
similarity index 50%
rename from UnitTests/Geopack/GepackTests.SphCar_08.cs
rename to UnitTests/Geopack/GepackTests.ToCartesian.cs
index c6cc27c..1252fe7 100644
--- a/UnitTests/Geopack/GepackTests.SphCar_08.cs
+++ b/UnitTests/Geopack/GepackTests.ToCartesian.cs
@@ -1,4 +1,7 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Spherical;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
using Shouldly;
namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
@@ -9,15 +12,17 @@ public partial class GeopackTests
public void SphCar_ShouldReturnCorrectValues()
{
// Arrange
- CartesianLocation approvedData = new(0.4637416876811D, 0.7222348866390D, 0.5511083519855D);
+ SphericalLocation testLocation = SphericalLocation.New(1.02D, 1.0D, 1.0D, CoordinateSystem.GSW);
+ CartesianLocation approvedData = CartesianLocation.New(0.4637416876811D, 0.7222348866390D, 0.5511083519855D, CoordinateSystem.GSW);
// Act
- CartesianLocation point = _geopack.SphCar_08(1.02D, 1.0D, 1.0D);
+ CartesianLocation result = testLocation.ToCartesian();
// Assert
- point.X.ShouldBe(approvedData.X, MinimalTestsPrecision);
- point.Y.ShouldBe(approvedData.Y, MinimalTestsPrecision);
- point.Z.ShouldBe(approvedData.Z, MinimalTestsPrecision);
+ result.X.ShouldApproximatelyBe(approvedData.X);
+ result.Y.ShouldApproximatelyBe(approvedData.Y);
+ result.Z.ShouldApproximatelyBe(approvedData.Z);
+ result.CoordinateSystem.ShouldBe(approvedData.CoordinateSystem);
}
[Theory(DisplayName = "Spherical to cartesian coordinates: zeroes and ones")]
@@ -33,12 +38,15 @@ public void SphCar_ShouldReturnCorrectValues()
public void SphCar_Variances_ReturnCorrectValues(double r, double theta, double phi, double x, double y, double z)
{
+ // Arrange
+ SphericalLocation testLocation = SphericalLocation.New(r, theta, phi, CoordinateSystem.GSW);
+
// Act
- CartesianLocation point = _geopack.SphCar_08(r, theta, phi);
+ CartesianLocation result = testLocation.ToCartesian();
// Assert
- point.X.ShouldBe(x);
- point.Y.ShouldBe(y);
- point.Z.ShouldBe(z);
+ result.X.ShouldApproximatelyBe(x);
+ result.Y.ShouldApproximatelyBe(y);
+ result.Z.ShouldApproximatelyBe(z);
}
}
diff --git a/UnitTests/Geopack/GepackTests.CarSph_08.cs b/UnitTests/Geopack/GepackTests.ToSpherical.cs
similarity index 52%
rename from UnitTests/Geopack/GepackTests.CarSph_08.cs
rename to UnitTests/Geopack/GepackTests.ToSpherical.cs
index e33e13a..5bd36b8 100644
--- a/UnitTests/Geopack/GepackTests.CarSph_08.cs
+++ b/UnitTests/Geopack/GepackTests.ToSpherical.cs
@@ -1,4 +1,7 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Spherical;
+using AuroraScienceHub.Geopack.UnitTests.Extensions;
using Shouldly;
namespace AuroraScienceHub.Geopack.UnitTests.Geopack;
@@ -9,15 +12,17 @@ public partial class GeopackTests
public void CarSph_ShouldReturnCorrectValues()
{
// Arrange
- SphericalLocation approvedData = new SphericalLocation(1.7320508075689, 0.9553166181245, 0.7853981633974);
+ CartesianLocation testData = CartesianLocation.New(1D, 1D, 1D, CoordinateSystem.GSW);
+ SphericalLocation approvedData = SphericalLocation.New(1.7320508075689, 0.9553166181245, 0.7853981633974, CoordinateSystem.GSW);
// Act
- SphericalLocation point = _geopack.CarSph_08(1.0D, 1.0D, 1.0D);
+ SphericalLocation result = testData.ToSpherical();
// Assert
- point.R.ShouldBe(approvedData.R, MinimalTestsPrecision);
- point.Theta.ShouldBe(approvedData.Theta, MinimalTestsPrecision);
- point.Phi.ShouldBe(approvedData.Phi, MinimalTestsPrecision);
+ result.R.ShouldApproximatelyBe(approvedData.R);
+ result.Theta.ShouldApproximatelyBe(approvedData.Theta);
+ result.Phi.ShouldApproximatelyBe(approvedData.Phi);
+ result.CoordinateSystem.ShouldBe(approvedData.CoordinateSystem);
}
[Theory(DisplayName = "Cartesian to spherical coordinates conversion: zeroes and ones")]
@@ -32,12 +37,16 @@ public void CarSph_ShouldReturnCorrectValues()
[InlineData(-1.0, -1.0, -1.0, 1.73205080756887719, 2.18627603546528393, 3.92699081680765527)]
public void CarSph_ZeroesAndOnes_ReturnsCorrectValues(double x, double y, double z, double r, double theta, double phi)
{
+ // Arrange
+ CartesianLocation testLocation = CartesianLocation.New(x, y, z, CoordinateSystem.GSW);
+
// Act
- SphericalLocation point = _geopack.CarSph_08(x, y, z);
+ SphericalLocation result = testLocation.ToSpherical();
// Assert
- point.R.ShouldBe(r);
- point.Theta.ShouldBe(theta);
- point.Phi.ShouldBe(phi);
+ result.R.ShouldApproximatelyBe(r);
+ result.Theta.ShouldApproximatelyBe(theta);
+ result.Phi.ShouldApproximatelyBe(phi);
+ result.CoordinateSystem.ShouldBe(testLocation.CoordinateSystem);
}
}
diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj
index 160f4a0..b931aff 100644
--- a/UnitTests/UnitTests.csproj
+++ b/UnitTests/UnitTests.csproj
@@ -20,6 +20,7 @@
+
diff --git a/benchmarks/AuroraScienceHub.Geopack.Benchmarks/Geopack/GeopackBenchmarks.cs b/benchmarks/AuroraScienceHub.Geopack.Benchmarks/Geopack/GeopackBenchmarks.cs
index f71c3d4..b31d308 100644
--- a/benchmarks/AuroraScienceHub.Geopack.Benchmarks/Geopack/GeopackBenchmarks.cs
+++ b/benchmarks/AuroraScienceHub.Geopack.Benchmarks/Geopack/GeopackBenchmarks.cs
@@ -1,5 +1,10 @@
-using AuroraScienceHub.Geopack.Contracts.Interfaces;
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+using AuroraScienceHub.Geopack.Contracts.PhysicalObjects;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
+using AuroraScienceHub.Geopack.Contracts.Spherical;
using AuroraScienceHub.Geopack.ExternalFieldModels.T89;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
@@ -29,32 +34,27 @@ public class GeopackBenchmarks
private const double Psi = 1;
private static readonly double[] s_parmod = new double[10];
private const int Lmax = 500;
+ private const double Rad = 57.295779513D;
- private const double Vgsex = -304.0D;
- private const double Vgsey = 14.78D;
- private const double Vgsez = 4.0D;
+ private static readonly CartesianVector s_gseVelocityVector = CartesianVector.New(-304.0, 14.78, 4.0, CoordinateSystem.GSE);
- private static readonly (double X, double Y, double Z) s_testPointNs = (-0.45455707401565865D, 0.4737969930623606D, 0.7542497890011055D);
- private static readonly (double X, double Y, double Z) s_testPointSn = (-0.1059965956329907D, 0.41975266827470664D, -0.9014246640527153D);
+ private static readonly CartesianLocation s_testPointNs = CartesianLocation.New(-0.45455707401565865D, 0.4737969930623606D, 0.7542497890011055D, CoordinateSystem.GSW);
+ private static readonly CartesianLocation s_testPointSn = CartesianLocation.New(-0.1059965956329907D, 0.41975266827470664D, -0.9014246640527153D, CoordinateSystem.GSW);
- private const double X = -1.02D;
- private const double Y = 1.02D;
- private const double Z = 1.02D;
- private const double BX = 1.0D;
- private const double BY = 1.0D;
- private const double BZ = 1.0D;
+ private static readonly CartesianLocation s_gswCartesian = CartesianLocation.New(-1.02D, -1.02D, 1.02D, CoordinateSystem.GSW);
+ private static readonly CartesianLocation s_geiCartesian = CartesianLocation.New(-1.02D, -1.02D, 1.02D, CoordinateSystem.GEI);
+ private static readonly CartesianLocation s_geoCartesian = CartesianLocation.New(-1.02D, -1.02D, 1.02D, CoordinateSystem.GEO);
+ private static readonly CartesianLocation s_magCartesian = CartesianLocation.New(-1.02D, -1.02D, 1.02D, CoordinateSystem.MAG);
+ private static readonly CartesianLocation s_smCartesian = CartesianLocation.New(-1.02D, -1.02D, 1.02D, CoordinateSystem.SM);
+ private static readonly CartesianVector s_gswFieldCar = CartesianVector.New(1.0, 1.0, 1.0, CoordinateSystem.GSW);
- private const double Theta = 0.7D;
- private const double Phi = 1.4D;
- private const double R = 1.02D;
- private const double BR = 1.0D;
- private const double BTh = 1.0D;
- private const double BPhi = 1.0D;
+ private static readonly SphericalLocation s_igrfGeoLocation = SphericalLocation.New(1.02, (90.0 - 73.0) / Rad, 45.0 / Rad, CoordinateSystem.GEO);
- private const double H = 100.0D;
- private const double XMU = 1.04719D;
- private const double RR = 6462.13176D;
- private const double THETAR = 0.526464D;
+ private static readonly SphericalLocation s_gswSph = SphericalLocation.New(1.02D, 1.0D, 1.0D, CoordinateSystem.GSW);
+ private static readonly SphericalVector s_gswFieldSph = SphericalVector.New(1.0, 1.0, 1.0, CoordinateSystem.GSW);
+
+ private static readonly GeodeticCoordinates s_geodetic = new(1.04719, 100.0);
+ private static readonly GeocentricCoordinates s_geocentric = new(6462.13176, 0.526464);
private const double XnPd = 5.0D;
private const double Vel = -350.0D;
@@ -62,130 +62,128 @@ public class GeopackBenchmarks
private static readonly DateTime s_testDate = new(1997, 12, 11, 10, 10, 0, DateTimeKind.Utc);
- [GlobalSetup]
- public void Setup()
- => s_geopack.Recalc_08(s_testDate, Vgsex, Vgsey, Vgsez);
+ private static readonly ComputationContext s_context = s_geopack.Recalc(s_testDate);
[Benchmark]
public void ToSphericalVector()
- => s_geopack.BCarSph_08(X, Y, Z, BX, BY, BZ);
+ => s_gswFieldCar.ToSphericalVector(s_gswCartesian);
[Benchmark]
public void ToCartesianVector()
- => s_geopack.BSphCar_08(Theta, Phi, BR, BTh, BPhi);
+ => s_gswFieldSph.ToCartesianVector(s_gswSph);
[Benchmark]
public void ToSpherical()
- => s_geopack.CarSph_08(X, Y, Z);
+ => s_gswCartesian.ToSpherical();
[Benchmark]
public void ToCartesian()
- => s_geopack.SphCar_08(R, Theta, Phi);
+ => s_gswSph.ToCartesian();
[Benchmark]
public void GeiToGeo()
- => s_geopack.GeiGeo_08(X, Y, Z);
+ => s_geopack.GeiToGeo(s_context, s_geiCartesian);
[Benchmark]
public void GeoToGei()
- => s_geopack.GeoGei_08(X, Y, Z);
+ => s_geopack.GeoToGei(s_context, s_geoCartesian);
[Benchmark]
public void ToGeocentric()
- => s_geopack.GeodGeo_08(H, XMU);
+ => s_geodetic.ToGeocentric();
[Benchmark]
public void ToGeodetic()
- => s_geopack.GeoGeod_08(RR, THETAR);
+ => s_geocentric.ToGeodetic();
[Benchmark]
public void GeoToGsw()
- => s_geopack.GeoGsw_08(X, Y, Z);
+ => s_geopack.GeoToGsw(s_context, s_geoCartesian);
[Benchmark]
public void GswToGeo()
- => s_geopack.GswGeo_08(X, Y, Z);
+ => s_geopack.GswToGeo(s_context, s_gswCartesian);
[Benchmark]
public void GeoToMag()
- => s_geopack.GeoMag_08(X, Y, Z);
+ => s_geopack.GeoToMag(s_context, s_geoCartesian);
[Benchmark]
public void MagToGeo()
- => s_geopack.MagGeo_08(X, Y, Z);
+ => s_geopack.MagToGeo(s_context, s_magCartesian);
[Benchmark]
public void GswToGse()
- => s_geopack.GswGse_08(X, Y, Z);
+ => s_geopack.GswToGse(s_context, s_gswFieldCar);
[Benchmark]
public void GseToGsw()
- => s_geopack.GseGsw_08(X, Y, Z);
+ => s_geopack.GseToGsw(s_context, s_gseVelocityVector);
[Benchmark]
public void MagToSm()
- => s_geopack.MagSm_08(X, Y, Z);
+ => s_geopack.MagToSm(s_context, s_magCartesian);
[Benchmark]
public void SmToMag()
- => s_geopack.SmMag_08(X, Y, Z);
+ => s_geopack.SmToMag(s_context, s_smCartesian);
[Benchmark]
public void SmToGsw()
- => s_geopack.SmGsw_08(X, Y, Z);
+ => s_geopack.SmToGsw(s_context, s_smCartesian);
[Benchmark]
public void GswToSm()
- => s_geopack.GswSm_08(X, Y, Z);
+ => s_geopack.GswToSm(s_context, s_gswFieldCar);
[Benchmark(Baseline = true)]
public void Recalc()
- => s_geopack.Recalc_08(s_testDate, Vgsex, Vgsey, Vgsez);
+ => s_geopack.Recalc(s_testDate, s_gseVelocityVector);
[Benchmark]
public void ShuMgnp()
- => s_geopack.ShuMgnp_08(XnPd, Vel, BzImf, X, Y, Z);
+ => s_geopack.ShuMgnp(XnPd, Vel, BzImf, s_gswCartesian);
[Benchmark]
public void T96Mgnp()
- => s_geopack.T96Mgnp_08(XnPd, Vel, X, Y, Z);
+ => s_geopack.T96Mgnp(XnPd, Vel, s_gswCartesian);
[Benchmark]
public void IgrfGeo()
- => s_geopack.IgrfGeo_08(R, Theta, Phi);
+ => s_geopack.IgrfGeo(s_context, s_igrfGeoLocation);
[Benchmark]
public void IgrfGsw()
- => s_geopack.IgrfGsw_08(X, Y, Z);
+ => s_geopack.IgrfGsw(s_context, s_gswCartesian);
[Benchmark]
public void Dip()
- => s_geopack.Dip_08(X, Y, Z);
+ => s_geopack.Dip(s_context, s_gswCartesian);
[Benchmark]
public void Sun()
- => s_geopack.Sun_08(s_testDate);
+ => s_geopack.Sun(s_testDate);
[Benchmark]
public void T89()
- => s_t89.Calculate(Iopt, s_parmod, Psi, X, Y, Z);
+ => s_t89.Calculate(Iopt, s_parmod, Psi, s_gswCartesian);
[Benchmark]
public void Trace_NS()
- => s_geopack.Trace_08(
- s_testPointNs.X, s_testPointNs.Y, s_testPointNs.Z,
+ => s_geopack.Trace(s_context,
+ s_testPointNs,
TraceDirection.AntiParallel, Dsmax, Err, Rlim, R0,
Iopt, s_parmod,
- s_t89, s_geopack.IgrfGsw_08,
+ s_t89, s_geopack.IgrfGsw,
Lmax);
[Benchmark]
public void Trace_SN()
- => s_geopack.Trace_08(
- s_testPointSn.X, s_testPointSn.Y, s_testPointSn.Z,
+ => s_geopack.Trace(s_context,
+ s_testPointSn,
TraceDirection.Parallel, Dsmax, Err, Rlim, R0,
Iopt, s_parmod,
- s_t89, s_geopack.IgrfGsw_08,
+ s_t89, s_geopack.IgrfGsw,
Lmax);
private class NativeAotConfig : ManualConfig
diff --git a/benchmarks/AuroraScienceHub.Geopack.Benchmarks/Geopack/results/v2/Issues_42_50_51_Linux.md b/benchmarks/AuroraScienceHub.Geopack.Benchmarks/Geopack/results/v2/Issues_42_50_51_Linux.md
new file mode 100644
index 0000000..568e515
--- /dev/null
+++ b/benchmarks/AuroraScienceHub.Geopack.Benchmarks/Geopack/results/v2/Issues_42_50_51_Linux.md
@@ -0,0 +1,70 @@
+```
+
+BenchmarkDotNet v0.15.4, Linux Ubuntu 24.04.2 LTS (Noble Numbat)
+AMD Ryzen 5 5500U with Radeon Graphics 0.40GHz, 1 CPU, 12 logical and 6 physical cores
+.NET SDK 10.0.100
+ [Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
+ .NET 10.0 : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
+ .NET 8.0 : .NET 8.0.22 (8.0.22, 8.0.2225.52707), X64 RyuJIT x86-64-v3
+
+
+```
+| Method | Job | Runtime | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
+|------------------ |---------- |---------- |----------------:|--------------:|--------------:|----------------:|--------:|--------:|--------:|----------:|------------:|
+| ToSphericalVector | .NET 10.0 | .NET 10.0 | 0.0012 ns | 0.0014 ns | 0.0013 ns | 0.0006 ns | 0.000 | 0.00 | - | - | 0.00 |
+| ToCartesianVector | .NET 10.0 | .NET 10.0 | 0.0017 ns | 0.0017 ns | 0.0015 ns | 0.0011 ns | 0.000 | 0.00 | - | - | 0.00 |
+| ToSpherical | .NET 10.0 | .NET 10.0 | 0.0017 ns | 0.0011 ns | 0.0011 ns | 0.0018 ns | 0.000 | 0.00 | - | - | 0.00 |
+| ToCartesian | .NET 10.0 | .NET 10.0 | 0.0001 ns | 0.0002 ns | 0.0002 ns | 0.0000 ns | 0.000 | 0.00 | - | - | 0.00 |
+| GeiToGeo | .NET 10.0 | .NET 10.0 | 0.0003 ns | 0.0005 ns | 0.0004 ns | 0.0002 ns | 0.000 | 0.00 | - | - | 0.00 |
+| GeoToGei | .NET 10.0 | .NET 10.0 | 0.0001 ns | 0.0002 ns | 0.0001 ns | 0.0000 ns | 0.000 | 0.00 | - | - | 0.00 |
+| ToGeocentric | .NET 10.0 | .NET 10.0 | 0.0001 ns | 0.0002 ns | 0.0001 ns | 0.0000 ns | 0.000 | 0.00 | - | - | 0.00 |
+| ToGeodetic | .NET 10.0 | .NET 10.0 | 329.3568 ns | 0.4036 ns | 0.3775 ns | 329.5001 ns | 0.290 | 0.00 | - | - | 0.00 |
+| GeoToGsw | .NET 10.0 | .NET 10.0 | 0.0000 ns | 0.0001 ns | 0.0001 ns | 0.0000 ns | 0.000 | 0.00 | - | - | 0.00 |
+| GswToGeo | .NET 10.0 | .NET 10.0 | 0.0001 ns | 0.0005 ns | 0.0004 ns | 0.0000 ns | 0.000 | 0.00 | - | - | 0.00 |
+| GeoToMag | .NET 10.0 | .NET 10.0 | 0.0010 ns | 0.0019 ns | 0.0017 ns | 0.0002 ns | 0.000 | 0.00 | - | - | 0.00 |
+| MagToGeo | .NET 10.0 | .NET 10.0 | 0.0009 ns | 0.0018 ns | 0.0016 ns | 0.0000 ns | 0.000 | 0.00 | - | - | 0.00 |
+| GswToGse | .NET 10.0 | .NET 10.0 | 0.0005 ns | 0.0004 ns | 0.0004 ns | 0.0006 ns | 0.000 | 0.00 | - | - | 0.00 |
+| GseToGsw | .NET 10.0 | .NET 10.0 | 0.0024 ns | 0.0016 ns | 0.0014 ns | 0.0019 ns | 0.000 | 0.00 | - | - | 0.00 |
+| MagToSm | .NET 10.0 | .NET 10.0 | 0.0007 ns | 0.0005 ns | 0.0005 ns | 0.0006 ns | 0.000 | 0.00 | - | - | 0.00 |
+| SmToMag | .NET 10.0 | .NET 10.0 | 0.0007 ns | 0.0006 ns | 0.0005 ns | 0.0006 ns | 0.000 | 0.00 | - | - | 0.00 |
+| SmToGsw | .NET 10.0 | .NET 10.0 | 0.0005 ns | 0.0006 ns | 0.0005 ns | 0.0004 ns | 0.000 | 0.00 | - | - | 0.00 |
+| GswToSm | .NET 10.0 | .NET 10.0 | 0.0007 ns | 0.0008 ns | 0.0007 ns | 0.0006 ns | 0.000 | 0.00 | - | - | 0.00 |
+| Recalc | .NET 10.0 | .NET 10.0 | 1,135.7724 ns | 3.9808 ns | 3.5289 ns | 1,134.5793 ns | 1.000 | 0.00 | 1.3828 | 2896 B | 1.00 |
+| ShuMgnp | .NET 10.0 | .NET 10.0 | 248.2120 ns | 0.2740 ns | 0.2288 ns | 248.2190 ns | 0.219 | 0.00 | - | - | 0.00 |
+| T96Mgnp | .NET 10.0 | .NET 10.0 | 0.0004 ns | 0.0009 ns | 0.0007 ns | 0.0001 ns | 0.000 | 0.00 | - | - | 0.00 |
+| IgrfGeo | .NET 10.0 | .NET 10.0 | 454.4769 ns | 1.6005 ns | 1.4188 ns | 453.9866 ns | 0.400 | 0.00 | 0.1297 | 272 B | 0.09 |
+| IgrfGsw | .NET 10.0 | .NET 10.0 | 518.6890 ns | 8.4863 ns | 7.9381 ns | 516.7420 ns | 0.457 | 0.01 | 0.1373 | 288 B | 0.10 |
+| Dip | .NET 10.0 | .NET 10.0 | 0.2110 ns | 0.0040 ns | 0.0035 ns | 0.2098 ns | 0.000 | 0.00 | - | - | 0.00 |
+| Sun | .NET 10.0 | .NET 10.0 | 153.8762 ns | 0.4146 ns | 0.3675 ns | 153.9401 ns | 0.135 | 0.00 | - | - | 0.00 |
+| T89 | .NET 10.0 | .NET 10.0 | 124.7912 ns | 0.5287 ns | 0.4945 ns | 124.5573 ns | 0.110 | 0.00 | - | - | 0.00 |
+| Trace_NS | .NET 10.0 | .NET 10.0 | 394,777.6689 ns | 1,984.5582 ns | 1,549.4125 ns | 394,729.2681 ns | 347.588 | 1.67 | 97.6563 | 204472 B | 70.60 |
+| Trace_SN | .NET 10.0 | .NET 10.0 | 376,446.2077 ns | 6,044.0743 ns | 5,653.6308 ns | 373,395.9683 ns | 331.448 | 4.92 | 87.4023 | 183200 B | 63.26 |
+| | | | | | | | | | | | |
+| ToSphericalVector | .NET 8.0 | .NET 8.0 | 58.3548 ns | 0.0434 ns | 0.0363 ns | 58.3503 ns | 0.048 | 0.00 | - | - | 0.00 |
+| ToCartesianVector | .NET 8.0 | .NET 8.0 | 35.8617 ns | 0.1579 ns | 0.1318 ns | 35.8871 ns | 0.029 | 0.00 | - | - | 0.00 |
+| ToSpherical | .NET 8.0 | .NET 8.0 | 35.0748 ns | 0.0731 ns | 0.0648 ns | 35.0476 ns | 0.029 | 0.00 | - | - | 0.00 |
+| ToCartesian | .NET 8.0 | .NET 8.0 | 0.0182 ns | 0.0019 ns | 0.0016 ns | 0.0178 ns | 0.000 | 0.00 | - | - | 0.00 |
+| GeiToGeo | .NET 8.0 | .NET 8.0 | 5.5140 ns | 0.0152 ns | 0.0142 ns | 5.5104 ns | 0.004 | 0.00 | - | - | 0.00 |
+| GeoToGei | .NET 8.0 | .NET 8.0 | 5.7500 ns | 0.0110 ns | 0.0097 ns | 5.7525 ns | 0.005 | 0.00 | - | - | 0.00 |
+| ToGeocentric | .NET 8.0 | .NET 8.0 | 158.6567 ns | 0.2616 ns | 0.2447 ns | 158.6431 ns | 0.129 | 0.00 | - | - | 0.00 |
+| ToGeodetic | .NET 8.0 | .NET 8.0 | 332.1483 ns | 0.3184 ns | 0.2486 ns | 332.0785 ns | 0.271 | 0.00 | - | - | 0.00 |
+| GeoToGsw | .NET 8.0 | .NET 8.0 | 6.5120 ns | 0.0118 ns | 0.0099 ns | 6.5145 ns | 0.005 | 0.00 | - | - | 0.00 |
+| GswToGeo | .NET 8.0 | .NET 8.0 | 6.2514 ns | 0.0187 ns | 0.0146 ns | 6.2569 ns | 0.005 | 0.00 | - | - | 0.00 |
+| GeoToMag | .NET 8.0 | .NET 8.0 | 6.0040 ns | 0.0188 ns | 0.0157 ns | 5.9964 ns | 0.005 | 0.00 | - | - | 0.00 |
+| MagToGeo | .NET 8.0 | .NET 8.0 | 6.0036 ns | 0.0144 ns | 0.0120 ns | 5.9971 ns | 0.005 | 0.00 | - | - | 0.00 |
+| GswToGse | .NET 8.0 | .NET 8.0 | 6.6122 ns | 0.0398 ns | 0.0372 ns | 6.6150 ns | 0.005 | 0.00 | - | - | 0.00 |
+| GseToGsw | .NET 8.0 | .NET 8.0 | 6.2298 ns | 0.0186 ns | 0.0156 ns | 6.2233 ns | 0.005 | 0.00 | - | - | 0.00 |
+| MagToSm | .NET 8.0 | .NET 8.0 | 5.4997 ns | 0.0058 ns | 0.0054 ns | 5.5004 ns | 0.004 | 0.00 | - | - | 0.00 |
+| SmToMag | .NET 8.0 | .NET 8.0 | 5.7476 ns | 0.0035 ns | 0.0032 ns | 5.7480 ns | 0.005 | 0.00 | - | - | 0.00 |
+| SmToGsw | .NET 8.0 | .NET 8.0 | 5.5090 ns | 0.0069 ns | 0.0061 ns | 5.5096 ns | 0.004 | 0.00 | - | - | 0.00 |
+| GswToSm | .NET 8.0 | .NET 8.0 | 5.7518 ns | 0.0091 ns | 0.0080 ns | 5.7499 ns | 0.005 | 0.00 | - | - | 0.00 |
+| Recalc | .NET 8.0 | .NET 8.0 | 1,225.5996 ns | 5.8998 ns | 5.2300 ns | 1,225.1386 ns | 1.000 | 0.01 | 1.3828 | 2896 B | 1.00 |
+| ShuMgnp | .NET 8.0 | .NET 8.0 | 509.3406 ns | 0.5872 ns | 0.5493 ns | 509.1909 ns | 0.416 | 0.00 | - | - | 0.00 |
+| T96Mgnp | .NET 8.0 | .NET 8.0 | 111.8199 ns | 0.1666 ns | 0.1301 ns | 111.8400 ns | 0.091 | 0.00 | - | - | 0.00 |
+| IgrfGeo | .NET 8.0 | .NET 8.0 | 462.8087 ns | 1.8867 ns | 1.7649 ns | 462.5069 ns | 0.378 | 0.00 | 0.1297 | 272 B | 0.09 |
+| IgrfGsw | .NET 8.0 | .NET 8.0 | 519.6007 ns | 1.3307 ns | 1.2447 ns | 519.7432 ns | 0.424 | 0.00 | 0.1373 | 288 B | 0.10 |
+| Dip | .NET 8.0 | .NET 8.0 | 129.1234 ns | 0.2076 ns | 0.1942 ns | 129.0977 ns | 0.105 | 0.00 | - | - | 0.00 |
+| Sun | .NET 8.0 | .NET 8.0 | 158.6323 ns | 0.1673 ns | 0.1483 ns | 158.6875 ns | 0.129 | 0.00 | - | - | 0.00 |
+| T89 | .NET 8.0 | .NET 8.0 | 126.9628 ns | 0.2185 ns | 0.2043 ns | 126.9991 ns | 0.104 | 0.00 | - | - | 0.00 |
+| Trace_NS | .NET 8.0 | .NET 8.0 | 435,764.6175 ns | 1,343.5597 ns | 1,121.9332 ns | 435,625.8623 ns | 355.558 | 1.71 | 97.6563 | 204608 B | 70.65 |
+| Trace_SN | .NET 8.0 | .NET 8.0 | 410,702.3057 ns | 2,027.2870 ns | 1,896.3255 ns | 410,279.8330 ns | 335.109 | 2.04 | 87.4023 | 183336 B | 63.31 |
diff --git a/docs/ReleaseNotes_GeopackV2.md b/docs/ReleaseNotes_GeopackV2.md
new file mode 100644
index 0000000..d33b2d8
--- /dev/null
+++ b/docs/ReleaseNotes_GeopackV2.md
@@ -0,0 +1,63 @@
+# Geopack v2.0.0 Release Notes
+
+## Issue #49 #50 #52 #63 (PR #44):
+
+This PR refactors the Geopack library to improve thread-safety and immutability by replacing shared mutable state (Common1/Common2 classes)
+with an immutable **ComputationContext** pattern.
+
+### Key changes:
+- Introduces ComputationContext as an immutable record to hold pre-calculated coefficients instead of mutable Common1/Common2 instances;
+- Updates all coordinate transformation and field calculation methods to accept ComputationContext as a parameter;
+- Converts data model records (CartesianLocation, SphericalLocation, etc.) from reference types to readonly record structs for better performance.
+
+## Issue #43 (PR #64):
+
+This PR refactors the Geopack library to improve thread-safety, immutability, and API design by replacing mutable shared state with an immutable ComputationContext pattern. Key changes include:
+
+### Key changes:
+- Introducing strongly-typed vector quantities using generics (CartesianVector, SphericalVector)
+- Converting coordinate/vector transformations from standalone methods to instance methods on model types
+- Refactoring methods to accept structured objects instead of individual coordinate parameters
+- Converting data models to readonly record structs for better performance
+- Adding validation with explicit exception throwing instead of returning NaN values
+
+## Issue #43 (PR #66):
+
+This PR adds dependency injection support for the Geopack library by introducing ServiceCollectionExtensions classes that provide convenient registration methods for the library's services. The PR adds the necessary Microsoft.Extensions.DependencyInjection.Abstractions package reference to enable this functionality.
+
+### Key changes
+- Added AddGeopack() extension method to register IGeopack implementation
+- 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 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 |
diff --git a/src/Contracts/Cartesian/CartesianLocation.cs b/src/Contracts/Cartesian/CartesianLocation.cs
new file mode 100644
index 0000000..d5f4084
--- /dev/null
+++ b/src/Contracts/Cartesian/CartesianLocation.cs
@@ -0,0 +1,79 @@
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+using AuroraScienceHub.Geopack.Contracts.Spherical;
+
+namespace AuroraScienceHub.Geopack.Contracts.Cartesian;
+
+///
+/// Location with cartesian coordinates
+///
+public readonly record struct CartesianLocation : ICartesian
+{
+ public double X { get; }
+
+ public double Y { get; }
+
+ public double Z { get; }
+
+ public CoordinateSystem CoordinateSystem { get; }
+
+ private CartesianLocation(double x, double y, double z, CoordinateSystem coordinateSystem)
+ {
+ X = x;
+ Y = y;
+ Z = z;
+ CoordinateSystem = coordinateSystem;
+ }
+
+ ///
+ /// Create new cartesian location
+ ///
+ /// X-coordinate
+ /// Y-coordinate
+ /// Z-coordinate
+ /// Coordinate system
+ public static CartesianLocation New(double x, double y, double z, CoordinateSystem coordinateSystem)
+ => new(x, y, z, coordinateSystem);
+
+ ///
+ /// Summ of two cartesian locations
+ ///
+ /// Location to be added
+ /// If different location coordinate systems
+ public CartesianLocation SumWith(CartesianLocation location)
+ => CoordinateSystem == location.CoordinateSystem
+ ? New(X + location.X, Y + location.Y, Z + location.Z, CoordinateSystem)
+ : throw new InvalidOperationException("Cannot sum locations in different coordinate systems.");
+
+ ///
+ /// Converts cartesian coordinates to spherical ones
+ ///
+ ///
+ /// Original Geopack-2008 method: CARSPH_08
+ ///
+ public SphericalLocation ToSpherical()
+ {
+ double phi;
+ double theta;
+ double sq = Math.FusedMultiplyAdd(X, X, Y * Y);
+ double r = Math.Sqrt(Math.FusedMultiplyAdd(Z, Z, sq));
+
+ if (Math.Abs(sq) > double.Epsilon)
+ {
+ sq = Math.Sqrt(sq);
+ phi = Math.Atan2(Y, X);
+ theta = Math.Atan2(sq, Z);
+ if (phi < 0.0d)
+ {
+ phi += GeopackConstants.TwoPi;
+ }
+ }
+ else
+ {
+ phi = 0.0;
+ theta = Z < 0.0d ? GeopackConstants.Pi : 0.0d;
+ }
+
+ return SphericalLocation.New(r, theta, phi, CoordinateSystem);
+ }
+}
diff --git a/src/Contracts/Cartesian/CartesianVector.cs b/src/Contracts/Cartesian/CartesianVector.cs
new file mode 100644
index 0000000..aa0dd10
--- /dev/null
+++ b/src/Contracts/Cartesian/CartesianVector.cs
@@ -0,0 +1,73 @@
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Spherical;
+
+namespace AuroraScienceHub.Geopack.Contracts.Cartesian;
+
+///
+/// Cartesian field vector
+///
+public readonly record struct CartesianVector
+ : ICartesian>
+ where TVector : IVectorQuantity
+{
+ public double X { get; }
+
+ public double Y { get; }
+
+ public double Z { get; }
+
+ public CoordinateSystem CoordinateSystem { get; }
+
+ public static CartesianVector New(double x, double y, double z, CoordinateSystem coordinateSystem)
+ => new(x, y, z, coordinateSystem);
+
+ private CartesianVector(double x, double y, double z, CoordinateSystem coordinateSystem)
+ {
+ X = x;
+ Y = y;
+ Z = z;
+ CoordinateSystem = coordinateSystem;
+ }
+
+ ///
+ /// Calculates local spherical field components from those in cartesian system.
+ ///
+ ///
+ /// Original Geopack-2008 method: BCARSP_08
+ ///
+ public SphericalVector ToSphericalVector(CartesianLocation location)
+ {
+ double rho2 = location.X * location.X + location.Y * location.Y;
+ double rho = Math.Sqrt(rho2);
+
+ double r = Math.Sqrt(rho2 + location.Z * location.Z);
+
+ if (Math.Abs(r) <= double.Epsilon)
+ {
+ throw new InvalidOperationException("Location radius should not be zero.");
+ }
+
+ double cphi;
+ double sphi;
+
+ if (rho > 0.0D)
+ {
+ cphi = location.X / rho;
+ sphi = location.Y / rho;
+ }
+ else
+ {
+ cphi = 1.0D;
+ sphi = 0.0D;
+ }
+
+ double ct = location.Z / r;
+ double st = rho / r;
+
+ 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.New(br, btheta, bphi, CoordinateSystem);
+ }
+};
diff --git a/src/Contracts/Models/CoordinateSystem.cs b/src/Contracts/Coordinates/CoordinateSystem.cs
similarity index 91%
rename from src/Contracts/Models/CoordinateSystem.cs
rename to src/Contracts/Coordinates/CoordinateSystem.cs
index aa11c92..72166ec 100644
--- a/src/Contracts/Models/CoordinateSystem.cs
+++ b/src/Contracts/Coordinates/CoordinateSystem.cs
@@ -1,4 +1,4 @@
-namespace AuroraScienceHub.Geopack.Contracts.Models;
+namespace AuroraScienceHub.Geopack.Contracts.Coordinates;
///
/// Available in Geopack-2008 coordinate systems
diff --git a/src/Contracts/Coordinates/GeocentricCoordinates.cs b/src/Contracts/Coordinates/GeocentricCoordinates.cs
new file mode 100644
index 0000000..0b1c99f
--- /dev/null
+++ b/src/Contracts/Coordinates/GeocentricCoordinates.cs
@@ -0,0 +1,72 @@
+using AuroraScienceHub.Geopack.Contracts.Engine;
+
+namespace AuroraScienceHub.Geopack.Contracts.Coordinates;
+
+///
+/// Represents (R, Theta) slice of ECEF spherical coordinates
+///
+public readonly record struct GeocentricCoordinates
+{
+ ///
+ /// Geocentric distance (in km, ECEF radial)
+ ///
+ public double R { get; }
+
+ ///
+ /// Spherical co-latitude (in radians, ECEF polar angle)
+ ///
+ public double Theta { get; }
+
+ ///
+ /// ctor
+ ///
+ /// Geocentric distance (in km, ECEF radial)
+ /// Spherical co-latitude (in radians, ECEF polar angle)
+ public GeocentricCoordinates(double r, double theta)
+ {
+ R = r;
+ Theta = theta;
+ }
+
+ ///
+ /// Converts geocentric coordinates R and THETA into vertical local height (altitude) H and geodetic latitude.
+ ///
+ ///
+ /// Original Geopack-2008 method: GEODGEO_08.
+ /// The subroutine uses World Geodetic System WGS84 parameters for the Earth's ellipsoid. The angular quantities
+ /// (geo co-latitude THETA and geodetic latitude XMU) are in radians, and the distances (geocentric radius R and
+ /// altitude H above the Earth's ellipsoid) are in kilometers.
+ ///
+ public GeodeticCoordinates ToGeodetic()
+ {
+ int n = 0;
+ double phi = GeopackConstants.HalfPi - Theta;
+ double phi1 = phi;
+ double r2 = R * R;
+
+ double xmus, h, dphi;
+
+ do
+ {
+ (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);
+ (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) > GeopackConstants.CoordinateConvergenceTolerance && n < 100);
+
+ return new GeodeticCoordinates(xmus, h);
+ }
+};
diff --git a/src/Contracts/Coordinates/GeodeticCoordinates.cs b/src/Contracts/Coordinates/GeodeticCoordinates.cs
new file mode 100644
index 0000000..162355d
--- /dev/null
+++ b/src/Contracts/Coordinates/GeodeticCoordinates.cs
@@ -0,0 +1,53 @@
+using AuroraScienceHub.Geopack.Contracts.Engine;
+
+namespace AuroraScienceHub.Geopack.Contracts.Coordinates;
+
+///
+/// Geodetic coordinates for 2D meridian plane transformations.
+/// Contains only altitude and geodetic latitude
+///
+public readonly record struct GeodeticCoordinates
+{
+ /// Geodetic latitude (in radians)
+ public double Latitude { get; }
+
+ /// Altitude above ellipsoid (in km)
+ public double Altitude { get; }
+
+ ///
+ /// ctor
+ ///
+ /// Geodetic latitude
+ /// Geodetic altitude
+ public GeodeticCoordinates(double geodLatitude, double altitude)
+ {
+ Latitude = geodLatitude;
+ Altitude = altitude;
+ }
+
+ ///
+ /// Converts vertical local height (altitude) altitude and geodetic latitude into geocentric coordinates R and THETA.
+ ///
+ ///
+ /// Original Geopack-2008 method: GEODGEO_08.
+ /// The subroutine uses World Geodetic System WGS84 parameters for the Earth's ellipsoid. The angular quantities
+ /// (geo co-latitude and geodetic latitude) are in radians, and the distances (geocentric radius R and
+ /// altitude H above the Earth's ellipsoid) are in kilometers.
+ ///
+ public GeocentricCoordinates ToGeocentric()
+ {
+ double sinxmu = Math.Sin(Latitude);
+ 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 * 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(x * x + z * z);
+ double theta = Math.Acos(z / r);
+
+ return new GeocentricCoordinates(r, theta);
+ }
+}
diff --git a/src/Contracts/Coordinates/OperationType.cs b/src/Contracts/Coordinates/OperationType.cs
new file mode 100644
index 0000000..45490dd
--- /dev/null
+++ b/src/Contracts/Coordinates/OperationType.cs
@@ -0,0 +1,21 @@
+namespace AuroraScienceHub.Geopack.Contracts.Coordinates;
+
+///
+/// Direct or reversed operations in Geopack methods.
+///
+public enum OperationType
+{
+ /// Direct operation
+ ///
+ /// If you use a Geopack method to convert from coordinate system A to B,
+ /// the direct operation corresponds to the conversion from A to B.
+ ///
+ Direct,
+
+ /// Reversed operation
+ ///
+ /// If you use a Geopack method to convert from coordinate system A to B,
+ /// the reversed operation corresponds to the conversion from B to A.
+ ///
+ Reversed
+}
diff --git a/src/Contracts/Engine/ComputationContext.cs b/src/Contracts/Engine/ComputationContext.cs
new file mode 100644
index 0000000..777de42
--- /dev/null
+++ b/src/Contracts/Engine/ComputationContext.cs
@@ -0,0 +1,19 @@
+namespace AuroraScienceHub.Geopack.Contracts.Engine;
+
+///
+/// Immutable computation context containing pre-calculated coefficients
+/// for specific datetime and solar wind conditions
+///
+public sealed record ComputationContext(
+ double ST0, double CT0, double SL0, double CL0,
+ double CTCL, double STCL, double CTSL, double STSL,
+ double SFI, double CFI,
+ double SPS, double CPS, double PSI,
+ double CGST, double SGST,
+ double A11, double A21, double A31,
+ double A12, double A22, double A32,
+ double A13, double A23, double A33,
+ double E11, double E21, double E31,
+ double E12, double E22, double E32,
+ double E13, double E23, double E33,
+ double[] H, double[] G, double[] REC);
diff --git a/src/Contracts/Engine/GeopackConstants.cs b/src/Contracts/Engine/GeopackConstants.cs
new file mode 100644
index 0000000..c7cf168
--- /dev/null
+++ b/src/Contracts/Engine/GeopackConstants.cs
@@ -0,0 +1,129 @@
+namespace AuroraScienceHub.Geopack.Contracts.Engine;
+
+///
+/// Constants applied in Geopack calculations
+///
+public static class GeopackConstants
+{
+ ///
+ /// PI value from original Geopack (lower precision for compatibility)
+ ///
+ public const double Pi = 3.141592654D;
+
+ ///
+ /// PI/2 value from original Geopack (lower precision for compatibility)
+ ///
+ public const double HalfPi = 1.570796327D;
+
+ ///
+ /// 2Ï€ value from original Geopack
+ ///
+ public const double TwoPi = 6.283185307D;
+
+ ///
+ /// Radians to degrees conversion factor (180/Ï€)
+ ///
+ public const double Rad = 57.295779513D;
+
+ ///
+ /// Equatorial Earth's radius in kilometers
+ ///
+ public const double REq = 6378.137D;
+
+ ///
+ /// WGS84 ellipsoid flattening coefficient (Beta = (a-b)/a where a is equatorial radius, b is polar radius)
+ ///
+ public const double WGS84Beta = 6.73949674228e-3;
+
+ ///
+ /// Convergence tolerance for iterative coordinate transformations
+ ///
+ public const double CoordinateConvergenceTolerance = 1e-6;
+
+ ///
+ /// WGS84 ellipsoid parameter: 1 + Beta
+ ///
+ public const double WGS84Ex = 1.0 + WGS84Beta;
+
+ ///
+ /// WGS84 ellipsoid parameter: Beta * (2 + Beta)
+ ///
+ public const double WGS84FirstEx = WGS84Beta * (2.0 + WGS84Beta);
+
+ ///
+ /// Earth's axial tilt at J2000 epoch (obliquity of the ecliptic) in degrees
+ ///
+ public const double EarthObliquityJ2000 = 23.45229;
+
+ ///
+ /// Rate of change of Earth's obliquity per Julian century in degrees
+ ///
+ public const double EarthObliquityRatePerCentury = 0.0130125;
+
+ ///
+ /// Number of days in a Julian century
+ ///
+ public const double DaysPerJulianCentury = 36525.0;
+
+ ///
+ /// 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
+ ///
+ public const double SolarWindDynamicPressureFactor = 1.94e-6;
+
+ ///
+ /// Seconds per hour
+ ///
+ public const double SecondsPerHour = 3600.0;
+
+ ///
+ /// Seconds per day
+ ///
+ public const double SecondsPerDay = 86400.0;
+
+ ///
+ /// Degrees in a full circle
+ ///
+ public const double DegreesPerCircle = 360.0;
+
+ ///
+ /// Degrees in a half circle (semicircle)
+ ///
+ public const double DegreesPerSemicircle = 180.0;
+
+ ///
+ /// Average number of days in a year (accounting for leap years)
+ ///
+ public const double DaysPerYear = 365.25;
+
+ ///
+ /// Reciprocal of IGRF interpolation interval (1/5 = 0.2) for optimization
+ ///
+ public const double IgrfInterpolationIntervalReciprocal = 0.2;
+
+ ///
+ /// Total number of IGRF coefficients
+ ///
+ public const int IgrfCoefficientCount = 105;
+
+ ///
+ /// Number of IGRF delta coefficients used in extrapolation
+ ///
+ public const int IgrfDeltaCoefficientCount = 45;
+
+ ///
+ /// IGRF extrapolation base year
+ ///
+ public const int IgrfExtrapolationBaseYear = 2025;
+
+ ///
+ /// Maximum iterations for Newton's method convergence
+ ///
+ public const int NewtonMaxIterations = 1000;
+
+ ///
+ /// Convergence tolerance for Newton's iterative methods
+ ///
+ public const double NewtonConvergenceTolerance = 1e-4;
+}
diff --git a/src/Geopack/Geopack.IgrfCoefficients.cs b/src/Contracts/Engine/IgrfCoefficients.cs
similarity index 92%
rename from src/Geopack/Geopack.IgrfCoefficients.cs
rename to src/Contracts/Engine/IgrfCoefficients.cs
index 9c4380c..aacb55e 100644
--- a/src/Geopack/Geopack.IgrfCoefficients.cs
+++ b/src/Contracts/Engine/IgrfCoefficients.cs
@@ -1,8 +1,11 @@
-namespace AuroraScienceHub.Geopack;
+namespace AuroraScienceHub.Geopack.Contracts.Engine;
-public sealed partial class Geopack
+///
+/// Immutable set of gauss and recursive IGRF coefficients
+///
+public static class IgrfCoefficients
{
- private static readonly double[] G65 = new double[105]
+ public static readonly double[] G65 = new double[105]
{
0.0D,-30334.0D,-2119.0D,-1662.0D,2997.0D,1594.0D,1297.0D,
-2038.0D,1292.0D,856.0D,957.0D,804.0D,479.0D,-390.0D,252.0D,
@@ -14,7 +17,7 @@ public sealed partial class Geopack
0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d
};
- private static readonly double[] H65 = new double[105]
+ public static readonly double[] H65 = new double[105]
{
0.0D,0.0D,5776.0D,0.0D,-2016.0D,114.0D,0.0D,-404.0D,
240.0D,-165.0D,0.0D,148.0D,-269.0D,13.0D,-269.0D,0.0D,19.0D,
@@ -26,7 +29,7 @@ public sealed partial class Geopack
0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d
};
- private static readonly double[] G70 = new double[105]
+ public static readonly double[] G70 = new double[105]
{
0.0D,-30220.0D,-2068.0D,-1781.0D,3000.0D,1611.0D,1287.0D,
-2091.0D,1278.0D,838.0D,952.0D,800.0D,461.0D,-395.0D,234.0D,
@@ -39,7 +42,7 @@ public sealed partial class Geopack
0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d
};
- private static readonly double[] H70 = new double[105]
+ public static readonly double[] H70 = new double[105]
{
0.0D,0.0D,5737.0D,0.0D,-2047.0D,25.0D,0.0D,-366.0D,
251.0D,-196.0D,0.0D,167.0D,-266.0D,26.0D,-279.0D,0.0D,26.0D,
@@ -51,7 +54,7 @@ public sealed partial class Geopack
0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d
};
- private static readonly double[] G75 = new double[105]
+ public static readonly double[] G75 = new double[105]
{
0.0D,-30100.0D,-2013.0D,-1902.0D,3010.0D,1632.0D,1276.0D,
-2144.0D,1260.0D,830.0D,946.0D,791.0D,438.0D,-405.0D,216.0D,
@@ -64,7 +67,7 @@ public sealed partial class Geopack
0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d
};
- private static readonly double[] H75 = new double[105]
+ public static readonly double[] H75 = new double[105]
{
0.0D,0.0D,5675.0D,0.0D,-2067.0D,-68.0D,0.0D,-333.0D,
262.0D,-223.0D,0.0D,191.0D,-265.0D,39.0D,-288.0D,0.0D,31.0D,
@@ -76,7 +79,7 @@ public sealed partial class Geopack
0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d
};
- private static readonly double[] G80 = new double[105]
+ public static readonly double[] G80 = new double[105]
{
0.0D,-29992.0D,-1956.0D,-1997.0D,3027.0D,1663.0D,1281.0D,
-2180.0D,1251.0D,833.0D,938.0D,782.0D,398.0D,-419.0D,199.0D,
@@ -89,7 +92,7 @@ public sealed partial class Geopack
0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d
};
- private static readonly double[] H80 = new double[105]
+ public static readonly double[] H80 = new double[105]
{
0.0D,0.0D,5604.0D,0.0D,-2129.0D,-200.0D,0.0D,-336.0D,
271.0D,-252.0D,0.0D,212.0D,-257.0D,53.0D,-297.0D,0.0D,46.0D,
@@ -102,7 +105,7 @@ public sealed partial class Geopack
0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d
};
- private static readonly double[] G85 = new double[105]
+ public static readonly double[] G85 = new double[105]
{
0.0D,-29873.0D,-1905.0D,-2072.0D,3044.0D,1687.0D,1296.0D,
-2208.0D,1247.0D,829.0D,936.0D,780.0D,361.0D,-424.0D,170.0D,
@@ -115,7 +118,7 @@ public sealed partial class Geopack
0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d
};
- private static readonly double[] H85 = new double[105]
+ public static readonly double[] H85 = new double[105]
{
0.0D,0.0D,5500.0D,0.0D,-2197.0D,-306.0D,0.0D,-310.0D,
284.0D,-297.0D,0.0D,232.0D,-249.0D,69.0D,-297.0D,0.0D,47.0D,
@@ -128,7 +131,7 @@ public sealed partial class Geopack
0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d
};
- private static readonly double[] G90 = new double[105]
+ public static readonly double[] G90 = new double[105]
{
0.0D,-29775.0D,-1848.0D,-2131.0D,3059.0D,1686.0D,1314.0D,
-2239.0D, 1248.0D, 802.0D, 939.0D, 780.0D, 325.0D,-423.0D,
@@ -144,7 +147,7 @@ public sealed partial class Geopack
0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d
};
- private static readonly double[] H90 = new double[105]
+ public static readonly double[] H90 = new double[105]
{
0.0D, 0.0D,5406.0D, 0.0D,-2279.0D,-373.0D, 0.0D,
-284.0D,293.0D,-352.0D, 0.0D, 247.0D,-240.0D, 84.0D,
@@ -160,7 +163,7 @@ public sealed partial class Geopack
0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d
};
- private static readonly double[] G95 = new double[105]
+ public static readonly double[] G95 = new double[105]
{
0.0D,-29692.0D,-1784.0D,-2200.0D,3070.0D,1681.0D,1335.0D,
-2267.0D, 1249.0D, 759.0D, 940.0D, 780.0D, 290.0D,-418.0D,
@@ -178,7 +181,7 @@ public sealed partial class Geopack
0.0d, 0.0d, 0.0d, 0.0d, 0.0d, 0.0d, 0.0d
};
- private static readonly double[] H95 = new double[105]
+ public static readonly double[] H95 = new double[105]
{
0.0D, 0.0D,5306.0D, 0.0D,-2366.0D,-413.0D, 0.0D,
-262.0D,302.0D,-427.0D, 0.0D, 262.0D,-236.0D, 97.0D,
@@ -194,7 +197,7 @@ public sealed partial class Geopack
0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d
};
- private static readonly double[] G00 = new double[105]
+ public static readonly double[] G00 = new double[105]
{
0.0D,-29619.4D,-1728.2D,-2267.7D,3068.4D,1670.9D,
1339.6D, -2288.0D, 1252.1D, 714.5D, 932.3D, 786.8D,
@@ -216,7 +219,7 @@ public sealed partial class Geopack
0.4D, 0.0D, 0.1D
};
- private static readonly double[] H00 = new double[105]
+ public static readonly double[] H00 = new double[105]
{
0.0D, 0.0D,5186.1D, 0.0D,-2481.6D,-458.0D, 0.0D,
-227.6D,293.4D,-491.1D, 0.0D, 272.6D,-231.9D,119.8D,
@@ -235,7 +238,7 @@ public sealed partial class Geopack
0.7D, 0.3D, 0.6D, 0.3D, -0.2D, -0.5D, -0.9D
};
- private static readonly double[] G05 = new double[105]
+ public static readonly double[] G05 = new double[105]
{
0.0D, -29554.6D,-1669.0D,-2337.2D,3047.7D,1657.8D,
1336.3D, -2305.8D, 1246.4D, 672.5D, 920.6D, 798.0D,
@@ -257,7 +260,7 @@ public sealed partial class Geopack
0.4D, -0.1D, -0.2D
};
- private static readonly double[] H05 = new double[105]
+ public static readonly double[] H05 = new double[105]
{
0.0D, 0.0D,5078.0D, 0.0D,-2594.5D,-515.4D, 0.0D,
-198.9D,269.7D,-524.7D, 0.0D, 282.1D,-225.2D,145.2D,
@@ -276,7 +279,7 @@ public sealed partial class Geopack
0.6D, 0.2D, 0.5D, 0.4D, -0.2D, -0.6D, -0.9D
};
- private static readonly double[] G10 = new double[105]
+ public static readonly double[] G10 = new double[105]
{
0.00D,-29496.57D,-1586.42D,-2396.06D,3026.34D,
1668.17D, 1339.85D,-2326.54D, 1232.10D, 633.73D,
@@ -301,7 +304,7 @@ public sealed partial class Geopack
0.38D, 0.02D, 0.42D, -0.26D, -0.26D
};
- private static readonly double[] H10 = new double[105]
+ public static readonly double[] H10 = new double[105]
{
0.00D, 0.00D,4944.26D, 0.00D,-2708.54D,
-575.73D, 0.00D,-160.40D, 251.75D, -537.03D, 0.00D,
@@ -323,7 +326,7 @@ public sealed partial class Geopack
0.44D, -0.25D, -0.53D, -0.79D
};
- private static readonly double[] G15 = new double[105]
+ public static readonly double[] G15 = new double[105]
{
0.00D,-29441.46D,-1501.77D,-2445.88D,3012.20D,
1676.35D,1350.33D,-2352.26D,1225.85D, 581.69D,907.42D,
@@ -345,7 +348,7 @@ public sealed partial class Geopack
0.08D, 0.46D, -0.35D, -0.36D
};
- private static readonly double[] H15 = new double[105]
+ public static readonly double[] H15 = new double[105]
{
0.00D,0.00D,4795.99D,0.00D,-2845.41D,-642.17D,0.0D,
-115.29D,245.04D,-538.7D, 0.0D, 283.54D,-188.43D,180.95D,
@@ -364,7 +367,7 @@ public sealed partial class Geopack
0.42D, -0.04D, 0.48D, 0.48D, -0.30D, -0.43D, -0.71D
};
- private static readonly double[] G20 = new double[105]
+ public static readonly double[] G20 = new double[105]
{
0.0D,-29403.41D,-1451.37D,-2499.78D,2981.96D,
1676.85D,1363.00D,-2380.80D,1236.06D,525.60D,902.82D,
@@ -382,7 +385,7 @@ public sealed partial class Geopack
-0.01D,0.76D,-0.05D,0.37D,0.13D,0.45D,-0.46D,-0.40D
};
- private static readonly double[] H20 = new double[105]
+ public static readonly double[] H20 = new double[105]
{
0.0D,0.0D,4653.35D,0.0D,-2991.72D,-734.62D,0.0D,
-81.96D,241.80D,-542.52D,0.0D,282.10D,-158.5D,199.75D,
@@ -399,7 +402,7 @@ public sealed partial class Geopack
-0.09D, 0.29D,-0.11D,0.47D,0.54D,-0.41D,-0.36D,-0.60D
};
- private static readonly double[] G25 = new double[105]
+ public static readonly double[] G25 = new double[105]
{
0.0D,-29350.0D,-1410.3D,-2556.2D,2950.9D,1648.7D,
1360.9D,-2404.2D,1243.8D,453.4D,894.7D,799.6D,55.8D,
@@ -415,7 +418,7 @@ public sealed partial class Geopack
0.5D,0.1D,0.7D,0.0D,0.3D,0.2D,0.4D,-0.5D,-0.4D
};
- private static readonly double[] H25 = new double[105]
+ public static readonly double[] H25 = new double[105]
{
0.0D,0.0D,4545.5D,0.0D,-3133.6D,-814.2D,0.0D,
-56.9D,237.6D,-549.6D,0.0D,278.6D,-134.0D,212.0D,-375.4D,
@@ -431,7 +434,7 @@ public sealed partial class Geopack
-0.6D,-0.3D,-0.5D
};
- private static readonly double[] DG25 = new double[45]
+ public static readonly double[] DG25 = new double[45]
{
0.0D,12.6D,10.0D,-11.2D,-5.3D,-8.3D,-1.5D,-4.4D,
0.4D,-15.6D,-1.7D,-2.3D,-5.8D,5.4D,-6.8D,0.6D,1.3D,0.0D,
@@ -440,7 +443,7 @@ public sealed partial class Geopack
0.0D,0.4D,-0.1D,0.3D,0.1D,0.0D,0.3D
};
- private static readonly double[] DH25 = new double[45]
+ public static readonly double[] DH25 = new double[45]
{
0.0D,0.0D,-21.5D,0.0D,-27.3D,-11.1D,0.0D,3.8D,
-.2D,-3.9D,0.0D,-1.3D,4.1D,1.6D,-4.1D,0.0D,-0.5D,2.1D,
diff --git a/src/Contracts/ICartesian.cs b/src/Contracts/ICartesian.cs
new file mode 100644
index 0000000..a57bfe9
--- /dev/null
+++ b/src/Contracts/ICartesian.cs
@@ -0,0 +1,31 @@
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+
+namespace AuroraScienceHub.Geopack.Contracts;
+
+///
+/// Basic interface for all cartesian objects
+///
+public interface ICartesian
+ where TSelf : ICartesian
+{
+ /// X-coordinate
+ double X { get; }
+
+ /// Y-coordinate
+ double Y { get; }
+
+ /// Z-coordinate
+ double Z { get; }
+
+ /// Coordinate system
+ CoordinateSystem CoordinateSystem { get; }
+
+ ///
+ /// Factory method to create new instances of ICartesian implementations
+ ///
+ /// X-coordinate in Earth's radii (Re)
+ /// Y-coordinate in Earth's radii (Re)
+ /// Z-coordinate in Earth's radii (Re)
+ /// Coordinate system
+ static abstract TSelf New(double x, double y, double z, CoordinateSystem coordinateSystem);
+}
diff --git a/src/Contracts/Interfaces/IExternalFieldModel.cs b/src/Contracts/IExternalFieldModel.cs
similarity index 69%
rename from src/Contracts/Interfaces/IExternalFieldModel.cs
rename to src/Contracts/IExternalFieldModel.cs
index 3b2e319..90bd458 100644
--- a/src/Contracts/Interfaces/IExternalFieldModel.cs
+++ b/src/Contracts/IExternalFieldModel.cs
@@ -1,6 +1,7 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
-namespace AuroraScienceHub.Geopack.Contracts.Interfaces;
+namespace AuroraScienceHub.Geopack.Contracts;
///
/// External contract for external magnetic field models
@@ -23,8 +24,6 @@ public interface IExternalFieldModel
/// Dummy array, not used in this subroutine, provided for compatibility with the new version of the GEOPACK software.
///
/// Geodipole tilt angle in radians
- /// GSM x-coordinate in Earth radii
- /// GSM y-coordinate in Earth radii
- /// GSM z-coordinate in Earth radii
- CartesianFieldVector Calculate(int iopt, double[] parmod, double psi, double x, double y, double z);
+ /// Location with cartesian GSM coordinates
+ CartesianVector Calculate(int iopt, double[] parmod, double psi, CartesianLocation location);
}
diff --git a/src/Contracts/ISpherical.cs b/src/Contracts/ISpherical.cs
new file mode 100644
index 0000000..2d98280
--- /dev/null
+++ b/src/Contracts/ISpherical.cs
@@ -0,0 +1,31 @@
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+
+namespace AuroraScienceHub.Geopack.Contracts;
+
+///
+/// Basic interface for all spherical objects
+///
+public interface ISpherical
+ where TSelf : ISpherical
+{
+ /// Radial component in Earth's radii
+ double R { get; }
+
+ /// Theta angle in radians
+ double Theta { get; }
+
+ /// Phi angle in radians
+ double Phi { get; }
+
+ /// Coordinate system
+ CoordinateSystem CoordinateSystem { get; }
+
+ ///
+ /// Factory method to create new instances of ICartesian implementations
+ ///
+ /// Radial coordinate in Earth's radii (Re)
+ /// Theta coordinate in radians (Re)
+ /// Phi coordinate in radians (Re)
+ /// Coordinate system
+ static abstract TSelf New(double r, double theta, double phi, CoordinateSystem coordinateSystem);
+}
diff --git a/src/Contracts/IVectorQuantity.cs b/src/Contracts/IVectorQuantity.cs
new file mode 100644
index 0000000..cceba4c
--- /dev/null
+++ b/src/Contracts/IVectorQuantity.cs
@@ -0,0 +1,7 @@
+namespace AuroraScienceHub.Geopack.Contracts;
+
+///
+/// Marker interface for physical quantities
+/// (magnetic field, electric field, velocity, location etc.)
+///
+public interface IVectorQuantity;
diff --git a/src/Contracts/Interfaces/InternalFieldModel.cs b/src/Contracts/Interfaces/InternalFieldModel.cs
deleted file mode 100644
index 4e9f5c6..0000000
--- a/src/Contracts/Interfaces/InternalFieldModel.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack.Contracts.Interfaces;
-
-///
-/// Delegate for internal field models
-///
-public delegate CartesianFieldVector InternalFieldModel(double x, double y, double z);
diff --git a/src/Contracts/InternalFieldModel.cs b/src/Contracts/InternalFieldModel.cs
new file mode 100644
index 0000000..6a23d90
--- /dev/null
+++ b/src/Contracts/InternalFieldModel.cs
@@ -0,0 +1,10 @@
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
+
+namespace AuroraScienceHub.Geopack.Contracts;
+
+///
+/// Delegate for internal field models
+///
+public delegate CartesianVector InternalFieldModel(ComputationContext ctx, CartesianLocation location);
diff --git a/src/Contracts/Models/CartesianFieldVector.cs b/src/Contracts/Models/CartesianFieldVector.cs
deleted file mode 100644
index 66ec948..0000000
--- a/src/Contracts/Models/CartesianFieldVector.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace AuroraScienceHub.Geopack.Contracts.Models;
-
-///
-/// Cartesian field vector
-///
-/// Vector x-component
-/// Vector y-component
-/// Vector z-component
-/// Coordinate system
-public record CartesianFieldVector(double Bx, double By, double Bz, CoordinateSystem? CoordinateSystem)
- : FieldVector(CoordinateSystem);
diff --git a/src/Contracts/Models/CartesianLocation.cs b/src/Contracts/Models/CartesianLocation.cs
deleted file mode 100644
index cafddeb..0000000
--- a/src/Contracts/Models/CartesianLocation.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace AuroraScienceHub.Geopack.Contracts.Models;
-
-///
-/// Position in Cartesian coordinates
-///
-/// X-coordinate
-/// Y-coordinate
-/// Z-coordinate
-/// Coordinate system
-public record CartesianLocation(double X, double Y, double Z, CoordinateSystem? CoordinateSystem = null)
- : Location(CoordinateSystem);
diff --git a/src/Contracts/Models/FieldVector.cs b/src/Contracts/Models/FieldVector.cs
deleted file mode 100644
index 8285611..0000000
--- a/src/Contracts/Models/FieldVector.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace AuroraScienceHub.Geopack.Contracts.Models;
-
-///
-/// Field vector abstraction
-///
-/// Coordinate system
-public abstract record FieldVector(CoordinateSystem? CoordinateSystem);
diff --git a/src/Contracts/Models/GeodeticGeocentricCoordinates.cs b/src/Contracts/Models/GeodeticGeocentricCoordinates.cs
deleted file mode 100644
index 61a2e1c..0000000
--- a/src/Contracts/Models/GeodeticGeocentricCoordinates.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace AuroraScienceHub.Geopack.Contracts.Models;
-
-///
-/// Represents geodetic and geocentric coordinates of a point on Earth.
-/// Geodetic coordinates (H, Xmu) account for the Earth's ellipsoidal shape (WGS84),
-/// where height is measured from the ellipsoid surface, and latitude is the angle between
-/// the ellipsoid normal and the equatorial plane.
-/// Geocentric coordinates (R, Theta) are spherical coordinates relative to the Earth's center,
-/// where distance is measured from the Earth's center, and colatitude is the angle from the rotation axis.
-///
-/// Height above the Earth's ellipsoid in kilometers (geodetic system)
-/// Geodetic latitude in radians (angle between the ellipsoid normal and equatorial plane)
-/// Geocentric distance in kilometers (distance from Earth's center)
-/// Geocentric colatitude in radians (angle from Earth's rotation axis, 0 at the North Pole)
-public record GeodeticGeocentricCoordinates(double H, double Xmu, double R, double Theta);
diff --git a/src/Contracts/Models/Location.cs b/src/Contracts/Models/Location.cs
deleted file mode 100644
index 28e93ed..0000000
--- a/src/Contracts/Models/Location.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace AuroraScienceHub.Geopack.Contracts.Models;
-
-///
-/// Spatial location abstraction
-///
-/// Coordinate system
-public abstract record Location(CoordinateSystem? CoordinateSystem = null);
diff --git a/src/Contracts/Models/Magnetopause.cs b/src/Contracts/Models/Magnetopause.cs
deleted file mode 100644
index d17531b..0000000
--- a/src/Contracts/Models/Magnetopause.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace AuroraScienceHub.Geopack.Contracts.Models;
-
-///
-/// Magnetopause location relative to any point in space
-///
-/// X-coordinate of the boundary point
-/// Y-coordinate of the boundary point
-/// Z-coordinate of the boundary point
-/// Distance (in Re) between observation point and the model magnetopause
-/// Position flag
-/// Coordinate system
-public sealed record Magnetopause(
- double X, double Y, double Z, double Dist,
- MagnetopausePosition Position,
- CoordinateSystem CoordinateSystem);
diff --git a/src/Contracts/Models/SphericalFieldVector.cs b/src/Contracts/Models/SphericalFieldVector.cs
deleted file mode 100644
index 1dabd2d..0000000
--- a/src/Contracts/Models/SphericalFieldVector.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace AuroraScienceHub.Geopack.Contracts.Models;
-
-///
-/// Field vector with spherical components
-///
-/// Vector radial component
-/// Vector theta component
-/// Vector phi component
-/// Coordinate system
-public record SphericalFieldVector(double Br, double Btheta, double Bphi, CoordinateSystem? CoordinateSystem)
- : FieldVector(CoordinateSystem);
diff --git a/src/Contracts/Models/SphericalLocation.cs b/src/Contracts/Models/SphericalLocation.cs
deleted file mode 100644
index 7f7c5ed..0000000
--- a/src/Contracts/Models/SphericalLocation.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace AuroraScienceHub.Geopack.Contracts.Models;
-
-///
-/// Location with spherical coordinates
-///
-/// Radial component
-/// Theta angle
-/// Phi angle
-/// Coordinate system
-public record SphericalLocation(double R, double Theta, double Phi, CoordinateSystem? CoordinateSystem = null)
- : Location(CoordinateSystem);
diff --git a/src/Contracts/Models/Sun.cs b/src/Contracts/Models/Sun.cs
deleted file mode 100644
index 9260664..0000000
--- a/src/Contracts/Models/Sun.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-namespace AuroraScienceHub.Geopack.Contracts.Models;
-
-///
-/// Sun position parameters
-///
-public sealed class Sun
-{
- public Sun(DateTime dateTime,
- double gst = 0,
- double slong = 0,
- double srasn = 0,
- double sdec = 0)
- {
- DateTime = dateTime;
- Gst = gst;
- Slong = slong;
- Srasn = srasn;
- Sdec = sdec;
- }
-
- ///
- /// Input UTC date and time
- ///
- public DateTime DateTime { get; }
-
- ///
- /// Greenwich mean sidereal time
- ///
- public double Gst { get; }
-
- ///
- /// Longitude along ecliptic
- ///
- public double Slong { get; }
-
- ///
- /// Right ascension
- ///
- public double Srasn { get; }
-
- ///
- /// Declination of the Sun (radians)
- ///
- public double Sdec { get; }
-}
diff --git a/src/Contracts/Models/FieldLine.cs b/src/Contracts/PhysicalObjects/FieldLine.cs
similarity index 60%
rename from src/Contracts/Models/FieldLine.cs
rename to src/Contracts/PhysicalObjects/FieldLine.cs
index 3dd40e8..4b8f239 100644
--- a/src/Contracts/Models/FieldLine.cs
+++ b/src/Contracts/PhysicalObjects/FieldLine.cs
@@ -1,16 +1,30 @@
-namespace AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+
+namespace AuroraScienceHub.Geopack.Contracts.PhysicalObjects;
///
/// Magnetic field line
///
public class FieldLine
{
+ ///
+ /// A set of field line points
+ ///
public List Points { get; set; }
+ ///
+ /// Field line end point
+ ///
public CartesianLocation EndPoint { get; set; }
+ ///
+ /// Field line points count
+ ///
public int ActualPointCount { get; set; }
+ ///
+ /// Field-line state message
+ ///
public string TerminationReason { get; set; }
public FieldLine(
diff --git a/src/Contracts/PhysicalObjects/Magnetopause.cs b/src/Contracts/PhysicalObjects/Magnetopause.cs
new file mode 100644
index 0000000..b1421d4
--- /dev/null
+++ b/src/Contracts/PhysicalObjects/Magnetopause.cs
@@ -0,0 +1,11 @@
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+
+namespace AuroraScienceHub.Geopack.Contracts.PhysicalObjects;
+
+///
+/// Magnetopause location relative to any point in space
+///
+///
+/// Distance (in Re) between observation point and the model magnetopause
+/// Position flag
+public readonly record struct Magnetopause(CartesianLocation BoundaryLocation, double Dist, MagnetopausePosition Position);
diff --git a/src/Contracts/Models/MagnetopausePosition.cs b/src/Contracts/PhysicalObjects/MagnetopausePosition.cs
similarity index 85%
rename from src/Contracts/Models/MagnetopausePosition.cs
rename to src/Contracts/PhysicalObjects/MagnetopausePosition.cs
index d731645..d1e1317 100644
--- a/src/Contracts/Models/MagnetopausePosition.cs
+++ b/src/Contracts/PhysicalObjects/MagnetopausePosition.cs
@@ -1,4 +1,4 @@
-namespace AuroraScienceHub.Geopack.Contracts.Models;
+namespace AuroraScienceHub.Geopack.Contracts.PhysicalObjects;
///
/// Object location relative to magnetopause
diff --git a/src/Contracts/PhysicalObjects/Sun.cs b/src/Contracts/PhysicalObjects/Sun.cs
new file mode 100644
index 0000000..69f0dd1
--- /dev/null
+++ b/src/Contracts/PhysicalObjects/Sun.cs
@@ -0,0 +1,11 @@
+namespace AuroraScienceHub.Geopack.Contracts.PhysicalObjects;
+
+///
+/// Sun position parameters
+///
+/// Input UTC date and time
+/// Greenwich mean sidereal time
+/// Longitude along ecliptic
+/// Right ascension
+/// Declination of the Sun (radians)
+public readonly record struct Sun(DateTime DateTime, double Gst = 0, double Slong = 0, double Srasn = 0, double Sdec = 0);
diff --git a/src/Contracts/Models/TraceDirection.cs b/src/Contracts/PhysicalObjects/TraceDirection.cs
similarity index 85%
rename from src/Contracts/Models/TraceDirection.cs
rename to src/Contracts/PhysicalObjects/TraceDirection.cs
index 0c194b2..6bcfeff 100644
--- a/src/Contracts/Models/TraceDirection.cs
+++ b/src/Contracts/PhysicalObjects/TraceDirection.cs
@@ -1,4 +1,4 @@
-namespace AuroraScienceHub.Geopack.Contracts.Models;
+namespace AuroraScienceHub.Geopack.Contracts.PhysicalObjects;
///
/// Specifies the direction of tracing relative to the Earth's main magnetic field.
diff --git a/src/Contracts/PhysicalQuantities/MagneticField.cs b/src/Contracts/PhysicalQuantities/MagneticField.cs
new file mode 100644
index 0000000..ab91f8e
--- /dev/null
+++ b/src/Contracts/PhysicalQuantities/MagneticField.cs
@@ -0,0 +1,6 @@
+namespace AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
+
+///
+/// Magnetic field vector
+///
+public readonly record struct MagneticField : IVectorQuantity;
diff --git a/src/Contracts/PhysicalQuantities/Velocity.cs b/src/Contracts/PhysicalQuantities/Velocity.cs
new file mode 100644
index 0000000..be172f4
--- /dev/null
+++ b/src/Contracts/PhysicalQuantities/Velocity.cs
@@ -0,0 +1,6 @@
+namespace AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
+
+///
+/// Velocity vector
+///
+public readonly record struct Velocity : IVectorQuantity;
diff --git a/src/Contracts/Spherical/SphericalLocation.cs b/src/Contracts/Spherical/SphericalLocation.cs
new file mode 100644
index 0000000..367a155
--- /dev/null
+++ b/src/Contracts/Spherical/SphericalLocation.cs
@@ -0,0 +1,49 @@
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+
+namespace AuroraScienceHub.Geopack.Contracts.Spherical;
+
+///
+/// Location with spherical coordinates (geocentric)
+///
+public readonly record struct SphericalLocation : ISpherical
+{
+ public double R { get; }
+
+ public double Theta { get; }
+
+ public double Phi { get; }
+
+ public CoordinateSystem CoordinateSystem { get; }
+
+ private SphericalLocation(double r, double theta, double phi, CoordinateSystem coordinateSystem)
+ {
+ R = r;
+ Theta = theta;
+ Phi = phi;
+ CoordinateSystem = coordinateSystem;
+ }
+
+ public static SphericalLocation New(double r, double theta, double phi, CoordinateSystem coordinateSystem)
+ => new(r, theta, phi, coordinateSystem);
+
+ ///
+ /// Convert spherical coordinates to cartesian ones
+ ///
+ ///
+ /// Original Geopack-2008 method: SPHCAR_08
+ ///
+ public CartesianLocation ToCartesian()
+ {
+ 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);
+ }
+}
diff --git a/src/Contracts/Spherical/SphericalVector.cs b/src/Contracts/Spherical/SphericalVector.cs
new file mode 100644
index 0000000..ac5f1ad
--- /dev/null
+++ b/src/Contracts/Spherical/SphericalVector.cs
@@ -0,0 +1,53 @@
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+
+namespace AuroraScienceHub.Geopack.Contracts.Spherical;
+
+///
+/// Field vector with spherical components
+///
+public readonly record struct SphericalVector
+ : ISpherical>
+ where TVector : IVectorQuantity
+{
+ public double R { get; }
+
+ public double Theta { get; }
+
+ public double Phi { get; }
+
+ public CoordinateSystem CoordinateSystem { get; }
+
+ public static SphericalVector New(double r, double theta, double phi, CoordinateSystem coordinateSystem)
+ => new(r, theta, phi, coordinateSystem);
+
+ private SphericalVector(double r, double theta, double phi, CoordinateSystem coordinateSystem)
+ {
+ R = r;
+ Theta = theta;
+ Phi = phi;
+ CoordinateSystem = coordinateSystem;
+ }
+
+ ///
+ /// Calculates Cartesian vector components from local spherical ones.
+ ///
+ ///
+ /// Original Geopack-2008 method: BSPCAR_08
+ ///
+ /// Point spherical coordinates
+ public CartesianVector ToCartesianVector(SphericalLocation location)
+ {
+ double s = Math.Sin(location.Theta);
+ double c = Math.Cos(location.Theta);
+ double sf = Math.Sin(location.Phi);
+ double cf = Math.Cos(location.Phi);
+
+ 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.New(bx, by, bz, CoordinateSystem);
+ }
+}
diff --git a/src/ExternalFieldModels/ExternalFieldModels.csproj b/src/ExternalFieldModels/ExternalFieldModels.csproj
index b639e71..1802d0c 100644
--- a/src/ExternalFieldModels/ExternalFieldModels.csproj
+++ b/src/ExternalFieldModels/ExternalFieldModels.csproj
@@ -20,6 +20,12 @@
+
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/ExternalFieldModels/Interfaces/IT89.cs b/src/ExternalFieldModels/Interfaces/IT89.cs
deleted file mode 100644
index cbdcd8b..0000000
--- a/src/ExternalFieldModels/Interfaces/IT89.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Interfaces;
-
-namespace AuroraScienceHub.Geopack.ExternalFieldModels.Interfaces;
-
-public interface IT89 : IExternalFieldModel;
diff --git a/src/ExternalFieldModels/ServiceCollectionExtensions.cs b/src/ExternalFieldModels/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..4e6ccb4
--- /dev/null
+++ b/src/ExternalFieldModels/ServiceCollectionExtensions.cs
@@ -0,0 +1,21 @@
+using AuroraScienceHub.Geopack.ExternalFieldModels.T89;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace AuroraScienceHub.Geopack.ExternalFieldModels;
+
+///
+/// Extensions for .
+///
+public static class ServiceCollectionExtensions
+{
+ ///
+ /// Registers external field model services with the dependency injection container.
+ ///
+ /// Service collection
+ public static IServiceCollection AddExternalFieldModels(this IServiceCollection services)
+ {
+ services.AddSingleton();
+
+ return services;
+ }
+}
diff --git a/src/ExternalFieldModels/T89/IT89.cs b/src/ExternalFieldModels/T89/IT89.cs
new file mode 100644
index 0000000..eed0628
--- /dev/null
+++ b/src/ExternalFieldModels/T89/IT89.cs
@@ -0,0 +1,5 @@
+using AuroraScienceHub.Geopack.Contracts;
+
+namespace AuroraScienceHub.Geopack.ExternalFieldModels.T89;
+
+public interface IT89 : IExternalFieldModel;
diff --git a/src/ExternalFieldModels/T89/T89.Coefficients.cs b/src/ExternalFieldModels/T89/T89.Coefficients.cs
index fde75a4..b672b0c 100644
--- a/src/ExternalFieldModels/T89/T89.Coefficients.cs
+++ b/src/ExternalFieldModels/T89/T89.Coefficients.cs
@@ -1,6 +1,6 @@
namespace AuroraScienceHub.Geopack.ExternalFieldModels.T89;
-public sealed partial class T89
+internal sealed partial class T89
{
private static readonly double[,] PARAM =
{
diff --git a/src/ExternalFieldModels/T89/T89.cs b/src/ExternalFieldModels/T89/T89.cs
index 0e265b1..348712a 100644
--- a/src/ExternalFieldModels/T89/T89.cs
+++ b/src/ExternalFieldModels/T89/T89.cs
@@ -1,9 +1,10 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-using AuroraScienceHub.Geopack.ExternalFieldModels.Interfaces;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
namespace AuroraScienceHub.Geopack.ExternalFieldModels.T89;
-public sealed partial class T89 : IT89
+internal sealed partial class T89 : IT89
{
private static double[] A = new double[30];
private static int IOP = 10;
@@ -14,8 +15,13 @@ public sealed partial class T89 : IT89
private static double AK13, AK14, AK15, AK16, AK17, SXA, SYA, SZA, AK610, AK711, AK812;
private static double AK913, RDXL, HRDXL, A6H, A9T, YNP, YND;
- public CartesianFieldVector Calculate(int IOPT, double[] PARMOD, double PS, double X, double Y, double Z)
+ public CartesianVector Calculate(int IOPT, double[] PARMOD, double PS, CartesianLocation location)
{
+ if (location.CoordinateSystem is not (CoordinateSystem.GSM or CoordinateSystem.GSW))
+ {
+ throw new InvalidOperationException("Location must be in GSM or GSW coordinate system.");
+ }
+
double A02 = 25D, XLW2 = 170D, YN = 30D, RPI = 0.31830989D, RT = 30D;
double XD = 0D, XLD2 = 40D;
double SXC = 4D, XLWC2 = 50D;
@@ -95,17 +101,16 @@ public CartesianFieldVector Calculate(int IOPT, double[] PARMOD, double PS, doub
YND = 2.0D * YN;
}
- double SPS = Math.Sin(PS);
- double CPS = Math.Cos(PS);
+ (double SPS, double CPS) = Math.SinCos(PS);
- double X2 = X * X;
- double Y2 = Y * Y;
- double Z2 = Z * Z;
+ double X2 = location.X * location.X;
+ double Y2 = location.Y * location.Y;
+ double Z2 = location.Z * location.Z;
double TPS = SPS / CPS;
double HTP = TPS * 0.5D;
double GSP = G * SPS;
- double XSM = X * CPS - Z * SPS;
- double ZSM = X * SPS + Z * CPS;
+ double XSM = location.X * CPS - location.Z * SPS;
+ double ZSM = location.X * SPS + location.Z * CPS;
double XRC = XSM + RC;
double XRC16 = XRC * XRC + 16.0D;
@@ -117,7 +122,7 @@ public CartesianFieldVector Calculate(int IOPT, double[] PARMOD, double PS, doub
double ZS1 = HTP * (XRC - SXRC);
double DZSX = -ZS1 / SXRC;
double ZS = ZS1 - GSY4 * Y4;
- double D2ZSGY = -SY4 / Y410 * 40000.0D * Y2 * Y;
+ double D2ZSGY = -SY4 / Y410 * 40000.0D * Y2 * location.Y;
double DZSY = G * D2ZSGY;
double XSM2 = XSM * XSM;
@@ -136,10 +141,10 @@ public CartesianFieldVector Calculate(int IOPT, double[] PARMOD, double PS, doub
double FC = FK * FK * DSFC;
double FACXY = 3.0D * ADRT * FC * RTR;
double XZR = XSM * ZR;
- double YZR = Y * ZR;
+ double YZR = location.Y * ZR;
double DBXDP = FACXY * XZR;
double DER25 = FACXY * YZR;
- double XZYZ = XSM * DZSX + Y * DZSY;
+ double XZYZ = XSM * DZSX + location.Y * DZSY;
double FAQ = ZR * XZYZ - DDR * DD * DFA0 * XSM;
double DBZDP = FC * (2.0D * ADRT2 - RO2) + FACXY * FAQ;
double DER15 = DBXDP * CPS + DBZDP * SPS;
@@ -185,7 +190,7 @@ public CartesianFieldVector Calculate(int IOPT, double[] PARMOD, double PS, doub
double FYDY = FYPR * FY;
double DWX = DVX * FY + FYDY * Q * OMSV;
double YDWY = -V * YFY1 * FY;
- double DDY = DBLDEL * Y;
+ double DDY = DBLDEL * location.Y;
double ATT = AT + T;
double S1 = Math.Sqrt(ATT * ATT + RO2);
double F5 = 1.0D / S1;
@@ -193,7 +198,7 @@ public CartesianFieldVector Calculate(int IOPT, double[] PARMOD, double PS, doub
double F1 = F5 * F7;
double F3 = F5 * F5 * F5;
double F9 = ATT * F3;
- double FS = ZR * XZYZ - D * Y * DDY + ADSL;
+ double FS = ZR * XZYZ - D * location.Y * DDY + ADSL;
double XDWX = XSM * DWX + YDWY;
double RTT = 1.0D / T;
double WT = W * RTT;
@@ -220,31 +225,31 @@ public CartesianFieldVector Calculate(int IOPT, double[] PARMOD, double PS, doub
double DER316 = DER31 * TLT2;
double DER317 = DER32 * TLT2;
- double ZPL = Z + RT;
- double ZMN = Z - RT;
+ double ZPL = location.Z + RT;
+ double ZMN = location.Z - RT;
double ROGSM2 = X2 + Y2;
double SPL = Math.Sqrt(ZPL * ZPL + ROGSM2);
double SMN = Math.Sqrt(ZMN * ZMN + ROGSM2);
- double XSXC = X - SXC;
+ double XSXC = location.X - SXC;
double RQC2 = 1.0D / (XSXC * XSXC + XLWC2);
double RQC = Math.Sqrt(RQC2);
double FYC = 1.0D / (1.0D + Y2 * RDYC2);
double WC = 0.5D * (1.0D - XSXC * RQC) * FYC;
double DWCX = HLWC2M * RQC2 * RQC * FYC;
- double DWCY = DRDYC2 * WC * FYC * Y;
+ double DWCY = DRDYC2 * WC * FYC * location.Y;
double SZRP = 1.0D / (SPL + ZPL);
double SZRM = 1.0D / (SMN - ZMN);
- double XYWC = X * DWCX + Y * DWCY;
+ double XYWC = location.X * DWCX + location.Y * DWCY;
double WCSP = WC / SPL;
double WCSM = WC / SMN;
double FXYP = WCSP * SZRP;
double FXYM = WCSM * SZRM;
- double FXPL = X * FXYP;
- double FXMN = -X * FXYM;
- double FYPL = Y * FXYP;
- double FYMN = -Y * FXYM;
- double FZPL = WCSP + XYWC * SZRP;
- double FZMN = WCSM + XYWC * SZRM;
+ double FXPL = location.X * FXYP;
+ double FXMN = -location.X * FXYM;
+ double FYPL = location.Y * FXYP;
+ double FYMN = -location.Y * FXYM;
+ 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;
@@ -252,18 +257,18 @@ public CartesianFieldVector Calculate(int IOPT, double[] PARMOD, double PS, doub
double DER33 = FZPL + FZMN;
double DER34 = (FZPL - FZMN) * SPS;
- double EX = Math.Exp(X / DX);
+ double EX = Math.Exp(location.X / DX);
double EC = EX * CPS;
double ES = EX * SPS;
- double ECZ = EC * Z;
- double ESZ = ES * Z;
+ double ECZ = EC * location.Z;
+ double ESZ = ES * location.Z;
double ESZY2 = ESZ * Y2;
double ESZZ2 = ESZ * Z2;
- double ECZ2 = ECZ * Z;
- double ESY = ES * Y;
+ double ECZ2 = ECZ * location.Z;
+ double ESY = ES * location.Y;
- double SX1 = AK6 * ECZ + AK7 * ES + AK8 * ESY * Y + AK9 * ESZ * Z;
- double SY1 = AK10 * ECZ * Y + AK11 * ESY + AK12 * ESY * Y2 + AK13 * ESY * Z2;
+ double SX1 = AK6 * ECZ + AK7 * ES + AK8 * ESY * location.Y + AK9 * ESZ * location.Z;
+ double SY1 = AK10 * ECZ * location.Y + AK11 * ESY + AK12 * ESY * Y2 + AK13 * ESY * Z2;
double SZ1 = AK14 * EC + AK15 * EC * Y2 + AK610 * ECZ2 + AK711 * ESZ + AK812 * ESZY2 + AK913 * ESZZ2;
double BXCL = AK3 * DER13 + AK4 * DER14;
double BYCL = AK3 * DER23 + AK4 * DER24;
@@ -275,6 +280,6 @@ public CartesianFieldVector Calculate(int IOPT, double[] PARMOD, double PS, doub
double BY = BYT + AK5 * DER25 + SY1 + SYA;
double BZ = BZT + AK5 * DER35 + SZ1 + SZA;
- return new CartesianFieldVector(BX, BY, BZ, CoordinateSystem.GSM);
+ return CartesianVector.New(BX, BY, BZ, CoordinateSystem.GSM);
}
}
diff --git a/src/Geopack/Common1.cs b/src/Geopack/Common1.cs
deleted file mode 100644
index 442a13b..0000000
--- a/src/Geopack/Common1.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace AuroraScienceHub.Geopack;
-
-///
-/// Elements of rotation matrices for transformation of vectors between
-/// several coordinate systems, most frequently used in space physics
-///
-public sealed class Common1
-{
- public double ST0, CT0, SL0, CL0, CTCL, STCL, CTSL, STSL, SFI, CFI;
- public double SPS, CPS, DS3, CGST, SGST, PSI;
- public double A11, A21, A31, A12, A22, A32, A13, A23, A33;
- public double E11, E21, E31, E12, E22, E32, E13, E23, E33;
-}
diff --git a/src/Geopack/Common2.cs b/src/Geopack/Common2.cs
deleted file mode 100644
index 035da41..0000000
--- a/src/Geopack/Common2.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace AuroraScienceHub.Geopack;
-
-///
-/// Coefficients used in the calculation of the main geomagnetic field (IGRF model)
-///
-public sealed class Common2
-{
- public double[] G = new double[105];
- public double[] H = new double[105];
- public double[] REC = new double[105];
-}
diff --git a/src/Geopack/Geopack.BCarSph_08.cs b/src/Geopack/Geopack.BCarSph_08.cs
deleted file mode 100644
index e3393d0..0000000
--- a/src/Geopack/Geopack.BCarSph_08.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack;
-
-public sealed partial class Geopack
-{
- public SphericalFieldVector BCarSph_08(
- double x, double y, double z,
- double bx, double by, double bz)
- {
- double rho2 = Math.Pow(x, 2.0D) + Math.Pow(y, 2.0D);
- double rho = Math.Sqrt(rho2);
-
- double r = Math.Sqrt(rho2 + Math.Pow(z, 2.0D));
-
- double cphi;
- double sphi;
-
- if (rho > 0.0D)
- {
- cphi = x / rho;
- sphi = y / rho;
- }
- else
- {
- cphi = 1.0D;
- sphi = 0.0D;
- }
-
- double ct = z / r;
- double st = rho / r;
-
- double br = (x * bx + y * by + z * bz) / r;
- double btheta = (bx * cphi + by * sphi) * ct - bz * st;
- double bphi = by * cphi - bx * sphi;
-
- return new SphericalFieldVector(br, btheta, bphi, null);
- }
-}
diff --git a/src/Geopack/Geopack.BSphCar_08.cs b/src/Geopack/Geopack.BSphCar_08.cs
deleted file mode 100644
index a0ee5dc..0000000
--- a/src/Geopack/Geopack.BSphCar_08.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack;
-
-public sealed partial class Geopack
-{
- public CartesianFieldVector BSphCar_08(
- double theta, double phi,
- double br, double btheta, double bphi)
- {
- double s = Math.Sin(theta);
- double c = Math.Cos(theta);
- double sf = Math.Sin(phi);
- double cf = Math.Cos(phi);
- double be = br * s + btheta * c;
- double bx = be * cf - bphi * sf;
- double by = be * sf + bphi * cf;
- double bz = br * c - btheta * s;
-
- return new CartesianFieldVector(bx, by, bz, null);
- }
-}
diff --git a/src/Geopack/Geopack.Dip.cs b/src/Geopack/Geopack.Dip.cs
new file mode 100644
index 0000000..3bb4e72
--- /dev/null
+++ b/src/Geopack/Geopack.Dip.cs
@@ -0,0 +1,40 @@
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
+
+namespace AuroraScienceHub.Geopack;
+
+internal sealed partial class Geopack
+{
+ public CartesianVector Dip(ComputationContext context, CartesianLocation location)
+ {
+ if (location.CoordinateSystem is not CoordinateSystem.GSW)
+ {
+ throw new InvalidOperationException("Location must be in GSW coordinate system.");
+ }
+
+ 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 = location.X * location.X;
+ double u = location.Z * location.Z;
+ double v = 3.0D * location.Z * location.X;
+ double t = location.Y * location.Y;
+
+ if (p + t + u is 0D)
+ {
+ throw new InvalidOperationException("Location radius should not be zero.");
+ }
+
+ double q = dipmom / Math.Pow(p + t + u, 2.5);
+
+ double bxgsw = q * ((t + u - 2.0D * p) * context.SPS - v * context.CPS);
+ double bygsw = -3.0D * location.Y * q * (location.X * context.SPS + location.Z * context.CPS);
+ double bzgsw = q * ((p + t - 2.0D * u) * context.CPS - v * context.SPS);
+
+ return CartesianVector.New(bxgsw, bygsw, bzgsw, CoordinateSystem.GSW);
+ }
+}
diff --git a/src/Geopack/Geopack.Dip_08.cs b/src/Geopack/Geopack.Dip_08.cs
deleted file mode 100644
index 4c77885..0000000
--- a/src/Geopack/Geopack.Dip_08.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack;
-
-public sealed partial class Geopack
-{
- public CartesianFieldVector Dip_08(double xgsw, double ygsw, double zgsw)
- {
- double dipmom = Math.Sqrt(Math.Pow(Common2.G[1], 2) + Math.Pow(Common2.G[2], 2) + Math.Pow(Common2.H[2], 2));
-
- double p = Math.Pow(xgsw, 2);
- double u = Math.Pow(zgsw, 2);
- double v = 3.0D * zgsw * xgsw;
- double t = Math.Pow(ygsw, 2);
-
- double q = dipmom / Math.Pow(p + t + u, 2.5);
-
- double bxgsw = q * ((t + u - 2.0D * p) * Common1.SPS - v * Common1.CPS);
- double bygsw = -3.0D * ygsw * q * (xgsw * Common1.SPS + zgsw * Common1.CPS);
- double bzgsw = q * ((p + t - 2.0D * u) * Common1.CPS - v * Common1.SPS);
-
- return new CartesianFieldVector(bxgsw, bygsw, bzgsw, CoordinateSystem.GSW);
- }
-}
diff --git a/src/Geopack/Geopack.GeiGeo.cs b/src/Geopack/Geopack.GeiGeo.cs
new file mode 100644
index 0000000..efaa652
--- /dev/null
+++ b/src/Geopack/Geopack.GeiGeo.cs
@@ -0,0 +1,36 @@
+using AuroraScienceHub.Geopack.Contracts;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+
+namespace AuroraScienceHub.Geopack;
+
+internal sealed partial class Geopack
+{
+ public T GeiToGeo(ComputationContext context, T components) where T : ICartesian
+ => GeiGeoInternal(context, components, OperationType.Direct);
+
+ public T GeoToGei(ComputationContext context, T components) where T : ICartesian
+ => GeiGeoInternal(context, components, OperationType.Reversed);
+
+ private static T GeiGeoInternal(ComputationContext context, T components, OperationType operation)
+ where T : ICartesian
+ => operation switch
+ {
+ OperationType.Direct => components.CoordinateSystem is CoordinateSystem.GEI
+ ? T.New(
+ 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(
+ 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."),
+ _ => throw new NotSupportedException($"Specify correct OperationType: {operation}. Available types are Direct and Reversed.")
+ };
+}
diff --git a/src/Geopack/Geopack.GeiGeo_08.cs b/src/Geopack/Geopack.GeiGeo_08.cs
deleted file mode 100644
index 203a5d8..0000000
--- a/src/Geopack/Geopack.GeiGeo_08.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack;
-
-public sealed partial class Geopack
-{
- public CartesianLocation GeiGeo_08(double xGei, double yGei, double zGei)
- {
- double xGeo = xGei * Common1.CGST + yGei * Common1.SGST;
- double yGeo = yGei * Common1.CGST - xGei * Common1.SGST;
- double zGeo = zGei;
-
- return new CartesianLocation(xGeo, yGeo, zGeo, CoordinateSystem.GEO);
- }
-
- public CartesianLocation GeoGei_08(double xGeo, double yGeo, double zGeo)
- {
- double xGei = xGeo * Common1.CGST - yGeo * Common1.SGST;
- double yGei = yGeo * Common1.CGST + xGeo * Common1.SGST;
- double zGei = zGeo;
-
- return new CartesianLocation(xGei, yGei, zGei, CoordinateSystem.GEI);
- }
-}
diff --git a/src/Geopack/Geopack.GeoGsw.cs b/src/Geopack/Geopack.GeoGsw.cs
new file mode 100644
index 0000000..4c3f23f
--- /dev/null
+++ b/src/Geopack/Geopack.GeoGsw.cs
@@ -0,0 +1,36 @@
+using AuroraScienceHub.Geopack.Contracts;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+
+namespace AuroraScienceHub.Geopack;
+
+internal sealed partial class Geopack
+{
+ public T GeoToGsw(ComputationContext context, T components) where T : ICartesian
+ => GeoGswInternal(context, components, OperationType.Direct);
+
+ public T GswToGeo(ComputationContext context, T components) where T : ICartesian
+ => GeoGswInternal(context, components, OperationType.Reversed);
+
+ private static T GeoGswInternal(ComputationContext context, T components, OperationType operation)
+ where T : ICartesian
+ => operation switch
+ {
+ OperationType.Direct => components.CoordinateSystem is CoordinateSystem.GEO
+ ? T.New(
+ 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(
+ 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.")
+ };
+}
diff --git a/src/Geopack/Geopack.GeoGsw_08.cs b/src/Geopack/Geopack.GeoGsw_08.cs
deleted file mode 100644
index 8706803..0000000
--- a/src/Geopack/Geopack.GeoGsw_08.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack;
-
-public sealed partial class Geopack
-{
- public CartesianLocation GeoGsw_08(double xGeo, double yGeo, double zGeo)
- {
- double xGsw = Common1.A11 * xGeo + Common1.A12 * yGeo + Common1.A13 * zGeo;
- double yGsw = Common1.A21 * xGeo + Common1.A22 * yGeo + Common1.A23 * zGeo;
- double zGsw = Common1.A31 * xGeo + Common1.A32 * yGeo + Common1.A33 * zGeo;
- return new CartesianLocation(xGsw, yGsw, zGsw, CoordinateSystem.GSW);
- }
-
- public CartesianLocation GswGeo_08(double xGsw, double yGsw, double zGsw)
- {
- double xGeo = Common1.A11 * xGsw + Common1.A21 * yGsw + Common1.A31 * zGsw;
- double yGeo = Common1.A12 * xGsw + Common1.A22 * yGsw + Common1.A32 * zGsw;
- double zGeo = Common1.A13 * xGsw + Common1.A23 * yGsw + Common1.A33 * zGsw;
- return new CartesianLocation(xGeo, yGeo, zGeo, CoordinateSystem.GEO);
- }
-}
diff --git a/src/Geopack/Geopack.GeoMag.cs b/src/Geopack/Geopack.GeoMag.cs
new file mode 100644
index 0000000..76c8b1c
--- /dev/null
+++ b/src/Geopack/Geopack.GeoMag.cs
@@ -0,0 +1,36 @@
+using AuroraScienceHub.Geopack.Contracts;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+
+namespace AuroraScienceHub.Geopack;
+
+internal sealed partial class Geopack
+{
+ public T GeoToMag(ComputationContext context, T components) where T : ICartesian
+ => GeoMagInternal(context, components, OperationType.Direct);
+
+ public T MagToGeo(ComputationContext context, T components) where T : ICartesian
+ => GeoMagInternal(context, components, OperationType.Reversed);
+
+ private static T GeoMagInternal(ComputationContext context, T components, OperationType operation)
+ where T : ICartesian
+ => operation switch
+ {
+ OperationType.Direct => components.CoordinateSystem is CoordinateSystem.GEO
+ ? T.New(
+ Math.FusedMultiplyAdd(components.X, context.CTCL, Math.FusedMultiplyAdd(components.Y, context.CTSL, -components.Z * context.ST0)),
+ Math.FusedMultiplyAdd(components.Y, context.CL0, -components.X * context.SL0),
+ Math.FusedMultiplyAdd(components.X, context.STCL, Math.FusedMultiplyAdd(components.Y, context.STSL, components.Z * context.CT0)),
+ CoordinateSystem.MAG)
+ : throw new InvalidOperationException("Input coordinates must be in GEO system."),
+
+ OperationType.Reversed => components.CoordinateSystem is CoordinateSystem.MAG
+ ? T.New(
+ Math.FusedMultiplyAdd(components.X, context.CTCL, Math.FusedMultiplyAdd(-components.Y, context.SL0, components.Z * context.STCL)),
+ Math.FusedMultiplyAdd(components.X, context.CTSL, Math.FusedMultiplyAdd(components.Y, context.CL0, components.Z * context.STSL)),
+ Math.FusedMultiplyAdd(components.Z, context.CT0, -components.X * context.ST0),
+ CoordinateSystem.GEO)
+ : throw new InvalidOperationException("Input coordinates must be in MAG system."),
+ _ => throw new NotSupportedException($"Specify correct OperationType: {operation}. Available types are Direct and Reversed.")
+ };
+}
diff --git a/src/Geopack/Geopack.GeoMag_08.cs b/src/Geopack/Geopack.GeoMag_08.cs
deleted file mode 100644
index ebf78a6..0000000
--- a/src/Geopack/Geopack.GeoMag_08.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack;
-
-public sealed partial class Geopack
-{
- public CartesianLocation GeoMag_08(double xgeo, double ygeo, double zgeo)
- {
- double xmag = xgeo * Common1.CTCL + ygeo * Common1.CTSL - zgeo * Common1.ST0;
- double ymag = ygeo * Common1.CL0 - xgeo * Common1.SL0;
- double zmag = xgeo * Common1.STCL + ygeo * Common1.STSL + zgeo * Common1.CT0;
-
- return new CartesianLocation(xmag, ymag, zmag, CoordinateSystem.MAG);
- }
-
- public CartesianLocation MagGeo_08(double xmag, double ymag, double zmag)
- {
- double xgeo = xmag * Common1.CTCL - ymag * Common1.SL0 + zmag * Common1.STCL;
- double ygeo = xmag * Common1.CTSL + ymag * Common1.CL0 + zmag * Common1.STSL;
- double zgeo = zmag * Common1.CT0 - xmag * Common1.ST0;
-
- return new CartesianLocation(xgeo, ygeo, zgeo, CoordinateSystem.GEO);
- }
-}
diff --git a/src/Geopack/Geopack.GeodGeo_08.cs b/src/Geopack/Geopack.GeodGeo_08.cs
deleted file mode 100644
index 3d31989..0000000
--- a/src/Geopack/Geopack.GeodGeo_08.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack;
-
-public sealed partial class Geopack
-{
- public GeodeticGeocentricCoordinates GeodGeo_08(double h, double xmu)
- {
- const double r_eq = 6378.137D;
- const double beta = 6.73949674228e-3;
-
- double cosxmu = Math.Cos(xmu);
- double sinxmu = Math.Sin(xmu);
- double den = Math.Sqrt(Math.Pow(cosxmu, 2) + Math.Pow(sinxmu / (1.0D + beta), 2));
- 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 x = rs * coslam + h * cosxmu;
- double z = rs * sinlam + h * sinxmu;
- double r = Math.Sqrt(Math.Pow(x, 2) + Math.Pow(z, 2));
- double theta = Math.Acos(z / r);
-
- return new GeodeticGeocentricCoordinates(h, xmu, r, theta);
- }
-
- public GeodeticGeocentricCoordinates GeoGeod_08(double r, double theta)
- {
- 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 phi1 = phi;
-
- double xmus, rs, cosfims, h, z, x, rr, 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));
- 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(Math.Pow(rs * cosfims, 2) + Math.Pow(r, 2) - Math.Pow(rs, 2)) - 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));
- dphi = Math.Asin(z / rr) - phi;
- phi1 -= dphi;
- n++;
- }
- while (Math.Abs(dphi) > tol && n < 100);
-
- return new GeodeticGeocentricCoordinates(h, xmus, r, theta);
- }
-}
diff --git a/src/Geopack/Geopack.GswGse.cs b/src/Geopack/Geopack.GswGse.cs
new file mode 100644
index 0000000..4ca4b5e
--- /dev/null
+++ b/src/Geopack/Geopack.GswGse.cs
@@ -0,0 +1,36 @@
+using AuroraScienceHub.Geopack.Contracts;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+
+namespace AuroraScienceHub.Geopack;
+
+internal sealed partial class Geopack
+{
+ public T GswToGse(ComputationContext context, T components) where T : ICartesian
+ => GswGseInternal(context, components, OperationType.Direct);
+
+ public T GseToGsw(ComputationContext context, T components) where T : ICartesian
+ => GswGseInternal(context, components, OperationType.Reversed);
+
+ private static T GswGseInternal(ComputationContext context, T components, OperationType operation)
+ where T : ICartesian
+ => operation switch
+ {
+ OperationType.Direct => components.CoordinateSystem is CoordinateSystem.GSW
+ ? T.New(
+ Math.FusedMultiplyAdd(context.E11, components.X, Math.FusedMultiplyAdd(context.E12, components.Y, context.E13 * components.Z)),
+ Math.FusedMultiplyAdd(context.E21, components.X, Math.FusedMultiplyAdd(context.E22, components.Y, context.E23 * components.Z)),
+ Math.FusedMultiplyAdd(context.E31, components.X, Math.FusedMultiplyAdd(context.E32, components.Y, context.E33 * components.Z)),
+ CoordinateSystem.GSE)
+ : throw new InvalidOperationException("Input coordinates must be in GSW system."),
+
+ OperationType.Reversed => components.CoordinateSystem is CoordinateSystem.GSE
+ ? T.New(
+ Math.FusedMultiplyAdd(context.E11, components.X, Math.FusedMultiplyAdd(context.E21, components.Y, context.E31 * components.Z)),
+ Math.FusedMultiplyAdd(context.E12, components.X, Math.FusedMultiplyAdd(context.E22, components.Y, context.E32 * components.Z)),
+ Math.FusedMultiplyAdd(context.E13, components.X, Math.FusedMultiplyAdd(context.E23, components.Y, context.E33 * components.Z)),
+ CoordinateSystem.GSW)
+ : throw new InvalidOperationException("Input coordinates must be in GSE system."),
+ _ => throw new NotSupportedException($"Specify correct OperationType: {operation}. Available types are Direct and Reversed.")
+ };
+}
diff --git a/src/Geopack/Geopack.GswGse_08.cs b/src/Geopack/Geopack.GswGse_08.cs
deleted file mode 100644
index 02ce8b8..0000000
--- a/src/Geopack/Geopack.GswGse_08.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack;
-
-public sealed partial class Geopack
-{
- public CartesianLocation GswGse_08(double xgsw, double ygsw, double zgsw)
- {
- double xgse = xgsw * Common1.E11 + ygsw * Common1.E12 + zgsw * Common1.E13;
- double ygse = xgsw * Common1.E21 + ygsw * Common1.E22 + zgsw * Common1.E23;
- double zgse = xgsw * Common1.E31 + ygsw * Common1.E32 + zgsw * Common1.E33;
-
- return new CartesianLocation(xgse, ygse, zgse, CoordinateSystem.GSE);
- }
-
- public CartesianLocation GseGsw_08(double xgse, double ygse, double zgse)
- {
- double xgsw = xgse * Common1.E11 + ygse * Common1.E21 + zgse * Common1.E31;
- double ygsw = xgse * Common1.E12 + ygse * Common1.E22 + zgse * Common1.E32;
- double zgsw = xgse * Common1.E13 + ygse * Common1.E23 + zgse * Common1.E33;
-
- return new CartesianLocation(xgsw, ygsw, zgsw, CoordinateSystem.GSW);
- }
-}
diff --git a/src/Geopack/Geopack.IgrfGeo_08.cs b/src/Geopack/Geopack.IgrfGeo.cs
similarity index 68%
rename from src/Geopack/Geopack.IgrfGeo_08.cs
rename to src/Geopack/Geopack.IgrfGeo.cs
index f3fe796..aa38196 100644
--- a/src/Geopack/Geopack.IgrfGeo_08.cs
+++ b/src/Geopack/Geopack.IgrfGeo.cs
@@ -1,24 +1,35 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
+using AuroraScienceHub.Geopack.Contracts.Spherical;
namespace AuroraScienceHub.Geopack;
-public sealed partial class Geopack
+internal sealed partial class Geopack
{
- public SphericalFieldVector IgrfGeo_08(double r, double coLatitude, double phi)
+ public SphericalVector IgrfGeo(ComputationContext context, SphericalLocation location)
{
- double c = Math.Cos(coLatitude);
- double s = Math.Sin(coLatitude);
- double cf = Math.Cos(phi);
- double sf = Math.Sin(phi);
+ if (Math.Abs(location.R) <= double.Epsilon)
+ {
+ throw new InvalidOperationException("Radial distance should not be zero.");
+ }
+
+ if (location.CoordinateSystem is not CoordinateSystem.GEO)
+ {
+ throw new InvalidOperationException("Location must be in GEO coordinate system.");
+ }
+
+ (double s, double c) = Math.SinCos(location.Theta);
+ (double sf, double cf) = Math.SinCos(location.Phi);
- double pp = 1.0D / r;
+ double pp = 1.0D / location.R;
double p = pp;
// IN THIS NEW VERSION, THE OPTIMAL VALUE OF THE PARAMETER NM (MAXIMAL ORDER OF THE SPHERICAL
// HARMONIC EXPANSION) IS NOT USER-PRESCRIBED, BUT CALCULATED INSIDE THE SUBROUTINE, BASED
// ON THE VALUE OF THE RADIAL DISTANCE R:
- int irp3 = (int)r + 2;
+ int irp3 = (int)location.R + 2;
int nm = 3 + 30 / irp3;
if (nm > 13)
{
@@ -69,9 +80,9 @@ public SphericalFieldVector IgrfGeo_08(double r, double coLatitude, double phi)
{
double an = a[n - 1];
int mn = n * (n - 1) / 2 + m;
- double e = Common2.G[mn - 1];
- double hh = Common2.H[mn - 1];
- double xk = Common2.REC[mn - 1];
+ double e = context.G[mn - 1];
+ double hh = context.H[mn - 1];
+ double xk = context.REC[mn - 1];
double w = e * y + hh * x;
bbr += b[n - 1] * w * q;
@@ -120,6 +131,6 @@ public SphericalFieldVector IgrfGeo_08(double r, double coLatitude, double phi)
bphi = bbf / s;
}
- return new SphericalFieldVector(br, btheta, bphi, CoordinateSystem.GEO);
+ return SphericalVector.New(br, btheta, bphi, CoordinateSystem.GEO);
}
}
diff --git a/src/Geopack/Geopack.IgrfGsw_08.cs b/src/Geopack/Geopack.IgrfGsw.cs
similarity index 56%
rename from src/Geopack/Geopack.IgrfGsw_08.cs
rename to src/Geopack/Geopack.IgrfGsw.cs
index e5d511d..7c8fe3e 100644
--- a/src/Geopack/Geopack.IgrfGsw_08.cs
+++ b/src/Geopack/Geopack.IgrfGsw.cs
@@ -1,19 +1,30 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
namespace AuroraScienceHub.Geopack;
-public sealed partial class Geopack
+internal sealed partial class Geopack
{
- public CartesianFieldVector IgrfGsw_08(double xgsw, double ygsw, double zgsw)
+ public CartesianVector IgrfGsw(ComputationContext context, CartesianLocation location)
{
- CartesianLocation geoLocation = GswGeo_08(xgsw, ygsw, zgsw);
- double xgeo = geoLocation.X;
- double ygeo = geoLocation.Y;
- double zgeo = geoLocation.Z;
-
- double rho2 = xgeo * xgeo + ygeo * ygeo;
- double r = Math.Sqrt(rho2 + zgeo * zgeo);
- double c = zgeo / r;
+ if (location.CoordinateSystem is not CoordinateSystem.GSW)
+ {
+ throw new InvalidOperationException("Location must be in GSW coordinate system.");
+ }
+
+ CartesianLocation geoLocation = GswToGeo(context, location);
+
+ double rho2 = geoLocation.X * geoLocation.X + geoLocation.Y * geoLocation.Y;
+ double r = Math.Sqrt(rho2 + geoLocation.Z * geoLocation.Z);
+
+ if (Math.Abs(r) <= double.Epsilon)
+ {
+ throw new InvalidOperationException("Location radius vector should not be zero.");
+ }
+
+ double c = geoLocation.Z / r;
double rho = Math.Sqrt(rho2);
double s = rho / r;
@@ -25,8 +36,8 @@ public CartesianFieldVector IgrfGsw_08(double xgsw, double ygsw, double zgsw)
}
else
{
- cf = xgeo / rho;
- sf = ygeo / rho;
+ cf = geoLocation.X / rho;
+ sf = geoLocation.Y / rho;
}
double pp = 1.0D / r;
@@ -81,11 +92,11 @@ public CartesianFieldVector IgrfGsw_08(double xgsw, double ygsw, double zgsw)
{
double an = a[n - 1];
int mn = n * (n - 1) / 2 + m;
- double e = Common2.G[mn - 1];
- double hh = Common2.H[mn - 1];
- double w_val = e * y + hh * x;
- bbr += b[n - 1] * w_val * q;
- bbt -= an * w_val * z;
+ double e = context.G[mn - 1];
+ double hh = context.H[mn - 1];
+ double wVal = e * y + hh * x;
+ bbr += b[n - 1] * wVal * q;
+ bbt -= an * wVal * z;
if (m != 1)
{
@@ -95,7 +106,7 @@ public CartesianFieldVector IgrfGsw_08(double xgsw, double ygsw, double zgsw)
bi += an * (e * x - hh * y) * qq;
}
- double xk = Common2.REC[mn - 1];
+ double xk = context.REC[mn - 1];
double dp = c * z - s * q - xk * d2;
double pm = c * q - xk * p2;
d2 = z;
@@ -107,11 +118,13 @@ public CartesianFieldVector IgrfGsw_08(double xgsw, double ygsw, double zgsw)
d = s * d + c * p;
p = s * p;
- if (m != 1)
+ if (m == 1)
{
- bi *= (m - 1);
- bbf += bi;
+ continue;
}
+
+ bi *= (m - 1);
+ bbf += bi;
}
double br = bbr;
@@ -129,15 +142,11 @@ public CartesianFieldVector IgrfGsw_08(double xgsw, double ygsw, double zgsw)
bf = bbf / s;
}
- // Convert spherical to Cartesian in GEO
- double he = br * s + bt * c;
- double hxgeo = he * cf - bf * sf;
- double hygeo = he * sf + bf * cf;
- double hzgeo = br * c - bt * s;
-
- // Convert GEO to GSW
- CartesianLocation gswField = GeoGsw_08(hxgeo, hygeo, hzgeo);
+ double he = Math.FusedMultiplyAdd(br, s, bt * c);
+ double bx = Math.FusedMultiplyAdd(he, cf, -bf * sf);
+ double by = Math.FusedMultiplyAdd(he, sf, bf * cf);
+ double bz = Math.FusedMultiplyAdd(br, c, -bt * s);
- return new CartesianFieldVector(gswField.X, gswField.Y, gswField.Z, CoordinateSystem.GSW);
+ return GeoToGsw(context, CartesianVector.New(bx, by, bz, CoordinateSystem.GEO));
}
}
diff --git a/src/Geopack/Geopack.MagSm.cs b/src/Geopack/Geopack.MagSm.cs
new file mode 100644
index 0000000..b389b1f
--- /dev/null
+++ b/src/Geopack/Geopack.MagSm.cs
@@ -0,0 +1,36 @@
+using AuroraScienceHub.Geopack.Contracts;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+
+namespace AuroraScienceHub.Geopack;
+
+internal sealed partial class Geopack
+{
+ public T MagToSm(ComputationContext context, T components) where T : ICartesian
+ => MagSmInternal(context, components, OperationType.Direct);
+
+ public T SmToMag(ComputationContext context, T components) where T : ICartesian
+ => MagSmInternal(context, components, OperationType.Reversed);
+
+ private static T MagSmInternal(ComputationContext context, T components, OperationType operation)
+ where T : ICartesian
+ => operation switch
+ {
+ OperationType.Direct => components.CoordinateSystem is CoordinateSystem.MAG
+ ? T.New(
+ Math.FusedMultiplyAdd(components.X, context.CFI, -components.Y * context.SFI),
+ Math.FusedMultiplyAdd(components.X, context.SFI, components.Y * context.CFI),
+ components.Z,
+ CoordinateSystem.SM)
+ : throw new InvalidOperationException("Input coordinates must be in MAG system."),
+
+ OperationType.Reversed => components.CoordinateSystem is CoordinateSystem.SM
+ ? T.New(
+ Math.FusedMultiplyAdd(components.X, context.CFI, components.Y * context.SFI),
+ Math.FusedMultiplyAdd(components.Y, context.CFI, -components.X * context.SFI),
+ components.Z,
+ CoordinateSystem.MAG)
+ : throw new InvalidOperationException("Input coordinates must be in SM system."),
+ _ => throw new NotSupportedException($"Specify correct OperationType: {operation}. Available types are Direct and Reversed.")
+ };
+}
diff --git a/src/Geopack/Geopack.MagSm_08.cs b/src/Geopack/Geopack.MagSm_08.cs
deleted file mode 100644
index a96b324..0000000
--- a/src/Geopack/Geopack.MagSm_08.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack;
-
-public sealed partial class Geopack
-{
- public CartesianLocation MagSm_08(double xMag, double yMag, double zMag)
- {
- double xSm = xMag * Common1.CFI - yMag * Common1.SFI;
- double ySm = xMag * Common1.SFI + yMag * Common1.CFI;
- double zSm = zMag;
- return new CartesianLocation(xSm, ySm, zSm, CoordinateSystem.SM);
- }
-
- public CartesianLocation SmMag_08(double xSm, double ySm, double zSm)
- {
- double xMag = xSm * Common1.CFI + ySm * Common1.SFI;
- double yMag = ySm * Common1.CFI - xSm * Common1.SFI;
- double zMag = zSm;
- return new CartesianLocation(xMag, yMag, zMag, CoordinateSystem.MAG);
- }
-}
diff --git a/src/Geopack/Geopack.Recalc.cs b/src/Geopack/Geopack.Recalc.cs
new file mode 100644
index 0000000..d5a9174
--- /dev/null
+++ b/src/Geopack/Geopack.Recalc.cs
@@ -0,0 +1,334 @@
+using System.Numerics;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+using AuroraScienceHub.Geopack.Contracts.PhysicalObjects;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
+using AuroraScienceHub.Geopack.Utilities;
+
+namespace AuroraScienceHub.Geopack;
+
+internal sealed partial class Geopack
+{
+ public ComputationContext Recalc(DateTime dateTime, CartesianVector? swVelocity = null)
+ {
+ swVelocity ??= CartesianVector.New(-400D, 0D, 0D, CoordinateSystem.GSE);
+
+ if (swVelocity.Required().CoordinateSystem is not CoordinateSystem.GSE)
+ {
+ throw new InvalidOperationException("Solar wind velocity must be in GSE coordinate system.");
+ }
+
+ int year = dateTime.Year;
+ int doy = dateTime.DayOfYear;
+ int hour = dateTime.Hour;
+ int minutes = dateTime.Minute;
+ int seconds = dateTime.Second;
+
+ if (year < 1965)
+ {
+ year = 1965;
+ Console.WriteLine($"Warning: Year {dateTime.Year} is out of range. Using {year} instead.");
+ }
+
+ if (year > 2030)
+ {
+ year = 2030;
+ Console.WriteLine($"Warning: Year {dateTime.Year} is out of range. Using {year} instead.");
+ }
+
+ double[] REC = new double[105];
+ for (int N = 1; N <= 14; N++)
+ {
+ int N2 = 2 * N - 1;
+ N2 *= N2 - 2;
+ for (int M = 1; M <= N; M++)
+ {
+ int MN = N * (N - 1) / 2 + M;
+ REC[MN - 1] = (double)((N - M) * (N + M - 2)) / N2;
+ }
+ }
+
+ double[] G, H;
+ switch (year)
+ {
+ case < 1970:
+ (G, H) = Interpolate(1965, year, doy, IgrfCoefficients.G65, IgrfCoefficients.G70, IgrfCoefficients.H65, IgrfCoefficients.H70);
+ break;
+ case < 1975:
+ (G, H) = Interpolate(1970, year, doy, IgrfCoefficients.G70, IgrfCoefficients.G75, IgrfCoefficients.H70, IgrfCoefficients.H75);
+ break;
+ case < 1980:
+ (G, H) = Interpolate(1975, year, doy, IgrfCoefficients.G75, IgrfCoefficients.G80, IgrfCoefficients.H75, IgrfCoefficients.H80);
+ break;
+ case < 1985:
+ (G, H) = Interpolate(1980, year, doy, IgrfCoefficients.G80, IgrfCoefficients.G85, IgrfCoefficients.H80, IgrfCoefficients.H85);
+ break;
+ case < 1990:
+ (G, H) = Interpolate(1985, year, doy, IgrfCoefficients.G85, IgrfCoefficients.G90, IgrfCoefficients.H85, IgrfCoefficients.H90);
+ break;
+ case < 1995:
+ (G, H) = Interpolate(1990, year, doy, IgrfCoefficients.G90, IgrfCoefficients.G95, IgrfCoefficients.H90, IgrfCoefficients.H95);
+ break;
+ case < 2000:
+ (G, H) = Interpolate(1995, year, doy, IgrfCoefficients.G95, IgrfCoefficients.G00, IgrfCoefficients.H95, IgrfCoefficients.H00);
+ break;
+ case < 2005:
+ (G, H) = Interpolate(2000, year, doy, IgrfCoefficients.G00, IgrfCoefficients.G05, IgrfCoefficients.H00, IgrfCoefficients.H05);
+ break;
+ case < 2010:
+ (G, H) = Interpolate(2005, year, doy, IgrfCoefficients.G05, IgrfCoefficients.G10, IgrfCoefficients.H05, IgrfCoefficients.H10);
+ break;
+ case < 2015:
+ (G, H) = Interpolate(2010, year, doy, IgrfCoefficients.G10, IgrfCoefficients.G15, IgrfCoefficients.H10, IgrfCoefficients.H15);
+ break;
+ case < 2020:
+ (G, H) = Interpolate(2015, year, doy, IgrfCoefficients.G15, IgrfCoefficients.G20, IgrfCoefficients.H15, IgrfCoefficients.H20);
+ break;
+ case < 2025:
+ (G, H) = Interpolate(2020, year, doy, IgrfCoefficients.G20, IgrfCoefficients.G25, IgrfCoefficients.H20, IgrfCoefficients.H25);
+ break;
+ case >= 2025:
+ (G, H) = Extrapolate(year, doy);
+ break;
+ }
+
+ double S = 1;
+ for (int N = 2; N <= 14; N++)
+ {
+ int MN = N * (N - 1) / 2 + 1;
+ S *= (2 * N - 3) / (double)(N - 1);
+ G[MN - 1] *= S;
+ H[MN - 1] *= S;
+ double P = S;
+ for (int M = 2; M <= N; M++)
+ {
+ double aa = M == 2 ? 2 : 1;
+ P *= Math.Sqrt(aa * (N - M + 1) / (N + M - 2));
+ int MNN = MN + M - 1;
+ G[MNN - 1] *= P;
+ H[MNN - 1] *= P;
+ }
+ }
+
+ double G_10 = -G[1];
+ double G_11 = G[2];
+ double H_11 = H[2];
+ double SQ = G_11 * G_11 + H_11 * H_11;
+ double SQQ = Math.Sqrt(SQ);
+ double SQR = Math.Sqrt(G_10 * G_10 + SQ);
+ double SL0 = -H_11 / SQQ;
+ double CL0 = -G_11 / SQQ;
+ double ST0 = SQQ / SQR;
+ double CT0 = G_10 / SQR;
+ double STCL = ST0 * CL0;
+ double STSL = ST0 * SL0;
+ double CTSL = CT0 * SL0;
+ double CTCL = CT0 * CL0;
+
+ Sun sun = Sun(dateTime);
+ (double sinSrasn, double cosSrasn) = Math.SinCos(sun.Srasn);
+ (double sinSdec, double cosSdec) = Math.SinCos(sun.Sdec);
+ double S1 = cosSrasn * cosSdec;
+ double S2 = sinSrasn * cosSdec;
+ double S3 = sinSdec;
+
+ double DJ = 365d * (year - 1900) + (year - 1901) / 4d + doy - 0.5d + (hour * 3600 + minutes * 60 + seconds) / 86400d;
+ double T = DJ / 36525d;
+ double OBLIQ = (23.45229d - 0.0130125d * T) / 57.2957795d;
+ (double sinOBLIQ, double cosOBLIQ) = Math.SinCos(OBLIQ);
+ double DZ2 = -sinOBLIQ;
+ double DZ3 = cosOBLIQ;
+
+ double DY1 = DZ2 * S3 - DZ3 * S2;
+ double DY2 = DZ3 * S1;
+ double DY3 = -DZ2 * S1;
+
+ CartesianVector sw = swVelocity.Required();
+ double vx = sw.X;
+ double vy = sw.Y;
+ double vz = sw.Z;
+ double V = Math.Sqrt(vx * vx + vy * vy + vz * vz);
+ double invV = 1.0d / V;
+
+ double DX1 = -vx * invV;
+ double DX2 = -vy * invV;
+ double DX3 = -vz * invV;
+
+ double X1 = DX1 * S1 + DX2 * DY1;
+ double X2 = DX1 * S2 + DX2 * DY2 + DX3 * DZ2;
+ double X3 = DX1 * S3 + DX2 * DY3 + DX3 * DZ3;
+
+ (double SGST, double CGST) = Math.SinCos(sun.Gst);
+ double DIP1 = STCL * CGST - STSL * SGST;
+ double DIP2 = STCL * SGST + STSL * CGST;
+ double DIP3 = CT0;
+
+ double Y1 = DIP2 * X3 - DIP3 * X2;
+ double Y2 = DIP3 * X1 - DIP1 * X3;
+ double Y3 = DIP1 * X2 - DIP2 * X1;
+ double Y = Math.Sqrt(Y1 * Y1 + Y2 * Y2 + Y3 * Y3);
+ double invY = 1.0d / Y;
+ Y1 *= invY;
+ Y2 *= invY;
+ Y3 *= invY;
+
+ double Z1 = X2 * Y3 - X3 * Y2;
+ double Z2 = X3 * Y1 - X1 * Y3;
+ double Z3 = X1 * Y2 - X2 * Y1;
+
+ double E11 = S1 * X1 + S2 * X2 + S3 * X3;
+ double E12 = S1 * Y1 + S2 * Y2 + S3 * Y3;
+ double E13 = S1 * Z1 + S2 * Z2 + S3 * Z3;
+ double E21 = DY1 * X1 + DY2 * X2 + DY3 * X3;
+ double E22 = DY1 * Y1 + DY2 * Y2 + DY3 * Y3;
+ double E23 = DY1 * Z1 + DY2 * Z2 + DY3 * Z3;
+ double E31 = DZ2 * X2 + DZ3 * X3;
+ double E32 = DZ2 * Y2 + DZ3 * Y3;
+ double E33 = DZ2 * Z2 + DZ3 * Z3;
+
+ double SPS = Math.FusedMultiplyAdd(DIP1, X1, Math.FusedMultiplyAdd(DIP2, X2, DIP3 * X3));
+ double SPS2 = SPS * SPS;
+ double CPS = Math.Sqrt(1.0d - SPS2);
+ double PSI = Math.Asin(SPS);
+
+ double A11 = Math.FusedMultiplyAdd(X1, CGST, X2 * SGST);
+ double A12 = Math.FusedMultiplyAdd(-X1, SGST, X2 * CGST);
+ double A13 = X3;
+ double A21 = Math.FusedMultiplyAdd(Y1, CGST, Y2 * SGST);
+ double A22 = Math.FusedMultiplyAdd(-Y1, SGST, Y2 * CGST);
+ double A23 = Y3;
+ double A31 = Math.FusedMultiplyAdd(Z1, CGST, Z2 * SGST);
+ double A32 = Math.FusedMultiplyAdd(-Z1, SGST, Z2 * CGST);
+ double A33 = Z3;
+
+ double EXMAGX = CT0 * (Math.FusedMultiplyAdd(CL0, CGST, -SL0 * SGST));
+ double EXMAGY = CT0 * (Math.FusedMultiplyAdd(CL0, SGST, SL0 * CGST));
+ double EXMAGZ = -ST0;
+ double EYMAGX = -(Math.FusedMultiplyAdd(SL0, CGST, CL0 * SGST));
+ double EYMAGY = -(Math.FusedMultiplyAdd(SL0, SGST, -CL0 * CGST));
+ double CFI = Math.FusedMultiplyAdd(Y1, EYMAGX, Y2 * EYMAGY);
+ double SFI = Math.FusedMultiplyAdd(Y1, EXMAGX, Math.FusedMultiplyAdd(Y2, EXMAGY, Y3 * EXMAGZ));
+
+ return new ComputationContext(
+ ST0: ST0, CT0: CT0, SL0: SL0, CL0: CL0,
+ CTCL: CTCL, STCL: STCL, CTSL: CTSL, STSL: STSL,
+ SFI: SFI, CFI: CFI,
+ SPS: SPS, CPS: CPS, PSI: PSI,
+ CGST: CGST, SGST: SGST,
+ A11: A11, A21: A21, A31: A31, A12: A12, A22: A22, A32: A32, A13: A13, A23: A23, A33: A33,
+ E11: E11, E21: E21, E31: E31, E12: E12, E22: E22, E32: E32, E13: E13, E23: E23, E33: E33,
+ H: H, G: G, REC: REC);
+ }
+
+ private static (double[] G, double[] H) Interpolate(
+ int year1, int IY, int doy,
+ double[] G1, double[] G2, double[] H1, double[] H2)
+ {
+ double[] G = new double[GeopackConstants.IgrfCoefficientCount];
+ double[] H = new double[GeopackConstants.IgrfCoefficientCount];
+
+ double F2 = (IY + (doy - 1) / GeopackConstants.DaysPerYear - year1) * GeopackConstants.IgrfInterpolationIntervalReciprocal;
+ double F1 = 1.0d - F2;
+
+ Vector vF1 = new(F1);
+ Vector vF2 = new(F2);
+ int vectorSize = Vector.Count;
+
+ int i = 0;
+
+ for (; i <= GeopackConstants.IgrfCoefficientCount - vectorSize; i += vectorSize)
+ {
+ Vector vG1 = new(G1, i);
+ Vector vG2 = new(G2, i);
+ Vector vH1 = new(H1, i);
+ Vector vH2 = new(H2, i);
+
+ (vG1 * vF1 + vG2 * vF2).CopyTo(G, i);
+ (vH1 * vF1 + vH2 * vF2).CopyTo(H, i);
+ }
+
+ if (i >= GeopackConstants.IgrfCoefficientCount)
+ {
+ return (G, H);
+ }
+
+ int remaining = GeopackConstants.IgrfCoefficientCount - i;
+ if (remaining >= vectorSize / 2)
+ {
+ Vector vG1 = new(G1, i);
+ Vector vG2 = new(G2, i);
+ Vector vH1 = new(H1, i);
+ Vector vH2 = new(H2, i);
+
+ Vector vG = vG1 * vF1 + vG2 * vF2;
+ Vector vH = vH1 * vF1 + vH2 * vF2;
+
+ for (int j = 0; j < remaining; j++)
+ {
+ G[i + j] = vG[j];
+ H[i + j] = vH[j];
+ }
+ }
+ else
+ {
+ for (; i < GeopackConstants.IgrfCoefficientCount; i++)
+ {
+ G[i] = G1[i] * F1 + G2[i] * F2;
+ H[i] = H1[i] * F1 + H2[i] * F2;
+ }
+ }
+
+ return (G, H);
+ }
+
+ private static (double[], double[]) Extrapolate(int iy, int iday)
+ {
+ double DT = iy + (iday - 1) / GeopackConstants.DaysPerYear - GeopackConstants.IgrfExtrapolationBaseYear;
+ double[] G = new double[GeopackConstants.IgrfCoefficientCount];
+ double[] H = new double[GeopackConstants.IgrfCoefficientCount];
+
+ int vectorSize = Vector.Count;
+ Vector vDT = new(DT);
+
+ int vectorizedLength = GeopackConstants.IgrfCoefficientCount;
+
+ for (int i = 0; i < vectorizedLength; i += vectorSize)
+ {
+ Vector vG25 = new(IgrfCoefficients.G25, i);
+ Vector vH25 = new(IgrfCoefficients.H25, i);
+ vG25.CopyTo(G, i);
+ vH25.CopyTo(H, i);
+ }
+
+ for (int i = vectorizedLength; i < GeopackConstants.IgrfCoefficientCount; i++)
+ {
+ G[i] = IgrfCoefficients.G25[i];
+ H[i] = IgrfCoefficients.H25[i];
+ }
+
+ int deltaVectorizedLength = (GeopackConstants.IgrfDeltaCoefficientCount / vectorSize) * vectorSize;
+
+ for (int i = 0; i < deltaVectorizedLength; i += vectorSize)
+ {
+ Vector vG = new(G, i);
+ Vector vH = new(H, i);
+ Vector vDG25 = new(IgrfCoefficients.DG25, i);
+ Vector vDH25 = new(IgrfCoefficients.DH25, i);
+
+ vG += vDG25 * vDT;
+ vG.CopyTo(G, i);
+
+ vH += vDH25 * vDT;
+ vH.CopyTo(H, i);
+ }
+
+ for (int i = deltaVectorizedLength; i < GeopackConstants.IgrfDeltaCoefficientCount; i++)
+ {
+ G[i] = Math.FusedMultiplyAdd(IgrfCoefficients.DG25[i], DT, G[i]);
+ H[i] = Math.FusedMultiplyAdd(IgrfCoefficients.DH25[i], DT, H[i]);
+ }
+
+ return (G, H);
+ }
+}
diff --git a/src/Geopack/Geopack.Recalc_08.cs b/src/Geopack/Geopack.Recalc_08.cs
deleted file mode 100644
index 295b349..0000000
--- a/src/Geopack/Geopack.Recalc_08.cs
+++ /dev/null
@@ -1,230 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack;
-
-public sealed partial class Geopack
-{
- public (Common1, Common2) Recalc_08(DateTime dateTime,
- double vgsex = -400.0D, double vgsey = 0.0D, double vgsez = 0.0D)
- {
- int IY = dateTime.Year;
- int IDAY = dateTime.DayOfYear;
- int IHOUR = dateTime.Hour;
- int MIN = dateTime.Minute;
- int ISEC = dateTime.Second;
-
- if (IY < 1965)
- {
- IY = 1965;
- Console.WriteLine($"Warning: Year {dateTime.Year} is out of range. Using {IY} instead.");
- }
-
- if (IY > 2030)
- {
- IY = 2030;
- Console.WriteLine($"Warning: Year {dateTime.Year} is out of range. Using {IY} instead.");
- }
-
- for (int N = 1; N <= 14; N++)
- {
- int N2 = 2 * N - 1;
- N2 *= (N2 - 2);
- for (int M = 1; M <= N; M++)
- {
- int MN = N * (N - 1) / 2 + M;
- Common2.REC[MN - 1] = (double)((N - M) * (N + M - 2)) / N2;
- }
- }
-
- switch (IY)
- {
- case < 1970:
- Interpolate(1965, IY, IDAY, G65, G70, H65, H70);
- break;
- case < 1975:
- Interpolate(1970, IY, IDAY, G70, G75, H70, H75);
- break;
- case < 1980:
- Interpolate(1975, IY, IDAY, G75, G80, H75, H80);
- break;
- case < 1985:
- Interpolate(1980, IY, IDAY, G80, G85, H80, H85);
- break;
- case < 1990:
- Interpolate(1985, IY, IDAY, G85, G90, H85, H90);
- break;
- case < 1995:
- Interpolate(1990, IY, IDAY, G90, G95, H90, H95);
- break;
- case < 2000:
- Interpolate(1995, IY, IDAY, G95, G00, H95, H00);
- break;
- case < 2005:
- Interpolate(2000, IY, IDAY, G00, G05, H00, H05);
- break;
- case < 2010:
- Interpolate(2005, IY, IDAY, G05, G10, H05, H10);
- break;
- case < 2015:
- Interpolate(2010, IY, IDAY, G10, G15, H10, H15);
- break;
- case < 2020:
- Interpolate(2015, IY, IDAY, G15, G20, H15, H20);
- break;
- case < 2025:
- Interpolate(2020, IY, IDAY, G20, G25, H20, H25);
- break;
- }
-
- // Extrapolate beyond 2025
- if (IY >= 2025)
- {
- double DT = IY + (IDAY - 1) / 365.25d - 2025;
- for (int N = 0; N <= 104; N++)
- {
- Common2.G[N] = G25[N];
- Common2.H[N] = H25[N];
- if (N <= 44)
- {
- Common2.G[N] += DG25[N] * DT;
- Common2.H[N] += DH25[N] * DT;
- }
- }
- }
-
- // Schmidt normalization factors
- double S = 1;
- for (int N = 2; N <= 14; N++)
- {
- int MN = N * (N - 1) / 2 + 1;
- S *= (2 * N - 3) / (double)(N - 1);
- Common2.G[MN - 1] *= S;
- Common2.H[MN - 1] *= S;
- double P = S;
- for (int M = 2; M <= N; M++)
- {
- double AA = (M == 2) ? 2 : 1;
- P *= Math.Sqrt(AA * (N - M + 1) / (N + M - 2));
- int MNN = MN + M - 1;
- Common2.G[MNN - 1] *= P;
- Common2.H[MNN - 1] *= P;
- }
- }
-
- // Calculate GEO components of the unit vector EzMAG
- double G_10 = -Common2.G[1];
- double G_11 = Common2.G[2];
- double H_11 = Common2.H[2];
- double SQ = G_11 * G_11 + H_11 * H_11;
- double SQQ = Math.Sqrt(SQ);
- double SQR = Math.Sqrt(G_10 * G_10 + SQ);
- Common1.SL0 = -H_11 / SQQ;
- Common1.CL0 = -G_11 / SQQ;
- Common1.ST0 = SQQ / SQR;
- Common1.CT0 = G_10 / SQR;
- Common1.STCL = Common1.ST0 * Common1.CL0;
- Common1.STSL = Common1.ST0 * Common1.SL0;
- Common1.CTSL = Common1.CT0 * Common1.SL0;
- Common1.CTCL = Common1.CT0 * Common1.CL0;
-
- Sun sun = Sun_08(dateTime);
- double S1 = Math.Cos(sun.Srasn) * Math.Cos(sun.Sdec);
- double S2 = Math.Sin(sun.Srasn) * Math.Cos(sun.Sdec);
- double S3 = Math.Sin(sun.Sdec);
-
- // Calculate GEI components of the unit vector EZGSE
- double DJ = 365d * (IY - 1900) + (IY - 1901) / 4d + IDAY - 0.5d + (IHOUR * 3600 + MIN * 60 + ISEC) / 86400d;
- double T = DJ / 36525d;
- double OBLIQ = (23.45229d - 0.0130125d * T) / 57.2957795d;
- double DZ1 = 0;
- double DZ2 = -Math.Sin(OBLIQ);
- double DZ3 = Math.Cos(OBLIQ);
-
- // Obtain GEI components of the unit vector EYGSE
- double DY1 = DZ2 * S3 - DZ3 * S2;
- double DY2 = DZ3 * S1 - DZ1 * S3;
- double DY3 = DZ1 * S2 - DZ2 * S1;
-
- // Calculate GEI components of the unit vector X = EXGSW
- double V = Math.Sqrt(vgsex * vgsex + vgsey * vgsey + vgsez * vgsez);
- double DX1 = -vgsex / V;
- double DX2 = -vgsey / V;
- double DX3 = -vgsez / V;
-
- // Then in GEI
- double X1 = DX1 * S1 + DX2 * DY1 + DX3 * DZ1;
- double X2 = DX1 * S2 + DX2 * DY2 + DX3 * DZ2;
- double X3 = DX1 * S3 + DX2 * DY3 + DX3 * DZ3;
-
- // Calculate GEI components of the unit vector DIP = EZ_SM = EZ_MAG
- Common1.CGST = Math.Cos(sun.Gst);
- Common1.SGST = Math.Sin(sun.Gst);
- double DIP1 = Common1.STCL * Common1.CGST - Common1.STSL * Common1.SGST;
- double DIP2 = Common1.STCL * Common1.SGST + Common1.STSL * Common1.CGST;
- double DIP3 = Common1.CT0;
-
- // Calculate GEI components of the unit vector Y = EYGSW
- double Y1 = DIP2 * X3 - DIP3 * X2;
- double Y2 = DIP3 * X1 - DIP1 * X3;
- double Y3 = DIP1 * X2 - DIP2 * X1;
- double Y = Math.Sqrt(Y1 * Y1 + Y2 * Y2 + Y3 * Y3);
- Y1 /= Y;
- Y2 /= Y;
- Y3 /= Y;
-
- // GEI components of the unit vector Z = EZGSW = EXGSW x EYGSW
- double Z1 = X2 * Y3 - X3 * Y2;
- double Z2 = X3 * Y1 - X1 * Y3;
- double Z3 = X1 * Y2 - X2 * Y1;
-
- // Elements of the matrix GSE to GSW
- Common1.E11 = S1 * X1 + S2 * X2 + S3 * X3;
- Common1.E12 = S1 * Y1 + S2 * Y2 + S3 * Y3;
- Common1.E13 = S1 * Z1 + S2 * Z2 + S3 * Z3;
- Common1.E21 = DY1 * X1 + DY2 * X2 + DY3 * X3;
- Common1.E22 = DY1 * Y1 + DY2 * Y2 + DY3 * Y3;
- Common1.E23 = DY1 * Z1 + DY2 * Z2 + DY3 * Z3;
- Common1.E31 = DZ1 * X1 + DZ2 * X2 + DZ3 * X3;
- Common1.E32 = DZ1 * Y1 + DZ2 * Y2 + DZ3 * Y3;
- Common1.E33 = DZ1 * Z1 + DZ2 * Z2 + DZ3 * Z3;
-
- // Geodipole tilt angle in the GSW system
- Common1.SPS = DIP1 * X1 + DIP2 * X2 + DIP3 * X3;
- Common1.CPS = Math.Sqrt(1 - Common1.SPS * Common1.SPS);
- Common1.PSI = Math.Asin(Common1.SPS);
-
- // Elements of the matrix GEO to GSW
- Common1.A11 = X1 * Common1.CGST + X2 * Common1.SGST;
- Common1.A12 = -X1 * Common1.SGST + X2 * Common1.CGST;
- Common1.A13 = X3;
- Common1.A21 = Y1 * Common1.CGST + Y2 * Common1.SGST;
- Common1.A22 = -Y1 * Common1.SGST + Y2 * Common1.CGST;
- Common1.A23 = Y3;
- Common1.A31 = Z1 * Common1.CGST + Z2 * Common1.SGST;
- Common1.A32 = -Z1 * Common1.SGST + Z2 * Common1.CGST;
- Common1.A33 = Z3;
-
- // Elements of the matrix MAG to SM
- double EXMAGX = Common1.CT0 * (Common1.CL0 * Common1.CGST - Common1.SL0 * Common1.SGST);
- double EXMAGY = Common1.CT0 * (Common1.CL0 * Common1.SGST + Common1.SL0 * Common1.CGST);
- double EXMAGZ = -Common1.ST0;
- double EYMAGX = -(Common1.SL0 * Common1.CGST + Common1.CL0 * Common1.SGST);
- double EYMAGY = -(Common1.SL0 * Common1.SGST - Common1.CL0 * Common1.CGST);
- Common1.CFI = Y1 * EYMAGX + Y2 * EYMAGY;
- Common1.SFI = Y1 * EXMAGX + Y2 * EXMAGY + Y3 * EXMAGZ;
-
- return (Common1, Common2);
- }
-
- private void Interpolate(int year1, int IY, int IDAY, double[] G1, double[] G2, double[] H1,
- double[] H2)
- {
- double F2 = (IY + (IDAY - 1) / 365.25d - year1) / 5d;
- double F1 = 1.0d - F2;
- for (int N = 0; N <= 104; N++)
- {
- Common2.G[N] = G1[N] * F1 + G2[N] * F2;
- Common2.H[N] = H1[N] * F1 + H2[N] * F2;
- }
- }
-}
diff --git a/src/Geopack/Geopack.ShuMgnp.cs b/src/Geopack/Geopack.ShuMgnp.cs
new file mode 100644
index 0000000..2d34b3a
--- /dev/null
+++ b/src/Geopack/Geopack.ShuMgnp.cs
@@ -0,0 +1,180 @@
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+using AuroraScienceHub.Geopack.Contracts.PhysicalObjects;
+
+namespace AuroraScienceHub.Geopack;
+
+internal sealed partial class Geopack
+{
+ ///
+ /// Shu magnetopause model coefficient 1
+ ///
+ private const double ShuMgnpCoeff1 = 10.22D;
+
+ ///
+ /// Shu magnetopause model coefficient 2
+ ///
+ private const double ShuMgnpCoeff2 = 1.29D;
+
+ ///
+ /// Shu magnetopause model coefficient 3
+ ///
+ private const double ShuMgnpCoeff3 = 0.184D;
+
+ ///
+ /// Shu magnetopause model coefficient 4
+ ///
+ private const double ShuMgnpCoeff4 = 8.14D;
+
+ ///
+ /// Shu magnetopause model pressure exponent (-1/6.6)
+ ///
+ private const double ShuMgnpPressureExponent = -0.15151515D;
+
+ ///
+ /// Shu magnetopause model alpha coefficient 1
+ ///
+ private const double ShuMgnpAlphaCoeff1 = 0.58D;
+
+ ///
+ /// Shu magnetopause model alpha coefficient 2
+ ///
+ private const double ShuMgnpAlphaCoeff2 = 0.007D;
+
+ ///
+ /// Shu magnetopause model alpha coefficient 3
+ ///
+ private const double ShuMgnpAlphaCoeff3 = 0.024D;
+
+ public Magnetopause ShuMgnp(
+ double xnPd,
+ double vel,
+ double bzImf,
+ CartesianLocation location)
+ {
+ if (Math.Abs(xnPd) <= double.Epsilon
+ || Math.Abs(vel) <= double.Epsilon)
+ {
+ throw new InvalidOperationException("Solar wind plasma parameters should not be zero.");
+ }
+
+ if (location.CoordinateSystem is not CoordinateSystem.GSW)
+ {
+ throw new InvalidOperationException("Location must be in GSW system.");
+ }
+
+ double p;
+ if (vel < 0.0)
+ {
+ p = xnPd;
+ }
+ else
+ {
+ // Solar wind dynamic pressure in nPa
+ p = GeopackConstants.SolarWindDynamicPressureFactor * xnPd * vel * vel;
+ }
+
+ double phi;
+ if (location.Y is not 0.0 || location.Z is not 0.0)
+ {
+ phi = Math.Atan2(location.Y, location.Z);
+ }
+ else
+ {
+ phi = 0.0D;
+ }
+
+ MagnetopausePosition id;
+ double r0 = (ShuMgnpCoeff1 + ShuMgnpCoeff2 * Math.Tanh(ShuMgnpCoeff3 * (bzImf + ShuMgnpCoeff4))) * Math.Pow(p, ShuMgnpPressureExponent);
+ double alpha = (ShuMgnpAlphaCoeff1 - ShuMgnpAlphaCoeff2 * bzImf) * (1.0D + ShuMgnpAlphaCoeff3 * Math.Log(p));
+ double r = Math.Sqrt(location.X * location.X + location.Y * location.Y + location.Z * location.Z);
+ double rm = r0 * Math.Pow(2.0D / (1.0D + location.X / r), alpha);
+
+ if (double.IsFinite(rm))
+ {
+ id = r <= rm ? MagnetopausePosition.Inside : MagnetopausePosition.Outside;
+ }
+ else
+ {
+ id = MagnetopausePosition.NotDefined;
+ }
+
+ // Get T96 magnetopause as starting approximation
+ Magnetopause t96Magnetopause = T96Mgnp(p, -1.0D, location);
+ double xmt96 = t96Magnetopause.BoundaryLocation.X;
+ double ymt96 = t96Magnetopause.BoundaryLocation.Y;
+ double zmt96 = t96Magnetopause.BoundaryLocation.Z;
+
+ double rho2 = ymt96 * ymt96 + zmt96 * zmt96;
+ r = Math.Sqrt(rho2 + xmt96 * xmt96);
+ double st = Math.Sqrt(rho2) / r;
+ double ct = xmt96 / r;
+
+ // Newton's iterative method to find nearest boundary point
+ (r, double sinT, double cosT) = FindMagnetopauseBoundaryPoint(r0, alpha, r, st, ct);
+ double xMgnp = r * cosT;
+ double rho = r * sinT;
+ (double sinPhi, double cosPhi) = Math.SinCos(phi);
+ double yMgnp = rho * sinPhi;
+ double zMgnp = rho * cosPhi;
+
+ double dx = location.X - xMgnp;
+ double dy = location.Y - yMgnp;
+ double dz = location.Z - zMgnp;
+ double dist = Math.Sqrt(dx * dx + dy * dy + dz * dz);
+
+ return new Magnetopause(CartesianLocation.New(xMgnp, yMgnp, zMgnp, CoordinateSystem.GSW), dist, id);
+ }
+
+ ///
+ /// Finds the magnetopause boundary point using Newton's iterative method
+ ///
+ /// Base magnetopause standoff distance
+ /// Magnetopause shape parameter
+ /// Initial radial distance
+ /// Initial sine of theta
+ /// Initial cosine of theta
+ /// Tuple containing final r, sin(theta), and cos(theta)
+ private static (double r, double sinT, double cosT) FindMagnetopauseBoundaryPoint(
+ double r0,
+ double alpha,
+ double r,
+ double st,
+ double ct)
+ {
+ int nit = 0;
+ double t, rm_val, f, gradf_r, gradf_t, gradf, dr, dt, ds;
+
+ do
+ {
+ t = Math.Atan2(st, ct);
+ rm_val = r0 * Math.Pow(2.0D / (1.0D + ct), alpha);
+
+ f = r - rm_val;
+ gradf_r = 1.0D;
+ gradf_t = -alpha / r * rm_val * st / (1.0D + ct);
+ gradf = Math.Sqrt(gradf_r * gradf_r + gradf_t * gradf_t);
+
+ dr = -f / (gradf * gradf);
+ dt = dr / r * gradf_t;
+
+ r += dr;
+ t += dt;
+ (st, ct) = Math.SinCos(t);
+
+ ds = Math.Sqrt(dr * dr + (r * dt) * (r * dt));
+ nit++;
+
+ if (nit > 1000)
+ {
+ throw new InvalidOperationException(
+ "BOUNDARY POINT COULD NOT BE FOUND; ITERATIONS DO NOT CONVERGE");
+ }
+ }
+ while (ds > 1e-4);
+
+ (double sinT, double cosT) = Math.SinCos(t);
+ return (r, sinT, cosT);
+ }
+}
diff --git a/src/Geopack/Geopack.ShuMgnp_08.cs b/src/Geopack/Geopack.ShuMgnp_08.cs
deleted file mode 100644
index 2ddeacd..0000000
--- a/src/Geopack/Geopack.ShuMgnp_08.cs
+++ /dev/null
@@ -1,105 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack;
-
-public sealed partial class Geopack
-{
- public Magnetopause ShuMgnp_08(
- double xnPd,
- double vel,
- double bzImf,
- double xgsw, double ygsw, double zgsw)
- {
- double p;
- if (vel < 0.0)
- {
- p = xnPd;
- }
- else
- {
- // Solar wind dynamic pressure in nPa
- p = 1.94e-6 * xnPd * Math.Pow(vel, 2);
- }
-
- double phi;
- if (ygsw is not 0.0 || zgsw is not 0.0)
- {
- phi = Math.Atan2(ygsw, zgsw);
- }
- else
- {
- phi = 0.0D;
- }
-
- MagnetopausePosition id;
- double r0 = (10.22D + 1.29D * Math.Tanh(0.184D * (bzImf + 8.14D))) * Math.Pow(p, -0.15151515D);
- double alpha = (0.58D - 0.007D * bzImf) * (1.0D + 0.024D * Math.Log(p));
- double r = Math.Sqrt(xgsw * xgsw + ygsw * ygsw + zgsw * zgsw);
- double rm = r0 * Math.Pow(2.0D / (1.0D + xgsw / r), alpha);
-
- if (double.IsFinite(rm))
- {
- id = r <= rm ? MagnetopausePosition.Inside : MagnetopausePosition.Outside;
- }
- else
- {
- id = MagnetopausePosition.NotDefined;
- }
-
- // Get T96 magnetopause as starting approximation
- Magnetopause t96Result = T96Mgnp_08(p, -1.0D, xgsw, ygsw, zgsw);
- double xmt96 = t96Result.X;
- double ymt96 = t96Result.Y;
- double zmt96 = t96Result.Z;
-
- double rho2 = ymt96 * ymt96 + zmt96 * zmt96;
- r = Math.Sqrt(rho2 + xmt96 * xmt96);
- double st = Math.Sqrt(rho2) / r;
- double ct = xmt96 / r;
-
- // Newton's iterative method to find nearest boundary point
- int nit = 0;
- double t, rm_val, f, gradf_r, gradf_t, gradf, dr, dt, ds;
-
- do
- {
- t = Math.Atan2(st, ct);
- rm_val = r0 * Math.Pow(2.0D / (1.0D + ct), alpha);
-
- f = r - rm_val;
- gradf_r = 1.0D;
- gradf_t = -alpha / r * rm_val * st / (1.0D + ct);
- gradf = Math.Sqrt(gradf_r * gradf_r + gradf_t * gradf_t);
-
- dr = -f / (gradf * gradf);
- dt = dr / r * gradf_t;
-
- r += dr;
- t += dt;
- st = Math.Sin(t);
- ct = Math.Cos(t);
-
- ds = Math.Sqrt(dr * dr + (r * dt) * (r * dt));
- nit++;
-
- if (nit > 1000)
- {
- throw new InvalidOperationException(
- "BOUNDARY POINT COULD NOT BE FOUND; ITERATIONS DO NOT CONVERGE");
- }
- }
- while (ds > 1e-4);
-
- double xMgnp = r * Math.Cos(t);
- double rho = r * Math.Sin(t);
- double yMgnp = rho * Math.Sin(phi);
- double zMgnp = rho * Math.Cos(phi);
-
- double dist = Math.Sqrt(
- Math.Pow(xgsw - xMgnp, 2) +
- Math.Pow(ygsw - yMgnp, 2) +
- Math.Pow(zgsw - zMgnp, 2));
-
- return new Magnetopause(xMgnp, yMgnp, zMgnp, dist, id, CoordinateSystem.GSW);
- }
-}
diff --git a/src/Geopack/Geopack.SmGsw.cs b/src/Geopack/Geopack.SmGsw.cs
new file mode 100644
index 0000000..61a84d4
--- /dev/null
+++ b/src/Geopack/Geopack.SmGsw.cs
@@ -0,0 +1,36 @@
+using AuroraScienceHub.Geopack.Contracts;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+
+namespace AuroraScienceHub.Geopack;
+
+internal sealed partial class Geopack
+{
+ public T SmToGsw(ComputationContext context, T components) where T : ICartesian
+ => SmGswInternal(context, components, OperationType.Direct);
+
+ public T GswToSm(ComputationContext context, T components) where T : ICartesian
+ => SmGswInternal(context, components, OperationType.Reversed);
+
+ private static T SmGswInternal(ComputationContext context, T components, OperationType operation)
+ where T : ICartesian
+ => operation switch
+ {
+ OperationType.Direct => components.CoordinateSystem is CoordinateSystem.SM
+ ? T.New(
+ Math.FusedMultiplyAdd(components.X, context.CPS, components.Z * context.SPS),
+ components.Y,
+ Math.FusedMultiplyAdd(components.Z, context.CPS, -components.X * context.SPS),
+ CoordinateSystem.GSW)
+ : throw new InvalidOperationException("Input coordinates must be in SM system."),
+
+ OperationType.Reversed => components.CoordinateSystem is CoordinateSystem.GSW
+ ? T.New(
+ Math.FusedMultiplyAdd(components.X, context.CPS, -components.Z * context.SPS),
+ components.Y,
+ Math.FusedMultiplyAdd(components.X, context.SPS, components.Z * context.CPS),
+ CoordinateSystem.SM)
+ : throw new InvalidOperationException("Input coordinates must be in GSW system."),
+ _ => throw new NotSupportedException($"Specify correct OperationType: {operation}. Available types are Direct and Reversed.")
+ };
+}
diff --git a/src/Geopack/Geopack.SmGsw_08.cs b/src/Geopack/Geopack.SmGsw_08.cs
deleted file mode 100644
index 4f334f1..0000000
--- a/src/Geopack/Geopack.SmGsw_08.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack;
-
-public sealed partial class Geopack
-{
- public CartesianLocation SmGsw_08(double xSm, double ySm, double zSm)
- {
- double xGsw = xSm * Common1.CPS + zSm * Common1.SPS;
- double yGsw = ySm;
- double zGsw = zSm * Common1.CPS - xSm * Common1.SPS;
-
- return new CartesianLocation(xGsw, yGsw, zGsw, CoordinateSystem.GSW);
- }
-
- public CartesianLocation GswSm_08(double xGsw, double yGsw, double zGsw)
- {
- double xSm = xGsw * Common1.CPS - zGsw * Common1.SPS;
- double ySm = yGsw;
- double zSm = xGsw * Common1.SPS + zGsw * Common1.CPS;
-
- return new CartesianLocation(xSm, ySm, zSm, CoordinateSystem.SM);
- }
-}
diff --git a/src/Geopack/Geopack.SphCar_08.cs b/src/Geopack/Geopack.SphCar_08.cs
deleted file mode 100644
index a534346..0000000
--- a/src/Geopack/Geopack.SphCar_08.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack;
-
-public sealed partial class Geopack
-{
- public CartesianLocation SphCar_08(double r, double theta, double phi)
- {
- double sq = r * Math.Sin(theta);
- double x = sq * Math.Cos(phi);
- double y = sq * Math.Sin(phi);
- double z = r * Math.Cos(theta);
-
- return new CartesianLocation(x, y, z);
- }
-
- public SphericalLocation CarSph_08(double x, double y, double z)
- {
- double phi;
- double theta;
- double sq = x * x + y * y;
- double r = Math.Sqrt(sq + z * z);
-
- if (sq != 0.0d)
- {
- sq = Math.Sqrt(sq);
- phi = Math.Atan2(y, x);
- theta = Math.Atan2(sq, z);
- if (phi < 0.0d)
- {
- phi += TwoPi;
- }
- }
- else
- {
- phi = 0.0;
- theta = z < 0.0d ? Pi : 0.0d;
- }
-
- return new SphericalLocation(r, theta, phi);
- }
-}
diff --git a/src/Geopack/Geopack.Sun.cs b/src/Geopack/Geopack.Sun.cs
new file mode 100644
index 0000000..9a4ecb4
--- /dev/null
+++ b/src/Geopack/Geopack.Sun.cs
@@ -0,0 +1,46 @@
+using AuroraScienceHub.Geopack.Contracts.Engine;
+using AuroraScienceHub.Geopack.Contracts.PhysicalObjects;
+
+namespace AuroraScienceHub.Geopack;
+
+internal sealed partial class Geopack
+{
+ public Sun Sun(DateTime dateTime)
+ {
+ if (dateTime.Year is < 1901 or > 2099)
+ {
+ return new Sun(dateTime);
+ }
+
+ double fday = (dateTime.Hour * GeopackConstants.SecondsPerHour + dateTime.Minute * 60 + dateTime.Second) / GeopackConstants.SecondsPerDay;
+ //TODO Ask Tsyganenko if there 4 is really not 4.0D. Here we lose fraction
+ double dj = 365 * (dateTime.Year - 1900) + (dateTime.Year - 1901) / 4 + dateTime.DayOfYear - 0.5D + fday;
+ double t = dj / GeopackConstants.DaysPerJulianCentury;
+ double vl = (279.696678D + 0.9856473354D * dj) % GeopackConstants.DegreesPerCircle;
+ double gst = (279.690983D + 0.9856473354D * dj + GeopackConstants.DegreesPerCircle * fday + GeopackConstants.DegreesPerSemicircle) % GeopackConstants.DegreesPerCircle / GeopackConstants.Rad;
+ double g = (358.475845D + 0.985600267D * dj) % GeopackConstants.DegreesPerCircle / GeopackConstants.Rad;
+ double slong = (vl + (1.91946D - 0.004789D * t) * Math.Sin(g) + 0.020094D * Math.Sin(2.0D * g)) / GeopackConstants.Rad;
+
+ if (slong > GeopackConstants.TwoPi)
+ {
+ slong -= GeopackConstants.TwoPi;
+ }
+
+ if (slong < 0.0D)
+ {
+ slong += GeopackConstants.TwoPi;
+ }
+
+ double obliq = Math.FusedMultiplyAdd(-GeopackConstants.EarthObliquityRatePerCentury, t, GeopackConstants.EarthObliquityJ2000) / GeopackConstants.Rad;
+ (double sob, double cob) = Math.SinCos(obliq);
+ double slp = slong - 9.924e-5D;
+ (double sinSlp, double cosSlp) = Math.SinCos(slp);
+ double sind = sob * sinSlp;
+ double cosd = Math.Sqrt(Math.FusedMultiplyAdd(-sind, sind, 1.0D));
+ double sc = sind / cosd;
+ double sdec = Math.Atan2(sind, cosd);
+ double srasn = GeopackConstants.Pi - Math.Atan2(cob / sob * sc, -cosSlp / cosd);
+
+ return new Sun(dateTime, gst, slong, srasn, sdec);
+ }
+}
diff --git a/src/Geopack/Geopack.Sun_08.cs b/src/Geopack/Geopack.Sun_08.cs
deleted file mode 100644
index 0c27d06..0000000
--- a/src/Geopack/Geopack.Sun_08.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack;
-
-public sealed partial class Geopack
-{
- public Sun Sun_08(DateTime dateTime)
- {
- if (dateTime.Year is < 1901 or > 2099)
- {
- return new Sun(dateTime);
- }
-
- double fday = (dateTime.Hour * 3600 + dateTime.Minute * 60 + dateTime.Second) / 86400.0D;
- //TODO Ask Tsyganenko if there 4 is really not 4.0D. Here we lose fraction
- double dj = 365 * (dateTime.Year - 1900) + (dateTime.Year - 1901) / 4 + dateTime.DayOfYear - 0.5D + fday;
- double t = dj / 36525.0D;
- double vl = (279.696678D + 0.9856473354D * dj) % 360.0D;
- double gst = (279.690983D + 0.9856473354D * dj + 360.0D * fday + 180.0D) % 360.0D / Rad;
- double g = (358.475845D + 0.985600267D * dj) % 360.0D / Rad;
- double slong = (vl + (1.91946D - 0.004789D * t) * Math.Sin(g) + 0.020094D * Math.Sin(2.0D * g)) / Rad;
-
- if (slong > 6.2831853D)
- {
- slong -= 6.283185307D;
- }
-
- if (slong < 0.0D)
- {
- slong += 6.283185307D;
- }
-
- double obliq = (23.45229D - 0.0130125D * t) / Rad;
- double sob = Math.Sin(obliq);
- double slp = slong - 9.924e-5D;
- double sind = sob * Math.Sin(slp);
- double cosd = Math.Sqrt(1.0D - sind * sind);
- double sc = sind / cosd;
- double sdec = Math.Atan2(sind, cosd);
- double srasn = Pi - Math.Atan2(Math.Cos(obliq) / sob * sc, -Math.Cos(slp) / cosd);
-
- return new Sun(dateTime, gst, slong, srasn, sdec);
- }
-}
diff --git a/src/Geopack/Geopack.T96Mgnp.cs b/src/Geopack/Geopack.T96Mgnp.cs
new file mode 100644
index 0000000..5a37693
--- /dev/null
+++ b/src/Geopack/Geopack.T96Mgnp.cs
@@ -0,0 +1,113 @@
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+using AuroraScienceHub.Geopack.Contracts.PhysicalObjects;
+
+namespace AuroraScienceHub.Geopack;
+
+internal sealed partial class Geopack
+{
+ public Magnetopause T96Mgnp(
+ double xnPd, double vel,
+ CartesianLocation location)
+ {
+ if (location.CoordinateSystem is not CoordinateSystem.GSW)
+ {
+ throw new InvalidOperationException("Location must be in GSW system.");
+ }
+
+ double pd;
+ if (vel < 0.0)
+ {
+ pd = xnPd;
+ }
+ else
+ {
+ pd = GeopackConstants.SolarWindDynamicPressureFactor * xnPd * vel * vel;
+ }
+
+ if (pd is 0D)
+ {
+ throw new InvalidOperationException("Dynamic pressure should not be zero.");
+ }
+
+ double rat = pd / 2.0D;
+ double rat16 = Math.Pow(rat, 0.14);
+
+ // Magnetopause parameters for PD = 2 nPa
+ double a0 = 70.0D;
+ double s00 = 1.08D;
+ double x00 = 5.48D;
+
+ // Scaled parameters for actual pressure
+ double a = a0 / rat16;
+ double s0 = s00;
+ double x0 = x00 / rat16;
+ double xm = x0 - a;
+
+ double phi;
+ if (location.Y is not 0.0 || location.Z is not 0.0)
+ {
+ phi = Math.Atan2(location.Y, location.Z);
+ }
+ else
+ {
+ phi = 0.0D;
+ }
+
+ double rho = Math.Sqrt(location.Y * location.Y + location.Z * location.Z);
+
+ if (location.X < xm)
+ {
+ double xMgnp = location.X;
+ double rhomGnp = a * Math.Sqrt(s0 * s0 - 1.0D);
+ (double sinPhi, double cosPhi) = Math.SinCos(phi);
+ double yMgnp = rhomGnp * sinPhi;
+ double zMgnp = rhomGnp * cosPhi;
+ double dist = Math.Sqrt(
+ (location.X - xMgnp) * (location.X - xMgnp) +
+ (location.Y - yMgnp) * (location.Y - yMgnp) +
+ (location.Z - zMgnp) * (location.Z - zMgnp));
+
+ MagnetopausePosition position = double.IsNaN(rhomGnp) ? MagnetopausePosition.NotDefined
+ : rhomGnp > rho
+ ? MagnetopausePosition.Inside
+ : MagnetopausePosition.Outside;
+
+ return new Magnetopause(CartesianLocation.New(xMgnp, yMgnp, zMgnp, CoordinateSystem.GSW), dist, position);
+ }
+
+ double xksi = (location.X - x0) / a + 1.0D;
+ double xdzt = rho / a;
+ double sq1 = Math.Sqrt((1.0D + xksi) * (1.0D + xksi) + xdzt * xdzt);
+ double sq2 = Math.Sqrt((1.0D - xksi) * (1.0D - xksi) + xdzt * xdzt);
+ double sigma = 0.5D * (sq1 + sq2);
+ double tau = 0.5D * (sq1 - sq2);
+
+ // Calculate closest point at magnetopause
+ double xMgnpOut = x0 - a * (1.0D - s0 * tau);
+ double arg = (s0 * s0 - 1.0D) * (1.0D - tau * tau);
+ if (arg < 0.0)
+ {
+ arg = 0.0D;
+ }
+
+ double rhomGnpOut = a * Math.Sqrt(arg);
+ (double sinPhiOut, double cosPhiOut) = Math.SinCos(phi);
+ double yMgnpOut = rhomGnpOut * sinPhiOut;
+ double zMgnpOut = rhomGnpOut * cosPhiOut;
+
+ double distOut = Math.Sqrt(
+ (location.X - xMgnpOut) * (location.X - xMgnpOut) +
+ (location.Y - yMgnpOut) * (location.Y - yMgnpOut) +
+ (location.Z - zMgnpOut) * (location.Z - zMgnpOut));
+
+ MagnetopausePosition idOut = double.IsNaN(sigma)
+ ? MagnetopausePosition.NotDefined
+ : sigma > s0
+ ? MagnetopausePosition.Outside
+ : MagnetopausePosition.Inside;
+
+ return new Magnetopause(CartesianLocation.New(xMgnpOut, yMgnpOut, zMgnpOut, CoordinateSystem.GSW), distOut, idOut);
+ }
+}
diff --git a/src/Geopack/Geopack.T96Mgnp_08.cs b/src/Geopack/Geopack.T96Mgnp_08.cs
deleted file mode 100644
index ed3dcb4..0000000
--- a/src/Geopack/Geopack.T96Mgnp_08.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack;
-
-public sealed partial class Geopack
-{
- public Magnetopause T96Mgnp_08(
- double xnPd, double vel,
- double xgsw, double ygsw, double zgsw)
- {
- double pd;
- if (vel < 0.0)
- {
- pd = xnPd;
- }
- else
- {
- pd = 1.94e-6 * xnPd * vel * vel;
- }
-
- double rat = pd / 2.0D;
- double rat16 = Math.Pow(rat, 0.14);
-
- // Magnetopause parameters for PD = 2 nPa
- double a0 = 70.0D;
- double s00 = 1.08D;
- double x00 = 5.48D;
-
- // Scaled parameters for actual pressure
- double a = a0 / rat16;
- double s0 = s00;
- double x0 = x00 / rat16;
- double xm = x0 - a;
-
- double phi;
- if (ygsw is not 0.0 || zgsw is not 0.0)
- {
- phi = Math.Atan2(ygsw, zgsw);
- }
- else
- {
- phi = 0.0D;
- }
-
- double rho = Math.Sqrt(Math.Pow(ygsw, 2) + Math.Pow(zgsw, 2));
-
- if (xgsw < xm)
- {
- double xMgnp = xgsw;
- double rhomGnp = a * Math.Sqrt(Math.Pow(s0, 2) - 1.0D);
- double yMgnp = rhomGnp * Math.Sin(phi);
- double zMgnp = rhomGnp * Math.Cos(phi);
- double dist = Math.Sqrt(
- (xgsw - xMgnp) * (xgsw - xMgnp) +
- (ygsw - yMgnp) * (ygsw - yMgnp) +
- (zgsw - zMgnp) * (zgsw - zMgnp));
-
- MagnetopausePosition position = double.IsNaN(rhomGnp) ? MagnetopausePosition.NotDefined
- : rhomGnp > rho
- ? MagnetopausePosition.Inside
- : MagnetopausePosition.Outside;
-
- return new Magnetopause(xMgnp, yMgnp, zMgnp, dist, position, CoordinateSystem.GSW);
- }
-
- double xksi = (xgsw - x0) / a + 1.0D;
- double xdzt = rho / a;
- double sq1 = Math.Sqrt((1.0D + xksi) * (1.0D + xksi) + xdzt * xdzt);
- double sq2 = Math.Sqrt((1.0D - xksi) * (1.0D - xksi) + xdzt * xdzt);
- double sigma = 0.5D * (sq1 + sq2);
- double tau = 0.5D * (sq1 - sq2);
-
- // Calculate closest point at magnetopause
- double xMgnpOut = x0 - a * (1.0D - s0 * tau);
- double arg = (s0 * s0 - 1.0D) * (1.0D - tau * tau);
- if (arg < 0.0)
- {
- arg = 0.0D;
- }
-
- double rhomGnpOut = a * Math.Sqrt(arg);
- double yMgnpOut = rhomGnpOut * Math.Sin(phi);
- double zMgnpOut = rhomGnpOut * Math.Cos(phi);
-
- double distOut = Math.Sqrt(
- (xgsw - xMgnpOut) * (xgsw - xMgnpOut) +
- (ygsw - yMgnpOut) * (ygsw - yMgnpOut) +
- (zgsw - zMgnpOut) * (zgsw - zMgnpOut));
-
- MagnetopausePosition idOut = double.IsNaN(sigma)
- ? MagnetopausePosition.NotDefined
- : sigma > s0
- ? MagnetopausePosition.Outside
- : MagnetopausePosition.Inside;
-
- return new Magnetopause(xMgnpOut, yMgnpOut, zMgnpOut, distOut, idOut, CoordinateSystem.GSW);
- }
-}
diff --git a/src/Geopack/Geopack.Trace_08.cs b/src/Geopack/Geopack.Trace.cs
similarity index 60%
rename from src/Geopack/Geopack.Trace_08.cs
rename to src/Geopack/Geopack.Trace.cs
index acb751e..f0768f6 100644
--- a/src/Geopack/Geopack.Trace_08.cs
+++ b/src/Geopack/Geopack.Trace.cs
@@ -1,13 +1,16 @@
using AuroraScienceHub.Geopack.Contracts;
-using AuroraScienceHub.Geopack.Contracts.Interfaces;
-using AuroraScienceHub.Geopack.Contracts.Models;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Coordinates;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+using AuroraScienceHub.Geopack.Contracts.PhysicalObjects;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
namespace AuroraScienceHub.Geopack;
-public sealed partial class Geopack
+internal sealed partial class Geopack
{
- public FieldLine Trace_08(
- double xi, double yi, double zi,
+ public FieldLine Trace(ComputationContext context,
+ CartesianLocation startingPoint,
TraceDirection dir,
double dsMax, double err, double rLim, double r0,
int iopt, double[] parmod,
@@ -15,23 +18,29 @@ public FieldLine Trace_08(
InternalFieldModel inName,
int lMax)
{
- List points = new();
+ if (startingPoint.CoordinateSystem is not CoordinateSystem.GSW)
+ {
+ throw new InvalidOperationException("Location must be in GSW system.");
+ }
+
+ List points = new(lMax + 1);
double direction = (double)dir;
int l = 0;
int nrev = 0;
- Common1.DS3 = direction;
+ double ds3 = direction;
double ds = 0.5D * direction;
- double x = xi;
- double y = yi;
- double z = zi;
+ double x = startingPoint.X;
+ double y = startingPoint.Y;
+ double z = startingPoint.Z;
double xr = x, yr = y, zr = z;
- FieldLineRhsVector initialRhs = Rhand_08(x, y, z, iopt, parmod, exName, inName);
+ FieldLineRhsVector initialRhs = Rhand(context, x, y, z, iopt, parmod, exName, inName, ds3);
double ad = 0.01D;
- if (x * initialRhs.R1 + y * initialRhs.R2 + z * initialRhs.R3 < 0.0D)
+ double dotProduct = x * initialRhs.R1 + y * initialRhs.R2 + z * initialRhs.R3;
+ if (dotProduct < 0.0D)
{
ad = -0.01D;
}
@@ -43,7 +52,7 @@ public FieldLine Trace_08(
{
l++;
- points.Add(new CartesianLocation(x, y, z, CoordinateSystem.GSW));
+ points.Add(CartesianLocation.New(x, y, z, CoordinateSystem.GSW));
double ryz = y * y + z * z;
double r2 = x * x + ryz;
@@ -83,7 +92,7 @@ public FieldLine Trace_08(
rr = r;
// Make step
- StepResult stepResult = Step_08(x, y, z, ds, dsMax, err, iopt, parmod, exName, inName);
+ (StepResult stepResult, ds3) = Step(context, x, y, z, ds, dsMax, err, iopt, parmod, exName, inName, ds3);
x = stepResult.X;
y = stepResult.Y;
z = stepResult.Z;
@@ -118,34 +127,37 @@ public FieldLine Trace_08(
if (points.Count > 0)
{
- points[^1] = new CartesianLocation(x, y, z, CoordinateSystem.GSW);
+ points[^1] = CartesianLocation.New(x, y, z, CoordinateSystem.GSW);
}
else
{
- points.Add(new CartesianLocation(x, y, z, CoordinateSystem.GSW));
+ points.Add(CartesianLocation.New(x, y, z, CoordinateSystem.GSW));
}
return new FieldLine(
points,
- new CartesianLocation(x, y, z, CoordinateSystem.GSW),
+ CartesianLocation.New(x, y, z, CoordinateSystem.GSW),
points.Count,
maxPointsExceeded ? "Maximum points exceeded" : "Boundary reached");
}
- private FieldLineRhsVector Rhand_08(
+ private FieldLineRhsVector Rhand(ComputationContext context,
double x, double y, double z,
int iopt, double[] parmod,
IExternalFieldModel exName,
- InternalFieldModel inName)
+ InternalFieldModel inName,
+ double ds3)
{
- CartesianFieldVector externalField = exName.Calculate(iopt, parmod, Common1.PSI, x, y, z);
- CartesianFieldVector internalField = inName(x, y, z);
+ CartesianLocation location = CartesianLocation.New(x, y, z, CoordinateSystem.GSW);
- double bx = externalField.Bx + internalField.Bx;
- double by = externalField.By + internalField.By;
- double bz = externalField.Bz + internalField.Bz;
+ CartesianVector externalField = exName.Calculate(iopt, parmod, context.PSI, location);
+ CartesianVector internalField = inName(context, location);
- double b = Common1.DS3 / Math.Sqrt(bx * bx + by * by + bz * bz);
+ double bx = externalField.X + internalField.X;
+ double by = externalField.Y + internalField.Y;
+ double bz = externalField.Z + internalField.Z;
+
+ double b = ds3 / Math.Sqrt(Math.FusedMultiplyAdd(bx, bx, Math.FusedMultiplyAdd(by, by, bz * bz)));
double r1 = bx * b;
double r2 = by * b;
@@ -154,36 +166,43 @@ private FieldLineRhsVector Rhand_08(
return new FieldLineRhsVector(r1, r2, r3);
}
- private StepResult Step_08(
+ private (StepResult, double) Step(ComputationContext context,
double x, double y, double z,
double ds, double dsMax, double errIn,
int iopt, double[] parmod,
IExternalFieldModel exName,
- InternalFieldModel inName)
+ InternalFieldModel inName,
+ double ds3)
{
double currentDs = ds;
while (true)
{
- Common1.DS3 = -currentDs / 3.0D;
+ ds3 = -currentDs / 3.0D;
+
+ // First Runge-Kutta stage
+ FieldLineRhsVector r1 = Rhand(context, x, y, z, iopt, parmod, exName, inName, ds3);
- FieldLineRhsVector r1 = Rhand_08(x, y, z, iopt, parmod, exName, inName);
- FieldLineRhsVector r2 = Rhand_08(x + r1.R1, y + r1.R2, z + r1.R3, iopt, parmod, exName, inName);
- FieldLineRhsVector r3 = Rhand_08(
+ // Second stage
+ FieldLineRhsVector r2 = Rhand(context, x + r1.R1, y + r1.R2, z + r1.R3, iopt, parmod, exName, inName, ds3);
+ FieldLineRhsVector r3 = Rhand(context,
x + 0.5D * (r1.R1 + r2.R1),
y + 0.5D * (r1.R2 + r2.R2),
z + 0.5D * (r1.R3 + r2.R3),
- iopt, parmod, exName, inName);
- FieldLineRhsVector r4 = Rhand_08(
+ iopt, parmod, exName, inName,
+ ds3);
+ FieldLineRhsVector r4 = Rhand(context,
x + 0.375D * (r1.R1 + 3.0D * r3.R1),
y + 0.375D * (r1.R2 + 3.0D * r3.R2),
z + 0.375D * (r1.R3 + 3.0D * r3.R3),
- iopt, parmod, exName, inName);
- FieldLineRhsVector r5 = Rhand_08(
+ iopt, parmod, exName, inName,
+ ds3);
+ FieldLineRhsVector r5 = Rhand(context,
x + 1.5D * (r1.R1 - 3.0D * r3.R1 + 4.0D * r4.R1),
y + 1.5D * (r1.R2 - 3.0D * r3.R2 + 4.0D * r4.R2),
z + 1.5D * (r1.R3 - 3.0D * r3.R3 + 4.0D * r4.R3),
- iopt, parmod, exName, inName);
+ iopt, parmod, exName, inName,
+ ds3);
double errCur = Math.Abs(r1.R1 - 4.5D * r3.R1 + 4.0D * r4.R1 - 0.5D * r5.R1)
+ Math.Abs(r1.R2 - 4.5D * r3.R2 + 4.0D * r4.R2 - 0.5D * r5.R2)
@@ -211,11 +230,11 @@ private StepResult Step_08(
nextDs *= 1.5D;
}
- return new StepResult(newX, newY, newZ, nextDs);
+ return (new StepResult(newX, newY, newZ, nextDs), ds3);
}
}
- private sealed record StepResult(double X, double Y, double Z, double NextStepSize);
+ private readonly record struct StepResult(double X, double Y, double Z, double NextStepSize);
- private sealed record FieldLineRhsVector(double R1, double R2, double R3);
+ private readonly record struct FieldLineRhsVector(double R1, double R2, double R3);
}
diff --git a/src/Geopack/Geopack.cs b/src/Geopack/Geopack.cs
index db71012..8e5b888 100644
--- a/src/Geopack/Geopack.cs
+++ b/src/Geopack/Geopack.cs
@@ -1,17 +1,6 @@
-using AuroraScienceHub.Geopack.Interfaces;
-
namespace AuroraScienceHub.Geopack;
///
/// Double precision original Geopack-2008
///
-public sealed partial class Geopack : IGeopack
-{
- private Common1 Common1 { get; set; } = new();
-
- private Common2 Common2 { get; set; } = new();
-
- private const double Pi = 3.141592654D;
- private const double TwoPi = 6.283185307D;
- private const double Rad = 57.295779513D;
-}
+internal sealed partial class Geopack : IGeopack;
diff --git a/src/Geopack/Geopack.csproj b/src/Geopack/Geopack.csproj
index e266f25..6c831d7 100644
--- a/src/Geopack/Geopack.csproj
+++ b/src/Geopack/Geopack.csproj
@@ -18,8 +18,14 @@
+
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/Geopack/IGeopack.cs b/src/Geopack/IGeopack.cs
new file mode 100644
index 0000000..10e1032
--- /dev/null
+++ b/src/Geopack/IGeopack.cs
@@ -0,0 +1,320 @@
+using AuroraScienceHub.Geopack.Contracts;
+using AuroraScienceHub.Geopack.Contracts.Cartesian;
+using AuroraScienceHub.Geopack.Contracts.Engine;
+using AuroraScienceHub.Geopack.Contracts.PhysicalObjects;
+using AuroraScienceHub.Geopack.Contracts.PhysicalQuantities;
+using AuroraScienceHub.Geopack.Contracts.Spherical;
+
+namespace AuroraScienceHub.Geopack;
+
+///
+/// Geopack-2008 Double Precision contracts
+///
+public interface IGeopack
+{
+ ///
+ /// Calculates components of the main (internal) geomagnetic field in the geocentric solar-wind
+ /// (GSW) coordinate system, using IAGA international geomagnetic reference model coefficients
+ /// (e.g., https://www.ngdc.noaa.gov/IAGA/vmod/coeffs/igrf13coeffs.txt, revised 01 January 2020)
+ ///
+ ///
+ /// Original Geopack-2008 method: IGRF_GSW_08
+ /// The GSW system is essentially similar to the standard GSM (the two systems become identical
+ /// to each other in the case of strictly anti-sunward solar wind flow).
+ ///
+ /// Context containing pre-calculated coefficients. Must be generated by Recalc method.
+ /// Location cartesian coordinates (in [GSW] Re)
+ CartesianVector IgrfGsw(ComputationContext context, CartesianLocation location);
+
+ ///
+ /// Calculates components of the main (internal) geomagnetic field in the spherical geographic
+ /// (geocentric) coordinate system, using IAGA international geomagnetic reference model
+ /// coefficients (e.g., http://www.ngdc.noaa.gov/IAGA/vmod/igrf.html, revised: 22 March 2005)
+ ///
+ ///
+ /// Original Geopack-2008 method: IGRF_GEO_08
+ ///
+ /// Context containing pre-calculated coefficients. Must be generated by Recalc method.
+ /// Point geographical coordinates
+ ///
+ /// R, Theta, Phi - spherical geographic (geocentric) coordinates:
+ /// radial distance R in units Re = 6371.2 km, colatitude Theta and longitude Phi in radians
+ ///
+ SphericalVector IgrfGeo(ComputationContext context, SphericalLocation location);
+
+ ///
+ /// Calculates GSW (geocentric solar-wind) components of geodipole field with the dipole moment
+ /// corresponding to the epoch, specified by ComputationContext.
+ ///
+ ///
+ /// Original Geopack-2008 method: DIP_08
+ /// The GSW coordinate system is essentially similar to the standard GSM (the two systems become
+ /// identical to each other in the case of strictly radial anti-sunward solar wind flow).
+ ///
+ /// Context containing pre-calculated coefficients. Must be generated by Recalc method.
+ /// Point cartesian coordinates location (in [GSW] Re)
+ CartesianVector Dip(ComputationContext context, CartesianLocation location);
+
+ ///
+ /// Calculates four quantities necessary for coordinate transformations
+ /// which depend on sun position (and, hence, on universal time and season)
+ ///
+ ///
+ /// Original Geopack-2008 method: SUN_08
+ ///
+ /// Year, day, and universal time in hours, minutes, and seconds
+ Sun Sun(DateTime dateTime);
+
+ ///
+ /// Prepares elements of rotation matrices for transformations of vectors between several coordinate systems,
+ /// most frequently used in space physics. Also prepares coefficients used in the calculation of the main geomagnetic field (IGRF model).
+ ///
+ ///
+ /// Original Geopack-2008 method: RECALC_08.
+ /// There is no need to repeatedly invoke Recalc if multiple calculations are made for the same date/time and solar wind flow direction.
+ /// Call Recalc() if date/time or/and solar wind velocity have changed.
+ ///
+ /// Date and time in UTC
+ /// Solar wind velocity cartesian vector (in [GSE] km/s)
+ ///
+ /// ComputationContext containing all needed coefficients.
+ ///
+ ComputationContext Recalc(DateTime dateTime, CartesianVector? swVelocity = null);
+
+ ///
+ /// Transforms components of any vector between the standard GSE
+ /// coordinate system and the geocentric solar-wind (GSW, aka GSWM).
+ ///
+ ///
+ /// Original Geopack-2008 method: GSWGSE_08.
+ /// In the GSW system, the X axis is antiparallel to the observed direction of the solar wind flow.
+ /// The Y and Z axes are defined similarly to the standard GSM system.
+ /// The GSW system becomes identical to the standard GSM in the case of a strictly radial solar wind flow.
+ ///
+ /// Context containing pre-calculated coefficients. Must be generated by Recalc method.
+ /// Cartesian components (in [GSW] Re)
+ T GswToGse(ComputationContext context, T components) where T : ICartesian;
+
+ ///
+ /// Transforms components of any vector between the standard GSE
+ /// coordinate system and the geocentric solar-wind (GSW, aka GSWM).
+ ///
+ ///
+ /// Original Geopack-2008 method: GSWGSE_08.
+ /// In the GSW system, the X axis is antiparallel to the observed direction of the solar wind flow.
+ /// The Y and Z axes are defined similarly to the standard GSM system.
+ /// The GSW system becomes identical to the standard GSM in the case of a strictly radial solar wind flow.
+ ///
+ /// Context containing pre-calculated coefficients. Must be generated by Recalc method.
+ /// Cartesian components (in [GSE] Re)
+ T GseToGsw(ComputationContext context, T components) where T : ICartesian;
+
+ ///
+ /// Converts geographic (GEO) to dipole (MAG) coordinates.
+ ///
+ ///
+ /// Original Geopack-2008 method: GEOMAG_08.
+ ///
+ /// Context containing pre-calculated coefficients. Must be generated by Recalc method.
+ /// Cartesian components (in [GEO] Re)
+ T GeoToMag(ComputationContext context, T components) where T : ICartesian;
+
+ ///
+ /// Converts dipole (MAG) coordinates to geographic (GEO).
+ ///
+ ///
+ /// Original Geopack-2008 method: GEOMAG_08.
+ ///
+ /// Context containing pre-calculated coefficients. Must be generated by Recalc method.
+ /// Cartesian components (in [MAG] Re)
+ T MagToGeo(ComputationContext context, T components) where T : ICartesian;
+
+ ///
+ /// Converts equatorial inertial (GEI) to geographical (GEO) coordinates.
+ ///
+ ///
+ /// Original Geopack-2008 method: GEIGEO_08.
+ ///
+ /// Context containing pre-calculated coefficients. Must be generated by Recalc method.
+ /// Cartesian components (in [GEI] Re)
+ T GeiToGeo(ComputationContext context, T components) where T : ICartesian;
+
+ ///
+ /// Converts geographical (GEO) coordinates to equatorial inertial (GEI).
+ ///
+ ///
+ /// Original Geopack-2008 method: GEIGEO_08.
+ ///
+ /// Context containing pre-calculated coefficients. Must be generated by Recalc method.
+ /// Cartesian components (in [GEO] Re)
+ T GeoToGei(ComputationContext context, T components) where T : ICartesian;
+
+ ///
+ /// Converts dipole (MAG) to solar magnetic (SM) coordinates.
+ ///
+ ///
+ /// Original Geopack-2008 method: MAGSM_08.
+ /// A non-standard definition is implied here for the solar magnetic coordinate system:
+ /// it is assumed that the XSM axis lies in the plane defined by the geodipole axis and the observed vector of the solar wind flow
+ /// (rather than the Earth-Sun line). In order to convert MAG coordinates to and from the standard SM coordinates,
+ /// invoke Recalc() with VGSEX=-400.0, VGSEY=0.0, VGSEZ=0.0.
+ ///
+ /// Context containing pre-calculated coefficients. Must be generated by Recalc method.
+ /// Cartesian components (in [MAG] Re)
+ T MagToSm(ComputationContext context, T components) where T : ICartesian;
+
+ ///
+ /// Converts solar magnetic (SM) to dipole (MAG) coordinates.
+ ///
+ ///
+ /// Original Geopack-2008 method: MAGSM_08.
+ /// Before calling SM_MAG_08, be sure to invoke the subroutine Recalc_08 in three cases:
+ /// A non-standard definition is implied here for the solar magnetic coordinate system:
+ /// it is assumed that the XSM axis lies in the plane defined by the geodipole axis and the observed vector of the solar wind flow
+ /// (rather than the Earth-Sun line). In order to convert MAG coordinates to and from the standard SM coordinates,
+ /// invoke Recalc() with VGSEX=-400.0, VGSEY=0.0, VGSEZ=0.0.
+ ///
+ /// Context containing pre-calculated coefficients. Must be generated by Recalc method.
+ /// Cartesian components (in [SM] Re)
+ T SmToMag(ComputationContext context, T components) where T : ICartesian;
+
+ ///
+ /// Converts solar magnetic (SM) to geocentric solar-wind (GSW) coordinates.
+ ///
+ ///
+ /// Original Geopack-2008 method: SMGSW_08.
+ /// A non-standard definition is implied here for the solar magnetic (SM) coordinate system:
+ /// it is assumed that the XSM axis lies in the plane defined by the geodipole axis and the observed vector of the solar wind flow
+ /// (rather than the Earth-Sun line). In order to convert MAG coordinates to and from the standard SM coordinates,
+ /// invoke Recalc() with VGSEX=-400.0, VGSEY=0.0, VGSEZ=0.0.
+ ///
+ /// Context containing pre-calculated coefficients. Must be generated by Recalc method.
+ /// Cartesian components (in [SM] Re)
+ T SmToGsw(ComputationContext context, T components) where T : ICartesian;
+
+ ///
+ /// Converts geocentric solar-wind (GSW) to solar magnetic (SM) coordinates.
+ ///
+ ///
+ /// Original Geopack-2008 method: SMGSW_08.
+ /// A non-standard definition is implied here for the solar magnetic (SM) coordinate system:
+ /// it is assumed that the XSM axis lies in the plane defined by the geodipole axis and the observed vector of the solar wind flow
+ /// (rather than the Earth-Sun line). In order to convert MAG coordinates to and from the standard SM coordinates,
+ /// invoke Recalc_08 with VGSEX=-400.0, VGSEY=0.0, VGSEZ=0.0.
+ ///
+ /// Context containing pre-calculated coefficients. Must be generated by Recalc method.
+ /// Cartesian components (in [GSW] Re)
+ T GswToSm(ComputationContext context, T components) where T : ICartesian;
+
+ ///
+ /// Converts GEO to GSW coordinates.
+ ///
+ ///
+ /// Original Geopack-2008 method: GEOGSW_08.
+ /// This subroutine converts geographic GEO vectors to the solar-wind GSW coordinate system,
+ /// taking into account possible deflections of the solar wind direction from strictly radial.
+ /// Before converting to standard GSM coordinates, use ComputationContext for Velocity(x = -400.0, y = 0.0, z = 0.0).
+ ///
+ /// Context containing pre-calculated coefficients. Must be generated by Recalc method.
+ /// Cartesian GEO coordinates
+ T GeoToGsw(ComputationContext context, T coordinates) where T : ICartesian;
+
+ ///
+ /// Converts GSW to GEO coordinates.
+ ///
+ ///
+ /// Original Geopack-2008 method: GEOGSW_08.
+ /// This subroutine converts the solar-wind GSW coordinates to geographic GEO coordinate system,
+ /// taking into account possible deflections of the solar wind direction from strictly radial.
+ /// Before converting from standard GSM coordinates use ComputationContext for Velocity(x = -400.0, y = 0.0, z = 0.0).
+ ///
+ /// Context containing pre-calculated coefficients. Must be generated by Recalc method.
+ /// Cartesian GSW coordinates
+ T GswToGeo(ComputationContext context, T coordinates) where T : ICartesian;
+
+ ///
+ /// Traces a field line from an arbitrary point in space to the Earth's surface or to a model limiting boundary.
+ ///
+ ///
+ /// Original Geopack-2008 method: TRACE_08..
+ ///
+ /// Context containing pre-calculated coefficients. Must be generated by Recalc method.
+ /// Location GSW coordinates
+ /// Sign of the tracing direction: if dir=1.0 then the tracing is made antiparallel
+ /// to the total field vector (e.g., from northern to southern conjugate point);
+ /// if dir=-1.0 then the tracing proceeds in the opposite direction, that is, parallel to
+ /// the total field vector.
+ /// Upper limit on the stepsize (sets a desired maximal spacing between the field line points)
+ /// Permissible step error. A reasonable estimate providing a sufficient accuracy for most
+ /// applications is err=0.0001. Smaller/larger values will result in larger/smaller number
+ /// of steps and, hence, of output field line points. Note that using much smaller values
+ /// of err may require using a double precision version of the entire package.
+ /// Radius of a sphere (in RE), defining the outer boundary of the tracing region;
+ /// if the field line reaches that boundary from inside, its outbound tracing is
+ /// terminated and the crossing point coordinates XF,YF,ZF are calculated.
+ /// Radius of a sphere (in RE), defining the inner boundary of the tracing region
+ /// (usually, Earth's surface or the ionosphere, where R0~1.0)
+ /// if the field line reaches that sphere from outside, its inbound tracing is
+ /// terminated and the crossing point coordinates XF,YF,ZF are calculated.
+ /// A model index; can be used for specifying a version of the external field
+ /// model (e.g., a number of the Kp-index interval). Alternatively, one can use the array
+ /// PARMOD for that purpose (see below); in that case IOPT is just a dummy parameter.
+ /// A 10-element array containing input parameters needed for a unique
+ /// specification of the external field model. The concrete meaning of the components
+ /// of PARMOD depends on a specific version of that model.
+ /// External model providing components of the external magnetic field
+ /// (e.g., T89, or T96_01, etc.)
+ /// Internal model providing components of the internal magnetic field
+ /// (either DIP_08 or IGRF_GSW_08).
+ /// Maximal length of the arrays XX,YY,ZZ, in which coordinates of the field
+ /// line points are stored. LMAX should be set equal to the actual length of
+ /// the arrays, defined in the main program as actual arguments of this subroutine.
+ FieldLine Trace(ComputationContext context,
+ CartesianLocation startingPoint,
+ TraceDirection dir,
+ double dsMax,
+ double err,
+ double rLim, double r0,
+ int iopt, double[] parmod,
+ IExternalFieldModel exModel,
+ InternalFieldModel inModel,
+ int lMax);
+
+ ///
+ /// For any point in space with coordinates (XGSW, YGSW, ZGSW) and specified conditions
+ /// in the incoming solar wind, this subroutine:
+ /// 1. Determines if the point (XGSW, YGSW, ZGSW) lies inside or outside the
+ /// model magnetopause of Shue et al. (JGR-A, V.103, P. 17691, 1998).
+ /// 2. Calculates the GSW position of a point {XMGNP, YMGNP, ZMGNP}, lying at the model
+ /// magnetopause and asymptotically tending to the nearest boundary point with
+ /// respect to the observation point {XGSW, YGSW, ZGSW}, as it approaches the magnetopause.
+ ///
+ ///
+ /// Original Geopack-2008 method: SHUETAL_MGNP_08.
+ ///
+ /// Either solar wind proton number density (per c.c.) (if VEL greater than 0)
+ /// or the solar wind ram pressure in nanopascals (if VEL less than 0)
+ /// Either solar wind velocity (km/sec)
+ /// or any negative number, which indicates that XN_PD stands
+ /// for the solar wind pressure, rather than for the density
+ /// IMF BZ in nanoteslas
+ /// Location GSW coordinates
+ Magnetopause ShuMgnp(double xnPd, double vel, double bzImf, CartesianLocation location);
+
+ ///
+ /// For any point in space with given coordinates (XGSW, YGSW, ZGSW), this subroutine defines
+ /// the position of a point (XMGNP, YMGNP, ZMGNP) at the T96 model magnetopause with the
+ /// same value of the ellipsoidal tau-coordinate, and the distance between them.
+ ///
+ ///
+ /// Original Geopack-2008 method: T96_MGNP_08.
+ /// This is not the shortest distance D_MIN to the boundary, but DIST asymptotically tends to D_MIN,
+ /// as the observation point gets closer to the magnetopause.
+ ///
+ /// Either solar wind proton number density (per c.c.) (if VEL greater than zero)
+ /// or the solar wind ram pressure in nanopascals (if VEL lower than zero)
+ /// Either solar wind velocity (km/sec) or any negative number, which indicates that XN_PD stands
+ /// for the solar wind pressure, rather than for the density
+ /// Location GSW coordinates
+ Magnetopause T96Mgnp(double xnPd, double vel, CartesianLocation location);
+}
diff --git a/src/Geopack/Interfaces/IGeopack.cs b/src/Geopack/Interfaces/IGeopack.cs
deleted file mode 100644
index aa72ab5..0000000
--- a/src/Geopack/Interfaces/IGeopack.cs
+++ /dev/null
@@ -1,475 +0,0 @@
-using AuroraScienceHub.Geopack.Contracts;
-using AuroraScienceHub.Geopack.Contracts.Interfaces;
-using AuroraScienceHub.Geopack.Contracts.Models;
-
-namespace AuroraScienceHub.Geopack.Interfaces;
-
-///
-/// Geopack-2008 Double Precision contracts
-///
-public interface IGeopack
-{
- ///
- /// Calculates components of the main (internal) geomagnetic field in the geocentric solar-wind
- /// (GSW) coordinate system, using IAGA international geomagnetic reference model coefficients
- /// (e.g., https://www.ngdc.noaa.gov/IAGA/vmod/coeffs/igrf13coeffs.txt, revised 01 January 2020)
- ///
- ///
- /// Original Geopack-2008 method: IGRF_GSW_08
- /// The GSW system is essentially similar to the standard GSM (the two systems become identical
- /// to each other in the case of strictly anti-sunward solar wind flow). For a detailed
- /// definition, see introductory comments for the subroutine GswGse_08.
- /// Before the first call of this subroutine, or, if the date/time,
- /// or the solar wind velocity components (VGSEX, VGSEY, VGSEZ) have changed, the model coefficients
- /// and geo-gsw rotation matrix elements should be updated by calling the subroutine Recalc_08.
- ///
- /// Cartesian GSW X-coordinate (in units RE=6371.2 km)
- /// Cartesian GSW Y-coordinate (in units RE=6371.2 km)
- /// Cartesian GSW Z-coordinate (in units RE=6371.2 km)
- CartesianFieldVector IgrfGsw_08(double xgsw, double ygsw, double zgsw);
-
- ///
- /// Calculates components of the main (internal) geomagnetic field in the spherical geographic
- /// (geocentric) coordinate system, using IAGA international geomagnetic reference model
- /// coefficients (e.g., http://www.ngdc.noaa.gov/IAGA/vmod/igrf.html, revised: 22 March 2005)
- ///
- ///
- /// Original Geopack-2008 method: IGRF_GEO_08
- /// Before the first call of this subroutine, or if the date was changed,
- /// the model coefficients should be updated by calling the subroutine Recalc_08
- ///
- /// Spherical geographic (geocentric) coordinates: radial distance R (in units RE=6371.2 km)
- /// Colatitude THETA in radians
- /// Longitude PHI in radians
- SphericalFieldVector IgrfGeo_08(double r, double theta, double phi);
-
- ///
- /// Calculates GSW (geocentric solar-wind) components of geodipole field with the dipole moment
- /// corresponding to the epoch, specified by calling subroutine Recalc_08 (should be
- /// invoked before the first use of this one, or if the date/time, and/or the observed
- /// solar wind direction, have changed).
- ///
- ///
- /// Original Geopack-2008 method: DIP_08
- /// The GSW coordinate system is essentially similar to the standard GSM (the two systems become
- /// identical to each other in the case of strictly radial anti-sunward solar wind flow). Its
- /// detailed definition is given in introductory comments for the subroutine GswGse_08.
- ///
- /// Cartesian GSW X-coordinate (in units RE=6371.2 km)
- /// Cartesian GSW Y-coordinate (in units RE=6371.2 km)
- /// Cartesian GSW Z-coordinate (in units RE=6371.2 km)
- CartesianFieldVector Dip_08(double xgsw, double ygsw, double zgsw);
-
- ///
- /// Calculates four quantities necessary for coordinate transformations
- /// which depend on sun position (and, hence, on universal time and season)
- ///
- ///
- /// Original Geopack-2008 method: SUN_08
- ///
- /// Year, day, and universal time in hours, minutes, and seconds
- Sun Sun_08(DateTime dateTime);
-
- ///
- /// Converts spherical coordinates into Cartesian ones.
- ///
- ///
- /// Original Geopack-2008 method: SPHCAR_08
- /// THETA and PHI in radians, R (in units RE=6371.2 km).
- /// At the poles (X=0 and Y=0), PHI is assumed to be 0.
- ///
- /// Radial distance (in units RE=6371.2 km)
- /// Co-latitude THETA in radians
- /// Longitude PHI in radians
- CartesianLocation SphCar_08(double r, double theta, double phi);
-
- ///
- /// Converts Cartesian into spherical coordinates.
- ///
- ///
- /// Original Geopack-2008 method: SPHCAR_08
- /// THETA and PHI in radians, R (in units RE=6371.2 km).
- /// At the poles (X=0 and Y=0), PHI is assumed to be 0.
- ///
- /// Cartesian X-coordinate (in units RE=6371.2 km)
- /// Cartesian Y-coordinate (in units RE=6371.2 km)
- /// Cartesian Z-coordinate (in units RE=6371.2 km)
- SphericalLocation CarSph_08(double x, double y, double z);
-
- ///
- /// Calculates Cartesian field components from local spherical ones.
- ///
- ///
- /// Original Geopack-2008 method: BSPCAR_08
- ///
- /// Spherical angle THETA of the point in radians
- /// Spherical angle PHI of the point in radians
- /// Local spherical component of the field (radial)
- /// Local spherical component of the field (co-latitude)
- /// Local spherical component of the field (longitude)
- CartesianFieldVector BSphCar_08(double theta, double phi, double br, double btheta, double bphi);
-
- ///
- /// Calculates local spherical field components from those in Cartesian system.
- ///
- ///
- /// Original Geopack-2008 method: BCARSP_08
- ///
- /// Cartesian component of the position vector
- /// Cartesian component of the position vector
- /// Cartesian component of the position vector
- /// Cartesian component of the field vector
- /// Cartesian component of the field vector
- /// Cartesian component of the field vector
- SphericalFieldVector BCarSph_08(double x, double y, double z, double bx, double by, double bz);
-
- ///
- /// Prepares elements of rotation matrices for transformations of vectors between several coordinate systems,
- /// most frequently used in space physics. Also prepares coefficients used in the calculation of the main geomagnetic field (IGRF model).
- ///
- ///
- /// Original Geopack-2008 method: RECALC_08.
- /// This subroutine should be invoked before using the following subroutines:
- /// IgrfGeo_08, IgrfGsw_08, Dip_08, GeoMag_08 / MagGeo, GeoGsw_08 / GswGeo_08,
- /// MagSm_08 / SmMag_08, SmGsw_08 / GswSm_08, GswGse_08 / GseGsw_08, GeiGeo_08 / GeoGei_08, Trace_08.
- /// There is no need to repeatedly invoke Recalc_08 if multiple calculations are made for the same date/time and solar wind flow direction.
- ///
- /// Date and time in UTC
- /// Cartesian GSE X-component of the observed solar wind flow velocity (in km/s)
- /// Cartesian GSE Y-component of the observed solar wind flow velocity (in km/s)
- /// Cartesian GSE Z-component of the observed solar wind flow velocity (in km/s)
- (Common1, Common2) Recalc_08(DateTime dateTime, double vgsex = -400.0, double vgsey = 0.0, double vgsez = 0.0);
-
- ///
- /// Transforms components of geocentric solar-wind (GSW) system to GSE coordinate.
- ///
- ///
- /// Original Geopack-2008 method: GSWGSE_08.
- /// In the GSW system, the X axis is antiparallel to the observed direction of the solar wind flow.
- /// The Y and Z axes are defined similarly to the standard GSM system.
- /// The GSW system becomes identical to the standard GSM in the case of a strictly radial solar wind flow.
- /// Before calling GSWGSE_08, be sure to invoke the subroutine Recalc_08 to define all necessary elements of transformation matrices.
- ///
- /// Cartesian GSW X-coordinate (in units RE=6371.2 km)
- /// Cartesian GSW Y-coordinate (in units RE=6371.2 km)
- /// Cartesian GSW Z-coordinate (in units RE=6371.2 km)
- CartesianLocation GswGse_08(double xgsw, double ygsw, double zgsw);
-
- ///
- /// Transforms GSE coordinate components to geocentric solar-wind (GSW) ones.
- ///
- ///
- /// Original Geopack-2008 method: GSWGSE_08.
- /// In the GSW system, the X axis is antiparallel to the observed direction of the solar wind flow.
- /// The Y and Z axes are defined similarly to the standard GSM system.
- /// The GSW system becomes identical to the standard GSM in the case of a strictly radial solar wind flow.
- /// Before calling GseGsw_08, be sure to invoke the subroutine Recalc_08 to define all necessary elements of transformation matrices.
- ///
- /// Cartesian GSE X-coordinate (in units RE=6371.2 km)
- /// Cartesian GSE Y-coordinate (in units RE=6371.2 km)
- /// Cartesian GSE Z-coordinate (in units RE=6371.2 km)
- CartesianLocation GseGsw_08(double xgse, double ygse, double zgse);
-
- ///
- /// Converts geographic (GEO) to dipole (MAG) coordinates.
- ///
- ///
- /// Original Geopack-2008 method: GEOMAG_08.
- /// Before calling GeoMag_08, be sure to invoke the subroutine Recalc_08 in two cases:
- /// 1. Before the first transformation of coordinates.
- /// 2. If the values of date/time have been changed.
- /// No information is required here on the solar wind velocity, so one can set VGSEX=-400.0, VGSEY=0.0, VGSEZ=0.0 in Recalc_08.
- ///
- /// Cartesian GEO X-coordinate (in units RE=6371.2 km)
- /// Cartesian GEO Y-coordinate (in units RE=6371.2 km)
- /// Cartesian GEO Z-coordinate (in units RE=6371.2 km)
- CartesianLocation GeoMag_08(double xgeo, double ygeo, double zgeo);
-
- ///
- /// Converts dipole (MAG) coordinates to geographic (GEO).
- ///
- ///
- /// Original Geopack-2008 method: GEOMAG_08.
- /// Before calling MAG_GEO_08, be sure to invoke the subroutine Recalc_08 in two cases:
- /// 1. Before the first transformation of coordinates.
- /// 2. If the values of date/time have been changed.
- /// No information is required here on the solar wind velocity, so one can set VGSEX=-400.0, VGSEY=0.0, VGSEZ=0.0 in Recalc_08.
- ///
- /// Cartesian MAG X-coordinate (in units RE=6371.2 km)
- /// Cartesian MAG Y-coordinate (in units RE=6371.2 km)
- /// Cartesian MAG Z-coordinate (in units RE=6371.2 km)
- CartesianLocation MagGeo_08(double xmag, double ymag, double zmag);
-
- ///
- /// Converts equatorial inertial (GEI) to geographical (GEO) coordinates.
- ///
- ///
- /// Original Geopack-2008 method: GEIGEO_08.
- /// Before calling GEI_GEO_08, be sure to invoke the subroutine Recalc_08 in two cases:
- /// 1. Before the first transformation of coordinates.
- /// 2. If the current values date/time have been changed.
- /// No information is required here on the solar wind velocity, so one can set VGSEX=-400.0, VGSEY=0.0, VGSEZ=0.0 in Recalc_08.
- ///
- /// Cartesian GEI X-coordinate (in units RE=6371.2 km)
- /// Cartesian GEI Y-coordinate (in units RE=6371.2 km)
- /// Cartesian GEI Z-coordinate (in units RE=6371.2 km)
- CartesianLocation GeiGeo_08(double xgei, double ygei, double zgei);
-
- ///
- /// Converts geographical (GEO) coordinates to equatorial inertial (GEI).
- ///
- ///
- /// Original Geopack-2008 method: GEIGEO_08.
- /// Before calling GEO_GEI_08, be sure to invoke the subroutine Recalc_08 in two cases:
- /// 1. Before the first transformation of coordinates.
- /// 2. If the current values date/time have been changed.
- /// No information is required here on the solar wind velocity, so one can set VGSEX=-400.0, VGSEY=0.0, VGSEZ=0.0 in Recalc_08.
- ///
- /// Cartesian GEO X-coordinate (in units RE=6371.2 km)
- /// Cartesian GEO Y-coordinate (in units RE=6371.2 km)
- /// Cartesian GEO Z-coordinate (in units RE=6371.2 km)
- CartesianLocation GeoGei_08(double xgeo, double ygeo, double zgeo);
-
- ///
- /// Converts dipole (MAG) to solar magnetic (SM) coordinates.
- ///
- ///
- /// Original Geopack-2008 method: MAGSM_08.
- /// Before calling MAG_SM_08, be sure to invoke the subroutine Recalc_08 in three cases:
- /// 1. Before the first transformation of coordinates.
- /// 2. If the values of date/time have changed.
- /// 3. If the values of components of the solar wind flow velocity have changed.
- /// A non-standard definition is implied here for the solar magnetic coordinate system:
- /// it is assumed that the XSM axis lies in the plane defined by the geodipole axis and the observed vector of the solar wind flow
- /// (rather than the Earth-Sun line). In order to convert MAG coordinates to and from the standard SM coordinates,
- /// invoke Recalc_08 with VGSEX=-400.0, VGSEY=0.0, VGSEZ=0.0.
- ///
- /// Cartesian MAG X-coordinate (in units RE=6371.2 km)
- /// Cartesian MAG Y-coordinate (in units RE=6371.2 km)
- /// Cartesian MAG Z-coordinate (in units RE=6371.2 km)
- CartesianLocation MagSm_08(double xmag, double ymag, double zmag);
-
- ///
- /// Converts solar magnetic (SM) to dipole (MAG) coordinates.
- ///
- ///
- /// Original Geopack-2008 method: MAGSM_08.
- /// Before calling SM_MAG_08, be sure to invoke the subroutine Recalc_08 in three cases:
- /// 1. Before the first transformation of coordinates.
- /// 2. If the values of date/time have changed.
- /// 3. If the values of components of the solar wind flow velocity have changed.
- /// A non-standard definition is implied here for the solar magnetic coordinate system:
- /// it is assumed that the XSM axis lies in the plane defined by the geodipole axis and the observed vector of the solar wind flow
- /// (rather than the Earth-Sun line). In order to convert MAG coordinates to and from the standard SM coordinates,
- /// invoke Recalc_08 with VGSEX=-400.0, VGSEY=0.0, VGSEZ=0.0.
- ///
- /// Cartesian SM X-coordinate (in units RE=6371.2 km)
- /// Cartesian SM Y-coordinate (in units RE=6371.2 km)
- /// Cartesian SM Z-coordinate (in units RE=6371.2 km)
- CartesianLocation SmMag_08(double xsm, double ysm, double zsm);
-
- ///
- /// Converts solar magnetic (SM) to geocentric solar-wind (GSW) coordinates.
- ///
- ///
- /// Original Geopack-2008 method: SMGSW_08.
- /// Before calling SM_GSW_08, be sure to invoke the subroutine Recalc_08 in three cases:
- /// 1. Before the first transformation of coordinates.
- /// 2. If the values of date/time have been changed.
- /// 3. If the values of components of the solar wind flow velocity have changed.
- /// A non-standard definition is implied here for the solar magnetic (SM) coordinate system:
- /// it is assumed that the XSM axis lies in the plane defined by the geodipole axis and the observed vector of the solar wind flow
- /// (rather than the Earth-Sun line). In order to convert MAG coordinates to and from the standard SM coordinates,
- /// invoke Recalc_08 with VGSEX=-400.0, VGSEY=0.0, VGSEZ=0.0.
- ///
- /// Cartesian SM X-coordinate (in units RE=6371.2 km)
- /// Cartesian SM Y-coordinate (in units RE=6371.2 km)
- /// Cartesian SM Z-coordinate (in units RE=6371.2 km)
- CartesianLocation SmGsw_08(double xsm, double ysm, double zsm);
-
- ///
- /// Converts geocentric solar-wind (GSW) to solar magnetic (SM) coordinates.
- ///
- ///
- /// Original Geopack-2008 method: SMGSW_08.
- /// Before calling GSW_SM_08, be sure to invoke the subroutine Recalc_08 in three cases:
- /// 1. Before the first transformation of coordinates.
- /// 2. If the values of date/time have been changed.
- /// 3. If the values of components of the solar wind flow velocity have changed.
- /// A non-standard definition is implied here for the solar magnetic (SM) coordinate system:
- /// it is assumed that the XSM axis lies in the plane defined by the geodipole axis and the observed vector of the solar wind flow
- /// (rather than the Earth-Sun line). In order to convert MAG coordinates to and from the standard SM coordinates,
- /// invoke Recalc_08 with VGSEX=-400.0, VGSEY=0.0, VGSEZ=0.0.
- ///
- /// Cartesian GSW X-coordinate (in units RE=6371.2 km)
- /// Cartesian GSW Y-coordinate (in units RE=6371.2 km)
- /// Cartesian GSW Z-coordinate (in units RE=6371.2 km)
- CartesianLocation GswSm_08(double xgsw, double ygsw, double zgsw);
-
- ///
- /// Converts geographic (GEO) to geocentric solar-wind (GSW) coordinates.
- ///
- ///
- /// Original Geopack-2008 method: GEOGSW_08.
- /// Before calling GEO_GSW_08, be sure to invoke the subroutine Recalc_08 in three cases:
- /// 1. Before the first transformation of coordinates.
- /// 2. If the values of date/time have changed.
- /// 3. If the values of components of the solar wind flow velocity have changed.
- /// This subroutine converts GEO vectors to and from the solar-wind GSW coordinate system,
- /// taking into account possible deflections of the solar wind direction from strictly radial.
- /// Before converting to/from standard GSM coordinates, invoke Recalc_08 with VGSEX=-400.0, VGSEY=0.0, VGSEZ=0.0.
- ///
- /// Cartesian GEO X-coordinate (in units RE=6371.2 km)
- /// Cartesian GEO Y-coordinate (in units RE=6371.2 km)
- /// Cartesian GEO Z-coordinate (in units RE=6371.2 km)
- CartesianLocation GeoGsw_08(double xgeo, double ygeo, double zgeo);
-
- ///
- /// Converts geocentric solar-wind (GSW) to geographic (GEO) coordinates.
- ///
- ///
- /// Original Geopack-2008 method: GEOGSW_08.
- /// Before calling GSW_GEO_08, be sure to invoke the subroutine Recalc_08 in three cases:
- /// 1. Before the first transformation of coordinates.
- /// 2. If the values of date/time have changed.
- /// 3. If the values of components of the solar wind flow velocity have changed.
- /// This subroutine converts GEO vectors to and from the solar-wind GSW coordinate system,
- /// taking into account possible deflections of the solar wind direction from strictly radial.
- /// Before converting to/from standard GSM coordinates, invoke Recalc_08 with VGSEX=-400.0, VGSEY=0.0, VGSEZ=0.0.
- ///
- /// Cartesian GSW X-coordinate (in units RE=6371.2 km)
- /// Cartesian GSW Y-coordinate (in units RE=6371.2 km)
- /// Cartesian GSW Z-coordinate (in units RE=6371.2 km)
- CartesianLocation GswGeo_08(double xgsw, double ygsw, double zgsw);
-
- ///
- /// Converts vertical local height (altitude) H and geodetic latitude XMU into geocentric coordinates R and THETA.
- ///
- ///
- /// Original Geopack-2008 method: GEODGEO_08.
- /// The subroutine uses World Geodetic System WGS84 parameters for the Earth's ellipsoid. The angular quantities
- /// (geo co-latitude THETA and geodetic latitude XMU) are in radians, and the distances (geocentric radius R and
- /// altitude H above the Earth's ellipsoid) are in kilometers.
- ///
- /// Altitude (km) for geodetic input or geocentric radius (km) for geocentric input
- /// Geodetic latitude (radians) for geodetic input or co-latitude (radians) for geocentric input
- GeodeticGeocentricCoordinates GeodGeo_08(double h, double xmu);
-
- ///
- /// Converts geocentric coordinates R and THETA into vertical local height (altitude) H and geodetic latitude XMU.
- ///
- ///
- /// Original Geopack-2008 method: GEODGEO_08.
- /// The subroutine uses World Geodetic System WGS84 parameters for the Earth's ellipsoid. The angular quantities
- /// (geo co-latitude THETA and geodetic latitude XMU) are in radians, and the distances (geocentric radius R and
- /// altitude H above the Earth's ellipsoid) are in kilometers.
- ///
- /// Geocentric radius (km) for geocentric output or altitude (km) for geodetic output
- /// Co-latitude (radians) for geocentric output or geodetic latitude (radians) for geodetic output
- GeodeticGeocentricCoordinates GeoGeod_08(double r, double theta);
-
- ///
- /// Traces a field line from an arbitrary point in space to the Earth's surface or to a model limiting boundary.
- ///
- ///
- /// Original Geopack-2008 method: TRACE_08.
- /// This subroutine allows two options:
- /// 1. If inName=IGRF_GSW_08, then the IGRF model will be used for calculating contribution from Earth's internal sources.
- /// In this case, subroutine Recalc_08 must be called before using TRACE_08, with properly specified date, universal time,
- /// and solar wind velocity components, to calculate in advance all quantities needed for the main field model and for
- /// transformations between involved coordinate systems.
- /// 2. If inName=DIP_08, then a pure dipole field will be used instead of the IGRF model. In this case, the subroutine Recalc_08
- /// must also be called before TRACE_08. Here one can choose either to (a) calculate dipole tilt angle based on date, time,
- /// and solar wind direction, or (b) explicitly specify that angle, without any reference to date/UT/solar wind. In the last
- /// case (b), the sine (SPS) and cosine (CPS) of the dipole tilt angle must be specified in advance (but after having called
- /// Recalc_08) and forwarded in the common block /GEOPACK1/ (in its 11th and 12th elements, respectively). In this case the
- /// role of the subroutine Recalc_08 is reduced to only calculating the components of the Earth's dipole moment.
- ///
- /// GSW X-coordinate of the field line starting point (in Earth radii, 1 RE = 6371.2 km)
- /// GSW Y-coordinate of the field line starting point (in Earth radii, 1 RE = 6371.2 km)
- /// GSW Z-coordinate of the field line starting point (in Earth radii, 1 RE = 6371.2 km)
- /// Sign of the tracing direction: if dir=1.0 then the tracing is made antiparallel
- /// to the total field vector (e.g., from northern to southern conjugate point);
- /// if dir=-1.0 then the tracing proceeds in the opposite direction, that is, parallel to
- /// the total field vector.
- /// Upper limit on the stepsize (sets a desired maximal spacing between the field line points)
- /// Permissible step error. A reasonable estimate providing a sufficient accuracy for most
- /// applications is err=0.0001. Smaller/larger values will result in larger/smaller number
- /// of steps and, hence, of output field line points. Note that using much smaller values
- /// of err may require using a double precision version of the entire package.
- /// Radius of a sphere (in RE), defining the outer boundary of the tracing region;
- /// if the field line reaches that boundary from inside, its outbound tracing is
- /// terminated and the crossing point coordinates XF,YF,ZF are calculated.
- /// Radius of a sphere (in RE), defining the inner boundary of the tracing region
- /// (usually, Earth's surface or the ionosphere, where R0~1.0)
- /// if the field line reaches that sphere from outside, its inbound tracing is
- /// terminated and the crossing point coordinates XF,YF,ZF are calculated.
- /// A model index; can be used for specifying a version of the external field
- /// model (e.g., a number of the Kp-index interval). Alternatively, one can use the array
- /// PARMOD for that purpose (see below); in that case IOPT is just a dummy parameter.
- /// A 10-element array containing input parameters needed for a unique
- /// specification of the external field model. The concrete meaning of the components
- /// of PARMOD depends on a specific version of that model.
- /// Name of a subroutine providing components of the external magnetic field
- /// (e.g., T89, or T96_01, etc.)
- /// Name of a subroutine providing components of the internal magnetic field
- /// (either DIP_08 or IGRF_GSW_08).
- /// Maximal length of the arrays XX,YY,ZZ, in which coordinates of the field
- /// line points are stored. LMAX should be set equal to the actual length of
- /// the arrays, defined in the main program as actual arguments of this subroutine.
- FieldLine Trace_08(
- double xi, double yi, double zi,
- TraceDirection dir,
- double dsMax,
- double err,
- double rLim, double r0,
- int iopt, double[] parmod,
- IExternalFieldModel exName, InternalFieldModel inName,
- int lMax);
-
- ///
- /// For any point in space with coordinates (XGSW, YGSW, ZGSW) and specified conditions
- /// in the incoming solar wind, this subroutine:
- /// 1. Determines if the point (XGSW, YGSW, ZGSW) lies inside or outside the
- /// model magnetopause of Shue et al. (JGR-A, V.103, P. 17691, 1998).
- /// 2. Calculates the GSW position of a point {XMGNP, YMGNP, ZMGNP}, lying at the model
- /// magnetopause and asymptotically tending to the nearest boundary point with
- /// respect to the observation point {XGSW, YGSW, ZGSW}, as it approaches the magnetopause.
- ///
- ///
- /// Original Geopack-2008 method: SHUETAL_MGNP_08.
- ///
- /// Either solar wind proton number density (per c.c.) (if VEL greater than 0)
- /// or the solar wind ram pressure in nanopascals (if VEL less than 0)
- /// Either solar wind velocity (km/sec)
- /// or any negative number, which indicates that XN_PD stands
- /// for the solar wind pressure, rather than for the density
- /// IMF BZ in nanoteslas
- /// Cartesian GSW X-coordinate of the observation point (in units RE=6371.2 km)
- /// Cartesian GSW Y-coordinate of the observation point (in units RE=6371.2 km)
- /// Cartesian GSW Z-coordinate of the observation point (in units RE=6371.2 km)
- Magnetopause ShuMgnp_08(
- double xnPd, double vel, double bzImf,
- double xgsw, double ygsw, double zgsw);
-
- ///
- /// For any point in space with given coordinates (XGSW, YGSW, ZGSW), this subroutine defines
- /// the position of a point (XMGNP, YMGNP, ZMGNP) at the T96 model magnetopause with the
- /// same value of the ellipsoidal tau-coordinate, and the distance between them.
- ///
- ///
- /// Original Geopack-2008 method: T96_MGNP_08.
- /// This is not the shortest distance D_MIN to the boundary, but DIST asymptotically tends to D_MIN,
- /// as the observation point gets closer to the magnetopause.
- ///
- /// Either solar wind proton number density (per c.c.) (if VEL greater than zero)
- /// or the solar wind ram pressure in nanopascals (if VEL lower than zero)
- /// Either solar wind velocity (km/sec) or any negative number, which indicates that XN_PD stands
- /// for the solar wind pressure, rather than for the density
- /// Cartesian GSW X-coordinate of the observation point (in units RE=6371.2 km)
- /// Cartesian GSW Y-coordinate of the observation point (in units RE=6371.2 km)
- /// Cartesian GSW Z-coordinate of the observation point (in units RE=6371.2 km)
- Magnetopause T96Mgnp_08(
- double xnPd, double vel,
- double xgsw, double ygsw, double zgsw);
-}
diff --git a/src/Geopack/ServiceCollectionExtensions.cs b/src/Geopack/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..9590bbe
--- /dev/null
+++ b/src/Geopack/ServiceCollectionExtensions.cs
@@ -0,0 +1,21 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace AuroraScienceHub.Geopack;
+
+///
+/// Extensions for .
+///
+public static class ServiceCollectionExtensions
+{
+ ///
+ /// Registers Geopack services with the dependency injection container.
+ ///
+ /// The service collection to which Geopack services will be added.
+ /// The updated instance.
+ public static IServiceCollection AddGeopack(this IServiceCollection services)
+ {
+ services.AddSingleton();
+
+ return services;
+ }
+}
diff --git a/src/Geopack/Utilities/ObjectExtensions.cs b/src/Geopack/Utilities/ObjectExtensions.cs
new file mode 100644
index 0000000..8ff563e
--- /dev/null
+++ b/src/Geopack/Utilities/ObjectExtensions.cs
@@ -0,0 +1,27 @@
+using System.Runtime.CompilerServices;
+
+namespace AuroraScienceHub.Geopack.Utilities;
+
+///
+/// Extensions for
+///
+public static class ObjectExtensions
+{
+ ///
+ /// Throws an if the value is
+ ///
+ public static T Required(
+ this T? value,
+ [CallerArgumentExpression("value")] string? paramName = null)
+ where T : class
+ => value ?? throw new ArgumentNullException(paramName);
+
+ ///
+ /// Throws an if the value is
+ ///
+ public static T Required(
+ this T? value,
+ [CallerArgumentExpression("value")] string? paramName = null)
+ where T : struct
+ => value ?? throw new ArgumentNullException(paramName);
+}