poisson_warp 0.0.6+6 copy "poisson_warp: ^0.0.6+6" to clipboard
poisson_warp: ^0.0.6+6 copied to clipboard

Symplectic Euler high-precision N-body simulation.

Poisson Warp #

Symplectic Euler high-precision N-body simulation. Software Hecho en Puerto Rico por Radamés Jomuel Valentín Reyes con la ayuda de Gemini.

Core Classes & Methods #

Vector3 #

A high-precision 3D vector class supporting arbitrary-precision arithmetic.

  • Methods: add, subtract, multiply, and magnitude.

Body #

Represents a celestial object.

  • Properties: name, gm (gravitational parameter), position, velocity, and properTimeExperienced.

Antikythera #

The primary simulation engine.

  • simulateMotion: Implements a Symplectic Euler integrator (Semi-Implicit Euler). It uses a staggered update (Velocity First → Position Second) to preserve phase-space volume and ensure long-term orbital stability.
  • calculateBarycenter: A utility method to find the system's center of mass based on the current gravitational distribution.
  • rotateToEquatorialFrame: Transforms coordinate vectors to account for Earth's axial tilt (obliquity).

Usage & Implementation #

High-Precision Orbital Simulation #

To maintain stability, the engine utilizes the "code flip" principle, ensuring orbits do not spiral outward over long durations. Fetched data from NASA Horizons API to test method.

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:big_dec/big_dec.dart';
import 'package:test/test.dart';
import 'package:poisson_warp/poisson_warp.dart';

// ============================================================================
// IMPROVED SCIENTIFIC NUMBER PARSER
// ============================================================================
BigDec parseHorizonsNumber(String raw) {
  // Clean string and normalize Fortran 'D' to standard 'E'
  String s = raw.trim().toUpperCase().replaceAll('D', 'E');
  if (s.isEmpty) return BigDec.fromString("0");

  // Split by scientific notation marker
  final parts = s.split('E');
  String coefficientPart = parts[0];

  // Fix common shorthand: ".5" -> "0.5" or "-.5" -> "-0.5"
  if (coefficientPart.startsWith('.')) coefficientPart = '0$coefficientPart';
  if (coefficientPart.startsWith('-.')) {
    coefficientPart = coefficientPart.replaceFirst('-.', '-0.');
  }

  // Create the coefficient as a high-precision BigDec
  BigDec value = BigDec.fromString(coefficientPart)..setDecimalPrecision(200);

  // Handle exponent (e.g., E+06 or E-11)
  if (parts.length > 1) {
    int exponent = int.parse(parts[1]);
    BigDec ten = BigDec.fromString("10")..setDecimalPrecision(200);

    if (exponent > 0) {
      // Multiply by 10^exp
      value = value.multiply(ten.pow(BigInt.from(exponent)));
    } else if (exponent < 0) {
      // Divide by 10^|exp|
      value = value.divide(ten.pow(BigInt.from(exponent.abs())));
    }
  }

  return value;
}

// ============================================================================
// DATE FORMATTER
// ============================================================================
String horizonsDate(DateTime dt) {
  // Literal single quotes are REQUIRED by the NASA back-end for space-separated dates
  return "'${dt.year.toString().padLeft(4, '0')}-"
         "${dt.month.toString().padLeft(2, '0')}-"
         "${dt.day.toString().padLeft(2, '0')} "
         "${dt.hour.toString().padLeft(2, '0')}:"
         "${dt.minute.toString().padLeft(2, '0')}'";
}

// ============================================================================
// NASA HORIZONS SERVICE
// ============================================================================
class NASAHorizonsService {
  final String _baseUrl = "https://ssd.jpl.nasa.gov/api/horizons.api";

  Future<Body> fetchBody(
    String bodyId,
    String name,
    DateTime start,
    DateTime end,
  ) async {
    // Horizons needs a duration (at least 1 minute) to generate an ephemeris table
    DateTime endAdj = end.add(const Duration(minutes: 10));

    final Map<String, String> queryParams = {
      "format": "json",
      "COMMAND": "'$bodyId'",
      "OBJ_DATA": "YES",
      "MAKE_EPHEM": "YES",
      "EPHEM_TYPE": "VECTORS",
      "CENTER": "'@ssb'",
      "OUT_UNITS": "KM-S",
      "CSV_FORMAT": "YES",
      "VEC_TABLE": "3",
      "START_TIME": horizonsDate(start),
      "STOP_TIME": horizonsDate(endAdj),
      "STEP_SIZE": "10m",
    };

    final uri = Uri.parse(_baseUrl).replace(queryParameters: queryParams);
    final response = await http.get(uri);
    
    if (response.statusCode != 200) {
      throw Exception("HTTP Error: ${response.statusCode}");
    }

    final Map<String, dynamic> data = jsonDecode(response.body);
    final String resultText = data["result"] ?? "";

    const String soeMarker = "\$\$SOE";
    const String eoeMarker = "\$\$EOE";

    final int soe = resultText.indexOf(soeMarker);
    final int eoe = resultText.indexOf(eoeMarker);

    if (soe == -1 || eoe == -1) {
      throw Exception("No vector table for $name. Check API parameters.");
    }

    // CSV line format: JDTDB, Calendar, X, Y, Z, VX, VY, VZ
    final String tableContent = resultText.substring(soe + soeMarker.length, eoe).trim();
    final List<String> lines = tableContent.split('\n');
    final List<String> cols = lines[0].split(',');

    final BigDec kmToM = BigDec.fromString("1000")..setDecimalPrecision(200);

    return Body(
      name: name,
      gm: _parseGM(resultText),
      position: Vector3(
        x: parseHorizonsNumber(cols[2]).multiply(kmToM),
        y: parseHorizonsNumber(cols[3]).multiply(kmToM),
        z: parseHorizonsNumber(cols[4]).multiply(kmToM),
      ),
      velocity: Vector3(
        x: parseHorizonsNumber(cols[5]).multiply(kmToM),
        y: parseHorizonsNumber(cols[6]).multiply(kmToM),
        z: parseHorizonsNumber(cols[7]).multiply(kmToM),
      ),
    );
  }

  BigDec _parseGM(String header) {
    // NASA often writes "GM= 1.327E+11"
    final RegExp r = RegExp(r"GM[^=]*=\s*([0-9.Ede+\-]+)", caseSensitive: false);
    final Match? m = r.firstMatch(header);
    if (m == null) {
      return BigDec.fromString("132712440018000000000")..setDecimalPrecision(200);
    }
    final BigDec gmKm3 = parseHorizonsNumber(m.group(1)!);
    return gmKm3.multiply(BigDec.fromString("1000000000")..setDecimalPrecision(200));
  }
}

// ============================================================================
// TEST SUITE
// ============================================================================
void main() {
  test("Symplectic Solar System Distance Check", () async {
    final nasa = NASAHorizonsService();
    const int dp = 200;
    final BigDec auMeters = BigDec.fromString("149597870700")..setDecimalPrecision(dp);
    
    // Fetch start state (15-day run)
    final DateTime t0 = DateTime(2005, 1, 1);
    final List<Body> initial = [];
    final bodiesConfig = [
      {"id": "10",  "name": "Sun",     "gm": "132712440041.93938"},
      {"id": "199", "name": "Mercury", "gm": "22031.78"},
      {"id": "299", "name": "Venus",   "gm": "324858.59"},
      {"id": "399", "name": "Earth",   "gm": "398600.435"},
      {"id": "301", "name": "Moon",    "gm": "4902.800"},
      {"id": "4",   "name": "Mars",    "gm": "42828.375"},
      {"id": "5",   "name": "Jupiter", "gm": "126712762.53"},
      {"id": "6",   "name": "Saturn",  "gm": "37931184.3"},
      {"id": "7",   "name": "Uranus",  "gm": "5793939.0"},
      {"id": "8",   "name": "Neptune", "gm": "6836529.0"},
    ];
    for (var cfg in bodiesConfig) {
      initial.add(await nasa.fetchBody(cfg["id"]!, cfg["name"]!, t0, t0));
    }

    final Antikythera sim = Antikythera(bodies: initial);
    final BigDec duration = BigDec.fromString("1209600")..setDecimalPrecision(dp); // 15 days
    DateTime lastUpdate = DateTime.now();
    BigInt steps = BigInt.from(50_000);
    sim.simulateMotion(
      durationInSeconds: duration, 
      steps: steps, 
      onStep: (stepsSimulated){
        DateTime thisInstance = DateTime.now();
        Duration timeElapsed = thisInstance.difference(lastUpdate);
        if(1 < timeElapsed.inSeconds){
          print("Progress: ${stepsSimulated.toString()}/${steps.toString()}");
          lastUpdate = thisInstance;
        }
      },
      dp: dp,
    );

    final Body sun = sim.getBodyByName("Sun")!;
    for (var b in initial) {
      if (b.name == "Sun") continue;
      
      // Calculate distance relative to the Sun
      final BigDec distAU = b.position.subtract(sun.position, dp: dp).magnitude(dp: dp).divide(auMeters);
      print("${b.name} Distance: ${distAU.toString()} AU");
    }
  });
}

Test results #

Mercury Distance: 0.598471688991146 AU
Venus Distance: 0.778193847646027 AU
Earth Distance: 1.012776034208995 AU
Moon Distance: 1.008962515323189 AU
Mars Distance: 1.563778329605912 AU
Jupiter Distance: 5.455510629116387 AU
Saturn Distance: 9.059694468740320 AU
Uranus Distance: 20.059031731107295 AU
Neptune Distance: 30.065865881193228 AU

References #

0
likes
0
points
1
downloads

Publisher

unverified uploader

Weekly Downloads

Symplectic Euler high-precision N-body simulation.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

big_dec, path, power_plant

More

Packages that depend on poisson_warp