Skip to content

Add reproducible random sampling and many new pmag.py and ipmag.py tests#826

Merged
Swanson-Hysell merged 25 commits into
masterfrom
test_exploration
Mar 7, 2026
Merged

Add reproducible random sampling and many new pmag.py and ipmag.py tests#826
Swanson-Hysell merged 25 commits into
masterfrom
test_exploration

Conversation

@Swanson-Hysell

Copy link
Copy Markdown
Member

Summary

  • Add an optional random_seed parameter to all functions in pmag.py, ipmag.py, and rockmag.py that use random number generation, using the modern numpy.random.default_rng() API. Closes include the ability to set a random.seed() in each function that uses random or np.random() #447.
  • Build a pytest-based test suite (369 tests) covering core scientific functions across coordinate transforms, Fisher/Kent/Bingham statistics, PCA, VGP calculations, field models, anisotropy, E/I corrections, bootstrap statistical tests, and data I/O.

Reproducible random sampling

All functions that use random number generation now accept an optional random_seed parameter (approach 1 from #447 — default behavior unchanged, but reproducibility is available when needed). This will close #447 :

# Default: non-reproducible (fresh entropy each call)
directions = ipmag.fishrot(k=50, n=100, dec=0, inc=45)

# Reproducible: pass an integer seed
directions = ipmag.fishrot(k=50, n=100, dec=0, inc=45, random_seed=42)

# Thread a Generator through call chains
rng = numpy.random.default_rng(42)
directions = ipmag.fishrot(k=50, n=100, dec=0, inc=45, random_seed=rng)

Implementation uses a _resolve_rng() helper in pmag.py that accepts None (fresh entropy), int (seeded Generator), or a Generator instance (passed through). All legacy np.random.* and random.* calls within converted functions are replaced with rng.* calls on the local Generator.

Converted functions

pmag.py (11 functions): fshdev, kentdev, gaussdev, get_unf, pseudosample, apseudo, mktk03, pseudo, di_boot, dir_df_boot, s_boot

ipmag.py (14 functions): fishrot, fisher_mean_resample, kentrot, tk03, bootstrap_fold_test, mean_bootstrap_confidence, common_mean_bootstrap, common_mean_bootstrap_H23, common_mean_watson, reversal_test_bootstrap, reversal_test_bootstrap_H23, find_svei_kent, plate_rate_mc, MADcrit

rockmag.py (1 function): backfield_MaxUnmix

Out of scope (cosmetic/plotting random, not scientific)

Site.eq_plot / Site.eq_plot_everything (random RGB colors for plot styling)

Test suite

369 tests across 24 test files, all passing. Organized by scientific topic with one pytest class per function.

Area Files Tests Key functions covered
Coordinate transforms test_pmag_directions.py, test_pmag_transforms.py, test_pmag_projection.py 64 dir2cart, cart2dir, angle, dotilt, dogeo, doflip, flip, dimap
Statistics test_pmag_statistics.py, test_ipmag_statistics.py, test_pmag_kent_bingham.py 46 fisher_mean, doprinc, Tmatrix, tauV, dokent, dobingham, bingham_mean, kent_mean
PCA / specimen fitting test_pmag_domean.py 17 domean (DE-FM, DE-BFL, DE-BFL-A, DE-BFP, DE-BFL-O)
Random sampling test_pmag_random.py, test_ipmag_random.py 27 fshdev, fishrot, fisher_mean_resample, kentrot, tk03
VGP / paleogeography test_pmag_vgp.py, test_ipmag_vgp.py 35 pinc, plat, dia_vgp, vgp_di, b_vdm, vdm_b, vgp_calc, sb_vgp_calc, lat_from_inc
Field models test_igrf.py, test_pmag_field_models.py 24 ipmag.igrf (IGRF14 reference values), pmag.doigrf (property-based + paleo models)
E/I corrections test_ipmag_ei.py 16 squish, unsquish, f_factor_calc, find_f, find_ei
Statistical tests test_ipmag_field_tests.py, test_ipmag_common_mean.py 22 bootstrap_fold_test, conglomerate_test_Watson, reversal tests, common mean tests
Anisotropy test_pmag_anisotropy.py 13 s2a, a2s, doseigs, dosgeo, dostilt
Data handling / I/O test_pmag_data_handling.py, test_pmag_io.py, test_pmag_utilities.py, test_ipmag_data_handling.py 64 magic_read, magic_write, get_dictitem, fillkeys, parse_site, make_di_block
Plotting test_ipmag_plotting.py 3 Smoke tests for plot_net, plot_di, fishqq
Correlation test_ipmag_correlation.py 4 simul_correlation_prob, rand_correlation_prob

Test design highlights

  • Analytical tests where possible (cardinal directions, dipole formula, equal-area projection formula, roundtrips at 1e-10 tolerance)
  • IGRF14 reference values verified against the NOAA magnetic field calculator across 14 date/location combinations spanning 1999-2027.5
  • Cross-validation between pmag.py and ipmag.py implementations (e.g., lat_from_inc vs plat, vgp_calc vs dia_vgp)
  • Reproducible stochastic tests using random_seed parameter rather than global np.random.seed()
  • pytest infrastructure: conftest.py with Agg backend fixture, pytest.ini configuration

Swanson-Hysell and others added 17 commits February 28, 2026 06:38
… (2023) functions

np.matrix is deprecated and broken in modern numpy, causing failures in
common_mean_bootstrap_H23, mean_bootstrap_confidence, and
reversal_test_bootstrap_H23.

Changes in pmag.py:
- form_Mhat, form_Ghat, form_Q, find_CMDT_CR, find_T, find_CR: replace
  np.matrix() with np.asarray(), .getT() with .T, .getH() with .conj().T,
  and matrix * with @ operator
- find_T: add condition number guard for singular covariance matrices
  that arise from degenerate bootstrap samples

Changes in ipmag.py:
- common_mean_bootstrap_H23: replace .getH()* with .conj().T @
- mean_bootstrap_confidence: filter degenerate bootstrap samples before
  computing quantile
- common_mean_watson: fix '%.1f' % array formatting (use angle[0])
- lat_from_pole: fix float() on 1-element array (use paleo_lat[0])
* Plot map fix (#810)

* develop some more tests

* update plot_map to return axes

* implement axes fix to show two continents

* new tests removed from this plot_map_fix branch

* use elope_path instead of rewriting

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix orthographic typo

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* edits to PR

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: apivarunas <apiv93@gmail.com>

* change from predetermined index for percentiles to direct percentile calculation in find_ei and find_ei_kent (#795)

After reviewing this PR and concurring that it fixes the issue, merging.

* remove duplication, fix docs, readability (#807)

* simple style fixes via ruff (#812)

* Working through errors / warnings in ipmag (#813)

* slowly fixing errors and warnings

* additional warning fixes

* additional Exception clarity

* clearing bare excepts

* addressing review comments

* adding ipmag tests

* add to same file

* added watson conglomerate tests

* renamed file

---------

Co-authored-by: Nick Swanson-Hysell <nicks-h@umn.edu>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses issue #447 by adding an optional random_seed parameter (using the modern numpy.random.default_rng() API) to all stochastic functions in pmag.py, ipmag.py, and rockmag.py, and adds a comprehensive 369-test pytest suite covering core paleomagnetic functions. A _resolve_rng() helper is introduced in pmag.py to standardize seed handling across call chains.

Changes:

  • Add _resolve_rng() helper to pmag.py and convert 11 functions in pmag.py, 14 in ipmag.py, and 1 in rockmag.py to use it for reproducible random sampling
  • Add 369 tests across 24 new test files covering coordinate transforms, statistics, VGP, field models, E/I corrections, bootstrap tests, anisotropy, and data I/O
  • Remove the old test_fisher_sample.py (superseded by test_ipmag_random.py)

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
pmagpy/pmag.py Adds _resolve_rng() and converts fshdev, kentdev, gaussdev, get_unf, pseudosample, apseudo, mktk03, pseudo, di_boot, dir_df_boot, s_boot to use it
pmagpy/ipmag.py Converts fishrot, fisher_mean_resample, kentrot, tk03, bootstrap_fold_test, mean_bootstrap_confidence, common_mean_bootstrap, common_mean_bootstrap_H23, common_mean_watson, reversal_test_bootstrap, reversal_test_bootstrap_H23, find_svei_kent, plate_rate_mc, MADcrit (signature only) to accept random_seed
pmagpy/rockmag.py Converts backfield_MaxUnmix bootstrap loop to use seeded RNG
pytest.ini Configures test discovery and warning filters
pmagpy/test/conftest.py Session-scoped fixture to force Agg matplotlib backend
pmagpy/test/test_fisher_sample.py Removed (superseded by test_ipmag_random.py)
pmagpy/test/test_pmag_*.py (10 files) New tests for pmag.py functions
pmagpy/test/test_ipmag_*.py (10 files) New tests for ipmag.py functions

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pmagpy/rockmag.py Outdated
Comment thread pmagpy/ipmag.py Outdated
Comment thread pmagpy/test/test_pmag_io.py Outdated
Comment thread pmagpy/test/test_ipmag_ei.py
Comment thread pmagpy/test/test_ipmag_correlation.py Outdated
Comment thread pmagpy/ipmag.py
Comment thread pmagpy/ipmag.py Outdated
…py and ipmag.py

Add random_seed parameter to scalc_vgp_df, find_ei, find_ei_kent,
find_compilation_kent, reversal_test_MM1990, simul_correlation_prob,
and rand_correlation_prob. Thread random_seed through internal calls
to pseudo, fishrot, fisher_mean_resample, and common_mean_watson.

Also fix verbose parameter not being forwarded in
reversal_test_bootstrap and correct 'indicies' typos.
@Swanson-Hysell

Copy link
Copy Markdown
Member Author

These four commits address the Copilot review feedback and extend random_seed coverage to functions that were missed in the initial pass:

  • Fix assert ordering bug in rockmag.py (a035e40)
  • Make correlation tests deterministic by seeding simul_correlation_prob and rand_correlation_prob (341fcd7)
  • Use tmp_path fixture instead of hardcoded /tmp in test_pmag_io.py (f6e16c8)
  • Add random_seed to 7 additional functions in pmag.py/ipmag.py: scalc_vgp_df, find_ei, find_ei_kent, find_compilation_kent, reversal_test_MM1990, simul_correlation_prob, rand_correlation_prob; also fix verbose not being forwarded in reversal_test_bootstrap and correct "indicies" typos (6e2aa47)

Remaining unseeded stochastic functions in svei.py (GGPrand, GGP_vMF_cdfs) are tracked separately in #825 .

@Swanson-Hysell

Copy link
Copy Markdown
Member Author

@apivarunas The tests you wrote in that previous 5 tests file are now spread across the new tests files.

@apivarunas

Copy link
Copy Markdown
Collaborator

@Swanson-Hysell I've given this PR a once-over. Nice comprehensive random_seed implementation and test coverage! The tests ran in about 14.82 seconds on my machine. I made 2 commits to it:

  1. Removing a legacy parameter (seed) in pmag.mtk03 which was explained via like 4 lines in a docstring but basically unnecessary. Instead, I had tk03.py use the same logic it always did, just to random_seed parameter. However, I think the tk03.py implementation is sort of odd. Something to check on, but not really in the scope of this PR.
  2. Adjusting tolerance in the test test_north_pole_vgp_matches_pinc. I ran the test suite a few times and that test failed (max difference of 1.7e-06). Don't know why exactly it failed for me and not you, maybe some little floating-point differences? I boosted the tolerance to 2e-06 for that test.

If that's okay with you, I think you can go ahead and merge. Having this test coverage will be nice.

@Swanson-Hysell

Copy link
Copy Markdown
Member Author

Thanks for the review and commits on this @apivarunas!

It looks like 8861e12 removed the legacy seed option from the docstring for the pmag.mktk03 function, but left it as a required position parameter in the function. Which I think that means that G2 would be received as seed in programs/tk03.py. I will dig in.

@Swanson-Hysell Swanson-Hysell merged commit 81b7647 into master Mar 7, 2026
2 checks passed
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.

include the ability to set a random.seed() in each function that uses random or np.random()

3 participants