Skip to content

Igrf14 - #1199

Merged
riclarsson merged 3 commits into
atmtools:mainfrom
riclarsson:igrf14
Oct 23, 2026
Merged

Igrf14#1199
riclarsson merged 3 commits into
atmtools:mainfrom
riclarsson:igrf14

Conversation

@riclarsson

Copy link
Copy Markdown
Contributor

AI PR message:

Summary

This replaces the hand-copied IGRF-13 tables with IGRF-14 coefficients generated at build time from arts-cat-data. Along the way it fixes several bugs in IGRF, in the magnetic-field Jacobian, and in Time.

Requires mag-field/igrf.txt on arts-cat-data main before merging. CI downloads it.

IGRF

  • Coefficients from data. A new generator, make_igrf_data, reads the official IAGA file mag-field/igrf.txt from arts-cat-data. Moving to a new IGRF generation means replacing that file. The model is now IGRF-14, valid 1900–2030.
  • Fixed swapped interpolation weights. Between epochs, the weights for the earlier and later coefficient sets were exchanged, so the field jumped at every epoch. At the Zeeman examples' date and place (2000-03-11) the error was about 250 nT.
  • Secular variation is now used after the last epoch. Previously the field stayed frozen at 2020.
  • Strict time range. Time is handled in decimal years, as IGRF defines it. Times outside the model's range throw. The range is in IGRF::Data::valid_range, and in Python as pyarts3.arts.igrf_valid_range. The docs repeat no dates or generation, so they stay correct after a data update.
  • Renamed: IGRF13 becomes IGRF, including the XML type name, and igrf13.* becomes igrf.*.
  • Missing data file. If a local arts-cat-data lacks the file, ARTS still builds, with a CMake warning. Using IGRF then throws an error asking to update arts-cat-data.
  • Default time. In Python, the default time of pyarts3.arts.igrf is now the time of the call, not the time of import.
  • Citations: Alken2021 and Beggan2026.

Legendre coefficients and magnetic-field retrievals

  • New triangular matrix type. matpack gains a packed lower-triangular type: an mdspan layout layout_triangular, TriangularMatrix, and a fixed-size triangular_cdata_t. It replaces Legendre::SchmidtMatrix; the Python equivalent is math.TriangularMatrix.
  • Fixed SchmidthLegendre::w(). The Jacobian used the geodetic altitude as the geocentric radius, so every derivative was wrong, often by orders of magnitude. For example, ∂B_r/∂g₁⁰ came out as about 4×10⁵ at 90 km and −∞ at the surface, instead of about 1.
  • Coefficients that can't affect the field are dropped:
    • The state vector holds only coefficients that change the field component: 195 for north and up, 182 for east. The east component doesn't depend on any gₙ⁰.
    • SchmidthLegendre::gh is now a vector in IGRF's order. The XML layout changes accordingly.
    • Coefficients that never affect the field (g₀⁰, h₀⁰, hₙ⁰) are NaN in the generated data, and schmidt_fieldcalc never reads them.
  • Exact rotation angle. The geocentric-to-geodetic rotation angle is now computed exactly.

Time

  • What a Time is. A point in time plus a fixed offset from UTC, by default UTC itself. Parsing and printing use C++20 <chrono> dates instead of std::tm, mktime and localtime. As a result:
    • dates before 1900 work;
    • results no longer depend on the machine's timezone;
    • strings without an offset now mean UTC. This is a behaviour change for users outside UTC.
  • Accepted strings: RFC 3339 (2000-01-01T12:00:00Z, …+09:00) and the existing space-separated form. Printing always includes the offset.
  • XML round trip fixed. A time used to come back shifted by the machine's UTC offset. The XML version is now 2; version-1 files are read as UTC.
  • Comparisons: Time has <=>. Python == and < now compare times; before, == compared object identity.
  • Python datetime: aware datetimes keep their offset, and naive ones are UTC. .time returns an aware datetime, and there is a new utc_offset property. A datetime is no longer implicitly converted when passed where a Time is expected; use Time(dt). The Python binding uses only nanobind's public API.
  • Exact seconds: Time(float) and the .sec setter are exact. Before, they could be off by about 1 µs, depending on when they were called.
  • Removed, unused or wrong:
    • Python: Time ± float.
    • C++: Time ± duration, time_steps, next_even, mean_time (which ignored its end index), median, mean and TimeStepType.
    • C++: PartOfSecond, seconds_into_day, operator Numeric, toStruct, toGMTStruct and toTimeT.
    • C++: the Time(std::tm) and Time(std::time_t) constructors, and test_time.cc.

Changed results

  • Zeeman examples: expected values are updated for the corrected field. Polarisation angles shift by about 1°, and brightness temperatures by at most 0.08 K.
  • 7-zeeman-refractive now uses a fixed date. Its default time ("now") would drift with the secular variation and eventually break the example.

Tests

  • tests/core/igrf/reference-values.py: 60 cases from one independent run of the official igrf14.f (not included in ARTS), with a tolerance of 0.1 nT. The measured agreement is 0.015 nT, limited by the Fortran program's single-precision constants; with those corrected it is 6×10⁻¹⁰ nT.
  • cpp.fast.core.test_schmidt_legendre_jacobian: every derivative against central finite differences.
  • tests/python/time-formats.py: accepted and rejected time strings, and round trips.
  • The full suite passes locally (309 tests, macOS, clang with conda). The new nanobind code also compiles against nanobind 2.10 and 3.1, with both clang and GCC 16.

🤖 Generated with Claude Code

@riclarsson
riclarsson merged commit 82c88e0 into atmtools:main Oct 23, 2026
9 checks passed
@riclarsson
riclarsson deleted the igrf14 branch October 23, 2026 04:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant